[领域驱动设计]-02-架构与其他重要概念
引言
领域驱动设计的最终目的是能够构建出良好的、可维护的软件架构。那么,一个设计优良的软件架构应该具有如下特点:
- 与外部依赖独立
- 与底层数据源独立
- 与前端展示逻辑独立
- 易于测试与更新
- 这部份的内容主要参考《实现领域驱动-中文版》
- 要满足以上特性,实际上需要软件的各个部分之间不依赖于具体的实现,只依赖于抽象的接口。
架构
一个典型的DDD架构设计
典型的DDD架构中,包含用户展示层、应用层、领域层与基础设施层。通常来说,只有相邻的层之间才会发生耦合,不相邻的层之间极少发生耦合(观察者模式)
- 应用层中主要执行应用服务逻辑,是相对简单的,对聚合的操作逻辑。若出现过于复杂的代码,则说明领域逻辑已经渗透到了应用层。
- 通常会使用依赖倒置与依赖注入的方式来改进这一经典架构。领域模型提供接口,基础设施层完成对应的实现。
六边形架构(端口与适配器)
在六边形架构中,不强调传统web中的前端与后端。而是以系统为单位,划分为内部与外部。外部(客户)调用系统的内部功能时,通过适配器进行适配。
CQRS架构
在这个架构中,对查询和命令进行了不同的处理链路,并使用了隔离的持久化区。通常来说查询命令是一个只读操作,只涉及简单的sql调用(如果使用RDB)。而在命令处理器中,则会对所需的数据进行收集,并对相应的聚合进行状态的更新,同时发布一个领域事件。此时事件订阅器就会根据事件更新查询模型这边的数据,保证了数据的最终一致性。(如果需要较强的一致性,可以通过加入更新时间的字段来保证)
事件驱动架构
通常会和六边形架构一起使用,用于各个六边形系统之间的通信。本质上是一个事件发布与发现、处理的过程。
其他重要概念
这一部分的知识需要对实体、值对象、聚合与领域等基础概念有基本的了解,相关的内容在01部分有介绍。
领域服务
在一些业务逻辑中,会涉及到多个不同的聚合或不同的实体。此时,将业务逻辑单一的归依给某一个实体或聚合是不够合理的。此时通常需要借助领域服务来明晰整体逻辑。但使用领域服务时应经过慎重的考虑,在不必要的场景过多的使用领域服务,将会导致实体模型退化为贫血模型。
- 在创建领域服务时,我们可以根据领域服务的泛用性,决定是否要创建对应的接口。当然,我们也可以跳过接口的创建,直接创建一个实现类,这种场景中,通常我们的领域模型不会有较多的变化与场景适配需求。一般来说,接口类会被放置在领域模型中,而实现类会被放在基础设施模块中。
领域事件
领域事件是领域专家关心的事件。通常我们会使用观察者模式来对领域事件进行发布、订阅与处理。单机版代码见《实现领域驱动设计-中文版》(P265-267)
在绝大多数的生产环境下,都涉及到与远程环境的领域事件发布。此时,需要借助消息中间件完成发布、订阅过程。此时,需要保证模型的最终一致性,我们至少需要在消息中间件的存储模块与领域模型的存储模块中保持一致性,这样不会因为各种网络问题,导致领域事件未能发布出去。这里有三种基本的方式保证一致性:
- 共享存储设施
- 使用全局事务控制
- 在领域模型的持久化中,额外增加领域事件的数据库表(P272)
通过领域事件的方式,可以将任何系统设置成自治的,避免对远程过程调用的使用(RPC有时候是不可靠的,当你依赖越多的RPC API,不可靠性就会越高)。使用消息中间件时,还需要注意消息的去重方案,避免相同的事件被订阅方多次处理,造成不幂等的后果。
再议聚合
- 在系统中,若设计出过大的聚合,则不同的客户对聚合进行并发修改时,因为要保证事务的正确执行,会造成严重并发问题。如书中所举例的,如果我们使用聚合版本号这一乐观锁的方式,则会导致许多并发请求失败的情况。我们可以将大聚和中不必要的模块,拆成若干小聚合,小聚合之间的修改互不影响,但弊端是可能会给客户端增加一些不便性。
- 同时,过大的聚合在执行业务逻辑时,需要加载许多不必要的实体对象,会浪费大量的I/O性能。如果大聚合在逻辑上难以分化成小聚合,那么在引用若干个其他聚合根时,需要避免直接引用对象,而是采用引用全局id的形式(比如 orderId),这样也能避免并发修改不同小聚合根时带来的事务失败问题。当然,如果你有足够的理由与经验,可以打破这些原则。(关于事务的使用,一般是出现在应用层中,而不是领域层中。)
- 同时,尽量避免在聚合中注入资源库或领域服务。通常我们会在应用服务中注入这些。
参考
《实现领域驱动设计》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?