赞
踩
本文主要基于张逸老师领域驱动设计教程1-7章与本人学习体会整理,后续会持续整理领域驱动设计相关内容,因时间关系,部分内容可能会出现疏忽,望各位高手指正,谢谢!
领域驱动设计(Domain Driven Design,DDD)是由 Eric Evans 最早提出的综合软件系统分析和设计的面向对象建模方法,如今已经发展成为了一种针对大型复杂系统的领域建模与分析方法。它完全改变了传统软件开发工程师针对数据库进行的建模方法,从而将要解决的业务概念和业务规则转换为软件系统中的类型以及类型的属性与行为,通过合理运用面向对象的封装、继承和多态等设计要素,降低或隐藏整个系统的业务复杂性,并使得系统具有更好的扩展性,应对纷繁多变的现实业务问题。
领域驱动设计是针对软件开发领域提出的一套系统与理论分析方法,它建立了以领域为核心驱动力的设计体系,因而具有一定的开放性。在这个体系中,你可以使用不限于领域驱动设计提出的任何一种方法来解决这些问题。例如,可以使用用例(Use Case)、测试驱动开发(TDD)、用户故事(User Story)来帮助我们对领域建立模型;可以引入整洁架构思想及六边形架构,以帮助我们建立一个层次分明、结构清晰的系统架构;还可以引入函数式编程思想,利用纯函数与抽象代数结构的不变性以及函数的组合性来表达领域模型。这些实践方法与模型已经超越了 Eric Evans 最初提出的领域驱动设计范畴,但在体系上却是一脉相承的。这也是为什么在领域驱动设计社区,能够不断诞生新的概念诸如 CQRS 模式、事件溯源(Event Sourcing)模式与事件风暴(Event Storming);领域驱动设计也以开放的心态拥抱微服务(Micro Service),甚至能够将它的设计思想与原则运用到微服务架构设计中。
Eric Evans 在创造性地提出领域驱动设计时,实则是针对当时项目中聚焦在以数据以及数据样式为核心的系统建模方法的批判。面向数据的建模方法是关系数据库理论的延续,关注的是数据表以及数据表之间关系的设计。这是典型的面向技术实现的建模方法,面对日渐复杂的业务逻辑,这种设计方法欠缺灵活性与可扩展性,也无法更好地利用面向对象设计思想及设计模式,建立可重用的、可扩展的代码单元。领域驱动设计的提出,是设计观念的转变,蕴含了全新的设计思想、设计原则与设计过程。
领域驱动设计主要是应用在复杂系统——在某种程度上可以预测,但会有很多出乎意料的事情发生。

领域驱动设计用以表示模型的主要要素包括:
领域驱动设计围绕着领域模型进行设计,通过分层架构(Layered Architecture)将领域独立出来。表示领域模型的对象包括:实体、值对象和领域服务,领域逻辑都应该封装在这些对象中。这一严格的设计原则可以避免业务逻辑渗透到领域层之外,导致技术实现与业务逻辑的混淆。在领域驱动设计的演进中,又引入了领域事件来丰富领域模型。
聚合是一种边界,它可以封装一到多个实体与值对象,并维持该边界范围之内的业务完整性。在聚合中,至少包含一个实体,且只有实体才能作为聚合根(Aggregate Root)。注意,在领域驱动设计中,没有任何一个类是单独的聚合,因为聚合代表的是边界概念,而非领域概念。在极端情况下,一个聚合可能有且只有一个实体。
工厂和资源库都是对领域对象生命周期的管理。前者负责领域对象的创建,往往用于封装复杂或者可能变化的创建逻辑;后者则负责从存放资源的位置(数据库、内存或者其他 Web 资源)获取、添加、删除或者修改领域对象。领域模型中的资源库不应该暴露访问领域对象的技术实现细节。
技术复杂度与业务复杂度并非完全独立,二者混合在一起产生的化合作用更让系统的复杂度变得不可预期,难以掌控

要避免业务逻辑的复杂度与技术实现的复杂度混淆在一起,首要任务就是确定业务逻辑与技术实现的边界,从而隔离各自的复杂度。在理想状态下,我们应该保证业务规则与技术实现是正交的。
分层架构
Domain Layer
)中,而将支撑业务逻辑的技术实现放到基础设施层(Infrastructure Layer
)中。同时,领域驱动设计又颇具创见的引入了应用层(Application Layer
),应用层扮演了双重角色。一方面它作为业务逻辑的外观(Facade
),暴露了能够体现业务用例的应用服务接口;另一方面它又是业务逻辑与技术实现的粘合剂,实现二者之间的协作
六边形架构
结合领域驱动设计分层架构思想,基于DDDSample的Demo模块分层示例:

