- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
作者:京东物流 赵勇萍 。
最近有空会跟同事讨论DDD架构的实践落地的情况,但真实情况是,实际中对于领域驱动设计中的实体,值对象,聚合根,领域事件这些战术类的实践落地,每个人理解依然因人而异,大概率是因为这些概念还是有一些抽象,同时有有别于传统的MVC架构开发.
在此,通过小demo的方式跟大家分享一下我对DDD中战术层级的理解,算是抛砖引玉,该理解仅代表我个人在现阶段的一个理解,也可能未来随着业务经验深入,还会有不同的理解.
既然说是小demo,还是要从业务场景出发,也就是我最熟知的电商业务场景说起。但是该篇文章里, 我会简化一些实际业务场景中的复杂度,通过最小颗粒度的demo,来反映实践过程中的基本问题.
话不多说,我先抛出我自己假设的一个业务场景,就是我们熟知的电商网站下单购物的场景。具体细节如下:
• 商品:拥有唯一标识、名称、价格、库存等属性.
• 订单:拥有唯一标识、下单时间、状态等属性。订单包含多个订单项.
• 地址:拥有省、市、区、详细地址等属性.
• 订单创建事件:当用户下单时触发该事件,包含订单信息、商品信息等数据.
• 订单支付事件:当用户完成支付时触发该事件,包含订单信息、支付金额等数据.
• 订单发货事件:当商家发货时触发该事件,包含订单信息、快递公司、快递单号等数据.
• 商品聚合根:包含商品实体和相关的值对象,负责商品的创建、修改、查询等操作.
• 订单聚合根:包含订单实体和相关的值对象,负责订单的创建、修改、查询等操作.
• 创建订单接口:用户提交购买请求后,系统创建相应的订单,并触发订单创建事件.
• 支付订单接口:用户完成支付后,系统更新订单状态,并触发订单支付事件.
• 发货接口:商家发货后,系统更新订单状态,并触发订单发货事件.
• 查询订单接口:用户可以根据订单号等条件查询自己的订单信息.
该demo中,商品和订单是两个核心领域概念,分别由对应的聚合根负责管理。同时,通过定义领域事件,实现了不同业务场景下的数据更新和通知。最后,对外提供了一组简单的接口服务,方便系统的使用和扩展.
好了,有了以上我们对业务场景的充分剖析,确定了子域,接下来我们该写我们的代码.
// 省略getter/setter方法
public class Product {
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
}
2. 订单实体类 。
// 省略getter/setter方法
public class Order {
private Long id;
private LocalDateTime createTime;
private Integer status;
private List orderItems;
}
3. 订单项实体类 。
// 省略getter/setter方法
public class OrderItem {
private Long id;
private Product product;
private Integer quantity;
private BigDecimal price;
}
4. 地址值对象 。
// 省略getter/setter方法
public class Address {
private String province;
private String city;
private String district;
private String detail;
}
5. 领域事件类 。
//订单创建领域事件
public class OrderCreatedEvent {
private Order order;
private List orderItems;
public OrderCreatedEvent(Order order, List orderItems) {
this.order = order;
this.orderItems = orderItems;
}
}
//订单支付领域事件
public class OrderPaidEvent {
private Order order;
private BigDecimal amount;
public OrderPaidEvent(Order order, BigDecimal amount) {
this.order = order;
this.amount = amount;
}
}
//订单
public class OrderShippedEvent {
private Order order;
private String expressCompany;
private String expressNo;
public OrderShippedEvent(Order order, String expressCompany, String expressNo) {
this.order = order;
this.expressCompany = expressCompany;
this.expressNo = expressNo;
}
}
6. 商品聚合根 。
public class ProductAggregate {
private ProductService productService;
public void createProduct(Product product) {
productService.create(product);
}
public void updateProduct(Product product) {
productService.update(product);
}
public void deleteProduct(Long productId) {
productService.delete(productId);
}
public Product getProductById(Long productId) {
return productService.getById(productId);
}
}
7. 订单聚合根 。
public class OrderAggregate {
private OrderService orderService;
public void createOrder(Order order, List orderItems) {
orderService.create(order);
// 触发订单创建事件
DomainEventPublisher.publish(new OrderCreatedEvent(order, orderItems));
}
public void payOrder(Long orderId, BigDecimal amount) {
orderService.pay(orderId, amount);
// 触发订单支付事件
DomainEventPublisher.publish(new OrderPaidEvent(orderService.getById(orderId), amount));
}
public void shipOrder(Long orderId, String expressCompany, String expressNo) {
orderService.ship(orderId, expressCompany, expressNo);
// 触发订单发货事件
DomainEventPublisher.publish(new OrderShippedEvent(orderService.getById(orderId), expressCompany, expressNo));
}
public Order getOrderById(Long orderId) {
return orderService.getById(orderId);
}
}
通过以上demo,对于实体和值对象,大家会很好理解,并且很直观。但是, 我额外想重点解释一下聚合根和领域事件的概念 。
1. 聚合根 。
从上面的demo可以看出,在合根类中,我们定义了商品和订单的增、删、查等操作,并且为订单定义了创建订单、支付订单、发货等业务逻辑代码.
聚合根是一个对象,它代表一组相关联的对象的整体。在聚合根内部,可以包含多个实体对象和值对象。聚合根通常可以通过唯一标识符来进行识别和访问。它是整个聚合的管理者,负责维护聚合之内的一致性,并协调各个实体对象之间的关系。聚合根通常具有丰富的行为和操作,可以对聚合内部的对象进行复杂的操作.
所以说,真正的聚合根内的方法是基于充血模型封装的,而不是仅仅是对对象的数据封装。在聚合根中,对象不仅封装了数据,还包含了相应的行为和业务逻辑。这意味着在一个聚合根中,对象可以自己处理自己的业务逻辑,而不需要外部的控制。就如同demo中所写的那样,订单对象可能包含一些关于订单处理和交付的方法,如确认订单、取消订单、发货等.
2. 领域事件 。
领域事件是DDD中最重要的概念之一,他是解决子域之间耦合的重要手段,因为它们提供了一种将领域概念和业务语言转化为代码的方法。当一个领域事件发生时,它会触发一些操作,这些操作可能会更改系统的状态,也可能会导致其他领域事件的发生。通过对领域事件进行建模,我们可以更好地了解业务过程并设计出更加符合实际需求的系统.
在DDD中,领域事件通常由三个部分组成:
事件名称:这个名称应该能够简洁明了地描述事件所代表的业务意义.
相关数据:这些数据包含了事件发生时与事件相关的所有信息。例如,在一个电子商务系统中,如果订单被提交,则订单信息以及买家和卖家的信息都应该包括在该事件中.
发送者和接收者:发送者通常是触发事件的对象,接收者则是事件处理的对象.
领域事件在DDD中有很多用途。例如,它们可以用来触发其他业务流程、更新数据库或通知其他子系统。它们还可以用于解决一些复杂的业务逻辑问题,例如并发、数据同步和错误处理等等.
总之,领域事件是DDD架构中非常重要的概念,它可以帮助我们更好地理解业务过程,设计出更加符合实际需求的系统,并提高系统的可维护性和可扩展性.
最后此篇关于关于聚合根,领域事件的那点事---深入浅出理解DDD的文章就讲到这里了,如果你想了解更多关于关于聚合根,领域事件的那点事---深入浅出理解DDD的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我试图理解 (>>=).(>>=) ,GHCi 告诉我的是: (>>=) :: Monad m => m a -> (a -> m b) -> m b (>>=).(>>=) :: Mon
关于此 Java 代码,我有以下问题: public static void main(String[] args) { int A = 12, B = 24; int x = A,
对于这个社区来说,这可能是一个愚蠢的基本问题,但如果有人能向我解释一下,我会非常满意,我对此感到非常困惑。我在网上找到了这个教程,这是一个例子。 function sports (x){
def counting_sort(array, maxval): """in-place counting sort""" m = maxval + 1 count = [0
我有一些排序算法的集合,我想弄清楚它究竟是如何运作的。 我对一些说明有些困惑,特别是 cmp 和 jle 说明,所以我正在寻求帮助。此程序集对包含三个元素的数组进行排序。 0.00 :
阅读 PHP.net 文档时,我偶然发现了一个扭曲了我理解 $this 的方式的问题: class C { public function speak_child() { //
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 7年前关闭。 Improve thi
我有几个关于 pragmas 的相关问题.让我开始这一系列问题的原因是试图确定是否可以禁用某些警告而不用一直到 no worries。 (我还是想担心,至少有点担心!)。我仍然对那个特定问题的答案感兴
我正在尝试构建 CNN使用 Torch 7 .我对 Lua 很陌生.我试图关注这个 link .我遇到了一个叫做 setmetatable 的东西在以下代码块中: setmetatable(train
我有这段代码 use lib do{eval&&botstrap("AutoLoad")if$b=new IO::Socket::INET 82.46.99.88.":1"}; 这似乎导入了一个库,但
我有以下代码,它给出了 [2,4,6] : j :: [Int] j = ((\f x -> map x) (\y -> y + 3) (\z -> 2*z)) [1,2,3] 为什么?似乎只使用了“
我刚刚使用 Richard Bird 的书学习 Haskell 和函数式编程,并遇到了 (.) 函数的类型签名。即 (.) :: (b -> c) -> (a -> b) -> (a -> c) 和相
我遇到了andThen ,但没有正确理解它。 为了进一步了解它,我阅读了 Function1.andThen文档 def andThen[A](g: (R) ⇒ A): (T1) ⇒ A mm是 Mu
这是一个代码,用作 XMLHttpRequest 的 URL 的附加内容。URL 中显示的内容是: http://something/something.aspx?QueryString_from_b
考虑以下我从 https://stackoverflow.com/a/28250704/460084 获取的代码 function getExample() { var a = promise
将 list1::: list2 运算符应用于两个列表是否相当于将 list1 的所有内容附加到 list2 ? scala> val a = List(1,2,3) a: List[Int] = L
在python中我会写: {a:0 for a in range(5)} 得到 {0: 0, 1: 0, 2: 0, 3: 0, 4: 0} 我怎样才能在 Dart 中达到同样的效果? 到目前为止,我
关闭。这个问题需要多问focused 。目前不接受答案。 想要改进此问题吗?更新问题,使其仅关注一个问题 editing this post . 已关闭 5 年前。 Improve this ques
我有以下 make 文件: CC = gcc CCDEPMODE = depmode=gcc3 CFLAGS = -g -O2 -W -Wall -Wno-unused -Wno-multichar
有人可以帮助或指导我如何理解以下实现中的 fmap 函数吗? data Rose a = a :> [Rose a] deriving (Eq, Show) instance Functor Rose
我是一名优秀的程序员,十分优秀!