数据视图

数据视图

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。

ActivityType

让隐性的上下文显性化(Make Implicit Context Explicit)

当要实现一个功能或进行逻辑判断依赖多个概念时,可以把这些概念封装到一个独立地完整概念,也是一种 Object Value:

ActivityStatus

封装多对象行为(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 类。

接口变得清晰可读,校验逻辑内聚,在接口边界外完成,无胶水代码,业务逻辑清晰可读,代码变得更容易测试,也更安全。

上一页