经典的四层架构将软件系统分为四个层次,每个层次都有不同的职责和功能。经典的四层 架构如图1所示。
图1 经典的四层架构
1. 用户接口(User Interface)层
用户接口层将应用层的服务按照一定协议对外暴露。用户接口层接收用户请求,并将请求的参数经过处理后,传递给应用层进行处理,最后将应用层的处理结果按照一定的协议向调用 者返回。
用户接口层是应用的最上层,通常表现为 Controller 接口、RPC 服务提供者的实现类、定 时任务、消息队列的监听器等。
用户接口层不应包含任何业务处理逻辑,仅用于暴露应用层服务。用户接口层的代码应该非常简单。
2. 应用(Application)层
应用层协调领域模型和基础设施层完成业务操作。应用层自身不包含业务逻辑处理的代 码,它收到来自用户接口层的请求后,通过基础设施层加载领域模型(聚合根),再由领域模 型完成业务操作,最后由基础设施层持久化领域模型。
应用层的代码也应该是简单的,仅用于编排基础设施和领域模型的执行过程,既不涉及业务操作,也不涉及基础设施的技术实现。
3. 领域(Domain)层
领域层是对业务进行领域建模的结果,包含所有的领域模型,如实体、值对象、领域服务等。
所有的业务概念、业务规则、业务流程都应在领域层中表达。
领域层不包括任何技术细节,相关的仓储、工厂、网关等基础设施应先在领域层进行定义,然后交给基础设施层或者应用层进行实现。
4. 基础设施(Infrastructure)层
基础设施层负责实现领域层定义的基础设施接口,例如,加载和保存聚合根的仓储 (Repository)接口、调用外部服务的网关(Gateway)接口、发布领域事件到消息中间件的消 息发布(Publisher)接口等。基础设施层实现这些接口后,供应用层调用。
基础设施层仅包含技术实现细节,不包含任何业务处理逻辑。基础设施层接口的输入和输 出应该是领域模型或基础数据类型。
端口和适配器架构
端口和适配器架构(Ports and Adapters Architecture)又被称为六边形架构(Hexagonal Architecture),其核心思想是将业务逻辑从技术细节中解耦,使业务逻辑能够独立于任何特定的技术实现。
端口和适配器架构通过引入两个关键概念来达到这个目标:端口(Port)和适配 器(Adapter)。
端口是系统与外部进行交互的接口,它定义了系统对外提供的服务以及需要外部提供的支持。
“定义系统对外提供的服务”通常是指定义可以被外部系统调用的接口,将业务逻辑实现在接 口的实现类中,这种端口属于入站端口(Inbound Port)。
“定义需要外部提供的支持”,是指执行业务逻辑的过程中,有时候需要依赖外部服务(例如从外部服务加载某些数据以用于完成计算),此时定义一个接口,通过调用该接口完成外部调用,这种端口属于出站端口(Outbound Port)。
适配器则细分为主动适配器(Driving Adapter)和被动适配器(Driven Adapter)两种。主 动适配器用于对外暴露端口,例如将端口暴露为 RESTful 接口,或者将端口暴露为 RPC 服务;被动适配器用于实现业务逻辑执行过程中需要使用的端口,如外部调用网关等。
六边形架构如图2所示。
图2 六边形架构
端口和适配器之间的交互关系如图 2-4 所示。
图3 端口和适配器之间的交互关系
主动适配器伪代码如下。
/**
* 主动适配器 , 将创建文章的 Port 暴露为 HTTP 服务
*/
@RestController
public class ArticleController {
@Resource
private ArticleService service;
@RequestMapping("/create")
public void create(DTO dto) {
service.create(dto);
}
}
进站端口伪代码如下。
public interface ArticleService {
/**
* 端口和适配器架构中的 Port, 提供创建文章的能力
* 这是一个进站端口
* @param dto
*/
void create(DTO dto);
}
出站端口伪代码如下。
public interface AuthorServiceGateway {
/**
* 端口和适配器架构中的 Port, 查询作者信息
* 这是一个出站端口
* @param authorId
* @return
*/
AuthorDto queryAuthor(String authorId);
}
被动适配器伪代码如下。
/**
* 被动适配器
*/
public interface AuthorServiceGatewayImpl implements AuthorServiceGateway {
/**
* 作家 RPC 服务
*/
@Resource
private AuthorServiceRpc rpc;
AuthorDto queryAuthor(String authorId) {
// 拼装报文
AuthorRequest req = this.createRequest(authorId);
// 执行 RPC 查询
AuthorResponse res = rpc.queryAuthor();
// 解析查询结果并返回
return this.handleAuthorResponse(res);
}
}