数据视图
数据视图
DAO 是对于数据持久化的抽象,而 Repository 则是面向对象集合的抽象。DAO 往往是与数据库中的表强映射。
-
VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
-
DTO(Data Transfer Object):数据传输对象,分布式应用提供粗粒度的数据实体,也是一种数据传输协议,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,这里泛指用于展示层与服务层之间的数据传输对象。RPC 对外暴露的服务涉及对象 API 就是 DTO,如 JSF(京东 RPC 框架)、Dubbo。对比 VO:绝大多数应用场景下,VO 与 DTO 的属性值基本一致,但对于设计层面来说,概念上还是存在区别,DTO 代表服务层需要接收的数据和返回的数据,而 VO 代表展示层需要显示的数据。
-
DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。DO 不是简单的 POJO,它具有领域业务逻辑。
-
PO(Persistent Object):持久化对象。对比 DO:DO 和 PO 在绝大部分情况下是一一对应的,但也存在区别,例如 DO 在某些场景下不需要进行显式的持久化,只驻留在静态内存。同样 PO 也可以没有对应的 DO,比如一对多表关系在领域模型层面不需要单独的领域对象。
表与接口设计
数据视图应用服务通过数据传输对象(DTO)完成外部数据交换。领域层通过领域对象(DO)作为领域实体和值对象的数据和行为载体。基础层利用持久化对象(PO)完成数据库的交换。DTO 与 VO 通过 Restful 协议实现 JSON 格式和对象转换。前端应用与应用层之间 DTO 与 DO 的转换发生在用户接口层。如微服务内应用服务需调用外部微服务的应用服务,则 DTO 的组装和 DTO 与 DO 的转换发生在应用层。领域层 DO 与 PO 的转换发生在基础层。
对象名 | 层名 | 描述 |
---|---|---|
Transfer Object/TO | Controller | 接入与返回层,提供视图数据聚合与统一的查询/返回格式 |
Business Object/BO | Service/Connector | 数据业务层,提供业务数据的聚合 |
Database Access Object/DAO | Model | 元数据访问层,与数据库进行直接交互 |
在设计数据库的时候,我们尽量避免给属性列添加额外的前缀,并且使用嵌套的结构返回多表联查的数据:
{
"user": {
"uuid": "{uuid}",
"name": "{name}"
},
"asset": {
"uuid": "{uuid}",
"name": "{name}"
},
"lessonss": []
}
/api/resource/get
/api/resource/getByIds
# 在交互层级上同样应该有所隐藏
/api/resource/getRelatedResourceById
/api/related-resource/getRelatedResourceByResourceId
query {
Resources{
id
}
}
query {
Resource($id: 1){
id
statisticsField(){}
oneToOneField() {}
oneToManyField(){}
}
}
Domain Primitive
Domain Primitive 是一个在特定领域里,拥有精准定义的、可自我验证的、拥有丰富行为和业务逻辑的 Value Object,DP 使用业务域中的原生语言,可以是业务域的最小组成部分、也可以构建复杂组合。Domain Primitive 是 Value Object 的进阶版,在原始 VO 的基础上要求每个 DP 拥有概念的整体,而不仅仅是值对象。在 VO 的 Immutable 基础上增加了 Validity 和行为。
在项目中,散落在各个服务或工具类里面的代码,都可以抽出来放在 DP 里,成为 DP 自己的行为或属性。原则是:所有抽离出来的方法要做到无状态,比如原来是 static 的方法。如果原来的方法有状态变更,需要将改变状态的部分和不改状态的部分分离,然后将无状态的部分融入 DP。因为 DP 也是一种 Object Value,本身不能带状态,所以一切需要改变状态的代码都不属于 DP 的范畴。Domain Primitive 涉及三种手段:
让隐性的概念显性化(Make Implicit Concepts Explicit)
活动类型就是一个简单的 int 类型,属于隐式概念,但活动类型包含了很多相关业务逻辑,比如类型名称,不同类型活动具有独特的 Icon,判断活动类型是否是判断等,我们把活动类型显性化,定义为一个 Value Object。
让隐性的上下文显性化(Make Implicit Context Explicit)
当要实现一个功能或进行逻辑判断依赖多个概念时,可以把这些概念封装到一个独立地完整概念,也是一种 Object Value:
封装多对象行为(Encapsulate Multi-Object Behavior)
常见推荐使用 Domain Primitive 的场景有:
- 有格式要求的 String:比如 Name,PhoneNumber,OrderNumber,ZipCode,Address 等。
- 限制的 Integer:比如 OrderId(>0),Percentage(0-100%),Quantity(>=0)等。
- 可枚举的 int:比如 Status(一般不用 Enum 因为反序列化问题)。
- Double 或 BigDecimal:一般用到的 Double 或 BigDecimal 都是有业务含义的,比如 Temperature、Money、Amount、ExchangeRate、Rating 等。
- 复杂的数据结构:比如
Map<String, List>
等,尽量能把 Map 的所有操作包装掉,仅暴露必要行为,如通天塔的活动 Map 类。
接口变得清晰可读,校验逻辑内聚,在接口边界外完成,无胶水代码,业务逻辑清晰可读,代码变得更容易测试,也更安全。