写点什么

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

发布于: 2020 年 12 月 10 日
原创 | 使用JPA实现DDD持久化-领域模型:对象的世界

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



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



领域模型内容体现在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映射元数据:映射注解分组"



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





发布于: 2020 年 12 月 10 日阅读数: 15
用户头像

高级架构师,技术顾问,交流公号:编程道与术 2020.04.28 加入

杨宇于2020年创立编程道与术,致力于研究领域分析与建模、测试驱动开发、架构设计、自动化构建和持续集成、敏捷开发方法论、微服务、云计算等顶尖技术领域。 了解更多公众号:编程道与术

评论

发布
暂无评论
原创 | 使用JPA实现DDD持久化-领域模型:对象的世界