领域驱动设计精粹pdf

领域驱动设计(DDD)实践之路(第二篇)

在领域驱动里面,infrastructure作为基础设施,是提供技术细节的模块。需要强调的是,很多人会误以为infrastructure就是传统的DAO层,其实infrastructure包括但不限于DAO层,比如文件处理,三方调用,使用缓存,发送异步消息等具体的技术细节实现都存在于infrastructure层。那么技术细节是什么呢。在我们看来,技术细节包含以下特征

案例1:我们的实体需要持久化(存储),所以我们需要提供存储的实现。领域层的repository.save等方法提供了持久化接口约定,对于infrastructure来说,如何实现这个方法的代码,就是技术细节。那么我们如何实现这个过程呢?自然是选择缓存,OSS存或者数据库存。如果选择数据库,则进而需要选择orm框架,配置…,实现repository.save的接口,这些都属于持久化所需的技术细节代码。
案例2:我们的应用需要导出资产包相关的excel形式数据,那么当导出资产包数据时,文件领域模块提供了导出的统一接口,资产领域模块提供了资产包的适配接口,而导出excel的代码需要使用easyExcel或者POI等第三方框架,属于技术细节代码。
案例3: 接案例2,为了实现导出时所需的excel排版格式,排版本身的格式与业务有关,比如在我们的业务场景下,我们导出调解明细(我们项目特定的一个领域模型)的时候,只需要按照常见的导出方式即可,而导出资产明细(我们项目特定的一个领域模型)则需要解析拼接所有的动态数据列,合并显示每条数据不同的动态列,而这一切是由业务决定的。根据业务不同有不同的排版要求这一点体现了资产域需要提供文件域的导出策略,调解域也需要实现文件域的导出策略。这些都属于描述业务信息的约定,而这些约定的具体实现比如怎么把实体的那一个属性映射到excel的哪一行哪一列,则属于技术细节。这种区分方式显性化了业务的概念,同时又将实现放在了基础设施层,提供了一定的解耦性。
说完了infrastructure的技术细节的定义,我们接下来聊几个在采用DDD研发模式下,infrastructure层开发过程中经常会遇到的一些问题及我们的解决方案。

为了让业务逻辑和代码实现解耦,在repository的约定中,我们通常用“save保存”代替我们通常说的“insert(插入)“,”update(更新)”这样的技术术语,以屏蔽技术细节。这样带来的一个副作用是,在save时就需要根据策略判断调用insert还是update,我们使用的策略是根据id是否是空决定,即我们所有的实体对象都有一个属性,类型为Id类的子类,id对象的属性(数据库里面实际存放的id值)可能为null,但是id对象,本身不会为null,根据这个对象可以判断当前实体id是否为空。

对于聚合场景,子实体是需要知道聚合根的id的,因为在存储到数据库时可能需要以外键的方式存储对象间的映射关系。

然而,在具体实现中,我们认为,实体之间的对象关系才是标识两个实体之间关系的方式,而不是id,所以生成实体时,先通过对象引用关联对象,表明聚合和实体之间的关系,在保存到数据库的时候,通过实体生成数据库映射类的时候就可以知道当前数据的id是否为空,同时又能知道当前数据之间的关系。

对象之间的关系在1:1聚合保存的时候可能体现不明显,但是当1:N或者N:N批量保存聚合的时候,作用就比较明显了。在我们的系统中发起调解业务就需要批量保存调解批次。代码如下(欢迎吐槽,拥抱进步)

通过这种方式就解决了批量插入不能返回id,同时又能继续复用id.isNew()判断是否为新数据的方式(这里我们没有创建entity基类,所以判断放在了Id上)。

以上方法提供了批量保存时如何区分是新增还是更新。下面我们来谈谈我们项目内提供的插入和更新模板代码。

对于领域来说,save是基本的保存代码。方法传入的参数往往是一个存在于内存中的聚合根对象,有时包含全量的子实体,VO和全量的字段,而在插入场景,对批量请求我们希望支持批量插入,减少对数据库的IO频率,在更新场景下,我们希望减少update时的更新字段的数量(只更新需要更新的字段),这有助于减少数据库IO次数、binlog大小和mysql数据库索引变更带来的开销,所以是非常有必要的。因此对于infrastructure来说,可以提供统一的定制化模板方便repository定制化更新字段的方法快速实现。

