DailyMart03:如何基于DDD设计商城的领域模型?

简介: DailyMart03:如何基于DDD设计商城的领域模型?

大家好,我是飘渺。既然有人催更那今天咱们就继续更新DDD&微服务系列!

在面向对象开发中,所有事物都可以看作是对象。然而,在日常开发中,我们通常从数据出发来设计对象的表现形式,这种做法侧重于数据属性的定义,而忽略了领域逻辑的处理过程。虽然这种做法很常见,但并不是DDD推荐的开发模式。在DDD中,我们关注的是领域数据对象,而非仅仅是数据本身。领域模型对象在本质上不同于数据,它包含了一系列的标识和行为定义,而不仅仅是数据属性的定义。

在DDD中,领域模型对象可以分为聚合、实体和值对象三种类型。实体和值对象是聚合的组成部分,而值对象同时也是实体的组成部分。它们之间的关系如下图所示:

在上一篇文章[[DailyMart02:DDD领域分解与微服务划分]]中,我们已经对DailyMart进行了领域分解和限界上下文的拆分。在本文中,我们将从DDD战术的角度出发,对DaliyMart进行设计,分析各个限界上下文中的实体、值对象和聚合对象。


1. 领域模型对象


让我们首先了解一下这几个关键对象。

  1. 实体(Entity):实体是具有唯一标识符(ID) 的对象,它在整个生命周期中保持不变。实体的属性可以发生变化,但其ID始终保持不变。例如,在DailyMart系统中,用户可以是一个实体,因为每个用户都有一个唯一的ID,即使他们的姓名、昵称、密码等信息发生变化,这个ID也不会改变。
  2. 值对象(ValueObject):值对象与实体对象相反,它是一种不具有唯一标识符(ID) 的对象,它们是通过其属性值来定义的,它的一个重要特点是它们是不可变的,一旦创建,它们的属性就不能被修改。如果需要修改值对象的属性,我们需要创建一个新的值对象来替换旧的值对象。值对象通常表示一些简单的概念,如颜色、地址或者金额等。例如在跨境电商系统中,商品的价格通常会被设计成值对象,价格由货币类型和数值组成,我们关心的是这两个属性的组合,而不是价格对象本身的唯一性。
  3. 聚合(Aggregate):聚合是一组相关的实体和值对象的集合,它们共同组成一个整体。聚合有一个根实体,也称为聚合根,它是聚合中最重要的实体,负责维护聚合内部的完整性和一致性。聚合根可以包含其他实体和值对象,但是其他实体和值对象不能直接访问聚合根以外的实体和值对象。这种限制可以保证聚合内部的一致性和完整性,同时也可以简化聚合的设计和实现。

2. 领域建模


接下来,我们将进行领域模型分析,并从用户限界上下文开始。

2.1 用户限界上下文

为了完成限界上下文的领域建模,我们需深入分析业务场景和需求,以形成业务领域的通用语言。

通过与业务人员的深入沟通,我们总结出以下通用语言:

  • 商城用户首先需要注册,填写账号、密码、邮箱、手机号码等信息,注册成功后,系统赠送100积分。
  • 用户购买商品后,系统会赠送一定积分。这些积分在支付时可以抵扣现金,用户还可以查看自己的积分记录。
  • 用户登录系统后,可以维护自己的收货地址。每个用户可以维护多个地址,可以选择删除其中一个地址或将其中一个地址设置为默认收货地址。商品购买后,系统会将商品配送到默认地址。

根据这些业务需求,我们识别出了一些关键的领域模型:

  1. CustomerUser - 用户,显然是一个实体。它包含UsernamePasswordEmailPhoneNumber等属性,并拥有Points(积分)属性、DeliveryAddresses(收货地址列表)和PointsRecord(积分记录)。
  2. PointsRecord - 积分记录。由于我们需要追踪每次积分的变化,即使属性相同,每个积分记录也是独一无二的,因此它是一个实体。
  3. DeliveryAddress - 收货地址。用户可以删除某个地址或将其他地址设置为默认地址,这意味着我们需要对地址进行操作和管理。在这种情况下,将收货地址作为实体可能更合适。
  4. Points - 积分,即使只有一个属性,它也是一个值对象。将Points设计为值对象的原因是它代表了一个具有特定含义和行为的概念。例如,积分不能为负数,并且在某些操作时需要增加或减少特定的数量。通过将其设计为值对象,我们可以封装这些规则和行为,保证数据的完整性。
  5. 在用户限界上下文中,CustomerUser也是一个聚合,它封装了与用户相关的所有数据和行为,包括用户的基本信息、积分、收货地址和积分历史记录。

在实际操作中,实体与值对象的区别可能并不那么明显。例如,我们也可以把PointsRecord看作是值对象,因为系统只允许查看记录,不允许修改。从这个角度来看,积分记录就像用户积分变化的一个快照。然而,在这里,我们还是选择将其设计为实体,因为我们关心的是每一条独特的积分变化记录。


2.2 订单限界上下文

