梳理
- 定位url
- 从 项目 -> app -> appname -> internal ->service ->appname 中 定位grpc 逻辑
biz
// 业务逻辑的组装层,类似 DDD 的 domain 层,data 类似 DDD 的 repo,而 repo 接口在这里定义,使用依赖倒置的原则。
data
// 业务数据访问,包含 cache、db 等封装,实现了 biz 的 repo 接口。我们可能会把 data 与 dao 混淆在一起,data 偏重业务的含义,它所要做的是将领域对象重新拿出来,我们去掉了 DDD 的 infra层(基础设施层(Infrastructure Layer))。
service
// 实现了 api 定义的服务层,类似 DDD 的 application 层,处理 DTO 到 biz 领域实体的转换(DTO -> DO),同时协同各类 biz 交互,但是不应处理复杂逻辑
- 用户界面(User Interface):
- 负责展示数据给用户,并收集用户的输入。
- 可以是 Web 界面、命令行界面、API 等。
- 应用层(Application Layer):
- 定义软件要执行的操作,如创建订单、删除用户等。
- 协调领域层来执行这些操作,并返回结果给用户界面。
- 领域层(Domain Layer):
- 包含业务逻辑和业务规则。
- 定义领域模型、实体(Entity)、值对象(Value Object)、聚合(Aggregate)、领域服务(Domain Service)和领域事件(Domain Event)。
- 是 DDD 中最核心的部分,负责业务逻辑的实现。
- 基础设施层(Infrastructure Layer):
- 提供技术能力,如数据库访问、消息传递、文件系统操作等。
- 为应用层和领域层提供支持,但不应包含业务逻辑。
- 持久化层(Persistence Layer):
- 负责数据的存储和检索。
- 通常位于基础设施层内,有时也被称为仓储(Repository)模式。
- 读模型(Read Model):
- 用于报告和显示目的,不同于写模型(Write Model)。
- 读模型优化了查询性能,可能包含对数据的变形或聚合。
在DDD中,数据访问和持久化通常通过以下概念来实现:
- 仓储(Repository):
- 仓储是DDD中用于数据访问的主要模式之一。
- 它封装了对聚合(Aggregate)的检索和持久化逻辑。
- 仓储定义了一组方法,用于查询和保存领域中的聚合根(Aggregate Root)。
- 工厂(Factory):
- 工厂用于创建复杂的聚合或对象。
- 它们封装了创建对象的逻辑,确保对象在创建时就处于有效状态。
- 应用服务(Application Service):
- 应用服务是协调领域层对象执行任务的入口点。
- 它们处理应用程序的工作流程,调用领域对象执行业务逻辑。
- 领域服务(Domain Service):
- 领域服务包含领域逻辑,但它们不自然属于任何聚合。
- 它们通常用于执行跨越多个聚合的操作。
- 实体(Entity)和值对象(Value Object):
- 实体具有唯一标识,并且可以随时间变化。
- 值对象则强调属性的值,没有唯一标识,且在逻辑上不可变。
常见的领域对象类型:
- 实体(Entity):
- 实体是具有唯一标识的对象, 即使其属性改变,实体的标识也保持不变。
- 实体可以是用户、订单、账户等。
- 值对象(Value Object):
- 值对象是基于其属性值的对象,没有唯一标识。
- 值对象通常用于描述实体的属性,如地址、尺寸、颜色等。
- 聚合(Aggregate):
- 聚合是一组相关对象的集合,它们一起作为数据修改的单元。
- 聚合根(Aggregate Root)是聚合的入口点,外部对象通过它与聚合内的对象交互。
- 领域服务(Domain Service):
- 领域服务包含领域逻辑,但它们不属于任何特定的实体或值对象。
- 它们通常用于执行跨越多个实体的操作。
- 领域事件(Domain Event):
- 领域事件表示领域中发生的有意义的业务事件。
- 它们通常用于实现领域驱动的事件处理。
- 工厂(Factory):
- 工厂用于创建复杂的对象,封装对象创建的逻辑。
- 仓储(Repository):
- 仓储用于访问领域聚合的集合,封装了检索和持久化聚合根的逻辑。
- 应用服务(Application Service):
- 应用服务作为协调领域对象执行业务逻辑的入口点。
- 它们处理应用程序的工作流程,调用领域对象执行业务逻辑。
- **Data Object (DO、数据对象):**实际上是我们在日常工作中最常见的数据模型。但是在DDD的规范里,DO应该仅仅作为数据库物理表格的映射,不能参与到业务逻辑中。为了简单明了,DO的字段类型和名称应该和数据库物理表格的字段类型和名称一一对应,这样我们不需要去跑到数 据库上去查一个字段的类型和名称。(当然,实际上也没必要一摸一样,只要你在Mapper那一层做到字段映射)
- **Entity(实体对象):**实体对象是我们正常业务应该用的业务模型,它的字段和方法应该和业务语言保持一致,和持久化方式无关。也就是说,Entity和DO很可能有着完全不一样的字段命名和字段类型,甚至嵌套关系。Entity的生命周期应该仅存在于内存中,不需要可序列化和可持久化。
- **DTO(传输对象):**主要作为Application层的入参和出参,比如CQRS里的Command、Query、Event,以及Request、Response等都属于DTO的范畴。DTO的价值在于适配不同的业务场景的入参和出参,避免让业务对象变成一个万能大对象。
在领域驱动设计(Domain-Driven Design,简称DDD)中,DAO(Data Access Object)模式通常指的是一种用于数据访问的技术模式,但是它并不是DDD核心概念的一部分。DAO模式主要用于提供对数据源的抽象,允许应用层通过一个中间层与数据源交互,而不是直接与数据库通信。
在DDD中,推荐使用仓储模式而不是DAO模式,因为仓储更符合DDD的领域模型和聚合根概念。仓储允许开发者专注于领域模型,而不是数据库表和记录。它们提供了一种更自然和声明性的方式来处理领域对象的生命周期。
在领域驱动设计(DDD)中,"DO"通常指的是"领域对象"(Domain Object)。领域对象是代表业务领域中的概念或实体的软件对象。它们是DDD中的核心组件,用于封装业务逻辑和业务状态
领域对象的设计和实现应遵循以下原则:
- 有界上下文(Bounded Context):
- 明确定义领域对象的作用域和业务规则。
- 领域模型(Domain Model):
- 构建一个丰 富的领域模型,反映业务专家的语言和业务逻辑。
- 持续集成(Continuous Integration):
- 领域专家和开发人员应持续集成他们的工作,确保领域模型与业务需求保持一致。
- 领域逻辑封装:
- 将业务逻辑封装在领域对象中,而不是散布在应用程序的其他部分。
Data Object数据类:AccountDO是单纯的和数据库表的映射关系,每个字段对应数据库表的一个column,这种对象叫Data Object。DO只有数据,没有行为
Domain Primitive(DP) 是一个在特定领域里,拥有精准定义的、可自我验证的、拥有行为的 Value Object 。
- DP是一个传统意义上的Value Object,拥有Immutable的特性
- DP是一个完整的概念整体,拥有精准定义
- DP使用业务域中的原生语言
- DP可以是业务域的最小组成部分、也可以构建复杂组合
电话区号 案例
将隐性的概念显性化 vo( value object)
因为 DP 的特性,只要是能够带到入参里的一定是正确的或 null(Bean Validation 或 lombok 的注解能解决 null 的问题)。所以我们把数据验证的工作量前置到了调用方,而调用方本来就是应该提供合法数据的,所以更加合适。
将 隐性的 上下文 显性化
转账
支付金额 + 支付货币
封装 多对象 行为
eg: 转换汇率
常见的 DP 的使用场景包括:
- 有格式限制的
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<Integer>>
等,尽量能把Map
的所有操作包装掉,仅暴露必要行为