由于我们的系统使用的是mybatisplus的ORM方案,所以我们根据api和mysql的批量语句开关提供了一个批量插入和批量更新的Mapper基类,其中insertBatchSomColumn是mybatisplus自带的,updateBa
最后总结一下

DDD领域驱动设计-DDD概览

# DDD概览

## 启迪

领域可以理解为业务,领域专家就是对业务很了解的人。

限界上下文也就是微服务的边界,也可以理解为微服务,一个限界上下文=一个微服务。

个人理解领域驱动设计就是微服务驱动设计,从战略上先进行微服务的划分,从战术上针对某个微服务进行领域模型的设计也就是业务模型的设计。

领域模型包括:
– 实体
– 值对象
– 聚合
– 领域服务
– 领域事件
– 资源库
– 应用服务

## 什么是领域驱动设计?

理解领域驱动设计是什么之前,我们先来理解下什么是领域?
领域可以理解为业务,领域专家就是对业务很了解的人。
领域驱动设计的核心就是和最了解业务的人也就是领域专家一起通过领域建模的方式去设计我们的软件程序。

– 那么领域如何驱动设计?或者说业务如何驱动设计?

传统开发过程我们都是基于面向数据开发,拿到产品原型脑海里想着都是应该创建哪些表和哪些字段才能满足需求。
而领域驱动设计开发过程是让我们基于面向业务开发、面向领域模型开发。

领域模型的核心是通过承载和保存领域知识,并通过模型与代码的映射将这些领域知识保存在程序代码中,
在传统开发中,当业务被转换为一张张数据表时,丢失最多的就是领域知识(领域知识也就是我们在模型中定义的一些业务逻辑行为)。

面向领域模型开发的优点:
– 存储方便,统一使用JSON进行存储。
》 例:
》 订单领域包含基础信息、商品信息、金额信息、支付信息等包含订单全生命周期的子域,
》 对于传统面向数据的开发模式我们需要创建N张表进行存储订单的信息,但是面向领域开发时我们
》 可以通过利用nosql数据库(mongo、es等)进行保存整个订单域的信息,提高查询、更新效率,简化代码
– 复用性高,引用某个领域模型,就可以拥有该领域模型的所有行为。
》 例:
》 基于微服务架构下,某个电商应用需要一个判断某个订单是否是在线支付订单的逻辑时,
》 对于传统的开发模式我们需要调用订单中心的服务查询订单信息,然后写一个判断是否在线支付订单的方法。
》 如果有多个应用都需要这个逻辑时,每个应该都需要重复写相同的方法。
》 但面向领域开发时,只需要引用订单中心的jar包,然后统一调用订单领域内的方法即可。
》 这样就实现了业务的高内聚

## DDD可以做什么

DDD主要分为两个部分,战略设计与战术设计

– 战略设计
– 围绕微服务拆分
– 战术设计
– 微服务内部设计

## DDD怎么做

– 战略设计
– 和领域专家一起通过(过往经验、事物联系、事件风暴等)划分【限界上下文】
》 限界上下文也就是微服务的边界,也可以理解为微服务。
》 一个限界上下文=一个微服务
– 战术设计
– 开发人员通过(领域模型)保存【领域知识】
》 领域知识也就是事物(角色)、行为(规则)和关系

## DDD领域模型

领域模型包含什么?