领域驱动设计建议我们在领域层建立资源库(Repository
)的抽象,它的实现则被放在基础设施层,然后采用依赖注入在运行时为业务逻辑注入具体的资源库实现。那么,对于处于内核之外的 Repositories 模块而言,即使选择从 MyBatis 迁移到 Sprint Data,领域代码都不会受到牵连:
package practiceddd.ecommerce.ordercontext.application; @Transaction public class OrderAppService { @Service private PlaceOrderService placeOrder; public void placeOrder(Identity buyerId, List<OrderItem> items, ShippingAddress shipping, BillingAddress billing) { try { palceOrder.execute(buyerId, items, shipping, billing); } catch (OrderRepositoryException | InvalidOrderException | Exception ex) { ex.printStackTrace(); logger.error(ex.getMessage()); } } } package practiceddd.ecommerce.ordercontext.domain; public interface OrderRepository { List<Order> forBuyerId(Identity buyerId); void add(Order order); } public class PlaceOrderService { @Repository private OrderRepository orderRepository; @Service private OrderValidator orderValidator; public void execute(Identity buyerId, List<OrderItem> items, ShippingAddress shipping, BillingAddress billing) { Order order = Order.create(buyerId, items, shipping, billing); if (orderValidator.isValid(order)) { orderRepository.add(order); } else { throw new InvalidOrderException(String.format("the order which placed by buyer with %s is invalid.", buyerId)); } } } package practiceddd.ecommerce.ordercontext.infrastructure.db; public class OrderMybatisRepository implements OrderRepository {} public class OrderSprintDataRepository implements OrderRepository {}
对缓存的处理可以如法炮制,但它与资源库稍有不同之处。资源库作为访问领域模型对象的入口,其本身提供的增删改查功能,在抽象层面上是对领域资源的访问。因此在领域驱动设计中,我们通常将资源库的抽象归属到领域层。对缓存的访问则不相同,它的逻辑就是对 key 和 value 的操作,与具体的领域无关。倘若要为缓存的访问方法定义抽象接口,在分层的归属上应该属于应用层,至于实现则属于技术范畴,应该放在基础设施层:
package practiceddd.ecommerce.ordercontext.application; @Transaction public class OrderAppService { @Repository private OrderRepository orderRepository; @Service private CacheClient<List<Order>> cacheClient; public List<Order> findBy(Identity buyerId) { Optional<List<Order>> cachedOrders = cacheClient.get(buyerId.value()); if (cachedOrders.isPresent()) { return orders.get(); } List<Order> orders = orderRepository.forBuyerId(buyerId); if (!orders.isEmpty()) { cacheClient.put(buyerId.value(), orders); } return orders; } } package practiceddd.ecommerce.ordercontext.application.cache; public interface CacheClient<T> { Optional<T> get(String key); void put(String key, T value); } package practiceddd.ecommerce.ordercontext.infrastructure.cache; public class RedisCacheClient<T> implements CacheClient<T> {}
缓存接口放在了系统的应用层,从层次的职责来看,这样的设计是合理的,但它却使得系统的应用层变得更加臃肿,职责也变得不够单一了。这是分层架构与六边形架构的局限所在,因为这两种架构模式仅仅体现了软件系统的逻辑划分。倘若我们将一个软件系统视为一个纵横交错的魔方,前述的逻辑划分仅仅是一种水平方向的划分;至于垂直方向的划分,则是面向垂直业务的切割。这种方式更利于控制软件系统的规模,将一个庞大的软件系统划分为松散耦合的多个小系统的组合。
领域模型是对业务需求的一种抽象,其表达了领域概念、领域规则以及领域概念之间的关系。一个好的领域模型是对统一语言的可视化表示,通过它可以减少需求沟通可能出现的歧义;通过提炼领域知识,并运用抽象的领域模型去表达,就可以达到对领域逻辑的化繁为简。模型是封装,实现了对业务细节的隐藏;模型是抽象,提取了领域知识的共同特征,保留了面对变化时能够良好扩展的可能性。
基于领域模型
对系统进行垂直划分,例如上述例子中的缓存可以视为一个独立的子系统,它同样拥有自己的业务逻辑和技术实现,因而也可以为其建立属于缓存领域的分层架构。在架构的宏观视角,这个缓存子系统与订单子系统处于同一个抽象层次。这一概念在领域驱动设计中,被称之为限界上下文
(Bounded Context)。

国际报税系统是为跨国公司的驻外出差雇员(系统中被称之为 Assignee)提供方便一体化的税收信息填报平台。客户是一家会计师事务所,该事务所的专员(Admin)通过该平台可以收集雇员提交的报税信息,然后对这些信息进行税务评审。如果 Admin 评审出信息有问题,则返回给 Assignee 重新修改和填报。一旦信息确认无误,则进行税收分析和计算,并获得最终的税务报告提交给当地政府以及雇员本人。
在早期的架构设计时,架构师并没有对整个系统的问题域进行拆分,而是基于用户角色对系统进行了简单粗暴的划分,分为了两个相对独立的子系统:Frond End 与 Office End,这两个子系统单独部署,分别面向 Assignee 与 Admin。系统之间的集成则通过消息和 Web Service 进行通信。两个子系统的开发分属不同的团队,Frond End 由美国的团队负责开发与维护,而 Office End 则由印度的团队负责。整个架构如下图所示:

采用这种架构面临的问题如下:

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。