# DDD-Demo **Repository Path**: jiming/DDD-Demo ## Basic Information - **Project Name**: DDD-Demo - **Description**: 抽空依旧DDD对业务简单的进行重构,主要用于深入DDD的设计理念,以及在项目中做尝试做一些了解 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2020-09-19 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## DDD程序设计 近期打算在项目上进一步实践DDD程序设计理念,主要的问题在于业务的复杂度上升。 ### 传统程序设计 目前在的程序设计上 遇到的一些阻碍。 传统的程序设计是一种三层的分层架构,即**Controller->Service->DAO**,毋庸置疑,传统的三层程序架构已经是一种非常精良的程序设计,也经历了很长的时间考验,但是在如今业务复杂度以及组织架构复杂度双层不断上升,已经逐渐出现了一些疲软。 传统三层架构的特点是什么呢? - **Controller**的核心职责在于接口定义,参数校验,权限校验等等 - **Service**的核心职责在也业务逻辑处理 - **DAO**的核心职责在于处理数据库的连接,访问等等,与持久化相关 事实上,三层体系中,**Controller**与**DAO**层都是比较轻的一层,比如 ```java @ReponseBody @GetMapping("/query/user/") public Result queryUser(@RequestParam Long userId){ return userService.queryUser(); } public User selectOne(UserQuery userQuery) { return sqlTemplete.selectOne(userQuery); } ``` 比较轻的原因在于,这两层是不包含业务逻辑的,但是轻,看着舒服,简单,而另外的问题则导致**Service**过重,过重的原因在于它囊括了数据校验,复杂的业务逻辑等等,比如 ```java public Result submitOrder(Order order) { // 校验Order信息 // 生成Order订单号 // 校验OrderItem信息 // 校验优惠券,折扣等等 // ... } ``` 下单业务复杂,以至于整个**Service**方法看起来很重。其次有些业务场景需要聚合多个**DAO**来完成一个特定的查询业务场景(语义上有一次数据库操作可以完成的含义),而这样的业务场景却可能只是一次操作单元中的一部分,把业务放在DAO层即DAO层相互依赖非是良好的解决方案,故而把它上升到Service层做处理,但是对于Service层而言并非是一个完整的业务形态。 后来有些设计引入了**Facade**,试图为**Service**分担,但是效果并不佳。 不能从既定的业务架构来对比孰优孰劣,传统程序设计自有它的优点,领域驱动设计自有它的独到之处。 ### 领域驱动设计+六边形构架设计 从**Entity**的贫血模型到**Domian**的饱满模型 ```java class UserEntity { Long id; // 数据库唯一id Integer age; // 年龄 String phone; // 手机号码 String identity; // 身份证 // .... // getter & setter } class UserDomain { UserId id; // Age age; Phone phone; Identity identity; // ... // getter & setter // ... } ``` 从**UserEntity**的实现来看,贫血模型的问题在于它只具备存储数据的能力,即属性,但是并不具备行为能力 如果要创建一个**UserEntity**需要经历什么步骤呢?比如 ```java public UserEntity createUser(Integer age, String phone, String identity) { if(age == null || age < 0 || age > 200) throw new AgeException(); if(phone == null || phone.match("正则表达式")) throw new PhoneException(); if(identity == null || identity.match("正则表达式")) throw new IdentityException(); UserEntity userEntity = new UserEntity(); userEntity.set... return userEntity; } // UserService public void changeIdentityCardNumber(Long userId, String identity) { UserEntity userEntity = userDAO.findById(userId); if(userEntity == null) throw UserNotExistsException(); if(identity == null || identity.match("正则表达式")) throw new IdentityException(); userEntity.setIdentity(identity); userDAO.saveOrUpdate(userEntity); } ``` 大量的数据校验,与创建用户实体对象的实际业务看似紧密相关,实则不然,如下: ```java public class Age { Integer age; public Age(Integer age) { if(age == null || age < 0 || age > 200) throw new AgeException(); this.age = age; } public Age incr() { return new Age(age + 1); } // ... } ``` 其他的类似,通过值对象来描述年龄,并赋予年龄更饱满的行为机制,而不是仅仅作为一个整型数据。 ```java public UserDomain createUserDomain(Integer age, String phone, String identity) { return UserFactory.create(new Age(age), new Phone(phone), new Identity(identit)); } // userService public void changeIdentityCardNumber(Long userId, String identity) { UserDomain user = userRepository.findById(userId); user.changeIdentity(identity); userRepository.save(user); } // userRepository public UserDomain findById(Long userId) { UserEntity userEntity = userDAO.findById(userId); return userFactory.toUser(userEntity); } // userFactory // 将userEntity -> userDomain // userDomain public void changeIdentity(String identity) { this.identity = new Identity(identity); // } ``` 二者相较之,即把业务无关的细节都隐藏到了各个丰富的值对象以及聚合跟去了,**Service**上的方法剩下最核心的流程。 那么什么是六边形架构?回看三层架构中的DAO层,它其实并非是一层接口抽象层,意味着同样的业务如果只需要切换数据库,比如从MySQL->MongoDB, 其成本可想而知,不只是再多一个DAO,还需要把Service层设计到的做修改。 回看DDD架构中的Repositroy层,语义上它是存储层,但是并不关心是何种存储介质,而更加关系业务逻辑,比如save(),它的语义是指将保存或更新对象,而具体的额实现则交给了再下层的DAO层。 如果需要切换存储介质,只需要重新实现Repository,并切换Service对Reposiroy的引用即可。如果是Spring更简单,一个@Repository迁移而已 六边形架构的核心在于将业务与基础设施完全隔离,业务不再关心底层技术,那么技术的更新换代对业务完全隔离。 来看一个具体的业务场景 - 创建订单 - 计算订单的总金额 - 根据优惠券从优惠券服务查询该优惠券值抵扣金额 - 计算订单折扣的金额 - 发送审计信息 ```java public class OrderApplication { private OrderRepository orderReponsitory; private CouponRemoteService couponRemoteService; private RocketMQProducer rocketMQProducer; public Result submit(Long orderId, Coupon coupon) { Order order = orderReponsitory.fingById(orderId); BigDecimal totalAmount = order.getItem() .stream() .sum(it.price * it.discount * it.quantity); BigDecimal couponAmount = couponRemoteService.queryCoupon(coupon.id); BigDecimal finalAmount = totalAmount - couponAmount; order.setFinalAmount(finalAmount); rocketMQProducer.send("Topic", "Tag", JSONObject.toJson(order)); return new Result(order); } } ``` 从业务的角度来看,业务关注的发送审计信息,而不是关注通过什么技术发送,比如RocketMQ, Kafka, 亦或者内存队列,远程调用, 同样的,业务关注优惠券最终值多少抵扣金额,而不关注优惠券是从远程服务查询,还是本地查询,还是其他 优惠券 ```java public interface CouponService { public BigDeciaml tranfer(Coupon coupon); } @Compeont public class CouponServiceImpl implements CouponService{ @Resurce private CouponRemoteService couponRemoteService; public BigDeciaml tranfer(Coupon coupon) { // TODO return couponRemoteService.queryCoupon(coupon.id); } } ``` 审计消息 ```java public class OrderAuditMessage { OrderId orderId; OrderStatus orderStatus; HandlerId handlerId; CreateTime createTime; // .... } public interface OrderAuditMessageService { public void send(OrderAuditMessage orderAuditMessage); } @Compeont public class OrderAuditMessageServiceImpl implements OrderAuditMessageService { @Resource private RocketMQProducer rocketMQProducer; public void send(OrderAuditMessage orderAuditMessage) { rocketMQProducer.send("Topic", "Tag", JSONObject.toJson(order)); } } ``` OrderApplicationService ```java public Result submit(Long orderId, Coupon coupon) { Order order = orderReponsitory.fingById(orderId); // 这里可以考虑将该业务封装到DomainService中 // reduceCouponAmountService.reduce(order, coupon); order.reduceCouponAmount(couponServiceImpl.tranfer(coupon)); orderAuditMessageService.send(OrderAuditMessageFactory.create(order)); // 将创建OrderAuditMessage放到工厂类中创建 return new Result(order); } ``` 从示例中看六边形架构,底层逻辑其实是面向接口编程,分离抽象跟具体实现,并隐藏具体的实现,让业务更关注业务,技术更关注技术 ```java ------------------------------------------------------------ ┆ UserInterface-> [Controller] [MessageListener] ┆ ------------------------------------------------------------ ┆ ApplicationService-> [ApplicationService] ┆ -> [QueryService] -------------------------┆---------------------------------- ┆ Domian-> [Domain] [DomainService] [ValueObject] .... ┆ ------------------------------------------------------------ ┆ Inteface-> [Adapte] [Repository] [MessageService] ┆ --------------------┆-------┆-------------┆--------┆-------- ┆ Infrastructure -> [MQ] [RemoteService] [MySQL] [Redis] ┆ ------------------------------------------------------------ ``` ### 干净架构 干净架构是12年提出的一种适用于复杂业务系统的架构方式,个人看来,它属于是传统三层架构的延展,强调业务与技术的分离 ```java -> Controller -> Service [Adapte] -> Infrastructure -> Entity ``` 干净架构的核心还是外绕着Entity不断的往外延展,附加了一层适配器来调节业务与技术,让业务与技术可以独立测试,一次屏蔽了技术更新迭代对业务的感知 不管是领域驱动设计还是干净架构设计,无不在强调**分离** ### CQRS架构 命令查询职责分离架构,简而言之,读写分离职责分离架构 CQRS架构可能涉及到Event Sourcing,但是其实二者没有直接的关系,个人理解是在使用了命令来改变系统状态时,可以通过ES来发布变更事件,至于变更事件做了什么,比如记录日志 领域驱动设计中并没有提及关于读接口需要如何来设计,所以很多实际落地应该是DDD+CQRS