– 实体
》 具有唯一标识,包含着业务知识的【充血模型】对象,用于对唯一性事物进行建模。
》 例:
》 “`
》 public class Order {
》 private long orderId;
》 private OrderAmount amount;
》 private List item;
》 }
》 “`
– 值对象
》 生成后即不可变对象,通常作为实体的属性,用于描述领域中的事物的某种特征。
》 例:
》 “`
》 public class OrderItem {
》 private long orderId;
》 private String productCode;
》 private String productName;
》 }
》 “`
– 聚合
》 将实体和值对象在一致性边界之内组成聚合,使用聚合划分微服务(限界上下文)内部的边界
– 领域服务
》 分担实体的功能,承接部分业务逻辑,做一些实体不变处理的业务流程。不是必须的
》 主要承接内部领域服务调用和外部微服务调用,及一些聚合业务逻辑处理。
》 例:
》 “`
》 @Service
》 public class ShoppingcartDomainService {
》 private final ShoppingcartRepository shoppingcartRepository;
》 private final ProductFacade productFacade;
》 private final UserFacade userFacade;
》 private final PromotionFacade promotionFacade;


》 // 1.查询购物车信息
》 ShoppingcartDO entity = shoppingcartRepository.loadShoppingcart(userId);

》 // 2.调用【用户中心】服务查询用户信息
》 User user = userFacade.getUser(userId);

》 // 3.调用【商品中心】服务查询商品信息
》 Product product = productFacade.getProduct(productCode);

》 // 4.调用【活动中心】服务查询活动信息
》 Promotion promotion = promotionFacade.getPromotionByProductCode(productCode);

》 // 5.创建购物车实体
》 Shoppingcart shoppingcart = new Shoppingcart(entity.getId, user, product, promotion);

》 // 6.购物车按活动分组
》 shoppingcart.groupby4Promotion();
》 }
》 “`


– 领域事件
》 表示领域中发生的事情,通过领域事件可以实现本地微服务(限界上下文)内的信息同步,同时也可以实现对外部系统的解耦
– 资源库
》 保存聚合的地方,将聚合实例存放在资源库(Repository)中,之后再通过该资源库来获取相同的实例。

– 应用服务
》 应用服务负责流程编排,它将要实现的功能委托给一个或多个领域服务来实现,
》 本身只负责处理业务用例的执行顺序以及结果的拼装同时也可以在应用服务做些权限验证等工作。
》 !(images/application-service.png)

领域驱动实战-支付系统

在Airwallex,领域驱动设计(DDD)方法被用来指导我们的工程师如何对复杂的业务问题和系统设计建模。在这篇博客中,我们提供了一个全面的工作流,我们使用DDD模式进行建模,然后对支付系统进行落地。

全球支付系统是复杂和不断变化的,涉及从订单、欺诈、通知、与各种支付方式的集成到清算和结算等广泛的板块。

在处理复杂的系统时,大多数开发人员可能会遇到一些一致的问题:

在Airwallex,领域驱动设计(DDD)方法被用来指导我们的工程师如何解决复杂的业务问题和系统建模。

然而,DDD只是各种模式的集合,很难将其应用于系统设计。在这篇博客中,我们提供了一个全面的工作流程,介绍了我们是如何在Airwallex应用领域驱动设计的,从中得到的经验教训,以及我们接下来要做什么。

领域驱动设计(由Eric Evans提出)是一组帮助基于业务领域的底层模型设计软件系统的思想、原则和模式。DDD有两个不同的空间,问题空间和解决方案空间。

在问题空间中,您使用战略模式定义系统的顶层的系统层次,这些战略模式关注域、子域和通用语言的分析。

在解决方案空间中,采用战术模式来提供一组设计模式,您可以使用这些设计模式创建领域模型。这些模式包括有界上下文、上下文映射、实体、聚合、领域事件、领域服务、应用程序服务和基础设施。这些战术模式将帮助您设计既松散耦合又具有内聚性的微服务。

下面是一个常见的案例:
一位顾客想在该商家的网站上购买一件价格为10美元的t恤。顾客可以用多种支付方式来支付这件t恤,比如Visa卡或微信钱包。在客户支付后,商家会从支付网关收到一个通知,显示客户的支付已经成功。然后,商家可以在Airwallex webapp中查看支付细节,包括购买价格、商家费用以及资金将何时进入Airwallex Global Account钱包。

下面是分析结果。

支付系统

付款处理:商家可以通过各种付款方式接受客户的付款。
金融:清算和解决商家的付款资金。

付款意向:商家创建的订单的价格,产品,客户等。

付款尝试:商家创建的交易以获得订单的客户。

