原创 | 使用JPA实现DDD持久化-领域模型:对象的世界

432 阅读4分钟

第三节 领域模型:对象的世界

我们以自营类电子商务领域为例,说明如何通过JPA实现对象持久化。本项目的代码可以在github网站github.com/dayatang/jp…下载。

领域模型内容体现在tmall-domain模块中。

下面是领域模型:

@startuml

package commons <<Frame>> {  
  class Address <<ValueObject>> {
    - province: String
    - city: String
    - detail: String
    - receiver: String
    - receiverPhone: String
  }
}

package products <<Frame>> {
  class Product <<Entity>> {
    - name: String
  }

  class ProductCategory <<Entity>> {
    - name: String
  }

  Product --> "category" ProductCategory
  ProductCategory "parent" -- "children*" ProductCategory
}

package buyers <<Frame>> {
  abstract class Buyer <<Entity>> {
    - name: String
    - mobileNo: String
    - wiredNo: String
    - email: String
  }

  class OrgBuyer <<Entity>> {
    - businessLicenseNo: String
    - taxNo: String
  }

  class ContactInfo <<ValueObject>> {
    - name: String
    - gender: Gender
    - mobileNo: String
    - email: String
  }

  class PersonalBuyer <<Entity>> {
    - gender: Gender
    - imInfos: Map
  }

  Buyer <|- OrgBuyer
  Buyer <|- PersonalBuyer
  Buyer *--> "shippingAddresses*" Address
  OrgBuyer *--> ContactInfo
}

package pricing <<Frame>> {
  class Pricing <<Entity>> {
    - unitPrice: Money
    - pricingTime: LocalDateTime
  }

  interface Pricings <<repository>> {
    + save(pricing): Pricing
    + getPricingAt(product, time): Pricing
  }

  class PricingService <<service>> {
    + setUnitPrice(setUnitPrice, unitPrice, effectiveTime)
    + adjustPriceByPercentage(product, percentage, effectiveTime)
    + getPriceAt(product, time): Money
  }

    Pricing --> Product
    Pricings --> Product
    Pricings --> Pricing
    PricingService --> Pricings
}

package sales <<Frame>> {
  class Order <<Entity>> {
    - orderNo:String
    - createdOn: LocalDateTime
    - totalPrice: Money
    - status: OrderStatus
    + calculateTotalPrice()
  }

  class OrderLine <<Entity>> {
    - quantity: BigDecimal
    - unitPrice: Money
    - discountRate: BigDecimal
    - subTotal: Money
    + calculateSubTotal()
  }

  class OrderStatusTransition <<Entity>> {
    - status: OrderStatus
    - seqNo: int
    - created: LocalDateTime
  }

  Order --> Buyer
  Order *--> "shippingAddress" Address
  Order *-- "lineItems*" OrderLine
  OrderLine --> Product
  OrderStatusTransition --> Order
}

@enduml

说明:

  • 值对象MoneyContactInfoAddress:分别作为单属性值对象和多属性值对象的例子。Money值对象代表金额,实质上是对BigDecimal的封装。为什么要定义Money值对象而不是直接使用BigDecimal?因为Money的含义比BigDecimal更加适合业务领域,而且可以定义各种金额特定的格式和方法。ContactInfo代表联系人信息。Address代表送货地址。
  • BaseEntity(为了防止UML图太复杂,没有出现在图中):所有实体的抽象基类,用于定义所有实体的共同属性。BaseEntity定义了实体类的两个共同属性:idversionid属性定义了实体的标识符。所有的实体都需要定义一个标识符属性,用来在同类型实体中区分每一个实体实例。通常映射到数据库表的主键列。version属性用于为并发处理持久化对象时添加乐观锁。
  • 实体类ProductCategoryProduct:分别代表商品类别和商品。每个商品归属到一个类别。类别之下可以定义若干个子类别。类别之间通过父子关系形成多层的类别树。没有父类别的产品类别是一级类别,相当于每棵类别树的树根。
  • 实体类Pricing:商品定价实体。用来记录对某个商品的每次定价。为什么不将商品单价建模为商品的一个简单属性?因为:(1)单价会由于成本变化或促销考虑而经常变动,而商品的其他属性很少发生变化。将不同变化频率的属性划分到不同的对象中是分析设计的最佳实践。(2)如果将商品单价建模为商品的属性,每次调价都会覆盖掉原来的单价,定价历史被抹掉了,既无法无法查询历史价格,也无法对价格和销量的关系进行统计分析。而使用单独的Pricing实体类会存留每次的调价信息,具有巨大的查询和分析价值。(3)企业中管理商品品类的人和负责定价的人通常分属不同的部门。应该尽量根据用户类别来划分软件结构。
  • 实体类Buyer:买家实体。有两种类型的买家:个人买家PersonalBuyer和组织买家OrgBuyer,前者表示买家是一个自然人而后者表示买家是一家组织机构,两者除了包含一些共同的属性之外还分别包含一些不同的属性。共同属性在父类中定义,不同属性在不同的子类中定义。
  • 实体类OrderOrderLine:分别代表订单和订单条目。从领域含义来说,订单条目OrderLine应该建模成为值对象,因为它的生命周期完全从属于订单实体Order。但是OrderLine是统计分析的首要目标对象,由于JPA的某些限制,只有将它建模为实体才能充分发挥针对订单条目的统计分析功能。因此我们通过级联持久化和孤儿删除等技巧,配合部分编码实现,使得OrderLine得到类似于值对象的效果。
  • 实体类OrderStatusTransition:订单状态转移实体。记录订单状态的每一次变迁。不将订单状态作为一个简单属性定义在订单类中的原因是:(1)我希望作为事务性数据的订单是不可变的。(2)存留订单状态变迁的历史有助于未来的查询和分析,例如计算收款与发货之间的平均时间差,以利于改进流程。

详细内容请戳这里↓↓↓

原创 | 使用JPA实现DDD持久化-领域模型:对象的世界

这一节就讲到这里,下一节我们讲**"O/R映射元数据:映射注解分组"**。

如果觉得有收获,点个【赞】鼓励一下呗!