根据软件开发设计原则,需要最少维护工作的软件被认为是好的设计。也就是说,维护应该是架构师必须考虑的重点。在本文中,讨论了一种这样的架构,称为六边形架构,它使软件易于维护、管理、测试和扩展。
六边形架构是Alistair Cockburn在 2006 年创造的一个术语。六边形架构的另一个名称是端口和适配器架构。该架构将应用程序分为两部分,即内部部分和外部部分。应用程序的核心逻辑被视为内部部分。数据库、UI 和消息队列可能是外部部分。这样一来,核心应用逻辑就与外界完全隔离了。现在这两部分之间的通信可以通过端口和适配器进行。现在,让我们了解这些的含义。
- 端口:端口充当网关,通过它作为入站或出站端口进行通信。入站端口类似于向外界公开核心逻辑的服务接口。出站端口类似于存储库接口,可促进从应用程序到持久性系统的通信。
- 适配器:适配器充当处理用户输入并将其转换为特定于语言的调用的端口的实现。它基本上封装了与消息队列、数据库等外部系统交互的逻辑,同时也转换了外部对象与核心之间的通信。适配器也有两种类型。
- 主要适配器:它使用应用程序的入站端口驱动应用程序,也称为驱动适配器。主要适配器的示例可以是 WebViews 或 Rest Controllers。
- 辅助适配器:这是由应用程序驱动的出站端口的实现,也称为驱动适配器。与消息队列、数据库和外部 API 调用的连接是辅助适配器的一些示例。
因此,六边形架构谈论在应用程序中公开多个端点以进行通信。如果我们的端口有合适的适配器,我们的请求就会得到满足。该架构为分层架构,主要由Framework、Application、Domain三层组成。
- Domain:它是一个核心业务逻辑层,外层的实现细节都隐藏了。
- 应用程序:它充当领域层和框架层之间的中介。
- 框架:该层具有域层将如何与外部世界交互的所有实现细节。
说明性示例:让我们通过一个实时示例来理解这个架构。我们将使用 Spring Boot 设计一个Cake Service应用程序。您也可以创建一个普通的 Spring 或基于 Maven 的项目,这取决于您的方便程度。以下是示例中的不同部分:
- 域:应用程序的核心。创建一个带有属性的 Cake 类,为了简单起见,我们将在此处添加名称。
// Consider this as a value object // around which the domain logic revolves. public class Cake implements Serializable { private static final long serialVersionUID = 100000000L; private String name; // Getters and setters for the name public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Cake [name=" + name + "]"; } }
- 入站端口:定义一个接口,我们的核心应用程序将通过该接口启用其通信。它将核心应用程序暴露给外界。
import java.util.List; // Interface through which the core // application communicates. For // all the classes implementing the // interface, we need to implement // the methods in this interface public interface CakeService { public void createCake(Cake cake); public Cake getCake(String cakeName); public List
listCake(); } - 出站端口:再创建一个接口来创建或访问外部世界,即 Cake。
import java.util.List; // Interface to access the cake public interface CakeRepository { public void createCake(Cake cake); public Cake getCake(String cakeName); public List
getAllCake(); } - 主要适配器:控制器可以是我们的主要适配器,它将提供用于创建和获取资源的端点。
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; // This is the REST endpoint @RestController @RequestMapping("/cake") public class CakeRestController implements CakeRestUI { @Autowired private CakeService cakeService; @Override public void createCake(Cake cake) { cakeService.createCake(cake); } @Override public Cake getCake(String cakeName) { return cakeService.getCake(cakeName); } @Override public List
listCake() { return cakeService.listCake(); } } 我们可以为CakeRestUI 再创建一个界面,如下所示:
import java.util.List; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; public interface CakeRestUI { @PostMapping void createCake(@RequestBody Cake cake); @GetMapping("/{name}") public Cake getCake(@PathVariable String name); @GetMapping public List
listCake(); } - 辅助适配器:这将是出站端口的实现。由于 CakeRepository 是我们的出站端口,所以让我们实现它。
import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.springframework.stereotype.Repository; // Implementing the interface and // all the methods which have been // defined in the interace @Repository public class CakeRepositoryImpl implements CakeRepository { private Map
cakeStore = new HashMap (); @Override public void createCake(Cake cake) { cakeStore.put(cake.getName(), cake); } @Override public Cake getCake(String cakeName) { return cakeStore.get(cakeName); } @Override public List getAllCake() { return cakeStore.values().stream().collect(Collectors.toList()); } } - 核心与数据源之间的通信:最后,让我们创建一个实现类,该类将负责使用出站端口在核心应用程序与数据源之间进行通信。
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; // This is the implementation class // for the CakeService @Service public class CakeServiceImpl implements CakeService { // Overriding the methods defined // in the interface @Autowired private CakeRepository cakeRepository; @Override public void createCake(Cake cake) { cakeRepository.createCake(cake); } @Override public Cake getCake(String cakeName) { return cakeRepository.getCake(cakeName); } @Override public List
listCake() { return cakeRepository.getAllCake(); } } 我们最终实现了给定示例中所有必需的方法。以下是运行上述代码的输出:
现在,让我们使用 REST API 为上述示例创建一些 Cake。以下 API 用于将蛋糕推送到存储库中。由于我们正在创建和添加数据,因此我们使用 POST 请求。例如:
- API: [ POST ]: http://localhost:8080/cake
输入体{ "name" : "Black Forest" }
- API: [ POST ]: http://localhost:8080/cake
输入体{ "name" : "Red Velvet" }
- API: [ GET ]: http://localhost:8080/cake
输出[ { "name": "Black Forest" }, { "name": "Red Velvet" } ]
六边形架构的优点:
- 易于维护:由于核心应用逻辑(类和对象)与外界隔离,松耦合,更易于维护。在任一层中添加一些新功能而不接触另一层会更容易。
- 易于适应新的变化:由于所有层都是独立的,如果我们要添加或替换新的数据库,我们只需要替换或添加数据库适配器,而无需更改应用程序的域逻辑。
- 易于测试:测试变得容易。我们可以通过使用模拟适配器模拟端口来为每一层编写测试用例。
- 入站端口:定义一个接口,我们的核心应用程序将通过该接口启用其通信。它将核心应用程序暴露给外界。