付款方式:客户支付产品的方式。

付款结算:付款之 后钱进入商家钱包。

付款视图:汇总付款详细信息视图,包含与一笔付款相关的所有数据。

有界上下文(BC)限定了域模型的范围。根据对问题空间的分析结果,我们可以定义以下边界上下文:

支付网关: API网关,为商家提供restful API来创建或查看支付。

支付核心模块: 支付意图,方法资源管理。

支付适配器: 与一个外部的PSP集成,例如微信,支付宝,Visa,万事达等。

支付结算: 计算并结算商户每次支付的原则和费用。支付融合:支付明细汇总视图。

下面是生成的上下文映射的一个示例:

从上面分析的场景和通用语言中,我们可以确定以下聚合、实体、值对象和域事件:

根据我们的经验,领域服务为单个聚合使用业务逻辑服务,遵循单一责任。通常,我们将封装领域仓储、聚合修改和在领域服务中发布的领域事件。以PaymentAttemptExecutorService为例:

领域事件可以使系统更具有可扩展性,并避免任何耦合,且一个聚合不应该决定其他聚合应该做什么。

例如,当PaymentCaptureCommand命令将支付状态更改为已支付时,会发出领域事件PaymentAttemptCapturedEvent。在PaymentAttemptCapturedEvent的领域事件处理程序(EventHandler)中,我们可以在该业务逻辑上加上你想要的逻辑。例如,通知支付聚合有界上下文更新支付详情,通知支付结算有界上下文计算结算金额和费用。

在DDD模式中,基础设施层作用于将核心业务领域与技术实现细节分开。该层通常采用ACL (anti – corruption-layer)模式。以领域仓储为例:

领域仓储只定义接口功能,但实现细节应该隐藏在基础设施层中,细节上你可以使用PostgreSQL或MongoDB来保存数据。例如,在基础设施层中,PaymentAttemptPgRepository是基于PostgreSQL的特定实现,而toPO是一个转换器,用于将域对象PaymentAttempt转换为持久化对象。

现在,我们已经为支付系统定义了一组有界上下文,并在每个有界上下文中标识了一组实体、聚合和领域事件服务。下一步是从域模型到应用程序微服务设计。这里,我们选择将一个有边界的上下文映射到一个微服务。

采用DDD可以提供许多好处,例如,在所有团队之间进行清晰的沟通,以及在设计系统时使用成熟的模式来管理复杂性并提供更好的可伸缩性。

使用通用语言,我们可以实现更多的自描述类名和函数名。
使用聚合模式,我们可以实现清晰的边界和单一的职责。
使用领域事件模式,我们可以分割核心业务流程,减少聚合之间的耦合。
通过基础设施层和ACL模式,我们可以将核心业务领域模型与技术实现细节分离开来。通过限定上下文模式,我们可以派生出潜在的微服务候选对象。

在实践中应用DDD时,我们想要分享一些挑战和经验教训:

原文地址: https://medium.com/@chaojie.xiao/domain-driven-design-practice-modeling-payments-system-f7bc5cf64bb3

DDD(领域驱动设计)从入门到精通

一、DDD领域驱动设计 – 基本原理

1、 DDD领域驱动设计 – 入门介绍
2、 DDD领域驱动设计 – 基本概念
    2.1. DDD领域驱动设计 – 领域和子域
    2.2. DDD领域驱动设计 – 限界上下文
    2.3. DDD领域驱动设计 – 实体和值对象
    2.4. DDD领域驱动设计 – 聚合和聚合根
    2.4. DDD领域驱动设计 – 领域事件
3、 DDD领域驱动设计 – 分层架构

二、DDD领域驱动设计 – COLA 4.0架构

4、 阿里开源COLA 4.0 – 应用架构的最佳实践
5、 阿里开源COLA 4.0 – 开发实践

参考资料:《中台架构与实现:基于DDD和微服务》

什么是领域驱动设计

领域驱动设计(Domain Driven Design)是一种软件开发方法,目的是让软件系统在实现时准确的基于对真实业务过程的建模并根据真实业务过程的调整而调整。