数据视图

数据视图

DAO是对于数据持久化的抽象,而Repository则是面向对象集合的抽象。DAO往往是与数据库中的表强映射。

元模型与数据间关系

  • VO(View Object:视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。

  • DTO(Data Transfer Object:数据传输对象,分布式应用提供粗粒度的数据实体,也是一种数据传输协议,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,这里泛指用于展示层与服务层之间的数据传输对象。RPC对外暴露的服务涉及对象API就是DTO,如JSF(京东RPC框架、Dubbo。对比VO:绝大多数应用场景下,VODTO的属性值基本一致,但对于设计层面来说,概念上还是存在区别,DTO代表服务层需要接收的数据和返回的数据,而VO代表展示层需要显示的数据。

  • DO(Domain Object:领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。DO不是简单的POJO,它具有领域业务逻辑。

  • PO(Persistent Object:持久化对象。对比DODOPO在绝大部分情况下是一一对应的,但也存在区别,例如DO在某些场景下不需要进行显式的持久化,只驻留在静态内存。同样PO也可以没有对应的DO,比如一对多表关系在领域模型层面不需要单独的领域对象。

数据横向视图

表与接口设计

数据视图应用服务通过数据传输对象(DTO)完成外部数据交换。领域层通过领域对象(DO)作为领域实体和值对象的数据和行为载体。基础层利用持久化对象(PO)完成数据库的交换。DTOVO通过Restful协议实现JSON格式和对象转换。前端应用与应用层之间DTODO的转换发生在用户接口层。如微服务内应用服务需调用外部微服务的应用服务,则DTO的组装和DTODO的转换发生在应用层。领域层DOPO的转换发生在基础层。

对象名 层名 描述
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 ObjectDP使用业务域中的原生语言,可以是业务域的最小组成部分、也可以构建复杂组合。Domain PrimitiveValue Object的进阶版,在原始VO的基础上要求每个DP拥有概念的整体,而不仅仅是值对象。在VOImmutable基础上增加了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因为反序列化问题
  • DoubleBigDecimal:一般用到的DoubleBigDecimal都是有业务含义的,比如Temperature、Money、Amount、ExchangeRate、Rating等。
  • 复杂的数据结构:比如 Map<String, List> 等,尽量能把Map的所有操作包装掉,仅暴露必要行为,如通天塔的活动Map类。

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

上一页