完成了用户限界上下文的设计后,现在我们进行订单限界上下文的领域模型设计。在“订单”限界上下文中,通过与业务人员深入沟通,我们总结出了以下通用语言:

  • 用户可以在DailyMart网站上下单购买书籍,每个订单可以包含多本书籍。
  • 订单需要包含收货人姓名、地址、联系电话等信息。
  • 订单需要记录订单状态,比如待支付、待发货、已发货、已完成等。
  • 用户可以在订单中查看每个书籍的详细信息,包括书名、价格、作者、ISBN号等。
  • 用户可以在订单中查看订单的配送状态,比如已发货、运输中、已签收等。

根据这些业务需求,我们确定了一些关键的领域模型:

  1. Order - 订单是一个聚合,因为它有其自己的生命周期,每个订单有唯一的订单ID作为标识。订单包含了RecipientInfo(收货人的信息,包括姓名、地址、联系电话),OrderStatus(订单的状态),以及订单中的书籍列表。
  2. OrderItem - 订单项是Order聚合中的一个实体,代表订单中的一本书。它包含了书籍的详细信息,例如书名、价格、作者、ISBN号等。由于每个订单项都是独一无二的,并且在订单中具有持久性,因此被设计为实体。
  3. RecipientInfo - 收货人信息是一个值对象,它没有自己的唯一标识,一旦订单创建就不能被修改,只能被替换。
  4. OrderStatus - 订单状态是一个值对象,可以被设计成枚举类型,表示订单的不同状态,例如待支付、待发货、已发货、已完成等。
  5. DeliveryStatus - 配送状态也是一个值对象,可以设计成枚举类型,表示配送的不同状态,例如已发货、运输中、已签收等。


2.3 商品限界上下文

接下来完成商品限界上下文的领域模型设计。在“商品”限界上下文中,通过与业务人员进行讨论和沟通,我们总结出了以下通用语言:

  • DailyMart主要是销售书籍(纸质版/电子版)。商品属性相对简单,只需要包含书名、价格、作者、ISBN号、简介、出版日期等几个重要属性。
  • 一本书会属于某一个分类,比如计算机类、设计类、心理学,管理员在另一个运营系统中可以配置分类。
  • 管理员可以对书籍进行推荐。
  • 用户登录后可以对书进行评论。

根据这些业务需求,我们识别出了一些关键的领域模型:

  1. Book - 书籍显然是一个实体,它需要一个唯一标识符。书籍包含ISBNtitleauthorprice、等属性,以及bookType(电子书或纸质书),这可以设计成一个枚举类型。此外,Book还有Category分类属性。
  2. Category - 分类,由于管理员在另一个运营系统中进行配置和维护,分类不需要有太多复杂的行为和属性。在此上下文中,我们可以将Category设计为值对象。
  3. Review - 评论,Review 具有持久性的唯一标识(评价ID),应该也是一个实体对象。
  4. 在商品限界上下文中,BookReview都设计成独立的聚合。Book作为核心实体设计为聚合是很自然的,因为它包含了书籍的所有重要属性。而Review则被设计为独立的聚合,使我们可以更方便地处理评论信息,包括添加、修改和查询等。

2.4 购物车上下文

最后,完成购物车限界上下文的领域模型设计。

在“购物车”限界上下文中,通过与业务人员进行深入沟通,我们总结出了以下通用语言:

  • 用户登录后可以将商品添加到自己的购物车中,每个商品可以添加多个,同时可以添加多个商品
  • 在购物车界面需要显示购物车所有商品的数量,以及每个商品的部分信息,如书名、作者、简介等。
  • 在购物车中可以对商品进行选中,选中商品后自动计算商品的价格合计。
  • 选中购物车的商品可以进行结算,但具体的订单生成和管理在另一个“订单”限界上下文中处理。

根据这些业务需求,我们识别出了一些关键的领域模型:

  1. CartItem - 购物车项,代表了用户添加到购物车的商品以及数量。由于购物车项有其自身的生命周期,可以单独创建、更新和删除,所以它应该被设计成一个实体。购物车项需要包含商品的部分信息,如书名、作者、简介等,以便在购物车界面中显示。
  2. Cart - 购物车,这也是一个实体,购物车包含了多个购物车项,每个购物车项代表了用户添加到购物车的商品以及数量。

在购物车限界上下文中,Cart被设计为核心聚合。它代表了用户的购物车状态,包含了用户希望购买的所有商品的信息。这样,我们可以更方便地处理购物车信息,包括添加商品、移除商品、计算总价等。

...

同样地,我们还可以分析出库存限界上下文和物流限界上下文的领域模型。下面是最终完整的领域模型:


3. 构建领域模型


完成领域建模后,我们可以根据领域模型生成关键领域对象的代码,以便更加准确地实现业务需求。例如,在订单上下文中,几个重要的对象包括:

