目录
什么是微服务
单体结构项目
微服务架构项目
微服务架构误区
DDD-toc" style="margin-left:40px;">什么是DDD
DDD%E9%A2%86%E5%9F%9F%E4%B8%8E%E9%A2%86%E5%9F%9F%E6%A8%A1%E5%9E%8B-toc" style="margin-left:40px;">DDD领域与领域模型
领域(Domain)
领域模型(Domain Model)
事务脚本
事务脚本的问题
通用语言与界限上下文
通用语言
界限上下文
实体与值对象
实体(Entity)
值对象(Value Object)
聚合与聚合根
聚合(Aggregate)
聚合的意义
聚合的划分很难
聚合的划分没有标准答案
聚合的划分的原则
领域服务与应用服务
简述
DDD%E5%85%B8%E5%9E%8B%E7%94%A8%E5%8A%9B%E7%9A%84%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B-toc" style="margin-left:80px;">DDD典型用力的处理流程
职责的划分
“仓储”(Repository)和“工作单元”(Unit Of Work)
领域事件与集成事件
事务脚本处理“事件”
事务脚本的实现:
问题
“开闭原则”
采用事件机制的伪代码
两种事件
什么是微服务
单体结构项目
所有代码都放到同一个应用程序中
- 优点:结构简单;部署简单。
- 缺点:耦合;技术栈统一,软件包版本锁定;一崩全崩;升级周期长;无法局部扩容。
微服务架构项目
把项目拆分为多个应用程序,每个应用程序单独构建和部署
- 优点:耦合性低,易于开发和维护;可以用不同技术栈;可以单独扩容;互相隔离,影响小;部署周期短;
- 缺点:对运维能力要求高;运行效率会降低;技术要求高,需要处理事务最终一致性等问题。
微服务架构误区
避免使用微服务,除非有充足的理由。
DDD">什么是DDD
- DDD(Domain-driven design,领域驱动设计)是一个很好的应用于微服务架构的方法论。
- 在项目的全生命周期内,所有岗位的人员都基于对业务的相同的理解来开展工作。所有人员站在用户的角度、业务的角度去思考问题,而不是站在技术的角度去思考问题。
- DDD2004年被提出,微服务2014年提出,DDD并不是为微服务而生的,DDD也可以用于单体结构项目的设计,但是在微服务架构中DDD能发挥出更加强大的作用。
- DDD并不是一个技术,而是一种架构设计的指导原则;DDD不是一种强制性的规范,DDD是方法论,不是行动指南。
DDD%E9%A2%86%E5%9F%9F%E4%B8%8E%E9%A2%86%E5%9F%9F%E6%A8%A1%E5%9E%8B" style="background-color:transparent;">DDD领域与领域模型
领域(Domain)
- 领域:一个组织做的所有事情。子领域。
- 领域的划分:
核心域:解决项目的核心问题,和组织业务紧密相关。
支撑域:解决项目的非核心问题,则具有组织特性,但不具有通用性。
通用域:解决通用问题,没有组织特性。 - 领域的不同分类决定了公司的研发重点。
领域模型(Domain Model)
- 对于领域内的对象进行建模,从而抽象出来模型。
- 我们的项目应该开始于创建领域模型,而不是考虑如何设计数据库和编写代码。使用领域模型,我们可以一直用业务语言去描述和构建系统,而不是使用技术人员的语言。
事务脚本
使用技术人员的语言去描述和实现业务事务。没有太多设计,没有考虑可扩展性、可维护性,流水账地编写代码。
事务脚本的问题
代码的可维护性、可扩展性非常差。比如如何增加“取款金额大于5万元需要主管审批”、“通知短信”等功能。
通用语言与界限上下文
通用语言
- “我想要商品可以被删除”→“我想要把删除的还原回来”→“Windows回收站都能”
- 此“用户”非彼“用户”。
- 通用语言:一个拥有确切含义的、没有二义性的语言。
界限上下文
通用语言离不开特定的语义环境,只有确定了通用语言所在的边界,才能没有歧义的描述一个业务对象。
实体与值对象
实体(Entity)
- “标识符”用来唯一定位一个对象,在数据库中我们一般用表的主键来实现“标识符”。主键和标识符的思考角度不同。
- 实体:拥有唯一的标识符,标识符的值不会改变,而对象的其他状态则会经历各种变化。标识符用来跟踪对象状态变化,一个实体的对象无论怎样变化,我们都能通过标识符定位这个对象。
- 实体一般的表现形式就是EF Core中的实体类。
值对象(Value Object)
- 没有标识符的对象,也有多个属性,依附于某个实体对象而存在。比如“商家”的地理位置、衣服的RGB颜色。
- 定义为值对象和普通属性的区别:体现整体关系。
聚合与聚合根
聚合(Aggregate)
- 目的:高内聚,低耦合。模块内各元素联系越紧密越好,即一个模块只做一件事,各模块之间联系越少越好,各模块相互独立。
- 把关系紧密的实体放到一个聚合中,每个聚合中有一个实体作为聚合根(Aggregate Root),所有对于聚合内对象的访问都通过聚合根来进行,外部对象只能持有对聚合根的引用。
- 聚合根不仅仅是实体,还是所在聚合的管理者。
聚合的意义
聚合体现的是现实世界中整体和部分的关系,比如订单与订单明细。整体封装了对部分的操作,部分与整体有相同的生命周期。部分不会单独与外部系统单独交互,与外部系统的交互都由整体来负责。
聚合的划分很难
- 系统中很多实体都存在着不同程度的关系,这些关系到底是设计为聚合之间的关系还是聚合之内的关系是很难的。
- 聚合的判断标准:实体是否是整体和部分的关系,是否存在着相同的生命周期。
- 订单与订单明细?用户与订单?
聚合的划分没有标准答案
不同的业务流程也就决定了不同的划分方式。新闻和新闻的评论?
聚合的划分的原则
- 尽量把聚合设计的小一点,一个聚合只包含一个聚合根实体和密不可分的实体,实体中只包含最小数量的属性。
- 小聚合有助于进行微服务的拆分。
领域服务与应用服务
- 聚合中的实体中没有业务逻辑代码,只有对象的创建、对象的初始化、状态管理等个体相关的代码。
- 对于聚合内的业务逻辑,我们编写领域服务(Domain Service),而对于跨聚合协作以及聚合与外部系统协作的逻辑,我们编写应用服务(Application Service)。
- 应用服务协调多个领域服务、外部系统来完成一个用例。
简述
- 实体中的逻辑代码:管理实体的创建、状态等非业务的逻辑。
- 领域服务:聚合内的业务逻辑。
- 应用服务:聚合间,与外部系统的业务逻辑
DDD%E5%85%B8%E5%9E%8B%E7%94%A8%E5%8A%9B%E7%9A%84%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B">DDD典型用力的处理流程
- 准备业务操作所需要的数据。
- 执行由一个或者多个领域模型做出的业务操作,这些操作会修改实体的状态,或者生成一些操作结果。
- 把对实体的改变或者操作结果应用于外部系统。
职责的划分
- 领域模型与外部系统不会发生直接交互,即领域服务不会涉及数据库操作。
- 业务逻辑放入领域服务,而与外部系统的交互由应用服务来负责。
- 领域服务不是必须的,在一些简单的业务处理中(比如增删改查)是没有领域知识(也就是业务逻辑)的,这种情况下应用服务可以完成所有操作,不需要引入领域服务。这样可以避免过度设计。
“仓储”(Repository)和“工作单元”(Unit Of Work)
- 仓储负责按照要求从数据库中读取数据以及把领域服务修改的数据保存回数据库。
- 聚合内的数据操作是关系非常紧密的,我们要保证事务的强一致性,而聚合间的协作是关系不紧密的,因此我们只要保证事务的最终一致性即可。
- 聚合内的若干相关联的操作组成一个“工作单元”,这些工作单元要么全部成功,要么全部失败。
领域事件与集成事件
事务脚本处理“事件”
“当发生某事件的时候,执行某个动作”。
当有人回复了用户的提问的时候,系统就向提问者的邮箱发送通知邮件。
事务脚本的实现:
void 保存答案(long id,string answer)
{
保存到数据库(id,answer);
string email = 获取提问者邮箱(id);
发送邮件(email,"你的问题被回答了");
}
问题
- 代码会随着需求的增加而持续膨胀。
- 代码可扩展性低。
- 容错性差。外部系统并不总是稳定的。
“开闭原则”
对扩展开放,对修改关闭;在不修改已有代码的情况下,增加新功能。
采用事件机制的伪代码
优点:关注点分离;容易扩展;容错性好;
void 保存答案(long id,string answer)
{long aId = 保存到数据库(id,answer);发布事件("答案已保存",aId,answer);
}[绑定事件("答案已保存")]
void 审核答案(long aId,string answer)
{if(检查是否疑似违规(answer)){隐藏答案(aId);发布事件("内容待审核",aId);}
}[绑定事件("答案已保存")]
void 发邮件给提问者(long aId,string answer)
{long qId = 获取问题Id(aId);string email = 获取提问者邮箱(qId);发送邮件(email,"你的问题被回答了");
}