@Data
public class RecipientInfo {
    private String name;
    private String address;
    private String phoneNumber;
}
public enum OrderStatus {
    AWAITING_PAYMENT, AWAITING_SHIPMENT, SHIPPED, COMPLETED
}
public enum DeliveryStatus {
    SHIPPED, IN_TRANSIT, DELIVERED
}
@Data
public class Order {
    private Long orderId;
    private Long userId;
    private LocalDateTime createdTime;
    private double totalAmount;
    private OrderStatus orderStatus;
    private DeliveryStatus deliveryStatus;
    private RecipientInfo recipientInfo;
    private List<OrderItem> orderItems;
}
@Data
public class OrderItem {
    private Long itemId;
    private String bookTitle;
    private String author;
    private String ISBN;
    private double price;
}


4. 设计数据库


同样的,还可以根据领域模型设计数据库表结构,以确保数据库能够准确地反映业务领域中的实体、值对象和聚合等概念。例如,下面是订单表的数据表设计:

create table order
(
    order_id               bigint         ,
    user_id                bigint         ,
    created_time           datetime       ,
    total_amount           decimal(10, 4) ,
    order_status           varchar(10)    ,
    deliveryStatus     varchar(10)    ,
    recipient_name         varchar(50)    ,
    recipient_address      varchar(255)   ,
    recipient_phone_number varchar(255)   ,
    constraint order_pk primary key (order_id)
);
create table order_item (
    item_id bigint,
    orderId bigint,
    bookTitle VARCHAR(255),
    author VARCHAR(255),
    ISBN VARCHAR(255),
    price decimal(10, 4),
    constraint order_item_pk primary key (item_id)
);


5. 小结


本文采用DDD战术,对 DailyMart 进行领域模型设计。通过识别出实体、值对象和聚合等概念,我们得到了 DailyMart 的完整领域模型,并根据该模型完成了关键领域对象的代码构建和关键表设计。

需要注意的是,领域模型并不是一成不变的,它会随着业务需求的变化而不断调整和优化,随着DailyMart商城系统的开发深入,我也会不断优化和补充它的领域模型。

目录
相关文章
|
12月前
|
消息中间件 JavaScript 小程序
领域驱动设计(DDD)的几种典型架构介绍
领域驱动设计(DDD)的几种典型架构介绍
|
12月前
|
Web App开发 机器学习/深度学习 数据可视化
OneCode 领域驱动设计(DDD)技术实践(一)
OneCode-DSM(以下简称DSM)工具集是建立是以OneCode低代码引擎为基础专注于低代码建模应用的高阶建模工具。 在OneCode引擎中,出了为普通用户提供无代码的拖动设计器,低代码的业务逻辑编排器,之外还提供了供专业业务领域专家的使用的DSM建模工具。
|
设计模式 Cloud Native 前端开发
DDD 实战之一:从需求到代码实现生鲜电商系统
DDD 实战之一:从需求到代码实现生鲜电商系统
|
JSON 前端开发 数据格式
再议DDD分层
之前整理过《DDD分层》[1] 以及《分层架构》[2] 最近看网友讨论,整理一些有亮点的地方 现在分层架构+整洁架构似乎是个万金油组合了 之前DDD的标准分层结构:
254 0
再议DDD分层
|
存储 敏捷开发 前端开发
DDD 是如何落地到数据库设计的?
DDD运用在数据库设计实战
291 0
DDD 是如何落地到数据库设计的?
|
存储 消息中间件 算法
还在搞三层架构?了解下 DDD 分层架构的三种模式吧 !
DDD(Domain Driven Design,领域驱动设计)作为一种软件开发方法,它可以帮助我们设计高质量的软件模型。在正确实现的情况下,我们通过DDD完成的设计恰恰就是软件的工作方式。UL(Ubiquitous Language,通用语言)是团队共享的语言,是DDD中最具威力的特性之一。不管你在团队中的角色如何,只要你是团队的一员,你都将使用UL。
还在搞三层架构?了解下 DDD 分层架构的三种模式吧 !
|
消息中间件 缓存 Java
DDD专题案例一《初识领域驱动设计DDD落地方案》
DDD(Domain-Driven Design 领域驱动设计)是由Eric Evans最先提出,目的是对软件所涉及到的领域进行建模,以应对系统规模过大时引起的软件复杂性的问题。整个过程大概是这样的,开发团队和领域专家一起通过 通用语言(Ubiquitous Language)去理解和消化领域知识,从领域知识中提取和划分为一个一个的子领域(核心子域,通用子域,支撑子域),并在子领域上建立模型,再重复以上步骤,这样周而复始,构建出一套符合当前领域的模型。
1739 0
DDD专题案例一《初识领域驱动设计DDD落地方案》
|
存储 搜索推荐 前端开发
DDD领域驱动设计实战电商活动中心重构
DDD领域驱动设计实战电商活动中心重构
599 0
DDD领域驱动设计实战电商活动中心重构
|
数据挖掘 Java 测试技术
DDD领域驱动设计实战(三)-深入理解实体(中)
DDD领域驱动设计实战(三)-深入理解实体(中)
224 0
|
前端开发 Java 数据库连接
DDD领域驱动设计实战(03)-深入理解实体
DDD领域驱动设计实战(03)-深入理解实体
350 0
DDD领域驱动设计实战(03)-深入理解实体