This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,858 @@
<audio id="audio" title="基于DDD的微服务设计实例代码详解" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d5/45/d5b286c3cf318d5d5b4daf510204c845.mp3"></audio>
你好,我是欧创新。好久不见,今天我带着你期待的案例来了。
还记得我们在 [[第 18 讲]](https://time.geekbang.org/column/article/169881) 中用事件风暴完成的“在线请假考勤”项目的领域建模和微服务设计吗今天我们就在这个项目的基础上看看用DDD方法设计和开发出来的微服务代码到底是什么样的点击 [Github](https://github.com/ouchuangxin/leave-sample) 获取完整代码,接下来的内容是我对代码的一个详解,期待能帮助你更好地实践我们这个专栏所学到的知识。
## 项目回顾
“在线请假考勤”项目中,请假的核心业务流程是:请假人填写请假单提交审批;根据请假人身份、请假类型和请假天数进行校验并确定审批规则;根据审批规则确定审批人,逐级提交上级审批,逐级核批通过则完成审批,否则审批不通过则退回申请人。
在 [[第 18 讲]](https://time.geekbang.org/column/article/169881) 的微服务设计中我们已经拆分出了两个微服务请假和考勤微服务。今天我们就围绕“请假微服务”来进行代码详解。微服务采用的开发语言和数据库分别是Java、Spring boot 和 PostgreSQL。
## 请假微服务采用的DDD设计思想
请假微服务中用到了很多的DDD设计思想和方法主要包括以下几个
<img src="https://static001.geekbang.org/resource/image/5f/92/5f22ed9bb3d5b6c63f21583469399892.jpg" alt="">
## 聚合中的对象
请假微服务包含请假leave、人员person和审批规则rule三个聚合。leave聚合完成请假申请和审核核心逻辑person聚合管理人员信息和上下级关系rule是一个单实体聚合提供请假审批规则查询。
Leave是请假微服务的核心聚合它有请假单聚合根leave、审批意见实体ApprovalInfo、请假申请人Applicant和审批人Approver值对象它们的数据来源于person聚合还有部分枚举类型如请假类型LeaveType请假单状态Status和审批状态类型ApprovalType等值对象。
下面我们通过代码来了解一下聚合根、实体以及值对象之间的关系。
### 1. 聚合根
聚合根leave中有属性、值对象、关联实体和自身的业务行为。Leave实体采用充血模型有自己的业务行为具体就是聚合根实体类的方法如代码中的getDuration和addHistoryApprovalInfo等方法。
聚合根引用实体和值对象,它可以组合聚合内的多个实体,在聚合根实体类方法中完成复杂的业务行为,这种复杂的业务行为也可以在聚合领域服务里实现。但为了职责和边界清晰,我建议聚合要根据自身的业务行为在实体类方法中实现,而涉及多个实体组合才能实现的业务能力由领域服务完成。
下面是聚合根leave的实体类方法它包含属性、对实体和值对象的引用以及自己的业务行为和方法。
```
public class Leave {
String id;
Applicant applicant;
Approver approver;
LeaveType type;
Status status;
Date startTime;
Date endTime;
long duration;
int leaderMaxLevel; //审批领导的最高级别
ApprovalInfo currentApprovalInfo;
List&lt;ApprovalInfo&gt; historyApprovalInfos;
public long getDuration() {
return endTime.getTime() - startTime.getTime();
}
public Leave addHistoryApprovalInfo(ApprovalInfo approvalInfo) {
if (null == historyApprovalInfos)
historyApprovalInfos = new ArrayList&lt;&gt;();
this.historyApprovalInfos.add(approvalInfo);
return this;
}
public Leave create(){
this.setStatus(Status.APPROVING);
this.setStartTime(new Date());
return this;
}
//其它方法
}
```
### 2. 实体
审批意见实体ApprovalInfo被leave聚合根引用用于记录审批意见它有自己的属性和值对象如approver等业务逻辑相对简单。
```
public class ApprovalInfo {
String approvalInfoId;
Approver approver;
ApprovalType approvalType;
String msg;
long time;
}
```
### 3. 值对象
在Leave聚合有比较多的值对象。
我们先来看一下审批人值对象Approver。这类值对象除了属性集之外还可以有简单的数据查询和转换服务。Approver数据来源于person聚合从person聚合获取审批人返回后从person实体获取personID、personName和level等属性重新组合为approver值对象因此需要数据转换和重新赋值。
Approver值对象同时被聚合根leave和实体approvalInfo引用。这类值对象的数据来源于其它聚合不可修改可重复使用。将这种对象设计为值对象而不是实体可以提高系统性能降低数据库实体关联的复杂度所以我一般建议优先设计为值对象。
```
public class Approver {
String personId;
String personName;
int level; //管理级别
public static Approver fromPerson(Person person){
Approver approver = new Approver();
approver.setPersonId(person.getPersonId());
approver.setPersonName(person.getPersonName());
approver.setLevel(person.getRoleLevel());
return approver;
}
}
```
下面是枚举类型的值对象Status的代码。
```
public enum Status {
APPROVING, APPROVED, REJECTED
}
```
这里你要记住一点,由于值对象只做整体替换、不可修改的特性,在值对象中基本不会有修改或新增的方法。
### 4. 领域服务
如果一个业务行为由多个实体对象参与完成,我们就将这部分业务逻辑放在领域服务中实现。领域服务与实体方法的区别是:实体方法完成单一实体自身的业务逻辑,是相对简单的原子业务逻辑,而领域服务则是多个实体组合出的相对复杂的业务逻辑。两者都在领域层,实现领域模型的核心业务能力。
一个聚合可以设计一个领域服务类,管理聚合内所有的领域服务。
请假聚合的领域服务类是LeaveDomainService。领域服务中会用到很多的DDD设计模式比如用工厂模式实现复杂聚合的实体数据初始化用仓储模式实现领域层与基础层的依赖倒置和用领域事件实现数据的最终一致性等。
```
public class LeaveDomainService {
@Autowired
EventPublisher eventPublisher;
@Autowired
LeaveRepositoryInterface leaveRepositoryInterface;
@Autowired
LeaveFactory leaveFactory;
@Transactional
public void createLeave(Leave leave, int leaderMaxLevel, Approver approver) {
leave.setLeaderMaxLevel(leaderMaxLevel);
leave.setApprover(approver);
leave.create();
leaveRepositoryInterface.save(leaveFactory.createLeavePO(leave));
LeaveEvent event = LeaveEvent.create(LeaveEventType.CREATE_EVENT, leave);
leaveRepositoryInterface.saveEvent(leaveFactory.createLeaveEventPO(event));
eventPublisher.publish(event);
}
@Transactional
public void updateLeaveInfo(Leave leave) {
LeavePO po = leaveRepositoryInterface.findById(leave.getId());
if (null == po) {
throw new RuntimeException(&quot;leave does not exist&quot;);
}
leaveRepositoryInterface.save(leaveFactory.createLeavePO(leave));
}
@Transactional
public void submitApproval(Leave leave, Approver approver) {
LeaveEvent event;
if (ApprovalType.REJECT == leave.getCurrentApprovalInfo().getApprovalType()) {
leave.reject(approver);
event = LeaveEvent.create(LeaveEventType.REJECT_EVENT, leave);
} else {
if (approver != null) {
leave.agree(approver);
event = LeaveEvent.create(LeaveEventType.AGREE_EVENT, leave); } else {
leave.finish();
event = LeaveEvent.create(LeaveEventType.APPROVED_EVENT, leave);
}
}
leave.addHistoryApprovalInfo(leave.getCurrentApprovalInfo());
leaveRepositoryInterface.save(leaveFactory.createLeavePO(leave));
leaveRepositoryInterface.saveEvent(leaveFactory.createLeaveEventPO(event));
eventPublisher.publish(event);
}
public Leave getLeaveInfo(String leaveId) {
LeavePO leavePO = leaveRepositoryInterface.findById(leaveId);
return leaveFactory.getLeave(leavePO);
}
public List&lt;Leave&gt; queryLeaveInfosByApplicant(String applicantId) {
List&lt;LeavePO&gt; leavePOList = leaveRepositoryInterface.queryByApplicantId(applicantId);
return leavePOList.stream().map(leavePO -&gt; leaveFactory.getLeave(leavePO)).collect(Collectors.toList());
}
public List&lt;Leave&gt; queryLeaveInfosByApprover(String approverId) {
List&lt;LeavePO&gt; leavePOList = leaveRepositoryInterface.queryByApproverId(approverId);
return leavePOList.stream().map(leavePO -&gt; leaveFactory.getLeave(leavePO)).collect(Collectors.toList());
}
}
```
**领域服务开发时的注意事项:**
在领域服务或实体方法中,我们应尽量避免调用其它聚合的领域服务或引用其它聚合的实体或值对象,这种操作会增加聚合的耦合度。在微服务架构演进时,如果出现聚合拆分和重组,这种跨聚合的服务调用和对象引用,会变成跨微服务的操作,导致这种跨聚合的领域服务调用和对象引用失效,在聚合分拆时会增加你代码解耦和重构的工作量。
以下是一段不建议使用的代码。在这段代码里Approver是leave聚合的值对象它作为对象参数被传到person聚合的findNextApprover领域服务。如果在同一个微服务内这种方式是没有问题的。但在架构演进时如果person和leave两个聚合被分拆到不同的微服务中那么leave中的Approver对象以及它的getPersonId()和fromPersonPO方法在person聚合中就会失效这时你就需要进行代码重构了。
```
public class PersonDomainService {
public Approver findNextApprover(Approver currentApprover, int leaderMaxLevel) {
PersonPO leaderPO = personRepository.findLeaderByPersonId(currentApprover.getPersonId());
if (leaderPO.getRoleLevel() &gt; leaderMaxLevel) {
return null;
} else {
return Approver.fromPersonPO(leaderPO);
}
}
}
```
那正确的方式是什么样的呢在应用服务组合不同聚合的领域服务时我们可以通过ID或者参数来传数如单一参数currentApproverId。这样聚合之间就解耦了下面是修改后的代码它可以不依赖其它聚合的实体独立完成业务逻辑。
```
public class PersonDomainService {
public Person findNextApprover(String currentApproverId, int leaderMaxLevel) {
PersonPO leaderPO = personRepository.findLeaderByPersonId(currentApproverId);
if (leaderPO.getRoleLevel() &gt; leaderMaxLevel) {
return null;
} else {
return personFactory.createPerson(leaderPO);
}
}
}
```
## 领域事件
在创建请假单和请假审批过程中会产生领域事件。为了方便管理我们将聚合内的领域事件相关的代码放在聚合的event目录中。领域事件实体在聚合仓储内完成持久化但是事件实体的生命周期不受聚合根管理。
### 1. 领域事件基类DomainEvent
你可以建立统一的领域事件基类DomainEvent。基类包含事件ID、时间戳、事件源以及事件相关的业务数据。
```
public class DomainEvent {
String id;
Date timestamp;
String source;
String data;
}
```
### 2. 领域事件实体
请假领域事件实体LeaveEvent继承基类DomainEvent。可根据需要扩展属性和方法如leaveEventType。data字段中存储领域事件相关的业务数据可以是XML或Json等格式。
```
public class LeaveEvent extends DomainEvent {
LeaveEventType leaveEventType;
public static LeaveEvent create(LeaveEventType eventType, Leave leave){
LeaveEvent event = new LeaveEvent();
event.setId(IdGenerator.nextId());
event.setLeaveEventType(eventType);
event.setTimestamp(new Date());
event.setData(JSON.toJSONString(leave));
return event;
}
}
```
### 3. 领域事件的执行逻辑
一般来说,领域事件的执行逻辑如下:
第一步:执行业务逻辑,产生领域事件。
第二步:完成业务数据持久化。
```
leaveRepositoryInterface.save(leaveFactory.createLeavePO(leave));
```
第三步:完成事件数据持久化。
```
leaveRepositoryInterface.saveEvent(leaveFactory.createLeaveEventPO(event));
```
第四步:完成领域事件发布。
```
eventPublisher.publish(event);
```
以上领域事件处理逻辑代码详见LeaveDomainService中submitApproval领域服务里面有请假提交审批事件的完整处理逻辑。
### 4. 领域事件数据持久化
为了保证事件发布方与事件订阅方数据的最终一致性和数据审计,有些业务场景需要建立数据对账机制。数据对账主要通过对源端和目的端的持久化数据比对,从而发现异常数据并进一步处理,保证数据最终一致性。
对于需要对账的事件数据我们需设计领域事件对象的持久化对象PO完成领域事件数据的持久化如LeaveEvent事件实体的持久化对象LeaveEventPO。再通过聚合的仓储完成数据持久化
```
leaveRepositoryInterface.saveEvent(leaveFactory.createLeaveEventPO(event))。
```
事件数据持久化对象LeaveEventPO格式如下
```
public class LeaveEventPO {
@Id
@GenericGenerator(name = &quot;idGenerator&quot;, strategy = &quot;uuid&quot;)
@GeneratedValue(generator = &quot;idGenerator&quot;)
int id;
@Enumerated(EnumType.STRING)
LeaveEventType leaveEventType;
Date timestamp;
String source;
String data;
}
```
## 仓储模式
领域模型中DO实体的数据持久化是必不可少的DDD采用仓储模式实现数据持久化使得业务逻辑与基础资源逻辑解耦实现依赖倒置。持久化时先完成DO与PO对象的转换然后在仓储服务中完成PO对象的持久化。
### 1. DO与PO对象的转换
Leave聚合根的DO实体除了自身的属性外还会根据领域模型引用多个值对象如Applicant和Approver等它们包含多个属性personId、personName和personType等属性。
在持久化对象PO设计时你可以将这些值对象属性嵌入PO属性中或设计一个组合属性字段以Json串的方式存储在PO中。
以下是leave的DO的属性定义
```
public class Leave {
String id;
Applicant applicant;
Approver approver;
LeaveType type;
Status status;
Date startTime;
Date endTime;
long duration;
int leaderMaxLevel;
ApprovalInfo currentApprovalInfo;
List&lt;ApprovalInfo&gt; historyApprovalInfos;
}
public class Applicant {
String personId;
String personName;
String personType;
}
public class Approver {
String personId;
String personName;
int level;
}
```
为了减少数据库表数量以及表与表的复杂关联关系我们将leave实体和多个值对象放在一个LeavePO中。如果以属性嵌入的方式Applicant值对象在LeavePO中会展开为applicantId、applicantName和applicantType三个属性。
以下为采用属性嵌入方式的持久化对象LeavePO的结构。
```
public class LeavePO {
@Id
@GenericGenerator(name=&quot;idGenerator&quot;, strategy=&quot;uuid&quot;)
@GeneratedValue(generator=&quot;idGenerator&quot;)
String id;
String applicantId;
String applicantName;
@Enumerated(EnumType.STRING)
PersonType applicantType;
String approverId;
String approverName;
@Enumerated(EnumType.STRING)
LeaveType leaveType;
@Enumerated(EnumType.STRING)
Status status;
Date startTime;
Date endTime;
long duration;
@Transient
List&lt;ApprovalInfoPO&gt; historyApprovalInfoPOList;
}
```
### 2. 仓储模式
为了解耦业务逻辑和基础资源,我们可以在基础层和领域层之间增加一层仓储服务,实现依赖倒置。通过这一层可以实现业务逻辑和基础层资源的依赖分离。在变更基础层数据库的时候,你只要替换仓储实现就可以了,上层核心业务逻辑不会受基础资源变更的影响,从而实现依赖倒置。
一个聚合一个仓储,实现聚合数据的持久化。领域服务通过仓储接口来访问基础资源,由仓储实现完成数据持久化和初始化。仓储一般包含:仓储接口和仓储实现。
**2.1仓储接口**
仓储接口面向领域服务提供接口。
```
public interface LeaveRepositoryInterface {
void save(LeavePO leavePO);
void saveEvent(LeaveEventPO leaveEventPO);
LeavePO findById(String id);
List&lt;LeavePO&gt; queryByApplicantId(String applicantId);
List&lt;LeavePO&gt; queryByApproverId(String approverId);
}
```
**2.2仓储实现**
仓储实现完成数据持久化和数据库查询。
```
@Repository
public class LeaveRepositoryImpl implements LeaveRepositoryInterface {
@Autowired
LeaveDao leaveDao;
@Autowired
ApprovalInfoDao approvalInfoDao;
@Autowired
LeaveEventDao leaveEventDao;
public void save(LeavePO leavePO) {
leaveDao.save(leavePO);
approvalInfoDao.saveAll(leavePO.getHistoryApprovalInfoPOList());
}
public void saveEvent(LeaveEventPO leaveEventPO){
leaveEventDao.save(leaveEventPO);
}
@Override
public LeavePO findById(String id) {
return leaveDao.findById(id)
.orElseThrow(() -&gt; new RuntimeException(&quot;leave not found&quot;));
}
@Override
public List&lt;LeavePO&gt; queryByApplicantId(String applicantId) {
List&lt;LeavePO&gt; leavePOList = leaveDao.queryByApplicantId(applicantId);
leavePOList.stream()
.forEach(leavePO -&gt; {
List&lt;ApprovalInfoPO&gt; approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId());
leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList);
});
return leavePOList;
}
@Override
public List&lt;LeavePO&gt; queryByApproverId(String approverId) {
List&lt;LeavePO&gt; leavePOList = leaveDao.queryByApproverId(approverId);
leavePOList.stream()
.forEach(leavePO -&gt; {
List&lt;ApprovalInfoPO&gt; approvalInfoPOList = approvalInfoDao.queryByLeaveId(leavePO.getId());
leavePO.setHistoryApprovalInfoPOList(approvalInfoPOList);
});
return leavePOList;
}
}
```
这里持久化组件采用了Jpa。
```
public interface LeaveDao extends JpaRepository&lt;LeavePO, String&gt; {
List&lt;LeavePO&gt; queryByApplicantId(String applicantId);
List&lt;LeavePO&gt; queryByApproverId(String approverId);
}
```
**2.3仓储执行逻辑**
以创建请假单为例,仓储的执行步骤如下。
第一步仓储执行之前将聚合内DO会转换为PO这种转换在工厂服务中完成
```
leaveFactory.createLeavePO(leave)。
```
第二步:完成对象转换后,领域服务调用仓储接口:
```
leaveRepositoryInterface.save。
```
第三步由仓储实现完成PO对象持久化。
代码执行步骤如下:
```
public void createLeave(Leave leave, int leaderMaxLevel, Approver approver) {
leave.setLeaderMaxLevel(leaderMaxLevel);
leave.setApprover(approver);
leave.create();
leaveRepositoryInterface.save(leaveFactory.createLeavePO(leave));
}
```
## 工厂模式
对于大型的复杂领域模型聚合内的聚合根、实体和值对象之间的依赖关系比较复杂这种过于复杂的依赖关系不适合通过根实体构造器来创建。为了协调这种复杂的领域对象的创建和生命周期管理在DDD里引入了工厂模式Factory在工厂里封装复杂的对象创建过程。
当聚合根被创建时,聚合内所有依赖的对象将会被同时创建。
工厂与仓储模式往往结对出现,应用于数据的初始化和持久化两类场景。
- DO对象的初始化获取持久化对象PO通过工厂一次构建出聚合根所有依赖的DO对象完数据初始化。
- DO的对象持久化将所有依赖的DO对象一次转换为PO对象完成数据持久化。
下面代码是leave聚合的工厂类LeaveFactory。其中createLeavePOleave方法组织leave聚合的DO对象和值对象完成leavePO对象的构建。getLeaveleave通过持久化对象PO构建聚合的DO对象和值对象完成leave聚合DO实体的初始化。
```
public class LeaveFactory {
public LeavePO createLeavePO(Leave leave) {
LeavePO leavePO = new LeavePO();
leavePO.setId(UUID.randomUUID().toString());
leavePO.setApplicantId(leave.getApplicant().getPersonId());
leavePO.setApplicantName(leave.getApplicant().getPersonName());
leavePO.setApproverId(leave.getApprover().getPersonId());
leavePO.setApproverName(leave.getApprover().getPersonName());
leavePO.setStartTime(leave.getStartTime());
leavePO.setStatus(leave.getStatus());
List&lt;ApprovalInfoPO&gt; historyApprovalInfoPOList = approvalInfoPOListFromDO(leave);
leavePO.setHistoryApprovalInfoPOList(historyApprovalInfoPOList);
return leavePO;
}
public Leave getLeave(LeavePO leavePO) {
Leave leave = new Leave();
Applicant applicant = Applicant.builder()
.personId(leavePO.getApplicantId())
.personName(leavePO.getApplicantName())
.build();
leave.setApplicant(applicant);
Approver approver = Approver.builder()
.personId(leavePO.getApproverId())
.personName(leavePO.getApproverName())
.build();
leave.setApprover(approver);
leave.setStartTime(leavePO.getStartTime());
leave.setStatus(leavePO.getStatus());
List&lt;ApprovalInfo&gt; approvalInfos = getApprovalInfos(leavePO.getHistoryApprovalInfoPOList());
leave.setHistoryApprovalInfos(approvalInfos);
return leave;
}
//其它方法
}
```
## 服务的组合与编排
应用层的应用服务完成领域服务的组合与编排。一个聚合的应用服务可以建立一个应用服务类管理聚合所有的应用服务。比如leave聚合有LeaveApplicationServiceperson聚合有PersonApplicationService。
在请假微服务中有三个聚合leave、person和rule。我们来看一下应用服务是如何跨聚合来进行服务的组合和编排的。以创建请假单createLeaveInfo应用服务为例分为这样三个步骤。
第一步根据请假单定义的人员类型、请假类型和请假时长从rule聚合中获取请假审批规则。这一步通过approvalRuleDomainService类的getLeaderMaxLevel领域服务来实现。
第二步根据请假审批规则从person聚合中获取请假审批人。这一步通过personDomainService类的findFirstApprover领域服务来实现。
第三步根据请假数据和从rule和person聚合获取的数据创建请假单。这一步通过leaveDomainService类的createLeave领域服务来实现。
由于领域核心逻辑已经很好地沉淀到了领域层中,领域层的这些核心逻辑可以高度复用。应用服务只需要灵活地组合和编排这些不同聚合的领域服务,就可以很容易地适配前端业务的变化。因此应用层不会积累太多的业务逻辑代码,所以会变得很薄,代码维护起来也会容易得多。
以下是leave聚合的应用服务类。代码是不是非常得少
```
public class LeaveApplicationService{
@Autowired
LeaveDomainService leaveDomainService;
@Autowired
PersonDomainService personDomainService;
@Autowired
ApprovalRuleDomainService approvalRuleDomainService;
public void createLeaveInfo(Leave leave){
//get approval leader max level by rule
int leaderMaxLevel = approvalRuleDomainService.getLeaderMaxLevel(leave.getApplicant().getPersonType(), leave.getType().toString(), leave.getDuration());
//find next approver
Person approver = personDomainService.findFirstApprover(leave.getApplicant().getPersonId(), leaderMaxLevel);
leaveDomainService.createLeave(leave, leaderMaxLevel, Approver.fromPerson(approver));
}
public void updateLeaveInfo(Leave leave){
leaveDomainService.updateLeaveInfo(leave);
}
public void submitApproval(Leave leave){
//find next approver
Person approver = personDomainService.findNextApprover(leave.getApprover().getPersonId(), leave.getLeaderMaxLevel());
leaveDomainService.submitApproval(leave, Approver.fromPerson(approver));
}
public Leave getLeaveInfo(String leaveId){
return leaveDomainService.getLeaveInfo(leaveId);
}
public List&lt;Leave&gt; queryLeaveInfosByApplicant(String applicantId){
return leaveDomainService.queryLeaveInfosByApplicant(applicantId);
}
public List&lt;Leave&gt; queryLeaveInfosByApprover(String approverId){
return leaveDomainService.queryLeaveInfosByApprover(approverId);
}
}
```
**应用服务开发注意事项:**
为了聚合解耦和微服务架构演进,应用服务在对不同聚合领域服务进行编排时,应避免不同聚合的实体对象,在不同聚合的领域服务中引用,这是因为一旦聚合拆分和重组,这些跨聚合的对象将会失效。
在LeaveApplicationService中leave实体和Applicant值对象分别作为参数被rule聚合和person聚合的领域服务引用这样会增加聚合的耦合度。下面是不推荐使用的代码。
```
public class LeaveApplicationService{
public void createLeaveInfo(Leave leave){
//get approval leader max level by rule
ApprovalRule rule = approvalRuleDomainService.getLeaveApprovalRule(leave);
int leaderMaxLevel = approvalRuleDomainService.getLeaderMaxLevel(rule);
leave.setLeaderMaxLevel(leaderMaxLevel);
//find next approver
Approver approver = personDomainService.findFirstApprover(leave.getApplicant(), leaderMaxLevel);
leave.setApprover(approver);
leaveDomainService.createLeave(leave);
}
}
```
那如何实现聚合的解耦呢我们可以将跨聚合调用时的对象传值调整为参数传值。一起来看一下调整后的代码getLeaderMaxLevel由leave对象传值调整为personTypeleaveType和duration参数传值。findFirstApprover中Applicant值对象调整为personId参数传值。
```
public class LeaveApplicationService{
public void createLeaveInfo(Leave leave){
//get approval leader max level by rule
int leaderMaxLevel = approvalRuleDomainService.getLeaderMaxLevel(leave.getApplicant().getPersonType(), leave.getType().toString(), leave.getDuration());
//find next approver
Person approver = personDomainService.findFirstApprover(leave.getApplicant().getPersonId(), leaderMaxLevel);
leaveDomainService.createLeave(leave, leaderMaxLevel, Approver.fromPerson(approver));
}
}
```
在微服务演进和聚合重组时,就不需要进行聚合解耦和代码重构了。
## 微服务聚合拆分时的代码演进
如果请假微服务未来需要演进为人员和请假两个微服务我们可以基于请假leave和人员person两个聚合来进行拆分。由于两个聚合已经完全解耦领域逻辑非常稳定在微服务聚合代码拆分时聚合领域层的代码基本不需要调整。调整主要集中在微服务的应用服务中。
我们以应用服务createLeaveInfo为例当一个微服务拆分为两个微服务时看看代码需要做什么样的调整
### 1. 微服务拆分前
createLeaveInfo应用服务的代码如下
```
public void createLeaveInfo(Leave leave){
//get approval leader max level by rule
int leaderMaxLevel = approvalRuleDomainService.getLeaderMaxLevel(leave.getApplicant().getPersonType(), leave.getType().toString(), leave.getDuration());
//find next approver
Person approver = personDomainService.findFirstApprover(leave.getApplicant().getPersonId(), leaderMaxLevel);
leaveDomainService.createLeave(leave, leaderMaxLevel, Approver.fromPerson(approver));
}
```
### 2. 微服务拆分后
leave和person两个聚合随微服务拆分后createLeaveInfo应用服务中下面的代码将会变成跨微服务调用。
```
Person approver = personDomainService.findFirstApprover(leave.getApplicant().getPersonId(), leaderMaxLevel);
```
由于跨微服务的调用是在应用层完成的我们只需要调整createLeaveInfo应用服务代码将原来微服务内的服务调用personDomainService.findFirstApprover修改为跨微服务的服务调用personFeignService. findFirstApprover。
同时新增ApproverAssembler组装器和PersonResponse的DTO对象以便将person微服务返回的person DTO对象转换为approver值对象。
```
// PersonResponse为调用微服务返回结果的封装
//通过personFeignService调用Person微服务用户接口层的findFirstApprover facade接口
PersonResponse approverResponse = personFeignService. findFirstApprover(leave.getApplicant().getPersonId(), leaderMaxLevel);
Approver approver = ApproverAssembler.toDO(approverResponse);
```
在原来的person聚合中由于findFirstApprover领域服务已经逐层封装为用户接口层的Facade接口所以person微服务不需要做任何代码调整只需将PersonApi的findFirstApprover Facade服务发布到API网关即可。
如果拆分前person聚合的findFirstApprover领域服务没有被封装为Facade接口我们只需要在person微服务中按照以下步骤调整即可。
第一步将person聚合PersonDomainService类中的领域服务findFirstApprover封装为应用服务findFirstApprover。
```
@Service
public class PersonApplicationService {
@Autowired
PersonDomainService personDomainService;
public Person findFirstApprover(String applicantId, int leaderMaxLevel) {
return personDomainService.findFirstApprover(applicantId, leaderMaxLevel);
}
}
```
第二步将应用服务封装为Facade服务并发布到API网关。
```
@RestController
@RequestMapping(&quot;/person&quot;)
@Slf4j
public class PersonApi {
@Autowired
@GetMapping(&quot;/findFirstApprover&quot;)
public Response findFirstApprover(@RequestParam String applicantId, @RequestParam int leaderMaxLevel) {
Person person = personApplicationService.findFirstApprover(applicantId, leaderMaxLevel);
return Response.ok(PersonAssembler.toDTO(person));
}
}
```
## 服务接口的提供
用户接口层是前端应用与微服务应用层的桥梁通过Facade接口封装应用服务适配前端并提供灵活的服务完成DO和DTO相互转换。
当应用服务接收到前端请求数据时组装器会将DTO转换为DO。当应用服务向前端返回数据时组装器会将DO转换为DTO。
### 1. facade接口
facade接口可以是一个门面接口实现类也可以是门面接口加一个门面接口实现类。项目可以根据前端的复杂度进行选择由于请假微服务前端功能相对简单我们就直接用一个门面接口实现类来实现就可以了。
```
public class LeaveApi {
@PostMapping
public Response createLeaveInfo(LeaveDTO leaveDTO){
Leave leave = LeaveAssembler.toDO(leaveDTO);
leaveApplicationService.createLeaveInfo(leave);
return Response.ok();
}
@PostMapping(&quot;/query/applicant/{applicantId}&quot;)
public Response queryByApplicant(@PathVariable String applicantId){
List&lt;Leave&gt; leaveList = leaveApplicationService.queryLeaveInfosByApplicant(applicantId);
List&lt;LeaveDTO&gt; leaveDTOList = leaveList.stream().map(leave -&gt; LeaveAssembler.toDTO(leave)).collect(Collectors.toList());
return Response.ok(leaveDTOList);
}
//其它方法
}
```
### 2. DTO数据组装
组装类Assembler负责将应用服务返回的多个DO对象组装为前端DTO对象或将前端请求的DTO对象转换为多个DO对象供应用服务作为参数使用。组装类中不应有业务逻辑主要负责格式转换、字段映射等。Assembler往往与DTO同时存在。LeaveAssembler完成请假DO和DTO数据相互转换。
```
public class LeaveAssembler {
public static LeaveDTO toDTO(Leave leave){
LeaveDTO dto = new LeaveDTO();
dto.setLeaveId(leave.getId());
dto.setLeaveType(leave.getType().toString());
dto.setStatus(leave.getStatus().toString());
dto.setStartTime(DateUtil.formatDateTime(leave.getStartTime()));
dto.setEndTime(DateUtil.formatDateTime(leave.getEndTime()));
dto.setCurrentApprovalInfoDTO(ApprovalInfoAssembler.toDTO(leave.getCurrentApprovalInfo()));
List&lt;ApprovalInfoDTO&gt; historyApprovalInfoDTOList = leave.getHistoryApprovalInfos()
.stream()
.map(historyApprovalInfo -&gt; ApprovalInfoAssembler.toDTO(leave.getCurrentApprovalInfo()))
.collect(Collectors.toList());
dto.setHistoryApprovalInfoDTOList(historyApprovalInfoDTOList);
dto.setDuration(leave.getDuration());
return dto;
}
public static Leave toDO(LeaveDTO dto){
Leave leave = new Leave();
leave.setId(dto.getLeaveId());
leave.setApplicant(ApplicantAssembler.toDO(dto.getApplicantDTO()));
leave.setApprover(ApproverAssembler.toDO(dto.getApproverDTO()));
leave.setCurrentApprovalInfo(ApprovalInfoAssembler.toDO(dto.getCurrentApprovalInfoDTO()));
List&lt;ApprovalInfo&gt; historyApprovalInfoDTOList = dto.getHistoryApprovalInfoDTOList()
.stream()
.map(historyApprovalInfoDTO -&gt; ApprovalInfoAssembler.toDO(historyApprovalInfoDTO))
.collect(Collectors.toList());
leave.setHistoryApprovalInfos(historyApprovalInfoDTOList);
return leave;
}
}
```
DTO类包括requestDTO和responseDTO两部分。
DTO应尽量根据前端展示数据的需求来定义避免过多地暴露后端业务逻辑。尤其对于多渠道场景可以根据渠道属性和要求为每个渠道前端应用定义个性化的DTO。由于请假微服务相对简单我们可以用leaveDTO代码做个示例。
```
@Data
public class LeaveDTO {
String leaveId;
ApplicantDTO applicantDTO;
ApproverDTO approverDTO;
String leaveType;
ApprovalInfoDTO currentApprovalInfoDTO;
List&lt;ApprovalInfoDTO&gt; historyApprovalInfoDTOList;
String startTime;
String endTime;
long duration;
String status;
}
```
## 总结
今天我们了解了用DDD开发出来的微服务代码到底是什么样的。你可以将这些核心设计思想逐步引入到项目中去慢慢充实自己的DDD知识体系。我还想再重点强调的是由于架构的演进微服务与生俱来就需要考虑聚合的未来重组。因此微服务的设计和开发要做到未雨绸缪而这最关键的就是解耦了。
**聚合与聚合的解耦:**当多个聚合在同一个微服务时,很多传统架构开发人员会下意识地引用其他聚合的实体和值对象,或者调用其它聚合的领域服务。因为这些聚合的代码在同一个微服务内,运行时不会有问题,开发效率似乎也更高,但这样会不自觉地增加聚合之间的耦合。在微服务架构演进时,如果聚合被分别拆分到不同的微服务中,原来微服务内的关系就会变成跨微服务的关系,原来微服务内的对象引用或服务调用将会失效。最终你还是免不了要花大量的精力去做聚合解耦。虽然前期领域建模和边界划分得很好,但可能会因为开发稍不注意,而导致解耦工作前功尽弃。
**微服务内各层的解耦:**微服务内有四层在应用层和领域层组成核心业务领域的两端有两个缓冲区或数据转换区。前端与应用层通过组装器实现DTO和DO的转换这种适配方式可以更容易地响应前端需求的变化隐藏核心业务逻辑的实现保证核心业务逻辑的稳定实现核心业务逻辑与前端应用的解耦。而领域层与基础层通过仓储和工厂模式实现DO和PO的转换实现应用逻辑与基础资源逻辑的解耦。
最后我想说DDD知识体系虽大但你可以根据企业的项目场景和成本要求逐步引入适合自己的DDD方法和技术建立适合自己的DDD开发模式和方法体系。
这一期的加餐到这就结束了,希望你能对照完整代码认真阅读今天的内容,有什么疑问,欢迎在留言区与我交流!

View File

@@ -0,0 +1,102 @@
<audio id="audio" title="抽奖《DDD实战课》沉淀成书了感谢有你" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d9/b9/d9b8054e172892149aec7418e818f6b9.mp3"></audio>
你好,我是欧创新。好久不见!
专栏结课已经快一年了,累计有 **10000** 余人加入学习,非常感谢大家的支持。每次登录后台查看留言,依然能看到大家的分享,真的非常开心能和你一直保持联系、保持交流!
《DDD实战课》完结后我并没有停止在DDD方面的深度探索他一直延伸至我工作的方方面面。做了很多次的实践与思考和诸多技术大牛进行切磋加之专栏的铺垫和积累历时一年我完成了这本书**《中台架构与实现基于DDD和微服务》**,特来与你分享好消息。
<img src="https://static001.geekbang.org/resource/image/6f/16/6f6a29f043eb83yyd59f9463a2f25316.jpeg" alt="">
完整学习过专栏的同学对DDD的优势应该了如指掌了有所遗忘的同学也可以再去看看[开篇词](https://time.geekbang.org/column/article/149941)这里不再赘述。但话说回来DDD与微服务乃至中台设计的结合目前仍是一个非常新的领域。对于如何利用DDD完成中台和微服务的协同设计其实还有很多难题等待攻克。
想必你在购买这个专栏的时候就已经了解到DDD相关的资料在市面上的表现是怎样的一个状态。微服务开发和技术的学习资料非常多但在中台数字化转型过程中关于如何进行业务领域边界划分如何完成中台领域建模实现能力复用如何完成单体应用拆分和微服务设计如何实现前中后台的协同设计等等可参考和借鉴的资料并不是很多。即便有一些真正理解和实施起来也是困难重重。这也是我写书的源动力。
目前这本书已经正式出版,极客商城、京东、当当同步开售。由于新上架,**现在极客商城刚好是有限时优惠活动的,感兴趣的话可以通过该链接查看:**[http://gk.link/a/10mEQ](http://gk.link/a/10mEQ)
全书共计22万余字系统地阐述了基于DDD的中台和微服务建设的方法体系主要包括中台业务边界划分和领域模型构建微服务、微前端设计理念与实践以及如何进行前中后台的协同设计和单元化设计等内容。另外大家在《DDD实战课》中提出的宝贵问题我也在本书中做出了解答。再次感谢你与我共创内容
**今天我带了3本我的签名图书与大家分享我会在文末的留言区中抽取3位幸运同学赠书期待看到你的身影。**
下面我来简单介绍下这本书,喜欢纸质书的同学,也可以多做一点了解。先睹为快了!
## 本书大纲
共计24章分为6部分。
**第一部分认识中台第14章**
这部分包括4章主要介绍中台相关背景知识认识并理解中台的真正含义从业务中台、数据中台、技术中台以及与之匹配的组织架构等多个方面分析传统企业中台转型应该具备的能力带你初步了解DDD是如何指导中台和微服务设计并厘清它们的协作关系。
**第二部分DDD基本原理第511章**
为了让你能够更加深刻地理解DDD这部分通过一些浅显易懂的案例帮助你学习并深刻理解DDD的核心基础知识、设计思想、原则和方法等内容了解它们之间的协作和依赖关系解决DDD概念理解困难的问题做好中台实践前的准备工作。
这部分包括7章主要讲解DDD的关键核心知识体系包括领域、子域、核心子域、通用子域、支撑子域、限界上下文、实体、值对象、聚合、聚合根、领域事件和DDD分层架构等知识。
**第三部分中台领域建模与微服务设计第1219章**
这部分包括8章主要介绍DDD是如何通过战略设计构建中台业务模型以及如何通过战术设计指导微服务拆分和设计的。在这一部分我会用多个实际案例带你用DDD方法完成中台和微服务的全流程设计深刻理解DDD在中台领域建模与微服务设计中的步骤、方法、设计思想和价值。
- 了解如何用事件风暴方法构建领域模型;
- 了解如何用DDD设计思想构建企业级可复用的中台业务模型
- 了解如何用DDD设计微服务代码模型如何将领域模型映射到微服务以建立领域模型与微服务代码模型的映射关系如何完成微服务架构演进等。
最后用一个案例将DDD所有知识点串联在一起带你深入了解如何用DDD的设计方法完成领域建模与微服务设计的全流程并对代码示例进行了详细分析和讲解。
**第四部分前端设计第20章和第21章**
这部分包括2章主要介绍微前端的设计思想通过前端微服务化和单元化的设计思想解决业务中台建设完成后前端应用解耦和前后端服务集成复杂的难点。书中阐述了如何借鉴微服务的设计思想来解构前端应用实现前端应用的拆分解耦并结合实践介绍前端架构的转型策略与技术落地。
另外,这部分还探讨了基于领域模型的单元化设计方法。通过微服务与微前端组合后的单元化设计,既可以降低企业级前台应用集成的复杂度,又可以让企业具有更强的产品快速发布和业务响应能力。这种能力能给我们的团队组建、研发模式、业务能力发布等带来非常大的价值。
**第五部分中台设计案例第22章**
这部分包括1章通过保险订单化设计案例采用自顶向下的领域建模策略带你走一遍中台设计的完整流程。案例中涵盖业务领域分解、中台领域建模、微服务和微前端设计、单元化设计以及如何实现业务和数据融合等内容希望能够帮助你加深对DDD、中台、微服务和微前端等知识体系、设计思想和技术体系的全面理解更好地投入DDD、中台和微服务建设实践中。
**第六部分总结第23章和第24章**
这部分是全书的总结包括2章。书中结合我多年的设计经验和思考带你了解单体应用向微服务架构的演进策略如何避免陷入DDD设计的常见误区微服务设计原则以及分布式架构下的关键设计等内容。
## 本书阅读对象
本书是一本关于中台、微服务和微前端设计与建设的书采用了DDD设计思想和方法适合的阅读对象主要分为下面几类
1. 从事企业数字化转型的企业管理者;
1. 从事企业技术架构和微服务设计的架构师;
1. 从事企业业务架构设计和业务建模的业务人员;
1. 从事微服务设计和开发的高级技术人员;
1. 希望从事中台和微服务架构设计的人员;
1. 对DDD、微服务和中台设计感兴趣的学习者。
最后我想说DDD不是新事物它2003年诞生于集中式架构盛行的年代。**技术常变,思想则永恒!**在充满不确定的时代,凡事预则立,不预则废。心中有边界,脚下有乾坤,分而治之,方能轻松应对业务和技术的快速发展和变化。
祝你在工作中一切顺利。更多关于专栏和图书的问题都可以在留言区中与我交流。如有任何疑问、技术交流需求或建议以及纰漏之处也可直接发送至我的邮箱chuangxinou@163.com期待你的来信
>
<p>**推荐语1知行合一**<br>
&nbsp;<br>
右军<br>
支付宝专家 《深入分布式缓存》《程序员的三门课》联合作者<br>
&nbsp;<br>
个人对DDD一直比较有兴趣也包括企业架构设计、在DDD之前的领域分析如分析模式、彩色建模等。如果把软件按照相对的“稳定性”来排序领域层应用层界面层。以营销为例撬动用户的还是老三样卡、券、积分本质就是营销资产+资金流而从产品包装上可以策划满减、满返、2件折扣、限时优惠、限定电商全场消费、限定活动线下商超、限定品类等活动不一而足。领域层是相对稳定的应用层业务逻辑层和具体规则可以有多种变化而广义界面层的实质包括产品包装、交互等可以有更多的互动玩法。窃以为领域分析的价值所在就是寻求“千变万化”中相对的“稳定性、第一性”然后通过合理的架构分层及抽象隔离的业务复杂度和技术复杂度隔离业务领域的稳定性和易变性从架构上精巧、快速地支持业务的变化。技术为业务服务但绝不是业务到IT的简单翻译。<br>
&nbsp;<br>
欧老师精于保险业务对于DDD也有自己的理解和看法。从经典的DDD战略设计到基于微服务的战术设计/实现的案例本书给出了全面的参考案例。知行合一则“限界上下文”“实体”“值对象”“聚合”“事件”“事务一致性”等都不再神秘。本书也有一些可喜的创见如对于“微前端”和“业务单元化”的提炼。本书以保险订单化销售业务领域为例采用自顶向下策略完成保险部分业务领域的中台设计带领读者了解中台设计全流程理解DDD、业务中台、微服务、微前端与单元化设计的关系以及它们的核心设计思想。<br>
&nbsp;<br>
本书价值不菲强烈推荐。无论对于DDD的初学者还是DDD的资深人士都有相应的启发。写作者的最大安慰莫过于读者觉得有价值有收获。祝大家阅读愉快<br>
&nbsp;<br>
**推荐语2为不确定而架构**<br>
&nbsp;<br>
王威<br>
ThoughtWorks中国区技术战略咨询服务负责人<br>
&nbsp;<br>
在过去的几年中因为工作的关系我同很多科技类企业和组织合作过。这些企业和组织分布在不同的行业和地区从电信、金融到物流供应链从国内到全球各地。几乎所有技术行业的同仁在谈到未来的时候都流露出了很强的改变意愿和紧迫感。例如今年出现的新冠肺炎疫情以及围绕疫情在全球范围出现的一系列连锁反应都导致大家逐渐形成了一个共识世界已经从根本上改变未来20年将要发生的事情可能是我们今天根本无法想象的。在这样的背景下每一个组织都希望能够通过加大科技的投入赋能自己的客户和业务从而做好应对未知挑战的准备。<br>
&nbsp;<br>
另一方面软件“侵蚀”世界已经是不争的事实在国内的很多城市中恐怕已经很难想象完全脱离软件的生活会是什么样子。即使我们不谈“不可见”的嵌入式软件和网络控制类软件仅仅脱离了智能手机以及建于其上的各种App我们熟悉的生活似乎将无法运转下去。新兴的科技公司在利用软件技术打造新的场景培养用户的使用习惯创造新的业务价值的同时也在倒逼前辈们对传统的业务进行数字化改造以适应新时代下技术的变化速度。同时科技公司又将自己的最佳实践标准化、产品化希望通过与传统企业的合作加速整个行业变革的进程。20年前的SOA架构、6年前的微服务架构和3年前阿里的“中台”都是这种模式的很好代表。<br>
&nbsp;<br>
客户习惯的改变技术的发展和快速演进以及在一些行业出现的外力作用都带来了价值、场景、技术、政策的不确定性。所有不确定性的综合使得软件的构建过程一定会面对这样的窘境软件永远跟不上业务变化。为了解决这样的问题业界的前辈们一直在通过管理、技术、工具平台等多种维度来解决同一个问题如何使软件的构建具备更高的响应力。敏捷、精益、DevOps、效能平台都是为解决这个问题而出现的解决方案。在这个过程中“如何在复杂业务场景下设计软件”逐渐成为架构师们关注的焦点。领域驱动设计以下简称DDD的提出恰恰解决了这一问题。但是在2010年之前因为单体应用仍然占据主流地位DDD“曲高和寡”。直到“微服务”的出现才消除了原来单体应用的桎梏使得DDD成为架构师们都在讨论的软件架构设计标准实践。<br>
&nbsp;<br>
近年来DDD在国内的影响力逐年增大。我仍然记得在2015年前后和企业交流的时候当时大家对于什么是DDD完全摸不着头脑很多组织直接把源自“产品线工程”的“领域工程”和DDD作为相同的概念加以实践。2017年我们举办了第一届DDD中国峰会那时有很多参与的同行对于DDD如何在自己的组织、场景中落地还存有这样或那样的疑虑。而到2019年的第三届峰会时大家更多是带着问题和经验来和业界的同行们一起交流心得探索在新场景下如何利用DDD带来更多的价值。<br>
&nbsp;<br>
我和欧创新老师正是在这样的背景下认识的。欧老师在过去几年中将DDD的思想、微服务以及中台的理念同自己企业的实际相结合积累了丰富的实践经验。每一次和欧老师交流我都能学到很多东西。当欧老师找到我为这本书作序的时候我既受宠若惊又诚惶诚恐。在拜读完本书后我惊讶于在这么短的时间内欧老师不仅将自己获得的经验提炼总结还用通俗易懂的语言和丰富的案例将DDD、微服务、中台的概念和围绕在它们周围的实践讲述得如此详细。本书确实是业界难得的一个针对架构设计和中台转型的技术层面的总结我个人从中获益匪浅相信本书的读者朋友会和我有同样的体会。</p>

View File

@@ -0,0 +1,124 @@
<audio id="audio" title="01 | 领域驱动设计微服务设计为什么要选择DDD" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7e/e3/7ef189df2d774075ed833e15bf92aee3.mp3"></audio>
你好,我是欧创新。
我们知道,微服务设计过程中往往会面临边界如何划定的问题,我经常看到项目团队为微服务到底应该拆多小而争得面红耳赤。不同的人会根据自己对微服务的理解而拆分出不同的微服务,于是大家各执一词,谁也说服不了谁,都觉得自己很有道理。
那在实际落地过程中,我也确实见过不少项目在面临这种微服务设计困惑时,是靠拍脑袋硬完成的,上线后运维的压力就可想而知了。那是否有合适的理论或设计方法来指导微服务设计呢?当你看到这一讲的题目时,我想你已经知道答案了。
没错就是DDD。那么今天我就给你详细讲解下“微服务设计为什么要选择领域驱动设计
## 软件架构模式的演进
在进入今天的主题之前,我们先来了解下背景。
我们知道,这些年来随着设备和新技术的发展,软件的架构模式发生了很大的变化。软件架构模式大体来说经历了从单机、集中式到分布式微服务架构三个阶段的演进。随着分布式技术的快速兴起,我们已经进入到了微服务架构时代。
<img src="https://static001.geekbang.org/resource/image/8a/f8/8a859915250ffcab04675fb02fdf34f8.jpg" alt="">
我们先来分析一下软件架构模式演进的三个阶段。
**第一阶段是单机架构:**采用面向过程的设计方法系统包括客户端UI层和数据库两层采用C/S架构模式整个系统围绕数据库驱动设计和开发并且总是从设计数据库和字段开始。
**第二阶段是集中式架构:**采用面向对象的设计方法系统包括业务接入层、业务逻辑层和数据库层采用经典的三层架构也有部分应用采用传统的SOA架构。这种架构容易使系统变得臃肿可扩展性和弹性伸缩性差。
**第三阶段是分布式微服务架构:**随着微服务架构理念的提出,集中式架构正向分布式微服务架构演进。微服务架构可以很好地实现应用之间的解耦,解决单体应用扩展性和弹性伸缩能力不足的问题。
我们知道,在单机和集中式架构时代,系统分析、设计和开发往往是独立、分阶段割裂进行的。
比如在系统建设过程中我们经常会看到这样的情形A负责提出需求B负责需求分析C负责系统设计D负责代码实现这样的流程很长经手的人也很多很容易导致信息丢失。最后就很容易导致需求、设计与代码实现的不一致往往到了软件上线后我们才发现很多功能并不是自己想要的或者做出来的功能跟自己提出的需求偏差太大。
而且在单机和集中式架构这两种模式下,软件无法快速响应需求和业务的迅速变化,最终错失发展良机。此时,分布式微服务的出现就有点恰逢其时的意思了。
## 微服务设计和拆分的困境
那进入微服务架构时代以后,微服务确实也解决了原来采用集中式架构的单体应用的很多问题,比如扩展性、弹性伸缩能力、小规模团队的敏捷开发等等。
但在看到这些好处的同时,微服务实践过程中也产生了不少的争论和疑惑:微服务的粒度应该多大呀?微服务到底应该如何拆分和设计呢?微服务的边界应该在哪里?
可以说很久以来都没有一套系统的理论和方法可以指导微服务的拆分包括微服务架构模式的提出者Martin Fowler在提出微服务架构的时候也没有告诉我们究竟应该如何拆分微服务。
于是,在这段较长的时间里,就有不少人对微服务的理解产生了一些曲解。有人认为:“微服务很简单,不过就是把原来一个单体包拆分为多个部署包,或者将原来的单体应用架构替换为一套支持微服务架构的技术框架,就算是微服务了。” 还有人说:“微服务嘛,就是要微要小,拆得越小效果越好。”
但我想,这两年,你在技术圈中一定听说过一些项目因为前期微服务拆分过度,导致项目复杂度过高,无法上线和运维。
综合来看,我认为微服务拆分困境产生的根本原因就是不知道业务或者微服务的边界到底在什么地方。换句话说,确定了业务边界和应用边界,这个困境也就迎刃而解了。
那如何确定,是否有相关理论或知识体系支持呢?在回答这些问题之前,我们先来了解一下领域驱动设计与微服务的前世今生。
2004年埃里克·埃文斯Eric Evans发表了《领域驱动设计》Domain-Driven Design Tackling Complexity in the Heart of Software这本书从此领域驱动设计Domain Driven Design简称DDD诞生。DDD核心思想是通过领域驱动设计方法定义领域模型从而确定业务和应用边界保证业务模型与代码模型的一致性。
但DDD提出后在软件开发领域一直都是“雷声大雨点小”直到Martin Fowler提出微服务架构DDD才真正迎来了自己的时代。
有些熟悉DDD设计方法的软件工程师在进行微服务设计时发现可以利用DDD设计方法来建立领域模型划分领域边界再根据这些领域边界从业务视角来划分微服务边界。而按照DDD方法设计出的微服务的业务和应用边界都非常合理可以很好地实现微服务内部和外部的“高内聚、低耦合”。于是越来越多的人开始把DDD作为微服务设计的指导思想。
现在很多大型互联网企业已经将DDD设计方法作为微服务的主流设计方法了。DDD也从过去“雷声大雨点小”开始真正火爆起来。
## 为什么DDD适合微服务
“众里寻他千百度。蓦然回首,那人却在灯火阑珊处。”在经历了多年的迷茫和争论后,微服务终于寻到了他的心上人。
那DDD到底是何方神圣拥有什么神器呢
DDD是一种处理高度复杂领域的设计思想它试图分离技术实现的复杂性并围绕业务概念构建领域模型来控制业务的复杂性以解决软件难以理解难以演进的问题。DDD不是架构而是一种架构设计方法论它通过边界划分将复杂业务领域简单化帮我们设计出清晰的领域和应用边界可以很容易地实现架构演进。
**DDD包括战略设计和战术设计两部分。**
战略设计主要从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言的限界上下文,限界上下文可以作为微服务设计的参考边界。
战术设计则从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,包括:聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
我们不妨来看看DDD是如何进行战略设计的。
DDD战略设计会建立领域模型领域模型可以用于指导微服务的设计和拆分。事件风暴是建立领域模型的主要方法它是一个从发散到收敛的过程。它通常采用用例分析、场景分析和用户旅程分析尽可能全面不遗漏地分解业务领域并梳理领域对象之间的关系这是一个发散的过程。事件风暴过程会产生很多的实体、命令、事件等领域对象我们将这些领域对象从不同的维度进行聚类形成如聚合、限界上下文等边界建立领域模型这就是一个收敛的过程。
<img src="https://static001.geekbang.org/resource/image/3b/73/3bb8915fd6e880d64e9029a1f8677473.jpg" alt="">
**我们可以用三步来划定领域模型和微服务的边界。**
第一步:在事件风暴中梳理业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出领域实体等领域对象。
第二步:根据领域实体之间的业务关联性,将业务紧密相关的实体进行组合形成聚合,同时确定聚合中的聚合根、值对象和实体。在这个图里,聚合之间的边界是第一层边界,它们在同一个微服务实例中运行,这个边界是逻辑边界,所以用虚线表示。
第三步:根据业务及语义边界等因素,将一个或者多个聚合划定在一个限界上下文内,形成领域模型。在这个图里,限界上下文之间的边界是第二层边界,这一层边界可能就是未来微服务的边界,不同限界上下文内的领域逻辑被隔离在不同的微服务实例中运行,物理上相互隔离,所以是物理边界,边界之间用实线来表示。
有了这两层边界,微服务的设计就不是什么难事了。
在战略设计中我们建立了领域模型,划定了业务领域的边界,建立了通用语言和限界上下文,确定了领域模型中各个领域对象的关系。到这儿,业务端领域模型的设计工作基本就完成了,这个过程同时也基本确定了应用端的微服务边界。
在从业务模型向微服务落地的过程中,也就是从战略设计向战术设计的实施过程中,我们会将领域模型中的领域对象与代码模型中的代码对象建立映射关系,将业务架构和系统架构进行绑定。当我们去响应业务变化调整业务架构和领域模型时,系统架构也会同时发生调整,并同步建立新的映射关系。
## DDD与微服务的关系
有了上面的讲解现在我们不妨再次总结下DDD与微服务的关系。
DDD是一种架构设计方法微服务是一种架构风格两者从本质上都是为了追求高响应力而从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务出发其核心要义是强调根据业务发展合理划分领域边界持续调整现有架构优化现有代码以保持架构和代码的生命力也就是我们常说的演进式架构。
DDD主要关注从业务领域视角划分领域边界构建通用语言进行高效沟通通过业务抽象建立领域模型维持业务和代码的逻辑一致性。
微服务主要关注:运行时的进程间通信、容错和故障隔离,实现去中心化数据管理和去中心化服务治理,关注微服务的独立开发、测试、构建和部署。
## 总结
今天我们主要讨论了微服务设计和拆分的难题。通过DDD战略设计可以建立领域模型划定领域边界解决微服务设计过程中边界难以划定的难题。如果你的业务焦点在领域和领域逻辑那么你就可以选择DDD作为微服务的设计方法
更关键的一点是DDD不仅可以用于微服务设计还可以很好地应用于企业中台的设计。如果你的企业正在做中台转型DDD将会是一把利器它可以帮你建立一个非常好的企业级中台业务模型。有关这点你还会在后面的文章中见到详解。
除此之外DDD战术设计对设计和开发人员的要求相对较高实现起来相对复杂。不同企业的研发管理能力和个人开发水平可能会存在差异。尤其对于传统企业而言在战术设计落地的过程中可能会存在一定挑战和困难我建议你和你的公司如果有这方面的想法就一定要谨慎评估自己的能力选择最合适的方法落地DDD。
也不妨根据收获权衡一下,**总体来说DDD可以给你带来以下收获**
1. DDD是一套完整而系统的设计方法它能带给你从战略设计到战术设计的标准设计过程使得你的设计思路能够更加清晰设计过程更加规范。
1. DDD善于处理与领域相关的拥有高复杂度业务的产品开发通过它可以建立一个核心而稳定的领域模型有利于领域知识的传递与传承。
1. DDD强调团队与领域专家的合作能够帮助你的团队建立一个沟通良好的氛围构建一致的架构体系。
1. DDD的设计思想、原则与模式有助于提高你的架构设计能力。
1. 无论是在新项目中设计微服务还是将系统从单体架构演进到微服务都可以遵循DDD的架构原则。
1. DDD不仅适用于微服务也适用于传统的单体应用。
## 思考题
你的公司是否在实施微服务架构,你在微服务设计过程中面临的最大问题是什么?
欢迎留言和我分享你的思考和疑惑,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。

View File

@@ -0,0 +1,93 @@
<audio id="audio" title="02 | 领域、子域、核心域、通用域和支撑域:傻傻分不清?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4c/1a/4c67484764fb15be0c50387fd4ac7d1a.mp3"></audio>
你好,我是欧创新。
DDD的知识体系提出了很多的名词领域、子域、核心域、通用域、支撑域、限界上下文、聚合、聚合根、实体、值对象等等非常多。这些名词都是关键概念但它们实在有些晦涩难懂可能导致你还没开始实践DDD就打起了退堂鼓。因此在基础篇中我希望能带着你一起做好实践前的准备工作。
除此之外我想说的是这些名词在你的微服务设计和开发过程中不一定都用得上但它可以帮你理解DDD的核心设计思想和理念。而这些思想和理念在IT战略设计、业务建模和微服务设计中都是可以借鉴的。
那么从这讲开始我就会围绕以上这些DDD关键概念进行讲解帮助你彻底理清它们与微服务的关系了解它们在微服务设计中的作用。今天我们重点了解DDD的领域、子域、核心域、通用域和支撑域等重要概念。
## 如何理解领域和子域?
我们先看一下汉语词典中对领域的解释:“领域是从事一种专门活动或事业的**范围**、部类或部门。”百度百科对领域的解释:“领域具体指一种特定的**范围**或区域。”
两个解释有一个共同点——范围。对了领域就是用来确定范围的范围即边界这也是DDD在设计中不断强调边界的原因。
在研究和解决业务问题时DDD会按照一定的规则将业务领域进行细分当领域细分到一定的程度后DDD会将问题范围限定在特定的边界内在这个边界内建立领域模型进而用代码实现该领域模型解决相应的业务问题。简言之DDD的领域就是这个边界内要解决的业务问题域。
既然领域是用来限定业务边界和范围的,那么就会有大小之分,领域越大,业务范围就越大,反之则相反。
领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
我们知道DDD是一种处理高度复杂领域的设计思想它试图分离技术实现的复杂度。那么面对错综复杂的业务领域DDD是如何使业务从复杂变得简单更容易让人理解技术实现更容易呢
其实很好理解DDD的研究方法与自然科学的研究方法类似。当人们在自然科学研究中遇到复杂问题时通常的做法就是将问题一步一步地细分再针对细分出来的问题域逐个深入研究探索和建立所有子域的知识体系。当所有问题子域完成研究时我们就建立了全部领域的完整知识体系了。
<img src="https://static001.geekbang.org/resource/image/c9/c3/c99d4ef4fe28f483938e4fa03afb98c3.jpg" alt="">
我们来看一下上面这张图。这个例子是在讲如何给桃树建立一个完整的生物学知识体系。初中生物课其实早就告诉我们研究方法了。**它的研究过程是这样的。**
第一步:确定研究对象,即研究领域,这里是一棵桃树。
第二步对研究对象进行细分将桃树细分为器官器官又分为营养器官和生殖器官两种。其中营养器官包括根、茎和叶生殖器官包括花、果实和种子。桃树的知识体系是我们已经确定要研究的问题域对应DDD的领域。根、茎、叶、花、果实和种子等器官则是细分后的问题子域。这个过程就是DDD将领域细分为多个子域的过程。
第三步对器官进行细分将器官细分为组织。比如叶子器官可细分为保护组织、营养组织和输导组织等。这个过程就是DDD将子域进一步细分为多个子域的过程。
第四步:对组织进行细分,将组织细分为细胞,细胞成为我们研究的最小单元。细胞之间的细胞壁确定了单元的边界,也确定了研究的最小边界。
这里先剧透一点聚合、聚合根、实体以及值对象的内容,我还会在 [第04讲] 和 [第05讲] 中详细讲解。
我们知道细胞核、线粒体、细胞膜等物质共同构成细胞这些物质一起协作让细胞具有这类细胞特定的生物功能。在这里你可以把细胞理解为DDD的聚合细胞内的这些物质就可以理解为聚合里面的聚合根、实体以及值对象等在聚合内这些实体一起协作完成特定的业务功能。这个过程类似DDD设计时确定微服务内功能要素和边界的过程。
这里总结一下就是说每一个细分的领域都会有一个知识体系也就是DDD的领域模型。在所有子域的研究完成后我们就建立了全域的知识体系了也就建立了全域的领域模型。
上面我们用自然科学研究的方法,说明了领域可以通过细分为子域的方法,来降低研究的复杂度。现在我们把这个话题再切换到业务领域,对比验证下,二者的细分过程是否是一致的。这里以我从事的保险行业为例。
保险是个比较大的领域,很早以前的保险核心系统把所有的功能都放在一个系统里来实现,这个系统就是我们常说的单体系统。后来单体系统开始无法适应保险业务的发展,因此保险公司开始了中台转型,引入分布式微服务架构来替换原来的单体系统。而分布式微服务架构就需要划分业务领域边界,建立领域模型,并实现微服务落地了。
为实现保险领域建模和微服务建设,我们可以根据业务关联度以及流程边界将保险领域细分为:承保、收付、再保以及理赔等子域,而承保子域还可以继续细分为投保、保全(寿险)、批改(财险)等子子域。
在投保这个限界上下文内可以建立投保的领域模型,投保的领域模型最后映射到系统就是投保微服务。这就是一个保险领域的细分和微服务的建设过程。
那么你可能会说,我不是保险行业的人,我怎么理解这个过程呢?我认为,不同行业的业务模型可能会不一样,但领域建模和微服务建设的过程和方法基本类似,其核心思想就是将问题域逐步分解,降低业务理解和系统实现的复杂度。
## 如何理解核心域、通用域和支撑域?
在领域不断划分的过程中,领域会细分为不同的子域,子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域。
决定产品和公司核心竞争力的子域是核心域,它是业务成功的主要因素和公司的核心竞争力。没有太多个性化的诉求,同时被多个子域使用的通用功能子域是通用域。还有一种功能子域是必需的,但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,它就是支撑域。
这三类子域相较之下,核心域是最重要的,我们下面讲目的的时候还会以核心域为例详细介绍。通用域和支撑域如果对应到企业系统,举例来说的话,通用域则是你需要用到的通用系统,比如认证、权限等等,这类应用很容易买到,没有企业特点限制,不需要做太多的定制化。而支撑域则具有企业特性,但不具有通用性,例如数据代码类的数据字典等系统。
**那为什么要划分核心域、通用域和支撑域,主要目的是什么呢?**
还是拿上图的桃树来说吧。我们将桃树细分为了根、茎、叶、花、果实和种子等六个子域,那桃树是否有核心域?有的话,到底哪个是核心域呢?
不同的人对桃树的理解是不同的。如果这棵桃树生长在公园里,在园丁的眼里,他喜欢的是“人面桃花相映红”的阳春三月,这时花就是桃树的核心域。但如果这棵桃树生长在果园里,对果农来说,他则是希望在丰收的季节收获硕果累累的桃子,这时果实就是桃树的核心域。
在不同的场景下,不同的人对桃树核心域的理解是不同的,因此对桃树的处理方式也会不一样。园丁更关注桃树花期的营养,而果农则更关注桃树落果期的营养,有时为了保证果实的营养供给,还会裁剪掉疯长的茎和叶(通用域或支撑域)。
同样的道理公司在IT系统建设过程中由于预算和资源有限对不同类型的子域应有不同的关注度和资源投入策略记住好钢要用在刀刃上。
很多公司的业务,表面看上去相似,但商业模式和战略方向是存在很大差异的,因此公司的关注点会不一样,在划分核心域、通用域和支撑域时,其结果也会出现非常大的差异。
比如同样都是电商平台的淘宝、天猫、京东和苏宁易购他们的商业模式是不同的。淘宝是C2C网站个人卖家对个人买家而天猫、京东和苏宁易购则是B2C网站是公司卖家对个人买家。即便是苏宁易购与京东都是B2C的模式他们的商业模式也是不一样的苏宁易购是典型的传统线下卖场转型成为电商京东则是直营加部分平台模式。
商业模式的不同会导致核心域划分结果的不同。有的公司核心域可能在客户服务,有的可能在产品质量,有的可能在物流。在公司领域细分、建立领域模型和系统建设时,我们就要结合公司战略重点和商业模式,找到核心域了,且重点关注核心域。
如果你的公司刚好有意向转型微服务架构的话,我建议你和你的技术团队要将核心域的建设排在首位,最好是有绝对的掌控能力和自主研发能力,如果资源实在有限的话,可以在支撑域或者通用域上想想办法,暂时采用外购的方式也未尝不可。
## 总结
领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小微服务需要解决的问题域,构建合适的领域模型,而领域模型映射成系统就是微服务了。
核心域、支撑域和通用域的主要目标是:通过领域划分,区分不同子域在公司内的不同功能属性和重要性,从而公司可对不同子域采取不同的资源投入和建设策略,其关注度也会不一样。
## 思考题
请结合你所在公司的业务情况,尝试给业务做一个领域拆分,看看哪些子域是核心域,哪些子域是通用域和支撑域?
欢迎留言和我分享你的思考和疑惑,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。

View File

@@ -0,0 +1,123 @@
<audio id="audio" title="03 | 限界上下文:定义领域边界的利器" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/70/0d/70b1dcf0026b969a1f5d8464045f030d.mp3"></audio>
你好,我是欧创新。今天我们重点学习“限界上下文”。
在DDD领域建模和系统建设过程中有很多的参与者包括领域专家、产品经理、项目经理、架构师、开发经理和测试经理等。对同样的领域知识不同的参与角色可能会有不同的理解那大家交流起来就会有障碍怎么办呢因此在DDD中就出现了“通用语言”和“限界上下文”这两个重要的概念。
这两者相辅相成,通用语言定义上下文含义,限界上下文则定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。你是不是感觉这么描述很抽象?没关系,接下来我会给你一一详细讲解。
在这之前,我想请你先看这样两个问题,这也是今天内容的核心。
1. 为什么要提出限界上下文的概念(也就是说除了解决交流障碍这个广义的原因,还有更具体的吗)?
1. 限界上下文在微服务设计中的作用和意义是什么?
## 什么是通用语言?
为了更好地理解限界上下文,回答这两个问题,我们先从通用语言讲起。
怎么理解通用语言这个概念呢?在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。也就是说,通用语言是团队统一的语言,不管你在团队中承担什么角色,在同一个领域的软件生命周期里都使用统一的语言进行交流。
那么,通用语言的价值也就很明了了,它可以解决交流障碍这个问题,使领域专家和开发人员能够协同合作,从而确保业务需求的正确表达。
但是,对这个概念的理解,到这里还不够。
通用语言包含术语和用例场景,并且能够直接反映在代码中。通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象;而动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。
通用语言贯穿DDD的整个设计过程。作为项目团队沟通和协商形成的统一语言基于它你就能够开发出可读性更好的代码将业务需求准确转化为代码设计。
下面我带你看一张图,这张图描述了从事件风暴建立通用语言到领域对象设计和代码落地的完整过程。
<img src="https://static001.geekbang.org/resource/image/69/ee/69f44e120de5019c0fbff4d3fbc0afee.png" alt="">
1. 在事件风暴的过程中,领域专家会和设计、开发人员一起建立领域模型,在领域建模的过程中会形成通用的业务术语和用户故事。事件风暴也是一个项目团队统一语言的过程。
1. 通过用户故事分析会形成一个个的领域对象,这些领域对象对应领域模型的业务对象,每一个业务对象和领域对象都有通用的名词术语,并且一一映射。
1. 微服务代码模型来源于领域模型,每个代码模型的代码对象跟领域对象一一对应。
这里我再给你分享一条经验我自己经常用特别有效。设计过程中我们可以用一些表格来记录事件风暴和微服务设计过程中产生的领域对象及其属性。比如领域对象在DDD分层架构中的位置、属性、依赖关系以及与代码模型对象的映射关系等。
下面是一个微服务设计实例的部分数据表格中的这些名词术语就是项目团队在事件风暴过程中达成一致、可用于团队内部交流的通用语言。在这个表格里面我们可以看到DDD分析过程中所有的领域对象以及它们的属性都被记录下来了除了DDD的领域对象我们还记录了在微服务设计过程中领域对象所对应的代码对象并将它们一一映射。
<img src="https://static001.geekbang.org/resource/image/09/b8/09ca1ccc982d02634a856b2e80cf24b8.jpg" alt="">
到这里我要再强调一次。DDD分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一在代码模型设计的时侯就要建立领域对象和代码对象的一一映射从而保证业务模型和代码模型的一致实现业务语言与代码语言的统一。
如果你做到了这一点,也就是建立了领域对象和代码对象的映射关系,那就可以指导软件开发人员准确无误地按照设计文档完成微服务开发了。即使是不熟悉代码的业务人员,也可以很快找到代码的位置。
## 什么是限界上下文?
那刚刚提到的限界上下文又是用来做什么的呢?
我们知道语言都有它的语义环境同样通用语言也有它的上下文环境。为了避免同样的概念或语义在不同的上下文环境中产生歧义DDD在战略设计上提出了“限界上下文”这个概念用来确定语义所在的领域边界。
我们可以将限界上下文拆解为两个词:限界和上下文。限界就是领域的边界,而上下文则是语义环境。通过领域的限界上下文,我们就可以在统一的领域边界内用统一的语言进行交流。
综合一下,我认为限界上下文的定义就是:用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。
### 进一步理解限界上下文
我们可以通过一些例子进一步理解一下这个概念不要小看它彻底弄懂会给你后面实践DDD打下一个坚实的基础。
都说中文这门语言非常丰富,在不同的时空和背景下,同样的一句话会有不同的涵义。有一个例子你应该听说过。
在一个明媚的早晨,孩子起床问妈妈:“今天应该穿几件衣服呀?”妈妈回答:“能穿多少就穿多少!”
那到底是穿多还是穿少呢?
如果没有具体的语义环境,还真不太好理解。但是,如果你已经知道了这句话的语义环境,比如是寒冬腊月或者是炎炎夏日,那理解这句话的涵义就会很容易了。
所以语言离不开它的语义环境。
而业务的通用语言就有它的业务边界,我们不大可能用一个简单的术语没有歧义地去描述一个复杂的业务领域。限界上下文就是用来细分领域,从而定义通用语言所在的边界。
现在我们用一个保险领域的例子来说明下术语的边界。保险业务领域有投保单、保单、批单、赔案等保险术语,它们分别应用于保险的不同业务流程。
1. 客户投保时,业务人员记录投保信息,系统对应有投保单实体对象。
1. 缴费完成后,业务人员将投保单转为保单,系统对应有保单实体对象,保单实体与投保单实体关联。
1. 如客户需要修改保单信息,保单变为批单,系统对应有批单实体对象,批单实体与保单实体关联。
1. 如果客户发生理赔,生成赔案,系统对应有报案实体对象,报案实体对象与保单或者批单实体关联。
投保单、保单、批单、赔案等,这些术语虽然都跟保单有关,但不能将保单这个术语作用在保险全业务领域。因为术语有它的边界,超出了边界理解上就会出现问题。
如果你对我从事的保险业不大了解也没关系,电商肯定再熟悉不过了吧?
正如电商领域的商品一样,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。看到这,我想你应该非常清楚了,领域边界就是通过限界上下文来定义的。
## 限界上下文和微服务的关系
接下来,我们对这个概念做进一步的延伸。看看限界上下文和微服务具体存在怎样的关系。
我想你买过车险吧,或者听过吧。车险承保的流程包含了投保、缴费、出单等几个主要流程。如果出险了还会有报案、查勘、定损、理算等理赔流程。
保险领域还是很复杂的,在这里我用一个简化的保险模型来说明下限界上下文和微服务的关系。这里还会用到我们在 [[第02讲]](https://time.geekbang.org/column/article/149945) 学到一些基础知识,比如领域和子域。
<img src="https://static001.geekbang.org/resource/image/4c/5b/4c26f42d035da0cd5cbe1f25c48c205b.jpg" alt="">
首先,领域可以拆分为多个子领域。一个领域相当于一个问题域,领域拆分为子域的过程就是大问题拆分为小问题的过程。在这个图里面保险领域被拆分为:投保、支付、保单管理和理赔四个子域。
子域还可根据需要进一步拆分为子子域,比如,支付子域可继续拆分为收款和付款子子域。拆到一定程度后,有些子子域的领域边界就可能变成限界上下文的边界了。
子域可能会包含多个限界上下文,如理赔子域就包括报案、查勘和定损等多个限界上下文(限界上下文与理赔的子子域领域边界重合)。也有可能子域本身的边界就是限界上下文边界,如投保子域。
每个领域模型都有它对应的限界上下文,团队在限界上下文内用通用语言交流。领域内所有限界上下文的领域模型构成整个领域的领域模型。
理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务,就完成了从问题域到软件的解决方案。
可以说,限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
不过,这里还是要提示一下:除了理论,微服务的拆分还是有很多限制因素的,在设计中不宜过度拆分。那这个度怎么把握好呢?有关微服务设计和具体的拆分方法,我会在实战篇中详细讲解。
## 总结
通用语言确定了项目团队内部交流的统一语言,而这个语言所在的语义环境则是由限界上下文来限定的,以确保语义的唯一性。
而领域专家、架构师和开发人员的主要工作就是通过事件风暴来划分限界上下文。限界上下文确定了微服务的设计和拆分方向,是微服务设计和拆分的主要依据。如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
可以说限界上下文在微服务设计中具有很重要的意义如果限界上下文的方向偏离那微服务的设计结果也就可想而知了。因此我们只有理解了限界上下文的真正涵义以及它在微服务设计中的作用才能真正发挥DDD的价值这是基础也是前提。
## 思考题
现在,不妨回头看看,开头的两个问题你能回答了吗?你可以尝试用自己的话来总结一下。
最后再给你留一个作业,你能找一找自己工作中的通用语言和限界上下文吗?可以把你的答案和感受写下来,分享到留言区,与我一起讨论。也欢迎你把今天的内容分享给同事和朋友,邀请他一起学习。

View File

@@ -0,0 +1,149 @@
<audio id="audio" title="04 | 实体和值对象:从领域模型的基础单元看系统设计" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/69/e8/69a2e44b51107f72973d4e437a1cdbe8.mp3"></audio>
你好我是欧创新。今天我们来学习DDD战术设计中的两个重要概念实体和值对象。
这两个概念都是领域模型中的领域对象。它们在领域模型中起什么作用,战术设计时如何将它们映射到代码和数据模型中去?就是我们这一讲重点要关注的问题。
另外,在战略设计向战术设计过渡的这个过程中,理解和区分实体和值对象在不同阶段的形态是很重要的,毕竟阶段不同,它们的形态也会发生变化,这与我们的设计和代码实现密切相关。
接下来,我们就分别看看实体和值对象的这些问题,从中找找答案。
## 实体
我们先来看一下实体是什么东西?
在DDD中有这样一类对象它们拥有唯一标识符且标识符在历经各种状态变更后仍能保持一致。对这些对象而言重要的不是其属性而是其延续性和标识对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。没理解没关系请继续阅读。
### 1. 实体的业务形态
在DDD不同的设计过程中实体的形态是不同的。在战略设计时实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。在事件风暴中我们可以根据命令、操作或者事件找出产生这些行为的业务实体对象进而按照一定的业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类形成聚合。你可以这么理解实体和值对象是组成领域模型的基础单元。
### 2. 实体的代码形态
在代码模型中实体的表现形式是实体类这个类包含了实体的属性和方法通过这些方法实现实体自身的业务逻辑。在DDD里这些实体类通常采用充血模型与这个实体相关的所有业务逻辑都在实体类的方法中实现跨多个实体的领域逻辑则在领域服务中实现。
### 3. 实体的运行形态
实体以DO领域对象的形式存在每个实体对象都有唯一的ID。我们可以对一个实体对象进行多次修改修改后的数据和原来的数据可能会大不相同。但是由于它们拥有相同的ID它们依然是同一个实体。比如商品是商品上下文的一个实体通过唯一的商品ID来标识不管这个商品的数据如何变化商品的ID一直保持不变它始终是同一个商品。
### 4. 实体的数据库形态
与传统数据模型设计优先不同DDD是先构建领域模型针对实际业务场景构建实体对象和行为再将实体对象映射到数据持久化对象。
在领域模型映射到数据模型时一个实体可能对应0个、1个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。在某些场景中有些实体只是暂驻静态内存的一个运行态实体它不需要持久化。比如基于多个价格配置数据计算后生成的折扣实体。
而在有些复杂场景下实体与持久化对象则可能是一对多或者多对一的关系。比如用户user与角色role两个持久化对象可生成权限实体一个实体对应两个持久化对象这是一对多的场景。再比如有些场景为了避免数据库的联表查询提升系统性能会将客户信息customer和账户信息account两类数据保存到同一张数据库表中客户和账户两个实体可根据需要从一个持久化对象中生成这就是多对一的场景。
## 值对象
值对象相对实体来说,会更加抽象一些,概念上我们会结合例子来讲。
我们先看一下《实现领域驱动设计》一书中对值对象的定义通过对象属性值来识别的对象它将多个相关属性组合为一个概念整体。在DDD中用来描述领域的特定方面并且是一个没有标识符的对象叫作值对象。
也就说,值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。当度量和描述改变时,可以用另外一个值对象予以替换。它可以和其它值对象进行相等性比较,且不会对协作对象造成副作用。这部分在后面讲“值对象的运行形态”时还会有例子。
上面这两段对于定义的阐述,如果你还是觉得有些晦涩,我们不妨“翻译”一下,用更通俗的语言把定义讲清楚。
简单来说,值对象本质上就是一个集。那这个集合里面有什么呢?若干个用于描述目的、具有整体概念和不可修改的属性。那这个集合存在的意义又是什么?在领域建模的过程中,值对象可以保证属性归类的清晰和概念的完整性,避免属性零碎。
这里我举个简单的例子,请看下面这张图:
<img src="https://static001.geekbang.org/resource/image/13/f6/136512ac4c65b3f2ed4b2898b40965f6.jpg" alt="">
人员实体原本包括:姓名、年龄、性别以及人员所在的省、市、县和街道等属性。这样显示地址相关的属性就很零碎了对不对?现在,我们可以将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”,这个集合就是值对象了。
### 1. 值对象的业务形态
值对象是DDD领域模型中的一个基础对象它跟实体一样都来源于事件风暴所构建的领域模型都包含了若干个属性它与实体一起构成聚合。
我们不妨对照实体,来看值对象的业务形态,这样更好理解。本质上,实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。而值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征。
在值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文,有自己的持久化对象,可以建立共享的数据类微服务,比如数据字典。
### 2. 值对象的代码形态
值对象在代码中有这样两种形态。如果值对象是单一属性则直接定义为实体类的属性如果值对象是属性集合则把它设计为Class类Class将具有整体概念的多个属性归集到属性集合这样的值对象没有ID会被实体整体引用。
我们看一下下面这段代码person这个实体有若干个单一属性的值对象比如Id、name等属性同时它也包含多个属性的值对象比如地址address。
<img src="https://static001.geekbang.org/resource/image/c5/1b/c597d53a1a1aeca274d355e8ac79cc1b.jpg" alt="">
### 3. 值对象的运行形态
实体实例化后的DO对象的业务属性和业务行为非常丰富但值对象实例化的对象则相对简单和乏味。除了值对象数据初始化和整体替换的行为外其它业务行为就很少了。
值对象嵌入到实体的话,有这样两种不同的数据格式,也可以说是两种方式,分别是属性嵌入的方式和序列化大对象的方式。
引用单一属性的值对象或只有一条记录的多属性值对象的实体,可以采用属性嵌入的方式嵌入。引用一条或多条记录的多属性值对象的实体,可以采用序列化大对象的方式嵌入。比如,人员实体可以有多个通讯地址,多个地址序列化后可以嵌入人员的地址属性。值对象创建后就不允许修改了,只能用另外一个值对象来整体替换。
**如果你对这两种方式不够了解,可以看看下面的例子。**
案例1以属性嵌入的方式形成的人员实体对象地址值对象直接以属性值嵌入人员实体中。
<img src="https://static001.geekbang.org/resource/image/32/fa/323c8aca1271cc043558dfd1f95f57fa.jpg" alt="">
案例2以序列化大对象的方式形成的人员实体对象地址值对象被序列化成大对象Json串后嵌入人员实体中。
<img src="https://static001.geekbang.org/resource/image/96/23/96253ac8bd1d93a2786b59d7b9c2c423.jpg" alt="">
### 4. 值对象的数据库形态
DDD引入值对象是希望实现从“数据建模为中心”向“领域建模为中心”转变减少数据库表的数量和表与表之间复杂的依赖关系尽可能地简化数据库设计提升数据库性能。
如何理解用值对象来简化数据库设计呢?
传统的数据建模大多是根据数据库范式设计的每一个数据库表对应一个实体每一个实体的属性值用单独的一列来存储一个实体主表会对应N个实体从表。而值对象在数据库持久化方面简化了设计它的数据库设计大多采用非数据库范式值对象的属性值和实体对象的属性值保存在同一个数据库实体表中。
举个例子,还是基于上述人员和地址那个场景,实体和数据模型设计通常有两种解决方案:第一是把地址值对象的所有属性都放到人员实体表中,创建人员实体,创建人员数据表;第二是创建人员和地址两个实体,同时创建人员和地址两张表。
第一个方案会破坏地址的业务涵义和概念完整性,第二个方案增加了不必要的实体和表,需要处理多个实体和表的关系,从而增加了数据库设计的复杂性。
**那到底应该怎样设计,才能让业务含义清楚,同时又不让数据库变得复杂呢?**
我们可以综合这两个方案的优势,扬长避短。在领域建模时,我们可以把地址作为值对象,人员作为实体,这样就可以保留地址的业务涵义和概念完整性。而在数据建模时,我们可以将地址的属性值嵌入人员实体数据库表中,只创建人员数据库表。这样既可以兼顾业务含义和表达,又不增加数据库的复杂度。
值对象就是通过这种方式,简化了数据库设计,总结一下就是:在领域建模时,我们可以将部分对象设计为值对象,保留对象的业务涵义,同时又减少了实体的数量;在数据建模时,我们可以将值对象嵌入实体,减少实体表的数量,简化数据库设计。
另外也有DDD专家认为要想发挥对象的威力就需要优先做领域建模弱化数据库的作用只把数据库作为一个保存数据的仓库即可。即使违反数据库设计原则也不用大惊小怪只要业务能够顺利运行就没什么关系。
### 5. 值对象的优势和局限
值对象是一把双刃剑,它的优势是可以简化数据库设计,提升数据库性能。但如果值对象使用不当,它的优势就会很快变成劣势。“知彼知己,方能百战不殆”,你需要理解值对象真正适合的场景。
值对象采用序列化大对象的方法简化了数据库设计,减少了实体表的数量,可以简单、清晰地表达业务概念。这种设计方式虽然降低了数据库设计的复杂度,但却无法满足基于值对象的快速查询,会导致搜索值对象属性值变得异常困难。
值对象采用属性嵌入的方法提升了数据库的性能,但如果实体引用的值对象过多,则会导致实体堆积一堆缺乏概念完整性的属性,这样值对象就会失去业务涵义,操作起来也不方便。
所以,你可以对照着以上这些优劣势,结合你的业务场景,好好想一想了。那如果在你的业务场景中,值对象的这些劣势都可以避免掉,那就请放心大胆地使用值对象吧。
## 实体和值对象的关系
实体和值对象是微服务底层的最基础的对象,一起实现实体最基本的核心领域逻辑。
值对象和实体在某些场景下可以互换很多DDD专家在这些场景下其实也很难判断到底将领域对象设计成实体还是值对象可以说值对象在某些场景下有很好的价值但是并不是所有的场景都适合值对象。你需要根据团队的设计和开发习惯以及上面的优势和局限分析选择最适合的方法。
关于值对象我还要多说几句。其实DDD引入值对象还有一个重要的原因就是到底领域建模优先还是数据建模优先
DDD提倡从领域模型设计出发而不是先设计数据模型。前面讲过了传统的数据模型设计通常是一个表对应一个实体一个主表关联多个从表当实体表太多的时候就很容易陷入无穷无尽的复杂的数据库设计领域模型就很容易被数据模型绑架。可以说值对象的诞生在一定程度上和实体是互补的。
我们还是以前面的图示为例:
<img src="https://static001.geekbang.org/resource/image/13/f6/136512ac4c65b3f2ed4b2898b40965f6.jpg" alt="">
在领域模型中人员是实体,地址是值对象,地址值对象被人员实体引用。在数据模型设计时,地址值对象可以作为一个属性集整体嵌入人员实体中,组合形成上图这样的数据模型;也可以以序列化大对象的形式加入到人员的地址属性中,前面表格有展示。
从这个例子中,我们可以看出,同样的对象在不同的场景下,可能会设计出不同的结果。有些场景中,地址会被某一实体引用,它只承担描述实体的作用,并且它的值只能整体替换,这时候你就可以将地址设计为值对象,比如收货地址。而在某些业务场景中,地址会被经常修改,地址是作为一个独立对象存在的,这时候它应该设计为实体,比如行政区划中的地址信息维护。
## 总结
今天我们主要学习了实体和值对象在DDD不同设计阶段的形态以及它们从战略设计向战术设计演进过程中的设计方法。
这个过程是从业务模型向系统模型落地的过程比较复杂很考验你的设计能力很多时候我们都要结合自己的业务场景选择合适的方法来进行微服务设计。强调一点我们不避讳传统的设计方法毕竟适合自己的才是最好的。希望你能充分理解实体和值对象的概念和应用将学到的知识复用最终将适合自己业务的DDD设计方法纳入到架构体系实现落地。
## 思考题
请用自己的话总结下,实体和值对象的主要区别是什么?
欢迎留言和我分享你的思考和疑惑,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。

View File

@@ -0,0 +1,95 @@
<audio id="audio" title="05 | 聚合和聚合根:怎样设计聚合?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/50/8d/507678a0ca5a2a764ed37f400595798d.mp3"></audio>
你好我是欧创新。今天我们来学习聚合Aggregate和聚合根AggregateRoot
我们先回顾下上一讲在事件风暴中我们会根据一些业务操作和行为找出实体Entity或值对象ValueObject进而将业务关联紧密的实体和值对象进行组合构成聚合再根据业务语义将多个聚合划定到同一个限界上下文Bounded Context并在限界上下文内完成领域建模。
那你知道为什么要在限界上下文和实体之间增加聚合和聚合根这两个概念吗?它们的作用是什么?怎么设计聚合?这就是我们这一讲重点要关注的问题。
## 聚合
在DDD中实体和值对象是很基础的领域对象。实体一般对应业务对象它具有业务属性和业务行为而值对象主要是属性集合对实体的状态和特征进行描述。但实体和值对象都只是个体化的对象它们的行为表现出来的是个体的能力。
**那聚合在其中起什么作用呢?**
举个例子。社会是由一个个的个体组成的,象征着我们每一个人。随着社会的发展,慢慢出现了社团、机构、部门等组织,我们开始从个人变成了组织的一员,大家可以协同一致的工作,朝着一个最大的目标前进,发挥出更大的力量。
领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。
你可以这么理解,聚合就是由业务和逻辑紧密关联的实体和值对象组合而成的,聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化。
聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。按照这种方式设计出来的微服务很自然就是“高内聚、低耦合”的。
聚合在DDD分层架构里属于领域层领域层包含了多个聚合共同实现核心业务逻辑。聚合内实体以充血模型实现个体业务能力以及业务逻辑的高内聚。跨多个实体的业务逻辑通过领域服务来实现跨多个聚合的业务逻辑通过应用服务来实现。比如有的业务场景需要同一个聚合的A和B两个实体来共同完成我们就可以将这段业务逻辑用领域服务来实现而有的业务逻辑需要聚合C和聚合D中的两个服务共同完成这时你就可以用应用服务来组合这两个服务。
## 聚合根
聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。
传统数据模型中的每一个实体都是对等的,如果任由实体进行无控制地调用和数据修改,很可能会导致实体之间数据逻辑的不一致。而如果采用锁的方式则会增加软件的复杂度,也会降低系统的性能。
如果把聚合比作组织,那聚合根就是这个组织的负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。
首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。
其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
最后在聚合之间它还是聚合对外的接口人以聚合根ID关联的方式接受外部任务和请求在上下文内实现聚合之间的业务协同。也就是说聚合之间通过聚合根ID关联引用如果需要访问其它聚合的实体就要先访问聚合根再导航到聚合内部实体外部对象不能直接访问聚合内实体。
## 怎样设计聚合?
DDD领域建模通常采用事件风暴它通常采用用例分析、场景分析和用户旅程分析等方法通过头脑风暴列出所有可能的业务行为和事件然后找出产生这些行为的领域对象并梳理领域对象之间的关系找出聚合根找出与聚合根业务紧密关联的实体和值对象再将聚合根、实体和值对象组合构建聚合。
下面我们以保险的投保业务场景为例,看一下聚合的构建过程主要都包括哪些步骤。
<img src="https://static001.geekbang.org/resource/image/d4/dc/d4975de95bc31f954d11yyaee32a65dc.png" alt="">
**第 1 步:**采用事件风暴,根据业务行为,梳理出在投保过程中发生这些行为的所有的实体和值对象,比如投保单、标的、客户、被保人等等。
**第 2 步:**从众多实体中选出适合作为对象管理者的根实体也就是聚合根。判断一个实体是否是聚合根你可以结合以下场景分析是否有独立的生命周期是否有全局唯一ID是否可以创建或修改其它对象是否有专门的模块来管这个实体。图中的聚合根分别是投保单和客户实体。
**第 3 步:**根据业务单一职责和高内聚原则,找出与聚合根关联的所有紧密依赖的实体和值对象。构建出 1 个包含聚合根(唯一)、多个实体和值对象的对象集合,这个集合就是聚合。在图中我们构建了客户和投保这两个聚合。
**第 4 步:**在聚合内根据聚合根、实体和值对象的依赖关系画出对象的引用和依赖模型。这里我需要说明一下投保人和被保人的数据是通过关联客户ID从客户聚合中获取的在投保聚合里它们是投保单的值对象这些值对象的数据是客户的冗余数据即使未来客户聚合的数据发生了变更也不会影响投保单的值对象数据。从图中我们还可以看出实体之间的引用关系比如在投保聚合里投保单聚合根引用了报价单实体报价单实体则引用了报价规则子实体。
**第 5 步:**多个聚合根据业务语义和上下文一起划分到同一个限界上下文内。
这就是一个聚合诞生的完整过程了。
## 聚合的一些设计原则
我们不妨先看一下《实现领域驱动设计》一书中对聚合设计原则的描述,原文是有点不太好理解的,我来给你解释一下。
**1. 在一致性边界内建模真正的不变条件。**聚合用来封装真正的不变性,而不是简单地将对象组合在一起。聚合内有一套不变的业务规则,各实体和值对象按照统一的业务规则运行,实现对象数据的一致性,边界之外的任何东西都与该聚合无关,这就是聚合能实现业务高内聚的原因。
**2. 设计小聚合。**如果聚合设计得过大,聚合会因为包含过多的实体,导致实体之间的管理过于复杂,高频操作时会出现并发冲突或者数据库锁,最终导致系统可用性变差。而小聚合设计则可以降低由于业务过大导致聚合重构的可能性,让领域模型更能适应业务的变化。
**3. 通过唯一标识引用其它聚合。**聚合之间是通过关联外部聚合根ID的方式引用而不是直接对象引用的方式。外部聚合的对象放在聚合边界内管理容易导致聚合的边界不清晰也会增加聚合之间的耦合度。
**4. 在边界之外使用最终一致性。**聚合内数据强一致性,而聚合之间数据最终一致性。在一次事务中,最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的方式异步修改相关的聚合,实现聚合之间的解耦(相关内容我会在领域事件部分详解)。
**5. 通过应用层实现跨聚合的服务调用。**为实现微服务内聚合之间的解耦,以及未来以聚合为单位的微服务组合和拆分,应避免跨聚合的领域服务调用和跨聚合的数据库表关联。
上面的这些原则是DDD的一些通用的设计原则还是那句话“适合自己的才是最好的。”在系统设计过程时你一定要考虑项目的具体情况如果面临使用的便利性、高性能要求、技术能力缺失和全局事务管理等影响因素这些原则也并不是不能突破的总之一切以解决实际问题为出发点。
## 总结
[[第04讲]](https://time.geekbang.org/column/article/152677) 和 [第05讲] 的内容,其实是有强关联的。我们不妨在这里总结下聚合、聚合根、实体和值对象它们之间的联系和区别。
**聚合的特点:**高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但我不建议你对微服务过度拆分。但在对性能有极致要求的场景中,聚合可以独立作为一个微服务,以满足版本的高频发布和极致的弹性伸缩能力。
一个微服务可以包含多个聚合,聚合之间的边界是微服务内天然的逻辑边界。有了这个逻辑边界,在微服务架构演进时就可以以聚合为单位进行拆分和组合了,微服务的架构演进也就不再是一件难事了。
**聚合根的特点:**聚合根是实体有实体的特点具有全局唯一标识有独立的生命周期。一个聚合只有一个聚合根聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调聚合根与聚合根之间通过ID关联的方式实现聚合之间的协同。
**实体的特点:**有ID标识通过ID判断相等性ID在聚合内唯一即可。状态可变它依附于聚合根其生命周期由聚合根管理。实体一般会持久化但与数据库持久化对象不一定是一对一的关系。实体可以引用聚合内的聚合根、实体和值对象。
**值对象的特点:**无ID不可变无生命周期用完即扔。值对象之间通过属性值判断相等性。它的核心本质是值是一组概念完整的属性组成的集合用于描述实体的状态和特征。值对象尽量只引用值对象。
## 思考题
请你结合公司的某个业务场景,试试能分析出哪些聚合?
欢迎留言和我分享你的思考,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进阶。
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,139 @@
<audio id="audio" title="11 | DDD实践如何用DDD重构中台业务模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b0/79/b0b0110790317f92ab46705c7fbed179.mp3"></audio>
你好,我是欧创新。
进入两千年后随着互联网应用的快速发展很多传统企业开始触网建设自己的互联网电商平台。后来又随着微信和App等移动互联应用的兴起又形成了新一轮的移动应用热潮。这些移动互联应用大多面向个人或者第三方市场和需求变化快需要以更敏捷的速度适应市场变化为了保持快速响应能力和频繁发版的要求很多时候这些移动互联网应用是独立于传统核心系统建设的但两者承载的业务大部分又都是同质的因此很容易出现业务能力重叠的问题。
阿里巴巴过去带动了传统企业向互联网电商转型。而如今又到了一个新的历史时期,在阿里巴巴提出中台战略后,很多企业又紧跟它的步伐,高举中台大旗,轰轰烈烈地开始了数字化转型之路。
那么传统企业在中台转型时该如何从错综复杂的业务中构建中台业务模型呢今天我就用一个传统企业中台建模的案例带你一起用DDD的设计思想来构建中台业务模型。
## 传统企业应用分析
互联网电商平台和传统核心应用,两者面向的渠道和客户不一样,但销售的产品却很相似,它们之间的业务模型既有相同的地方,又有不同的地方。
现在我拿保险行业的互联网电商和传统核心应用来做个对比分析。我们看一下下面这张图,这两者在业务功能上会有很多相似和差异,这种相似和差异主要体现在四个方面。
<img src="https://static001.geekbang.org/resource/image/25/07/255beadd4cf7a842c083ccea0ec19807.jpg" alt="">
**1. 核心能力的重复建设。**由于销售同质保险产品,二者在核心业务流程和功能上必然相似,因此在核心业务能力上存在功能重叠是不可避免的。传统保险核心应用有报价、投保、核保和出单功能,同样在互联网电商平台也有。这就是核心能力的重复建设。
**2. 通用能力的重复建设。**传统核心应用的通用平台大而全,通常会比较重。而互联网电商平台离不开这些通用能力的支撑,但为了保持敏捷性,一般会自己建设缩小版的通用功能,比如用户、客户等。这是通用能力的重复建设。
**3. 业务职能的分离建设。**有一类业务功能在互联网电商平台中建设了一部分在传统核心应用中也建设了一部分二者功能不重叠而且还互补组合在一起是一个完整的业务职能。比如缴费功能互联网电商平台主要面向个人客户于是采用了支付宝和微信支付的方式。而传统核心应用主要是柜台操作仍在采用移动POS机的缴费方式。二者都是缴费为了保证业务模型的完整性在构建中台业务模型时我们可以考虑将这两部分模型重组为一个完整的业务模型。
**4. 互联网电商平台和传统核心功能前后完全独立建设。**传统核心应用主要面向柜台,不需要互联网电商平台的在线客服、话务、订单和购物车等功能。而互联网电商平台主要面向个人客户,它不需要后端比较重的再保、佣金、打印等功能。在构建中台业务模型时,对这种情况应区别对待,将面向后端业务管理的应用沉淀到后台,将前端能力构建为面向互联网渠道的通用中台,比如订单等。
## 如何避免重复造轮子?
要避免重复建设,就要理解中台的理念和思想。前面说了“**中台是企业级能力复用平台**”,“**复用**”用白话说就是重复使用,就是要避免重复造轮子的事情。
中台的设计思想与“高内聚、低耦合”的设计原则是高度一致的。高内聚是把相关的业务行为聚集在一起,把不相关的行为放在其它地方,如果你要修改某个业务行为,只需要修改一处。对了!中台就是要这样做,按照“高内聚、松耦合”的原则,实现企业级的能力复用!
那如果你的企业遇到了重复造轮子的情况,应该怎么处理?
你需要站在企业高度,将重复的需要共享的通用能力、核心能力沉淀到中台,将分离的业务能力重组为完整的业务板块,构建可复用的中台业务模型。前端个性能力归前端,后端管理能力归后台。建立前、中、后台边界清晰,融合协作的企业级可复用的业务模型。
## 如何构建中台业务模型?
我们可以用DDD领域建模的方法来构建中台业务模型。你可以选择两种建模策略自顶向下和自底向上的策略。具体采用哪种策略你需要结合公司的具体情况来分析下面我就来介绍一下这两种策略。
**1. 自顶向下的策略**
第一种策略是自顶向下。这种策略是先做顶层设计,从最高领域逐级分解为中台,分别建立领域模型,根据业务属性分为通用中台或核心中台。领域建模过程主要基于业务现状,暂时不考虑系统现状。自顶向下的策略适用于全新的应用系统建设,或旧系统推倒重建的情况。
由于这种策略不必受限于现有系统你可以用DDD领域逐级分解的领域建模方法。从下面这张图我们可以看出它的主要步骤第一步是将领域分解为子域子域可以分为核心域、通用域和支撑域第二步是对子域建模划分领域边界建立领域模型和限界上下文第三步则是根据限界上下文进行微服务设计。
<img src="https://static001.geekbang.org/resource/image/e6/da/e665d85381a9b2c599555cac6a06deda.jpg" alt="">
**2. 自底向上的策略**
第二种策略是自底向上。这种策略是基于业务和系统现状完成领域建模。首先分别完成系统所在业务域的领域建模;然后对齐业务域,找出具有同类或相似业务功能的领域模型,对比分析领域模型的差异,重组领域对象,重构领域模型。这个过程会沉淀公共和复用的业务能力,会将分散的业务模型整合。自底向上策略适用于遗留系统业务模型的演进式重构。
下面我以互联网电商和传统核心应用的几个典型业务域为例,带你了解具体如何采用自底向上的策略来构建中台业务模型,主要分为这样三个步骤。
**第一步:锁定系统所在业务域,构建领域模型。**
锁定系统所在的业务域,采用事件风暴,找出领域对象,构建聚合,划分限界上下文,建立领域模型。看一下下面这张图,我们选取了传统核心应用的用户、客户、传统收付和承保四个业务域以及互联网电商业务域,共计五个业务域来完成领域建模。
<img src="https://static001.geekbang.org/resource/image/f5/46/f537a7a43e77212c8a85241439b2f246.jpg" alt="">
从上面这张图中我们可以看到传统核心共构建了八个领域模型。其中用户域构建了用户认证和权限两个领域模型客户域构建了个人和团体两个领域模型传统收付构建了POS刷卡领域模型承保域构建了定报价、投保和保单管理三个领域模型。
互联网电商构建了报价、投保、订单、客户、用户认证和移动收付六个领域模型。
在这些领域模型的清单里,我们可以看到二者之间有很多名称相似的领域模型。深入分析后你会发现,这些名称相似的领域模型存在业务能力重复,或者业务职能分散(比如移动支付和传统支付)的问题。那在构建中台业务模型时,你就需要重点关注它们,将这些不同领域模型中重复的业务能力沉淀到中台业务模型中,将分散的领域模型整合到统一的中台业务模型中,对外提供统一的共享的中台服务。
**第二步:对齐业务域,构建中台业务模型。**
在下面这张图里,你可以看到右侧的传统核心领域模型明显多于左侧的互联网电商,那我们是不是就可以得出一个初步的结论:传统核心面向企业内大部分应用,大而全,领域模型相对完备,而互联网电商面向单一渠道,领域模型相对单一。
这个结论也给我们指明了一个方向:首先我们可以将传统核心的领域模型作为主领域模型,将互联网电商领域模型作为辅助模型来构建中台业务模型。然后再将互联网电商中重复的能力沉淀到传统核心的领域模型中,只保留自己的个性能力,比如订单。中台业务建模时,既要关注领域模型的完备性,也要关注不同渠道敏捷响应市场的要求。
<img src="https://static001.geekbang.org/resource/image/25/1d/25cd1e7fe14bfa22a752c1b184b9c91d.jpg" alt="">
有了上述这样一个思路,我们就可以开始构建中台业务模型了。
我们从互联网电商和传统核心的领域模型中,归纳并分离出能覆盖两个域的所有业务子域。通过分析,我们找到了用户、客户、承保、收付和订单五个业务域,它们是可以用于领域模型对比分析的基准域。
**下面我以客户为例,来给你讲一下客户中台业务模型的构建过程。**
互联网电商客户主要面向个人客户,除了有个人客户信息管理功能外,基于营销目的它还有客户积分功能,因此它的领域模型有个人和积分两个聚合。
而传统核心客户除了支持个人客户外,还有单位和组织机构等团体客户,它有个人和团体两个领域模型。其中个人领域模型中除了个人客户信息管理功能外,还有个人客户的评级、重复客户的归并和客户的统一视图等功能,因此它的领域模型有个人、视图、评级和归并四个聚合。
构建多业务域的中台业务模型的过程,就是找出同一业务域内所有同类业务的领域模型,对比分析域内领域模型和聚合的差异和共同点,打破原有的模型,完成新的中台业务模型重组或归并的过程。
我们将互联网电商和传统核心的领域模型分解后,我们找到了五个与个人客户领域相关的聚合,包括:个人、积分、评级、归并和视图。这五个聚合原来分别分散在互联网电商和传统核心的领域模型中,我们需要打破原有的领域模型,进行功能沉淀和聚合的重组,重新找出这些聚合的限界上下文,重构领域模型。
**最终个人客户的领域模型重构为:**个人、归并和视图三个聚合重构为个人领域模型(客户信息管理),评级和积分两个聚合重构为评级积分领域模型(面向个人客户)。到这里我们就完成了个人客户领域模型的构建了。
好像还漏掉点什么东西呢?对了,还有团队客户领域模型!其实团体客户很简单。由于它只在传统核心中出现,我们将它在传统核心中的领域模型直接拿过来用就行了。
至此我们就完成了客户中台业务模型的构建了,客户中台构建了个人、团体和评级积分三个领域模型。
通过客户中台业务模型的构建你是否get到构建中台业务模型的要点了呢总结成一句话就是“分域建模型找准基准域划定上下文聚合重归类。”
其它业务域其实也是一样的过程,在这里我就不一一讲述了,你可以自己练习一下,作为课后作业。完成后你可以对照下面这张图看一下,这就是其它业务域重构后的中台业务模型。
<img src="https://static001.geekbang.org/resource/image/fb/70/fb11e6941fc471c734d0b85c25cc5370.jpg" alt="">
**第三步:中台归类,根据领域模型设计微服务。**
完成中台业务建模后,我们就有了下面这张图。从这张图中我们可以看到总共构建了多少个中台,中台下面有哪些领域模型,哪些中台是通用中台,哪些中台是核心中台,中台的基本信息等等,都一目了然。你根据中台下的领域模型就可以设计微服务了。
<img src="https://static001.geekbang.org/resource/image/a8/c5/a88e9695c7198a1f88f537564ada0bc5.jpg" alt="">
## 重构过程中的领域对象
上面主要是从聚合的角度来描述中台业务模型的重组,是相对高阶的业务模块的重构。业务模型重构和聚合重组,往往会带来领域对象和业务行为的变化。下面我带你了解一下,在领域模型重组过程中,发生在更底层的领域对象的活动。
我们还是以客户为例来讲述。由于对象过多,我只选取了部分领域对象和业务行为。
传统核心客户领域模型重构之前,包含个人、团体和评级三个聚合,每个聚合内部都有自己的聚合根、实体、方法和领域服务等。
<img src="https://static001.geekbang.org/resource/image/ae/3c/ae33bc5c0cda28740363e39edbc1e53c.jpg" alt="">
互联网电商客户领域模型重构前包含个人和积分两个聚合,每个聚合包含了自己的领域对象、方法和领域服务等。
<img src="https://static001.geekbang.org/resource/image/d0/7d/d0f8fb06797a5983c7fd00d59d8be57d.jpg" alt="">
传统核心和互联网电商客户领域模型重构成客户中台后,建立了个人、团体和评级积分三个领域模型。其中个人领域模型有个人聚合,团体领域模型有团体聚合,评级积分领域模型有评级和积分两个聚合。这些领域模型的领域对象来自原来的领域模型,但积分评级是重组后的领域模型,它们原来的聚合会带着各自的领域对象,加入到新的领域模型中。
这里还要注意:部分领域对象可能会根据新的业务要求,从原来的聚合中分离,重组到其它聚合。新领域模型的领域对象,比如实体、领域服务等,在重组后可能还会根据新的业务场景和需求进行代码重构。
<img src="https://static001.geekbang.org/resource/image/f1/c4/f1b2e04d38ba13d8c318aa3539604bc4.jpg" alt="">
## 总结
今天我们一起讨论了传统企业中台数字化转型在面对多个不同渠道应用重复建设时如何用DDD领域建模的思想来构建中台业务模型。中台业务建模有自顶向下和自底向上两种策略这两种策略有自己的适用场景你需要结合自己公司的情况选择合适的策略。
其实呢,中台业务模型的重构过程,也是微服务架构演进的过程。业务边界即微服务边界,业务边界做好了,微服务的边界自然就会很好。
## 思考题
思考一下你公司的应用系统建设现状,是否存在重复建设的问题?你能否借用今天学到的方法来尝试构建中台呢?
欢迎留言分享,你也可以把今天所学分享给身边的朋友,邀请他一同交流、打卡。

View File

@@ -0,0 +1,126 @@
<audio id="audio" title="12 | 领域建模:如何用事件风暴构建领域模型?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/82/06/822b54f1f9197724bfe5a62530ef7b06.mp3"></audio>
你好,我是欧创新。
还记得我在 [[第01讲]](https://time.geekbang.org/column/article/149943) 中说过微服务设计为什么要选择DDD吗其中有一个非常重要的原因就是采用DDD方法建立的领域模型可以清晰地划分微服务的逻辑边界和物理边界。可以说在DDD的实践中好的领域模型直接关乎微服务的设计水平。因此我认为DDD的战略设计是比战术设计更为重要的也正是这个原因我们的内容会更侧重于战略设计。
那么我们该采用什么样的方法,才能从错综复杂的业务领域中分析并构建领域模型呢?
它就是我在前面多次提到的事件风暴。事件风暴是一项团队活动领域专家与项目团队通过头脑风暴的形式罗列出领域中所有的领域事件整合之后形成最终的领域事件集合然后对每一个事件标注出导致该事件的命令再为每一个事件标注出命令发起方的角色。命令可以是用户发起也可以是第三方系统调用或者定时器触发等最后对事件进行分类整理出实体、聚合、聚合根以及限界上下文。而事件风暴正是DDD战略设计中经常使用的一种方法它可以快速分析和分解复杂的业务领域完成领域建模。
那到底怎么做事件风暴呢?事件风暴需要提前准备些什么?又如何用事件风暴来构建领域模型呢?今天我们就来重点解决这些问题,深入了解事件风暴的全过程。
## 事件风暴需要准备些什么?
### 1. 事件风暴的参与者
事件风暴采用工作坊的方式,将项目团队和领域专家聚集在一起,通过可视化、高互动的方式一步一步将领域模型设计出来。领域专家是事件风暴中必不可少的核心参与者。很多公司可能并没有这个角色,那我们该寻找什么样的人来担当领域专家呢?
领域专家就是对业务或问题域有深刻见解的主题专家,他们非常了解业务和系统是怎么做的,同时也深刻理解为什么要这样设计。如果你的公司里并没有这个角色,那也没关系,你可以从业务人员、需求分析人员、产品经理或者在这个领域有多年经验的开发人员里,按照这个标准去选择合适的人选。
除了领域专家事件风暴的其他参与者可以是DDD专家、架构师、产品经理、项目经理、开发人员和测试人员等项目团队成员。
领域建模是统一团队语言的过程,因此项目团队应尽早地参与到领域建模中,这样才能高效建立起团队的通用语言。到了微服务建设时,领域模型也更容易和系统架构保持一致。
### 2. 事件风暴要准备的材料
事件风暴参与者会将自己的想法和意见写在即时贴上,并将贴纸贴在墙上的合适位置,我们戏称这个过程是“刷墙”。所以即时贴和水笔是必备材料,另外,你还可以准备一些胶带或者磁扣,以便贴纸随时能更换位置。
值得提醒一下的是,在这个过程中,我们要用不同颜色的贴纸区分领域行为。如下图,我们可以用蓝色表示命令,用绿色表示实体,橙色表示领域事件,黄色表示补充信息等。补充信息主要用来说明注意事项,比如外部依赖等。颜色并不固定,这只是我的习惯,团队内统一才是重点。
<img src="https://static001.geekbang.org/resource/image/3a/f8/3a80b8e7648440a49b809d945e6439f8.jpg" alt="">
### 3. 事件风暴的场地
什么样的场地适合做事件风暴呢?是不是需要跟组织会议一样,准备会议室、投影,还有椅子?这些都不需要!你只需要一堵足够长的墙和足够大的空间就可以了。墙是用来贴纸的,大空间可以让人四处走动,方便合作。撤掉会议桌和椅子的事件风暴,你会发现参与者们的效率更高。
事件风暴的发明者曾经建议要准备八米长的墙,这样设计就不会受到空间的限制了。当然,这个不是必要条件,看各自的现实条件吧,不要让思维受限就好。
### 4. 事件风暴分析的关注点
在领域建模的过程中,我们需要重点关注这类业务的语言和行为。比如某些业务动作或行为(事件)是否会触发下一个业务动作,这个动作(事件)的输入和输出是什么?是谁(实体)发出的什么动作(命令),触发了这个动作(事件)…我们可以从这些暗藏的词汇中,分析出领域模型中的事件、命令和实体等领域对象。
## 如何用事件风暴构建领域模型?
领域建模的过程主要包括产品愿景、业务场景分析、领域建模和微服务拆分与设计这几个重要阶段。下面我以用户中台为例,介绍一下如何用事件风暴构建领域模型。
### 1. 产品愿景
产品愿景的主要目的是对产品顶层价值的设计,使产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。
产品愿景的参与角色:领域专家、业务需求方、产品经理、项目经理和开发经理。
在建模之前,项目团队要思考这样两点:
- 用户中台到底能够做什么?
- 它的业务范围、目标用户、核心价值和愿景,与其它同类产品的差异和优势在哪里?
这个过程也是明确用户中台建设方向和统一团队思想的过程。参与者要对每一个点(下图最左侧列的内容)发表意见,用水笔写在贴纸上,贴在黄色贴纸的位置。这个过程会让参与者充分发表意见,最后会将发散的意见统一为通用语言,建立如下图的产品愿景墙。如果你的团队的产品愿景和目标已经很清晰了,那这个步骤你可以忽略。
<img src="https://static001.geekbang.org/resource/image/b8/c4/b85983fa6a8c877e77387fdafe1598c4.jpg" alt="">
### 2. 业务场景分析
场景分析是从用户视角出发的,根据业务流程或用户旅程,采用用例和场景分析,探索领域中的典型场景,找出领域事件、实体和命令等领域对象,支撑领域建模。事件风暴参与者要尽可能地遍历所有业务细节,充分发表意见,不要遗漏业务要点。
场景分析的参与角色:领域专家、产品经理、需求分析人员、架构师、项目经理、开发经理和测试经理。
用户中台有这样三个典型的业务场景:
- 第一个是系统和岗位设置,设置系统中岗位的菜单权限;
- 第二个是用户权限配置,为用户建立账户和密码,设置用户岗位;
- 第三个是用户登录系统和权限校验,生成用户登录和操作日志。
我们可以按照业务流程一步一步搜寻用户业务流程中的关键领域事件比如岗位已创建用户已创建等事件。再找出什么行为会引起这些领域事件这些行为可能是一个或若干个命令组合在一起产生的比如创建用户时第一个命令是从公司HR系统中获取用户信息第二个命令是根据HR的员工信息在用户中台创建用户创建完用户后就会产生用户已创建的领域事件。当然这个领域事件可能会触发下一步的操作比如发布到邮件系统通知用户已创建但也可能到此就结束了你需要根据具体情况来分析是否还有下一步的操作。
场景分析时会产生很多的命令和领域事件。我用蓝色来表示命令用橙色表示领域事件用黄色表示补充信息比如用户信息数据来源于HR系统的说明。
<img src="https://static001.geekbang.org/resource/image/e2/e4/e2f91189e25bbaa81307d1fea694aee4.jpg" alt="">
### 3. 领域建模
领域建模时,我们会根据场景分析过程中产生的领域对象,比如命令、事件等之间关系,找出产生命令的实体,分析实体之间的依赖关系组成聚合,为聚合划定限界上下文,建立领域模型以及模型之间的依赖。领域模型利用限界上下文向上可以指导微服务设计,通过聚合向下可以指导聚合根、实体和值对象的设计。
领域建模的参与角色:领域专家、产品经理、需求分析人员、架构师、项目经理、开发经理和测试经理。
具体可以分为这样三步。
第一步:从命令和事件中提取产生这些行为的实体。用绿色贴纸表示实体。通过分析用户中台的命令和事件等行为数据,提取了产生这些行为的用户、账户、认证票据、系统、菜单、岗位和用户日志七个实体。
<img src="https://static001.geekbang.org/resource/image/cf/fd/cf35a9437319169784db9e5aab97b1fd.jpg" alt="">
第二步:根据聚合根的管理性质从七个实体中找出聚合根,比如,用户管理用户相关实体以及值对象,系统可以管理与系统相关的菜单等实体等,可以找出用户和系统等聚合根。然后根据业务依赖和业务内聚原则,将聚合根以及它关联的实体和值对象组合为聚合,比如系统和菜单实体可以组合为“系统功能”聚合。按照上述方法,用户中台就有了系统功能、岗位、用户信息、用户日志、账户和认证票据六个聚合。
第三步:划定限界上下文,根据上下文语义将聚合归类。根据用户域的上下文语境,用户基本信息和用户日志信息这两个聚合共同构成用户信息域,分别管理用户基本信息、用户登录和操作日志。认证票据和账户这两个聚合共同构成认证域,分别实现不同方式的登录和认证。系统功能和岗位这两个聚合共同构成权限域,分别实现系统和菜单管理以及系统的岗位配置。根据业务边界,我们可以将用户中台划分为三个限界上下文:用户信息、认证和权限。
<img src="https://static001.geekbang.org/resource/image/d0/e1/d0191d4e4c51ff91dc830bf38c0e7ae1.jpg" alt="">
到这里我们就完成了用户中台领域模型的构建了。那由于领域建模的过程中产生的领域对象实在太多了,我们可以借助表格来记录。
<img src="https://static001.geekbang.org/resource/image/04/2c/04893881bfc410fac43ba5462f3be92c.jpg" alt="">
### 4. 微服务拆分与设计
我们在基础篇讲过,原则上一个领域模型就可以设计为一个微服务,但由于领域建模时只考虑了业务因素,没有考虑微服务落地时的技术、团队以及运行环境等非业务因素,因此在微服务拆分与设计时,我们不能简单地将领域模型作为拆分微服务的唯一标准,它只能作为微服务拆分的一个重要依据。
微服务的设计还需要考虑服务的粒度、分层、边界划分、依赖关系和集成关系。除了考虑业务职责单一外,我们还需要考虑将敏态与稳态业务的分离、非功能性需求(如弹性伸缩要求、安全性等要求)、团队组织和沟通效率、软件包大小以及技术异构等非业务因素。
微服务设计建议参与的角色:领域专家、产品经理、需求分析人员、架构师、项目经理、开发经理和测试经理。
用户中台微服务设计如果不考虑非业务因素,我们完全可以按照领域模型与微服务一对一的关系来设计,将用户中台设计为:用户、认证和权限三个微服务。但如果用户日志数据量巨大,大到需要采用大数据技术来实现,这时用户信息聚合与用户日志聚合就会有技术异构。虽然在领域建模时,我们将他们放在一个了领域模型内,但如果考虑技术异构,这两个聚合就不适合放到同一个微服务里了。我们可以以聚合作为拆分单位,将用户基本信息管理和用户日志管理拆分为两个技术异构的微服务,分别用不同的技术来实现它们。
## 总结
今天我们讲了事件风暴的设计方法以及如何用事件风暴来构建领域模型。事件风暴是一种不同于传统需求分析和系统设计的方法,最好的学习方法就是找几个业务场景多做几次。
综合我的经验,一般来说一个中型规模的项目,领域建模的时间大概在两周左右,这与我们传统的需求分析和系统设计的时间基本差不多。但是如果在领域建模的过程中,团队成员全员参与,在项目开发之前就建立了共同语言,这对于后续的微服务设计与开发是很有帮助的,时间成本也可以视情况降低。
其实我也了解到了很多开发人员在初次学习DDD时似乎并不太关心领域建模而只是想学学DDD的战术设计思想快速上手开发微服务。我想这是对DDD的一个误解这已经偏离了DDD的核心设计思想即先有边界清晰的领域模型才能设计出清晰的微服务边界这两个阶段一前一后是刚需我们不能忽略。
## 思考题
请找一个你擅长的业务领域,试着用事件风暴来构建一下领域模型。
欢迎留言分享,期待你的打卡!
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,128 @@
<audio id="audio" title="13 | 代码模型如何使用DDD设计微服务代码模型" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1b/f4/1bfc04771ecedc86c2ab37445801aff4.mp3"></audio>
你好,我是欧创新。
上一讲我们完成了领域模型的设计,接下来我们就要开始微服务的设计和落地了。那微服务落地时首先要确定的就是微服务的代码结构,也就是我今天要讲的微服务代码模型。
只有建立了标准的微服务代码模型和代码规范后,我们才可以将领域对象所对应的代码对象放在合适的软件包的目录结构中。标准的代码模型可以让项目团队成员更好地理解代码,根据代码规范实现团队协作;还可以让微服务各层的逻辑互不干扰、分工协作、各据其位、各司其职,避免不必要的代码混淆。另外,标准的代码模型还可以让你在微服务架构演进时,轻松完成代码重构。
那在DDD里微服务的代码结构长什么样子呢我们又是依据什么来建立微服务代码模型这就是我们今天重点要解决的两个问题。
## DDD分层架构与微服务代码模型
我们参考DDD分层架构模型来设计微服务代码模型。没错微服务代码模型就是依据DDD分层架构模型设计出来的。那为什么是DDD分层架构模型呢
<img src="https://static001.geekbang.org/resource/image/a3/01/a308123994f87a5ce99adc85dd9b4d01.jpg" alt="">
我们先简单回顾一下 [[第 07 讲]](https://time.geekbang.org/column/article/156849) 介绍过的DDD分层架构模型。它包括用户接口层、应用层、领域层和基础层分层架构各层的职责边界非常清晰又能有条不紊地分层协作。
- 用户接口层:面向前端提供服务适配,面向资源层提供资源适配。这一层聚集了接口适配相关的功能。
- 应用层职责:实现服务组合和编排,适应业务流程快速变化的需求。这一层聚集了应用服务和事件相关的功能。
- 领域层:实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。
- 基础层:贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力。
业务逻辑从领域层、应用层到用户接口层逐层封装和协作对外提供灵活的服务既实现了各层的分工又实现了各层的协作。因此毋庸置疑DDD分层架构模型就是设计微服务代码模型的最佳依据。
## 微服务代码模型
现在我们来看一下按照DDD分层架构模型设计出来的微服务代码模型到底长什么样子呢
其实DDD并没有给出标准的代码模型不同的人可能会有不同理解。下面要说的这个微服务代码模型是我经过思考和实践后建立起来的主要考虑的是微服务的边界、分层以及架构演进。
### 微服务一级目录结构
微服务一级目录是按照DDD分层架构的分层职责来定义的。从下面这张图中我们可以看到在代码模型里分别为用户接口层、应用层、领域层和基础层建立了 interfaces、application、domain 和 infrastructure 四个一级代码目录。
<img src="https://static001.geekbang.org/resource/image/d1/71/d1bea7dc6bd93f3bd30ced821f36bb71.jpg" alt="">
这些目录的职能和代码形态是这样的。
**Interfaces用户接口层**它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口向应用服务获取展现所需的数据。这一层主要用来处理用户发送的Restful请求解析用户输入的配置文件并将数据传递给Application层。数据的组装、数据传输格式以及Facade接口等代码都会放在这一层目录里。
**Application应用层**它主要存放应用层服务组合和编排相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的应用服务完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。
**Domain领域层**它主要存放领域层核心业务逻辑相关的代码。领域层可以包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。
**Infrastructure基础层**它主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。
### 各层目录结构
**1. 用户接口层**
Interfaces 的代码目录结构有assembler、dto 和 façade 三类。
<img src="https://static001.geekbang.org/resource/image/c6/ef/c6ea040a520c91dfe6400f206ff36fef.jpg" alt="">
**Assembler**实现DTO与领域对象之间的相互转换和数据交换。一般来说Assembler与DTO总是一同出现。
**Dto**它是数据传输的载体内部不存在任何业务逻辑我们可以通过DTO把内部的领域对象与外界隔离。
**Facade**提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。
**2. 应用层**
Application 的代码目录结构有event 和 service。
<img src="https://static001.geekbang.org/resource/image/30/61/30caee3ceaf1085b7aa2cc388f996e61.jpg" alt="">
**Event事件**这层目录主要存放事件相关的代码。它包括两个子目录publish 和 subscribe。前者主要存放事件发布相关代码后者主要存放事件订阅相关代码事件处理相关的核心业务逻辑在领域层实现
这里提示一下:虽然应用层和领域层都可以进行事件的发布和处理,但为了实现事件的统一管理,我建议你将微服务内所有事件的发布和订阅的处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。通过应用层调用领域层服务,来实现完整的事件发布和订阅处理流程。
**Service应用服务**这层的服务是应用服务。应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。应用服务主要实现服务组合和编排,是一段独立的业务逻辑。你可以将所有应用服务放在一个应用服务类里,也可以把一个应用服务设计为一个应用服务类,以防应用服务类代码量过大。
**3. 领域层**
Domain 是由一个或多个聚合包构成共同实现领域模型的核心业务逻辑。聚合内的代码模型是标准和统一的包括entity、event、repository 和 service 四个子目录。
<img src="https://static001.geekbang.org/resource/image/68/2c/688dd55b8399779baff8fc5b7c124c2c.jpg" alt="">
而领域层聚合内部的代码目录结构是这样的。
**Aggregate聚合**它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。
以聚合为单位的代码放在一个包里的主要目的是为了业务内聚,而更大的目的是为了以后微服务之间聚合的重组。聚合之间清晰的代码边界,可以让你轻松地实现以聚合为单位的微服务重组,在微服务架构演进中有着很重要的作用。
**Entity实体**它存放聚合根、实体、值对象以及工厂模式Factory相关代码。实体类采用充血模型同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。
**Event事件**它存放事件实体以及与事件活动相关的业务逻辑代码。
**Service领域服务**它存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,我建议你将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。
**Repository仓储**它存放所在聚合的查询或持久化领域对象的代码,通常包括仓储接口和仓储实现方法。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个仓储。
特别说明按照DDD分层架构仓储实现本应该属于基础层代码但为了在微服务架构演进时保证代码拆分和重组的便利性我是把聚合仓储实现的代码放到了聚合包内。这样如果需求或者设计发生变化导致聚合需要拆分或重组时我们就可以将包括核心业务逻辑和仓储代码的聚合包整体迁移轻松实现微服务架构演进。
**4. 基础层**
Infrastructure 的代码目录结构有config 和 util 两个子目录。
<img src="https://static001.geekbang.org/resource/image/5b/5a/5bbe3454e2ecf4ff4770e887a4967b5a.jpg" alt="">
**Config**主要存放配置相关代码。
**Util**主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。
### 代码模型总目录结构
在完成一级和二级代码模型设计后,你就可以看到下图这样的微服务代码模型的总目录结构了。
<img src="https://static001.geekbang.org/resource/image/91/b8/915ad8d830d925a893cd09ff6cbdadb8.jpg" alt="">
## 总结
今天我们根据DDD分层架构模型建立了标准的微服务代码模型在代码模型里面各代码对象各据其位、各司其职共同协作完成微服务的业务逻辑。
那关于代码模型我还需要强调两点内容。
第一点:聚合之间的代码边界一定要清晰。聚合之间的服务调用和数据关联应该是尽可能的松耦合和低关联,聚合之间的服务调用应该通过上层的应用层组合实现调用,原则上不允许聚合之间直接调用领域服务。这种松耦合的代码关联,在以后业务发展和需求变更时,可以很方便地实现业务功能和聚合代码的重组,在微服务架构演进中将会起到非常重要的作用。
第二点你一定要有代码分层的概念。写代码时一定要搞清楚代码的职责将它放在职责对应的代码目录内。应用层代码主要完成服务组合和编排以及聚合之间的协作它是很薄的一层不应该有核心领域逻辑代码。领域层是业务的核心领域模型的核心逻辑代码一定要在领域层实现。如果将核心领域逻辑代码放到应用层你的基于DDD分层架构模型的微服务慢慢就会演变成传统的三层架构模型了。
## 思考题
对比一下DDD分层架构和三层架构的代码结构的差异
期待你的分享,我们一同交流!

View File

@@ -0,0 +1,176 @@
<audio id="audio" title="14 | 代码模型(下):如何保证领域模型与代码模型的一致性?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/b7/f2/b7d8a2501c35001d951129638084aff2.mp3"></audio>
你好,我是欧创新。
在 [[第 12 讲]](https://time.geekbang.org/column/article/163961) 中,我们了解了如何用事件风暴来构建领域模型,在构建领域模型的过程中,我们会提取出很多的领域对象,比如聚合、实体、命令和领域事件等。到了 [[第 13 讲]](https://time.geekbang.org/column/article/165248)我们又根据DDD分层架构模型建立了标准的微服务代码模型为代码对象定义好了分层和目录结构。
那要想完成微服务的设计和落地,这之后其实还有一步,也就是我们今天的重点——将领域对象映射到微服务代码模型中。那为什么这一步如此重要呢?
DDD强调先构建领域模型然后设计微服务以保证领域模型和微服务的一体性因此我们不能脱离领域模型来谈微服务的设计和落地。但在构建领域模型时我们往往是站在业务视角的并且有些领域对象还带着业务语言。我们还需要将领域模型作为微服务设计的输入对领域对象进行设计和转换让领域对象与代码对象建立映射关系。
接下来我们围绕今天的重点,详细来讲一讲。
## 领域对象的整理
完成微服务拆分后,领域模型的边界和领域对象就基本确定了。
我们第一个重要的工作就是,整理事件风暴过程中产生的各个领域对象,比如:聚合、实体、命令和领域事件等内容,将这些领域对象和业务行为记录到下面的表格中。
你可以看到,这张表格里包含了:领域模型、聚合、领域对象和领域类型四个维度。一个领域模型会包含多个聚合,一个聚合包含多个领域对象,每个领域对象都有自己的领域类型。领域类型主要标识领域对象的属性,比如:聚合根、实体、命令和领域事件等类型。
<img src="https://static001.geekbang.org/resource/image/b5/ce/b5570b95095fd9103506fef3fa6a87ce.jpg" alt="">
## 从领域模型到微服务的设计
从领域模型到微服务落地,我们还需要做进一步的设计和分析。事件风暴中提取的领域对象,还需要经过用户故事或领域故事分析,以及微服务设计,才能用于微服务系统开发。
这个过程会比事件风暴来的更深入和细致。主要关注内容如下:
- 分析微服务内有哪些服务?
- 服务所在的分层?
- 应用服务由哪些服务组合和编排完成?
- 领域服务包括哪些实体的业务逻辑?
- 采用充血模型的实体有哪些属性和方法?
- 有哪些值对象?
- 哪个实体是聚合根等?
- 最后梳理出所有的领域对象和它们之间的依赖关系,我们会给每个领域对象设计对应的代码对象,定义它们所在的软件包和代码目录。
这个设计过程建议参与的角色有DDD专家、架构师、设计人员和开发经理。
### 领域层的领域对象
事件风暴结束时,领域模型聚合内一般会有:聚合、实体、命令和领域事件等领域对象。在完成故事分析和微服务设计后,微服务的聚合内一般会有:聚合、聚合根、实体、值对象、领域事件、领域服务和仓储等领域对象。
下面我们就来看一下这些领域对象是怎么得来的?
**1. 设计实体**
大多数情况下,领域模型的业务实体与微服务的数据库实体是一一对应的。但某些领域模型的实体在微服务设计时,可能会被设计为多个数据实体,或者实体的某些属性被设计为值对象。
我们分析个人客户时,还需要有地址、电话和银行账号等实体,它们被聚合根引用,不容易在领域建模时发现,我们需要在微服务设计过程中识别和设计出来。
在分层架构里,实体采用充血模型,在实体类内实现实体的全部业务逻辑。这些不同的实体都有自己的方法和业务行为,比如地址实体有新增和修改地址的方法,银行账号实体有新增和修改银行账号的方法。
实体类放在领域层的Entity目录结构下。
**2. 找出聚合根**
聚合根来源于领域模型,在个人客户聚合里,个人客户这个实体是聚合根,它负责管理地址、电话以及银行账号的生命周期。个人客户聚合根通过工厂和仓储模式,实现聚合内地址、银行账号等实体和值对象数据的初始化和持久化。
聚合根是一种特殊的实体它有自己的属性和方法。聚合根可以实现聚合之间的对象引用还可以引用聚合内的所有实体。聚合根类放在代码模型的Entity目录结构下。聚合根有自己的实现方法比如生成客户编码新增和修改客户信息等方法。
**3. 设计值对象**
根据需要将某些实体的某些属性或属性集设计为值对象。值对象类放在代码模型的Entity目录结构下。在个人客户聚合中客户拥有客户证件类型它是以枚举值的形式存在所以将它设计为值对象。
有些领域对象可以设计为值对象,也可以设计为实体,我们需要根据具体情况来分析。如果这个领域对象在其它聚合内维护生命周期,且在它依附的实体对象中只允许整体替换,我们就可以将它设计为值对象。如果这个对象是多条且需要基于它做查询统计,我建议将它设计为实体。
**4. 设计领域事件**
如果领域模型中领域事件会触发下一步的业务操作,我们就需要设计领域事件。首先确定领域事件发生在微服务内还是微服务之间。然后设计事件实体对象,事件的发布和订阅机制,以及事件的处理机制。判断是否需要引入事件总线或消息中间件。
在个人客户聚合中有客户已创建的领域事件,因此它有客户创建事件这个实体。
领域事件实体和处理类放在领域层的Event目录结构下。领域事件的发布和订阅类我建议放在应用层的Event目录结构下。
**5. 设计领域服务**
如果一个业务动作或行为跨多个实体,我们就需要设计领域服务。领域服务通过对多个实体和实体方法进行组合,完成核心业务逻辑。你可以认为领域服务是位于实体方法之上和应用服务之下的一层业务逻辑。
按照严格分层架构层的依赖关系,如果实体的方法需要暴露给应用层,它需要封装成领域服务后才可以被应用服务调用。所以如果有的实体方法需要被前端应用调用,我们会将它封装成领域服务,然后再封装为应用服务。
个人客户聚合根这个实体创建个人客户信息的方法,被封装为创建个人客户信息领域服务。然后再被封装为创建个人客户信息应用服务,向前端应用暴露。
领域服务类放在领域层的Service目录结构下。
**6. 设计仓储**
每一个聚合都有一个仓储,仓储主要用来完成数据查询和持久化操作。仓储包括仓储的接口和仓储实现,通过依赖倒置实现应用业务逻辑与数据库资源逻辑的解耦。
仓储代码放在领域层的Repository目录结构下。
### 应用层的领域对象
应用层的主要领域对象是应用服务和事件的发布以及订阅。
在事件风暴或领域故事分析时,我们往往会根据用户或系统发起的命令,来设计服务或实体方法。为了响应这个命令,我们需要分析和记录:
- 在应用层和领域层分别会发生哪些业务行为;
- 各层分别需要设计哪些服务或者方法;
- 这些方法和服务的分层以及领域类型(比如实体方法、领域服务和应用服务等),它们之间的调用和组合的依赖关系。
在严格分层架构模式下,不允许服务的跨层调用,每个服务只能调用它的下一层服务。服务从下到上依次为:实体方法、领域服务和应用服务。
如果需要实现服务的跨层调用,我们应该怎么办?我建议你采用服务逐层封装的方式。
<img src="https://static001.geekbang.org/resource/image/eb/b2/eb626396fcb9f541ec46a799275e04b2.png" alt="">
我们看一下上面这张图,服务的封装和调用主要有以下几种方式。
**1. 实体方法的封装**
实体方法是最底层的原子业务逻辑。如果单一实体的方法需要被跨层调用,你可以将它封装成领域服务,这样封装的领域服务就可以被应用服务调用和编排了。如果它还需要被用户接口层调用,你还需要将这个领域服务封装成应用服务。经过逐层服务封装,实体方法就可以暴露给上面不同的层,实现跨层调用。
封装时服务前面的名字可以保持一致,你可以用*DomainService或*AppService后缀来区分领域服务或应用服务。
**2. 领域服务的组合和封装**
领域服务会对多个实体和实体方法进行组合和编排,供应用服务调用。如果它需要暴露给用户接口层,领域服务就需要封装成应用服务。
**3.应用服务的组合和编排**
应用服务会对多个领域服务进行组合和编排,暴露给用户接口层,供前端应用调用。
在应用服务组合和编排时,你需要关注一个现象:多个应用服务可能会对多个同样的领域服务重复进行同样业务逻辑的组合和编排。当出现这种情况时,你就需要分析是不是领域服务可以整合了。你可以将这几个不断重复组合的领域服务,合并到一个领域服务中实现。这样既省去了应用服务的反复编排,也实现了服务的演进。这样领域模型将会越来越精炼,更能适应业务的要求。
应用服务类放在应用层Service目录结构下。领域事件的发布和订阅类放在应用层Event目录结构下。
## 领域对象与微服务代码对象的映射
在完成上面的分析和设计后,我们就可以建立像下图一样的,领域对象与微服务代码对象的映射关系了。
### 典型的领域模型
个人客户领域模型中的个人客户聚合,就是典型的领域模型,从聚合内可以提取出多个实体和值对象以及它的聚合根。
我们看一下下面这个图,我们对个人客户聚合做了进一步的分析。提取了个人客户表单这个聚合根,形成了客户类型值对象,以及电话、地址、银行账号等实体,为实体方法和服务做了封装和分层,建立了领域对象的关联和依赖关系,还有仓储等设计。关键是这个过程,我们建立了领域对象与微服务代码对象的映射关系。
<img src="https://static001.geekbang.org/resource/image/c1/70/c1fce57f9e2a88ab2728db79ff45c770.png" alt="">
下面我对表格的各栏做一个简要的说明。
- 层:定义领域对象位于分层架构中的哪一层,比如:接口层、应用层、领域层以及基础层等。
- 领域对象:领域模型中领域对象的具体名称。
- 领域类型根据DDD知识体系定义的领域对象的类型包括限界上下文、聚合、聚合根、实体、值对象、领域事件、应用服务、领域服务和仓储服务等领域类型。
- 依赖的领域对象:根据业务对象依赖或分层调用的依赖关系,建立的领域对象的依赖关系,比如:服务调用依赖、关联对象聚合等。
- 包名:代码模型中的包名,对应领域对象所在的软件包。
- 类名:代码模型中的类名,对应领域对象的类名。
- 方法名:代码模型中的方法名,对应领域对象实现或操作的方法名。
在建立这种映射关系后,我们就可以得到如下图的微服务代码结构了。
<img src="https://static001.geekbang.org/resource/image/84/5e/84a486d4c0d9146462b31c7fcd5d835e.png" alt="">
### 非典型领域模型
有些业务场景可能并不能如你所愿,你可能无法设计出典型的领域模型。这类业务中有多个实体,实体之间相互独立,是松耦合的关系,这些实体主要参与分析或者计算,你找不出聚合根,但就业务本身来说它们是高内聚的。而它们所组合的业务与其它聚合是在一个限界上下文内,你也不大可能将它单独设计为一个微服务。
这种业务场景其实很常见。比如,在个人客户领域模型内有客户归并的聚合,它扫描所有客户,按照身份证号码、电话号码等是否重复的业务规则,判断是否是重复的客户,然后对重复的客户进行归并。这种业务场景你就找不到聚合根。
那对于这类非典型模型,我们怎么办?
我们还是可以借鉴聚合的思想仍然用聚合来定义这部分功能并采用与典型领域模型同样的分析方法建立实体的属性和方法对方法和服务进行封装和分层设计设计仓储建立领域对象之间的依赖关系。唯一可惜的就是我们依然找不到聚合根不过也没关系除了聚合根管理功能外我们还可以用DDD的其它设计方法。
## 总结
今天我们学习了从领域模型到微服务的设计过程,这个过程在微服务设计过程中非常的关键。你需要从微服务系统的角度,对领域模型做深入、细致的分析,为领域对象分层,找出各个领域对象的依赖关系,建立领域对象与微服务代码对象的映射关系,从而保证领域模型与代码模型的一致性,最终完成微服务的设计。
在建立这种业务模型与微服务系统架构的关系后,整个项目团队就可以在统一的通用语言下工作,即使不熟悉业务的开发人员,或者不熟悉代码的业务人员,也可以很快就定位到代码位置。
## 思考题
分析一下基于DDD领域模型的微服务设计方式和你公司现在所进行的微服务设计或者和你了解到的微服务设计有什么不同
期待你的分享,我们一同交流!

View File

@@ -0,0 +1,97 @@
<audio id="audio" title="15 | 边界:微服务的各种边界在架构演进中的作用?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/47/c1/47adac19535a0d7a70623f70a98e45c1.mp3"></audio>
你好,我是欧创新。
前几讲我们已经介绍过了在用DDD进行微服务设计时我们可以通过事件风暴来确定领域模型边界划定微服务边界定义业务和系统运行边界从而保证微服务的单一职责和随需而变的架构演进能力。
那重点落到边界的时候,总结一下就是,微服务的设计要涉及到逻辑边界、物理边界和代码边界等等。
那么这些边界在微服务架构演进中到底起到什么样的作用?我们又该如何理解这些边界呢?这就是我们今天重点要解决的问题。
## 演进式架构
在微服务设计和实施的过程中,很多人认为:“将单体拆分成多少个微服务,是微服务的设计重点。”可事实真的是这样吗?其实并非如此!
Martin [Fowler](http://www.baidu.com/link?url=YyTvvp3mcbbqbI5LH2o8zWknPkc42pT_71P2crD_LZ7nXMjI9dYbKmcHADU15M5zCZvQjqvj9P7NR6QKHTJzpq2-c6LwhMCz50sZMvmdlPu) 在提出微服务时,他提到了微服务的一个重要特征——演进式架构。那什么是演进式架构呢?演进式架构就是以支持增量的、非破坏的变更作为第一原则,同时支持在应用程序结构层面的多维度变化。
那如何判断微服务设计是否合理呢?其实很简单,只需要看它是否满足这样的情形就可以了:随着业务的发展或需求的变更,在不断重新拆分或者组合成新的微服务的过程中,不会大幅增加软件开发和维护的成本,并且这个架构演进的过程是非常轻松、简单的。
这也是微服务设计的重点,就是看微服务设计是否能够支持架构长期、轻松的演进。
那用DDD方法设计的微服务不仅可以通过限界上下文和聚合实现微服务内外的解耦同时也可以很容易地实现业务功能积木式模块的重组和更新从而实现架构演进。
## 微服务还是小单体?
有些项目团队在将集中式单体应用拆分为微服务时,首先进行的往往不是建立领域模型,而只是按照业务功能将原来单体应用的一个软件包拆分成多个所谓的“微服务”软件包,而这些“微服务”内的代码仍然是集中式三层架构的模式,“微服务”内的代码高度耦合,逻辑边界不清晰,这里我们暂且称它为“小单体微服务”。
下面这张图也很好地展示了这个过程。
<img src="https://static001.geekbang.org/resource/image/cc/eb/cc697f4e8eef2629a660d247c8a1eceb.jpg" alt="">
而随着新需求的提出和业务的发展,这些小单体微服务会慢慢膨胀起来。当有一天你发现这些膨胀了的微服务,有一部分业务功能需要拆分出去,或者部分功能需要与其它微服务进行重组时,你会发现原来这些看似清晰的微服务,不知不觉已经摇身一变,变成了臃肿油腻的大单体了,而这个大单体内的代码依然是高度耦合且边界不清的。
“辛辛苦苦好多年,一夜回到解放前啊!”这个时候你就需要一遍又一遍地重复着从大单体向单体微服务重构的过程。想想,这个代价是不是有点高了呢?
其实这个问题已经很明显了,那就是边界。
这种单体式微服务只定义了一个维度的边界,也就是微服务之间的物理边界,本质上还是单体架构模式。微服务设计时要考虑的不仅仅只有这一个边界,别忘了还要定义好微服务内的逻辑边界和代码边界,这样才能得到你想要的结果。
那现在你知道了我们一定要避免将微服务设计为小单体微服务那具体该如何避免呢清晰的边界人人想要可该如何保证呢DDD已然给出了答案。
## 微服务边界的作用
你应该还记得DDD设计方法里的限界上下文和聚合吧它们就是用来定义领域模型和微服务边界的。
我们再来回顾一下DDD的设计过程。
在事件风暴中,我们会梳理出业务过程中的用户操作、事件以及外部依赖关系等,根据这些要素梳理出实体等领域对象。根据实体对象之间的业务关联性,将业务紧密相关的多个实体进行组合形成聚合,聚合之间是第一层边界。根据业务及语义边界等因素将一个或者多个聚合划定在一个限界上下文内,形成领域模型,限界上下文之间的边界是第二层边界。
为了方便理解,我们将这些边界分为:**逻辑边界、物理边界和代码边界**。
**逻辑边界**主要定义同一业务领域或应用内紧密关联的对象所组成的不同聚类的组合之间的边界。事件风暴对不同实体对象进行关联和聚类分析后,会产生多个聚合和限界上下文,它们一起组成这个领域的领域模型。微服务内聚合之间的边界就是逻辑边界。一般来说微服务会有一个以上的聚合,在开发过程中不同聚合的代码隔离在不同的聚合代码目录中。
逻辑边界在微服务设计和架构演进中具有非常重要的意义!
微服务的架构演进并不是随心所欲的需要遵循一定的规则这个规则就是逻辑边界。微服务架构演进时在业务端以聚合为单位进行业务能力的重组在微服务端以聚合的代码目录为单位进行微服务代码的重组。由于按照DDD方法设计的微服务逻辑边界清晰业务高内聚聚合之间代码松耦合因此在领域模型和微服务代码重构时我们就不需要花费太多的时间和精力了。
现在我们来看一个微服务实例,在下面这张图中,我们可以看到微服务里包含了两个聚合的业务逻辑,两个聚合分别内聚了各自不同的业务能力,聚合内的代码分别归到了不同的聚合目录下。
那随着业务的快速发展,如果某一个微服务遇到了高性能挑战,需要将部分业务能力独立出去,我们就可以以聚合为单位,将聚合代码拆分独立为一个新的微服务,这样就可以很容易地实现微服务的拆分。
<img src="https://static001.geekbang.org/resource/image/88/3d/88d709569367264d368b08a7d9658c3d.png" alt="">
另外,我们也可以对多个微服务内有相似功能的聚合进行功能和代码重组,组合为新的聚合和微服务,独立为通用微服务。现在你是不是有点做中台的感觉呢?
**物理边界**主要从部署和运行的视角来定义微服务之间的边界。不同微服务部署位置和运行环境是相互物理隔离的,分别运行在不同的进程中。这种边界就是微服务之间的物理边界。
**代码边界**主要用于微服务内的不同职能代码之间的隔离。微服务开发过程中会根据代码模型建立相应的代码目录,实现不同功能代码的隔离。由于领域模型与代码模型的映射关系,代码边界直接体现出业务边界。代码边界可以控制代码重组的影响范围,避免业务和服务之间的相互影响。微服务如果需要进行功能重组,只需要以聚合代码为单位进行重组就可以了。
## 正确理解微服务的边界
从上述内容中我们知道了按照DDD设计出来的逻辑边界和代码边界让微服务架构演进变得不那么费劲了。
微服务的拆分可以参考领域模型,也可以参考聚合,因为聚合是可以拆分为微服务的最小单位的。但实施过程是否一定要做到逻辑边界与物理边界一致性呢?也就是说聚合是否也一定要设计成微服务呢?答案是不一定的,这里就涉及到微服务过度拆分的问题了。
微服务的过度拆分会使软件维护成本上升,比如:集成成本、发布成本、运维成本以及监控和定位问题的成本等。在项目建设初期,如果你不具备较强的微服务管理能力,那就不宜将微服务拆分过细。当我们具备一定的能力以后,且微服务内部的逻辑和代码边界也很清晰,你就可以随时根据需要,拆分出新的微服务,实现微服务的架构演进了。
当然,还要记住一点,微服务内聚合之间的服务调用和数据依赖需要符合高内聚松耦合的设计原则和开发规范,否则你也不能很快完成微服务的架构演进。
## 总结
今天我们主要讨论了微服务架构设计中的各种边界在架构演进中的作用。
**逻辑边界:**微服务内聚合之间的边界是逻辑边界。它是一个虚拟的边界,强调业务的内聚,可根据需要变成物理边界,也就是说聚合也可以独立为微服务。
**物理边界:**微服务之间的边界是物理边界。它强调微服务部署和运行的隔离,关注微服务的服务调用、容错和运行等。
**代码边界:**不同层或者聚合之间代码目录的边界是代码边界。它强调的是代码之间的隔离,方便架构演进时代码的重组。
通过以上边界,我们可以让业务能力高内聚、代码松耦合,且清晰的边界,可以快速实现微服务代码的拆分和组合,轻松实现微服务架构演进。但有一点一定要格外注意,边界清晰的微服务,不是大单体向小单体的演进。
## 思考题
分享一下你们公司目前采用了什么样的方法来实现微服务的架构演进和DDD设计方法相比有何区别你觉得哪种方式更好呢可结合业务场景进行分析。
期待你的分享,你可以在留言区中畅所欲言,我们一同交流!

View File

@@ -0,0 +1,165 @@
<audio id="audio" title="16 | 视图:如何实现服务和数据在微服务各层的协作?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/5f/8a/5fd9e9e65b56d3402563d030b9d9898a.mp3"></audio>
你好,我是欧创新。
在DDD分层架构和微服务代码模型里我们根据领域对象的属性和依赖关系将领域对象进行分层定义了与之对应的代码对象和代码目录结构。分层架构确定了微服务的总体架构微服务内的主要对象有服务和实体等它们一起协作完成业务逻辑。
那在运行过程中这些服务和实体在微服务各层是如何协作的呢今天我们就来解剖一下基于DDD分层架构的微服务看看它的内部结构到底是什么样的。
## 服务的协作
### 1. 服务的类型
我们先来回顾一下分层架构中的服务。按照分层架构设计出来的微服务其内部有Facade服务、应用服务、领域服务和基础服务。各层服务的主要功能和职责如下。
**Facade服务**位于用户接口层包括接口和实现两部分。用于处理用户发送的Restful请求和解析用户输入的配置文件等并将数据传递给应用层。或者在获取到应用层数据后将DO组装成DTO将数据传输到前端应用。
**应用服务:**位于应用层。用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果拼装,对外提供粗粒度的服务。
**领域服务:**位于领域层。领域服务封装核心的业务逻辑,实现需要多个实体协作的核心领域逻辑。它对多个实体或方法的业务逻辑进行组合或编排,或者在严格分层架构中对实体方法进行封装,以领域服务的方式供应用层调用。
**基础服务:**位于基础层。提供基础资源服务(比如数据库、缓存等),实现各层的解耦,降低外部资源变化对业务应用逻辑的影响。基础服务主要为仓储服务,通过依赖倒置提供基础资源服务。领域服务和应用服务都可以调用仓储服务接口,通过仓储服务实现数据持久化。
### 2. 服务的调用
我们看一下下面这张图。微服务的服务调用包括三类主要场景:微服务内跨层服务调用,微服务之间服务调用和领域事件驱动。
<img src="https://static001.geekbang.org/resource/image/e5/db/e5d025a6fd69d1f2cf2a1af53253abdb.png" alt="">
**微服务内跨层服务调用**
微服务架构下往往采用前后端分离的设计模式前端应用独立部署。前端应用调用发布在API网关上的Facade服务Facade定向到应用服务。应用服务作为服务组织和编排者它的服务调用有这样两种路径
- 第一种是应用服务调用并组装领域服务。此时领域服务会组装实体和实体方法,实现核心领域逻辑。领域服务通过仓储服务获取持久化数据对象,完成实体数据初始化。
- 第二种是应用服务直接调用仓储服务。这种方式主要针对像缓存、文件等类型的基础层数据访问。这类数据主要是查询操作,没有太多的领域逻辑,不经过领域层,不涉及数据库持久化对象。
**微服务之间的服务调用**
微服务之间的应用服务可以直接访问也可以通过API网关访问。由于跨微服务操作在进行数据新增和修改操作时你需关注分布式事务保证数据的一致性。
**领域事件驱动**
领域事件驱动包括微服务内和微服务之间的事件(详见 [[第 06 讲]](https://time.geekbang.org/column/article/155444)。微服务内通过事件总线EventBus完成聚合之间的异步处理。微服务之间通过消息中间件完成。异步化的领域事件驱动机制是一种间接的服务访问方式。
当应用服务业务逻辑处理完成后,如果发生领域事件,可调用事件发布服务,完成事件发布。
当接收到订阅的主题数据时,事件订阅服务会调用事件处理领域服务,完成进一步的业务操作。
### 3. 服务的封装与组合
我们看一下下面这张图。微服务的服务是从领域层逐级向上封装、组合和暴露的。
<img src="https://static001.geekbang.org/resource/image/2d/1d/2d6a328a9fd8b4b3906bb9f59435ca1d.png" alt="">
**基础层**
基础层的服务形态主要是仓储服务。仓储服务包括接口和实现两部分。仓储接口服务供应用层或者领域层服务调用,仓储实现服务,完成领域对象的持久化或数据初始化。
**领域层**
领域层实现核心业务逻辑,负责表达领域模型业务概念、业务状态和业务规则。主要的服务形态有实体方法和领域服务。
实体采用充血模型,在实体类内部实现实体相关的所有业务逻辑,实现的形式是实体类中的方法。实体是微服务的原子业务逻辑单元。在设计时我们主要考虑实体自身的属性和业务行为,实现领域模型的核心基础能力。不必过多考虑外部操作和业务流程,这样才能保证领域模型的稳定性。
DDD提倡富领域模型尽量将业务逻辑归属到实体对象上实在无法归属的部分则设计成领域服务。领域服务会对多个实体或实体方法进行组装和编排实现跨多个实体的复杂核心业务逻辑。
对于严格分层架构,如果单个实体的方法需要对应用层暴露,则需要通过领域服务封装后才能暴露给应用服务。
**应用层**
应用层用来表述应用和用户行为,负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,负责不同聚合之间的服务和数据协调,负责微服务之间的事件发布和订阅。
通过应用服务对外暴露微服务的内部功能,这样就可以隐藏领域层核心业务逻辑的复杂性以及内部实现机制。应用层的主要服务形态有:应用服务、事件发布和订阅服务。
应用服务内用于组合和编排的服务,主要来源于领域服务,也可以是外部微服务的应用服务。除了完成服务的组合和编排外,应用服务内还可以完成安全认证、权限校验、初步的数据校验和分布式事务控制等功能。
为了实现微服务内聚合之间的解耦,聚合之间的服务调用和数据交互应通过应用服务来完成。原则上我们应该禁止聚合之间的领域服务直接调用和聚合之间的数据表关联。
**用户接口层**
用户接口层是前端应用和微服务之间服务访问和数据交换的桥梁。它处理前端发送的Restful请求和解析用户输入的配置文件等将数据传递给应用层。或获取应用服务的数据后进行数据组装向前端提供数据服务。主要服务形态是Facade服务。
Facade服务分为接口和实现两个部分。完成服务定向DO与DTO数据的转换和组装实现前端与应用层数据的转换和交换。
### 4. 两种分层架构的服务依赖关系
现在我们回顾一下DDD分层架构分层架构有一个重要的原则就是每层只能与位于其下方的层发生耦合。
那根据耦合的紧密程度,分层架构可以分为两种:严格分层架构和松散分层架构。在严格分层架构中,任何层只能与位于其直接下方的层发生依赖。在松散分层架构中,任何层可以与其任意下方的层发生依赖。
下面我们来详细分析和比较一下这两种分层架构。
**松散分层架构的服务依赖**
我们看一下下面这张图,在松散分层架构中,领域层的实体方法和领域服务可以直接暴露给应用层和用户接口层。松散分层架构的服务依赖关系,无需逐级封装,可以快速暴露给上层。
但它存在一些问题,第一个是容易暴露领域层核心业务的实现逻辑;第二个是当实体方法或领域服务发生服务变更时,由于服务同时被多层服务调用和组合,不容易找出哪些上层服务调用和组合了它,不方便通知到所有的服务调用方。
<img src="https://static001.geekbang.org/resource/image/5e/a1/5e901b4f7fa964b349e4d6f344786ea1.png" alt="">
我们再来看一张图在松散分层架构中实体A的方法在应用层组合后暴露给用户接口层aFacade。abDomainService领域服务直接越过应用层暴露给用户接口层abFacade服务。松散分层架构中任意下层服务都可以暴露给上层服务。
<img src="https://static001.geekbang.org/resource/image/b3/a0/b35d6fed54e26423c0d61de040ab04a0.jpeg" alt="">
**严格分层架构的服务依赖**
我们看一下下面这张图,在严格分层架构中,每一层服务只能向紧邻的上一层提供服务。虽然实体、实体方法和领域服务都在领域层,但实体和实体方法只能暴露给领域服务,领域服务只能暴露给应用服务。
在严格分层架构中,服务如果需要跨层调用,下层服务需要在上层封装后,才可以提供跨层服务。比如实体方法需要向应用服务提供服务,它需要封装成领域服务。
这是因为通过封装你可以避免将核心业务逻辑的实现暴露给外部,将实体和方法封装成领域服务,也可以避免在应用层沉淀过多的本该属于领域层的核心业务逻辑,避免应用层变得臃肿。还有就是当服务发生变更时,由于服务只被紧邻上层的服务调用和组合,你只需要逐级告知紧邻上层就可以了,服务可管理性比松散分层架构要好是一定的。
<img src="https://static001.geekbang.org/resource/image/ab/07/ab304d69ee174b5e69cb63d79864ca07.png" alt="">
我们还是看图A实体方法需封装成领域服务aDomainService才能暴露给应用服务aAppService。abDomainService领域服务组合和封装A和B实体的方法后暴露给应用服务abAppService。
<img src="https://static001.geekbang.org/resource/image/34/f9/348d60eac28c9dbf7d120d1b7159cdf9.png" alt="">
## 数据对象视图
在DDD中有很多的数据对象这些对象分布在不同的层里。它们在不同的阶段有不同的形态。你可以再回顾一下 [[第 04 讲]](https://time.geekbang.org/column/article/152677),这一讲有详细的讲解。
我们先来看一下微服务内有哪些类型的数据对象?它们是如何协作和转换的?
- 数据持久化对象PO(Persistent Object),与数据库结构一一映射,是数据持久化过程中的数据载体。
- 领域对象DODomain Object微服务运行时的实体是核心业务的载体。
- 数据传输对象DTOData Transfer Object用于前端与应用层或者微服务之间的数据组装和传输是应用之间数据传输的载体。
- 视图对象VOView Object用于封装展示层指定页面或组件的数据。
我们结合下面这张图,看看微服务各层数据对象的职责和转换过程。
<img src="https://static001.geekbang.org/resource/image/26/13/26dec215ba4359bdc30a1e2cc6007213.png" alt="">
**基础层**
基础层的主要对象是PO对象。我们需要先建立DO和PO的映射关系。当DO数据需要持久化时仓储服务会将DO转换为PO对象完成数据库持久化操作。当DO数据需要初始化时仓储服务从数据库获取数据形成PO对象并将PO转换为DO完成数据初始化。
大多数情况下PO和DO是一一对应的。但也有DO和PO多对多的情况在DO和PO数据转换时需要进行数据重组。
**领域层**
领域层的主要对象是DO对象。DO是实体和值对象的数据和业务行为载体承载着基础的核心业务逻辑。通过DO和PO转换我们可以完成数据持久化和初始化。
**应用层**
应用层的主要对象是DO对象。如果需要调用其它微服务的应用服务DO会转换为DTO完成跨微服务的数据组装和传输。用户接口层先完成DTO到DO的转换然后应用服务接收DO进行业务处理。如果DTO与DO是一对多的关系这时就需要进行DO数据重组。
**用户接口层**
用户接口层会完成DO和DTO的互转完成微服务与前端应用数据交互及转换。Facade服务会对多个DO对象进行组装转换为DTO对象向前端应用完成数据转换和传输。
**前端应用**
前端应用主要是VO对象。展现层使用VO进行界面展示通过用户接口层与应用层采用DTO对象进行数据交互。
## 总结
今天我们分析了DDD分层架构下微服务的服务和数据的协作关系。为了实现聚合之间以及微服务各层之间的解耦我们在每层定义了不同职责的服务和数据对象。在软件开发过程中我们需要严格遵守各层服务和数据的职责要求各据其位各司其职。这样才能保证核心领域模型的稳定同时也可以灵活应对外部需求的快速变化。
## 思考题
你知道在微服务内为什么要设计不同的服务和不同的数据对象吗?它体现的是一种什么样的设计思想?
欢迎留言和我分享你的思考,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,157 @@
<audio id="audio" title="17 | 从后端到前端:微服务后,前端如何设计?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e6/f5/e66533b9b16aa0144e16588e60aa9af5.mp3"></audio>
你好,我是欧创新。
微服务架构通常采用前后端分离的设计方式。作为企业级的中台,在完成单体应用拆分和微服务建设后,前端项目团队会同时面对多个中台微服务项目团队,这时候的前端人员就犹如维修电工一样了。
面对如此多的微服务暴露出来的API服务如何进行正确的连接和拼装才能保证不出错这显然不是一件很容易的事情。而当服务出现变更时又如何通知所有受影响的项目团队这里面的沟通成本相信也不小。
相应的要从一定程度上解决上述问题我们是不是可以考虑先有效降低前端集成的复杂度呢先做到前端聚合后端解耦——这是一个很有意思的话题。今天我们就一起来聊聊微前端Micro Frontend的设计思想探讨一下中台微服务后前后端的设计和集成方式。
## 单体前端的困境
传统企业在完成中台转型后虽然后台的业务完成了微服务架构的升级但前端仍然是单体模式由一个团队创建并维护一个前端应用。随着时间推移和业务发展前端会变得越来越臃肿越来越难维护。而随着5G和移动互联技术的应用企业业务活动将会进一步移动化和线上化。过去很多企业的做法是为不同的业务开发出独立的APP。但很显然用户并不想装那么多的APP
为了提高用户体验实现统一运营很多企业开始缩减和整合APP将企业内所有的业务能力都尽量集中到一个APP中。试想如果仍然沿用单体前端的设计模式。前端项目团队将面对多个中台微服务团队需要集成成千上万的API服务这就需要相当高的沟通成本和技术要求。这绝对会是一场灾难。
相对互联网企业而言传统企业的渠道应用更加多样化有面向内部人员的门店类应用、面向外部客户的互联网电商平台或移动APP还有面向第三方的API集成。由于渠道的差异前端将更加多样化和复杂化。那如何有效降低前端集成的复杂度呢
## 从单体前端到微前端
为了解决单体前端的问题,我们可以借鉴微服务的设计思想,引入微前端概念。将微服务理念扩展到前端,解决中台微服务化后,前端由于仍为单体而存在的逻辑复杂和臃肿的问题。
在前端设计时我们需要遵循单一职责和复用原则按照领域模型和微服务边界将前端页面进行拆分。同时构建多个可以独立部署、完全自治、松耦合的页面组合其中每个组合只负责特定业务单元的UI元素和功能这些页面组合就是微前端。
微前端与微服务一样,都是希望将单体应用,按照规则拆分,并重组为多个可以独立开发、独立测试、独立部署和独立运维,松耦合的微前端或者微服务。以适应业务快速变化及分布式多团队并行开发的要求。
微前端页面只包括业务单元前端操作必需的页面要素,它只是企业级完整业务流程中的一个业务拼图块,不包含页面导航等内容。微前端除了可以实现前端页面的解耦外,还可实现页面复用,这也与中台服务共享理念是一脉相承的。
## 业务单元的组合形态
我们可以参照领域模型和微服务边界,建立与微服务对应的前端操作界面,将它与微服务组成业务单元,以业务组件的方式对外提供服务。业务单元包括微前端和微服务,可以独立开发、测试、部署和运维,可以自包含地完成领域模型中部分或全部的业务功能。
我们看一下下面这个图。一个虚框就是一个业务单元,微前端和微服务独立部署,业务单元内的微前端和微服务已完成前后端集成。你可以将这个业务单元理解为一个特定业务领域的组件。业务单元可以有多种组合方式,以实现不同的业务目标。
<img src="https://static001.geekbang.org/resource/image/35/6f/35d5b6465f6978b16a5ddfa49a910d6f.jpg" alt="">
### 1. 单一业务单元
一个微前端和一个微服务组成单一业务单元。微前端和微服务分别实现同一个领域模型从前端到后端的功能。
### 2. 组合业务单元
一个微前端与多个微服务组成组合业务单元。微前端具有多个微服务的前端功能,完成较复杂的页面和操作。多个微服务实现各自领域模型的功能,向微前端提供可组合的服务。
记住一点:微前端不宜与过多的微服务组合,否则容易变成单体前端。
### 3. 通用共享业务单元
一个微前端与一个或多个通用中台微服务组合为通用共享业务单元。通用共享微前端以共享页面的方式与其它微前端页面协作,完成业务流程。很多通用中台微服务的微前端是共享的,比如订单和支付等微服务对应的订单和支付微前端界面。
所有业务单元的功能都应该自包含,业务单元之间的边界清晰。业务单元之间要避免功能交叉而出现耦合,一旦出现就会影响项目团队职责边界,进而影响到业务单元独立开发、测试、部署和运维等。
## 微前端的集成方式
我们看一下下面这个图,微前端位于前端主页面和微服务之间,它需要与两者完成集成。
<img src="https://static001.geekbang.org/resource/image/44/5a/44a99543af27faabeda4f7fa959b875a.jpg" alt="">
### 1. 微前端与前端主页面的集成
前端主页面是企业级的前端页面,微前端是业务单元的前端页面。微前端通过主页面的微前端加载器,利用页面路由和动态加载等技术,将特定业务单元的微前端页面动态加载到前端主页面,实现前端主页面与微前端页面的“拼图式”集成。
微前端完成开发、集成和部署后,在前端主页面完成微前端注册以及页面路由配置,即可实现动态加载微前端页面。
### 2. 微前端与微服务的集成
微前端与微服务独立开发独立部署。在微前端注册到前端主页面前微前端需要与微服务完成集成。它的集成方式与传统前后端分离的集成方式没有差异。微服务将服务发布到API网关微前端调用发布在API网关中的服务即完成业务单元内的前后端集成。
## 团队职责边界
当你采用业务单元化的开发方式后,前后端项目团队职责和应用边界会更清晰,可以降低前后端集成的复杂度。我们看一下前中台团队的职责分工。
前端项目团队专注于前端集成主页面与微前端的集成,完成前端主页面的企业级主流程的页面和流程编排以及微前端页面的动态加载,确保主流程业务逻辑和流程正确。前端项目除了要负责企业内页面风格的整体风格设计、业务流程的流转和控制外,还需要负责微前端页面动态加载、微前端注册、页面路由和页面数据共享等前端技术的实现。
中台项目团队完成业务单元组件的开发、测试和集成,确保业务单元内的业务逻辑、页面和流程正确,向外提供包含页面逻辑和业务逻辑的业务单元组件。
这样,前端项目团队只需要完成企业级前端主页面与业务单元的融合,前端只关注前端主页面与微前端页面之间的集成。这样就可以降低前端团队的技术敏感度、团队的沟通成本和集成复杂度,提高交付效率和用户体验。
中台项目团队关注业务单元功能的完整性和自包含能力,完成业务单元内微服务和微前端开发、集成和部署,提供业务单元组件。这样,业务单元的微前端与微服务的集成就会由一个中台团队完成,熟悉的人干熟悉的事情,可以降低集成过程中的沟通和技术成本,加快开发效率。
## 一个有关保险微前端设计的案例
保险公司有很多面向不同场景的保险产品,由于业务场景不同,其核心领域模型就会有差异,在页面要素、业务规则和流程等方面前端界面也会不同。为了避免领域模型差异较大的产品之间的相互影响和干扰,我们可以将相似的领域模型的保险产品聚合在一起,完成核心中台设计。
那有的保险集团为了统一运营,会实现寿险、财险等集团化的全险种销售。这样前端项目团队就需要用一个前端应用,集成非常多的不同产品的核心中台微服务,前端应用与中台微服务之间的集成将会更复杂。
**如果仍然采用传统的单体前端模式,将会面临比较大的困难。**
第一是前端页面开发和设计的复杂性。以录单前端为例,如果用一个前端页面来适配全险种,由于不同产品的前端页面要素不同,需要妥协并兼容所有产品界面的差异,这会增加前端开发的复杂度,也影响用户体验。而如果为每类产品开发不同的前端,前端项目团队需要在页面开发和设计上,投入巨大的工作量。
第二是前端与微服务集成的复杂性。在前端与微服务集成时前端项目团队需要了解所有产品的API详细信息完成前端与微服务的集成还要根据主页面流程实现不同产品的API服务路由。大量的API服务集成和服务路由会增加系统集成的复杂度和出错的概率。
第三是前后端软件版本的协同发布。关联的应用多了以后一旦某一个中台微服务的API服务出现重大调整就需要协调所有受影响的应用同时完成版本发布频繁的版本发布会影响不同产品的正常运营。
那如何用一个前端应用实现全险种产品销售呢?怎样设计才能降低集成的复杂度,实现前端界面融合,后端中台解耦呢?
我们看一下下面这个图。我们借鉴了电商的订单模式实现保险产品的全险种订单化销售,在一个前端主页面可以将所有业务流程和业务操作无缝串联起来。虽然后端有很多业务单元(包含微服务和微前端),但用户始终感觉是在一个前端应用中操作。
要在一个前端应用中实现全险种销售,需要完成以下内容的设计。
<img src="https://static001.geekbang.org/resource/image/7d/d4/7d0eff75e60913a01aadfc7c6b24dad4.jpg" alt="">
### 1. 微服务
微服务分为两类,一类是核心中台微服务,包括:投保微服务,实现核心出单业务逻辑;另一类是通用中台微服务,包括如:商品、订单、购物车和支付等微服务,实现通用共享业务逻辑。
### 2. 微前端
每个微服务都有自己的微前端页面,实现领域模型的微服务前端页面操作。核心中台投保微服务有出单微前端。订单、商品以及支付微服务都有自己的微前端页面。
### 3. 业务单元
微服务与微前端组合为一个业务单元。由一个中台团队完成业务单元的开发、集成、测试和部署,确保业务单元内页面操作和业务逻辑正确。比如:投保微服务和出单微前端组合为投保业务单元,独立完成保险产品从前端到后端的投保业务。
### 4. 前端主页面
前端主页面类似门户,包括页面导航以及部分通用的常驻主页面的共享页面,比如购物车。前端主页面和所有微前端应统一界面风格,符合统一的前端集成规范。按照正确的业务逻辑和规则,动态加载不同业务单元的微前端页面。前端主页面作为一个整体,协调核心和通用业务单元的微前端页面,完成业务操作和业务流程,提供全险种销售接触界面,包括商品目录、录单、购物车、订单、支付等操作。
### 5. 业务流程说明
我来简要说明一下用户在前端主页面的投保的主要业务流程。
- 第1步用户在前端主页面从商品目录微前端页面选择保险产品。
- 第2步前端主页面根据选择的产品从主页面配置数据中获取产品出单微前端路由地址。加载出单微前端页面完成录单投保微服务实现投保业务逻辑在业务单元内生成投保单。
- 第3步加载购物车微前端将投保单加入购物车。
- 第4步重复1-3步生成多个投保单。
- 第5步从购物车微前端中选择多个投保单加载订单微前端生成订单。
- 第6步加载支付微前端完成支付。
- 第7步在投保微服务中将订单中的投保单生成保单。
虽然后端有很多业务单元在支持,但用户所有的页面操作和流转是在一个前端主页面完成的。在进行全险种的订单化销售时,用户始终感觉是在操作一个系统。这种设计方式很好地体现了前端的融合和中台的解耦。
## 总结
今天我们主要探讨了微前端的设计方法。虽然微前端和微服务也采用前后端分离的设计方式,但在业务单元内,它们是在同一个领域模型下,分别实现前端和后端的业务逻辑,对外提供组件化的服务。
微前端和业务单元化的设计模式可以减轻企业级中台,前后端应用开发和集成的复杂度,真正实现前端融合和中台解耦。它的主要价值和意义如下:
**1. 前端集成简单:**前端项目只需关注前端集成主页面与微前端的集成,实现模块化集成和拼图式的开发,降低前端集成的复杂度和成本。
**2. 项目职责专一:**中台项目从数据库、中台微服务到微前端界面,端到端地完成领域逻辑功能开发,以业务组件的方式整体提供服务。在业务单元内,由团队自己完成前后端集成,可以降低开发和集成团队的沟通成本和集成复杂度。
**3. 隔离和依赖性:**业务单元在代码、逻辑和物理边界都是隔离的,可降低应用之间的依赖性。出现问题时可快速定位和修复,问题可以控制在一个业务单元内。业务单元之间相互无影响。
**4. 降低沟通和测试成本:**中台团队实现从微前端页面到中台微服务的业务单元逻辑,实现业务单元的开发、测试、集成和部署的全流程和全生命周期管理,降低前后端集成的测试和沟通成本。
**5. 更敏捷地发布:**业务单元之间有很好的隔离性和依赖性低,业务单元的变化都可以被控制在业务单元内。项目团队可以独立按照自己的步调进行迭代开发,实现更快的发布周期。版本发布时不会影响其它业务单元的正常运行。
**6. 降低技术敏感性:**前端项目关注前端主页面与微前端的集成。降低了前端项目团队对中台微服务技术的敏感性。中台项目团队可以更独立地尝试新技术和架构,实现架构的演进。
**7. 高度复用性:**微前端和中台微服务都有高度的复用性。微前端可快速加载到多个APP还可以将一个微前端直接发布为APP或微信小程序实现灵活的前端组合、复用和快速发布。
## 思考题
结合你公司的业务场景,思考一下是否可以采用微前端的设计,降低前后端集成的复杂度?期待你的分享!
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,223 @@
<audio id="audio" title="18 | 知识点串讲基于DDD的微服务设计实例" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f5/1d/f56794153ce3d9c39b14cadae6506e1d.mp3"></audio>
你好,我是欧创新。
为了更好地理解DDD的设计流程今天我会用一个项目来带你了解DDD的战略设计和战术设计走一遍从领域建模到微服务设计的全过程一起掌握DDD的主要设计流程和关键点。
## 项目基本信息
项目的目标是实现在线请假和考勤管理。功能描述如下:
1. 请假人填写请假单提交审批,根据请假人身份、请假类型和请假天数进行校验,根据审批规则逐级递交上级审批,逐级核批通过则完成审批,否则审批不通过退回申请人。
1. 根据考勤规则,核销请假数据后,对考勤数据进行校验,输出考勤统计。
## 战略设计
战略设计是根据用户旅程分析,找出领域对象和聚合根,对实体和值对象进行聚类组成聚合,划分限界上下文,建立领域模型的过程。
战略设计采用的方法是事件风暴,包括:产品愿景、场景分析、领域建模和微服务拆分等几个主要过程。
战略设计阶段建议参与人员:领域专家、业务需求方、产品经理、架构师、项目经理、开发经理和测试经理。
### 1. 产品愿景
产品愿景是对产品顶层价值设计,对产品目标用户、核心价值、差异化竞争点等信息达成一致,避免产品偏离方向。
事件风暴时,所有参与者针对每一个要点,在贴纸上写出自己的意见,贴到白板上。事件风暴主持者会对每个贴纸,讨论并对发散的意见进行收敛和统一,形成下面的产品愿景图。
<img src="https://static001.geekbang.org/resource/image/ef/b2/ef218e23ea2a8dc756af885ae06e61b2.jpg" alt="">
**我们把这个产品愿景图整理成一段文字就是:**为了满足内外部人员他们的在线请假、自动考勤统计和外部人员管理的需求我们建设这个在线请假考勤系统它是一个在线请假平台可以自动考勤统计。它可以同时支持内外网请假同时管理内外部人员请假和定期考勤分析而不像HR系统只管理内部人员且只能内网使用。我们的产品内外网皆可使用可实现内外部人员无差异管理。
通过产品愿景分析项目团队统一了系统名称——在线请假考勤系统明确了项目目标和关键功能与竞品HR的关键差异以及自己的优势和核心竞争力等。
产品愿景分析对于初创系统明确系统建设重点,统一团队建设目标和建立通用语言是很有价值的。但如果你的系统目标和需求非常清晰,这一步可以忽略。
### 2. 场景分析
场景分析是从用户视角出发,探索业务领域中的典型场景,产出领域中需要支撑的场景分类、用例操作以及不同子域之间的依赖关系,用以支撑领域建模。
项目团队成员一起用事件风暴分析请假和考勤的用户旅程。根据不同角色的旅程和场景分析,尽可能全面地梳理从前端操作到后端业务逻辑发生的所有操作、命令、领域事件以及外部依赖关系等信息。
**下面我就以请假和人员两个场景作为示例。**
第一个场景:请假<br>
用户:请假人
- 请假人登录系统:从权限微服务获取请假人信息和权限数据,完成登录认证。
- 创建请假单:打开请假页面,选择请假类型和起始时间,录入请假信息。保存并创建请假单,提交请假审批。
- 修改请假单:查询请假单,打开请假页面,修改请假单,提交请假审批。
- 提交审批:获取审批规则,根据审批规则,从人员组织关系中获取审批人,给请假单分配审批人。
第二个场景:审批<br>
用户:审批人
- 审批人登录系统:从权限微服务获取审批人信息和权限数据,完成登录认证。
- 获取请假单:获取审批人名下请假单,选择请假单。
- 审批:填写审批意见。
- 逐级审批如果还需要上级审批根据审批规则从人员组织关系中获取审批人给请假单分配审批人。重复以上4步。
- 最后审批人完成审批。
完成审批后,产生请假审批已通过领域事件。后续有两个进一步的业务操作:发送请假审批已通过的通知,通知邮件系统告知请假人;将请假数据发送到考勤以便核销。
<img src="https://static001.geekbang.org/resource/image/33/c5/33cbdd0f30a400a0cb9a2bd7ca5d54c5.jpg" alt="">
下面这个图是人员组织关系场景分析结果图,详细的分析过程以及考勤的场景分析就不描述了。
<img src="https://static001.geekbang.org/resource/image/18/74/18acc5f5681c61a37957e5036f176274.jpg" alt="">
### 3. 领域建模
领域建模是通过对业务和问题域进行分析,建立领域模型。向上通过限界上下文指导微服务边界设计,向下通过聚合指导实体对象设计。
领域建模是一个收敛的过程,分三步:
- 第一步找出领域实体和值对象等领域对象;
- 第二步找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合;
- 第三步根据业务及语义边界等因素,定义限界上下文。
下面我们就逐步详细讲解一下。
**第一步:找出实体和值对象等领域对象**
根据场景分析,分析并找出发起或产生这些命令或领域事件的实体和值对象,将与实体或值对象有关的命令和事件聚集到实体。
下面这个图是分析后的实体与命令的关系。通过分析,我们找到了:请假单、审批意见、审批规则、人员、组织关系、刷卡明细、考勤明细以及考勤统计等实体和值对象。
<img src="https://static001.geekbang.org/resource/image/97/28/97c049d18f7e7032f6feef70992a4828.jpg" alt="">
**第二步:定义聚合**
定义聚合前,先找出聚合根。从上面的实体中,我们可以找出“请假单”和“人员”两个聚合根。然后找出与聚合根紧密依赖的实体和值对象。我们发现审批意见、审批规则和请假单紧密关联,组织关系和人员紧密关联。
找出这些实体的关系后,我们发现还有刷卡明细、考勤明细和考勤统计,这几个实体没有聚合根。这种情形在领域建模时你会经常遇到,对于这类场景我们需要分情况特殊处理。
刷卡明细、考勤明细和考勤统计这几个实体它们之间相互独立找不出聚合根不是富领域模型但它们一起完成考勤业务逻辑具有很高的业务内聚性。我们将这几个业务关联紧密的实体放在一个考勤聚合内。在微服务设计时我们依然采用DDD的设计和分析方法。由于没有聚合根来管理聚合内的实体我们可以用传统的方法来管理实体。
经过分析,我们建立了请假、人员组织关系和考勤三个聚合。其中请假聚合有请假单、审批意见实体和审批规则等值对象。人员组织关系聚合有人员和组织关系等实体。考勤聚合有刷卡明细、考勤明细和考勤统计等实体。
<img src="https://static001.geekbang.org/resource/image/bb/76/bb9f2a7095da0c72504e0195dca34376.jpg" alt="">
**第三步:定义限界上下文**
由于人员组织关系聚合与请假聚合,共同完成请假的业务功能,两者在请假的限界上下文内。考勤聚合则单独构成考勤统计限界上下文。因此我们为业务划分请假和考勤统计两个限界上下文,建立请假和考勤两个领域模型。
### 4. 微服务的拆分
理论上一个限界上下文就可以设计为一个微服务,但还需要综合考虑多种外部因素,比如:职责单一性、敏态与稳态业务分离、非功能性需求(如弹性伸缩、版本发布频率和安全等要求)、软件包大小、团队沟通效率和技术异构等非业务要素。
在这个项目,我们划分微服务主要考虑职责单一性原则。因此根据限界上下文就可以拆分为请假和考勤两个微服务。其中请假微服务包含人员组织关系和请假两个聚合,考勤微服务包含考勤聚合。
到这里,战略设计就结束了。通过战略设计,我们建立了领域模型,划分了微服务边界。下一步就是战术设计了,也就是微服务设计。下面我们以请假微服务为例,讲解其设计过程。
## 战术设计
战术设计是根据领域模型进行微服务设计的过程。这个阶段主要梳理微服务内的领域对象,梳理领域对象之间的关系,确定它们在代码模型和分层架构中的位置,建立领域模型与微服务模型的映射关系,以及服务之间的依赖关系。
战术设计阶段建议参与人员:领域专家、产品经理、架构师、项目经理、开发经理和测试经理等。
战术设计包括以下两个阶段:分析微服务领域对象和设计微服务代码结构。
### 1. 分析微服务领域对象
领域模型有很多领域对象,但是这些对象带有比较重的业务属性。要完成从领域模型到微服务的落地,还需要进一步的分析和设计。在事件风暴基础上,我们进一步细化领域对象以及它们的关系,补充事件风暴可能遗漏的业务和技术细节。
我们分析微服务内应该有哪些服务?服务的分层?应用服务由哪些服务组合和编排完成?领域服务包括哪些实体和实体方法?哪个实体是聚合根?实体有哪些属性和方法?哪些对象应该设计为值对象等。
**服务的识别和设计**
事件风暴的命令是外部的一些操作和业务行为,也是微服务对外提供的能力。它往往与微服务的应用服务或者领域服务对应。我们可以将命令作为服务识别和设计的起点。具体步骤如下:
- 根据命令设计应用服务,确定应用服务的功能,服务集合,组合和编排方式。服务集合中的服务包括领域服务或其它微服务的应用服务。
- 根据应用服务功能要求设计领域服务,定义领域服务。这里需要注意:应用服务可能是由多个聚合的领域服务组合而成的。
- 根据领域服务的功能,确定领域服务内的实体以及功能。
- 设计实体基本属性和方法。
另外,我们还要考虑领域事件的异步化处理。
我以提交审批这个动作为例,来说明服务的识别和设计。提交审批的大体流程是:
- 根据请假类型和时长,查询请假审批规则,获取下一步审批人的角色。
- 根据审批角色从人员组织关系中查询下一审批人。
- 为请假单分配审批人,并将审批规则保存至请假单。
- 通过分析,我们需要在应用层和领域层设计以下服务和方法。
**应用层:**提交审批应用服务。
**领域层:**领域服务有查询审批规则、修改请假流程信息服务以及根据审批规则查询审批人服务,分别位于请假和人员组织关系聚合。请假单实体有修改请假流程信息方法,审批规则值对象有查询审批规则方法。人员实体有根据审批规则查询审批人方法。下图是我们分析出来的服务以及它们之间的依赖关系。
<img src="https://static001.geekbang.org/resource/image/ec/f7/eca31d653a3171a8272c6b1f25140bf7.png" alt="">
服务的识别和设计过程就是这样了,我们再来设计一下聚合内的对象。
**聚合中的对象**
在请假单聚合中,聚合根是请假单。
请假单经多级审核后,会产生多条审批意见,为了方便查询,我们可以将审批意见设计为实体。请假审批通过后,会产生请假审批通过的领域事件,因此还会有请假事件实体。请假聚合有以下实体:审批意见(记录审批人、审批状态和审批意见)和请假事件实体。
我们再来分析一下请假单聚合的值对象。请假人和下一审批人数据来源于人员组织关系聚合中的人员实体,可设计为值对象。人员类型、请假类型和审批状态是枚举值类型,可设计为值对象。确定请假审批规则后,审批规则也可作为请假单的值对象。请假单聚合将包含以下值对象:请假人、人员类型、请假类型、下一审批人、审批状态和审批规则。
综上,我们就可以画出请假聚合对象关系图了。
<img src="https://static001.geekbang.org/resource/image/af/be/af21beade34a5f121f673c25a7c979be.jpg" alt="">
在人员组织关系聚合中,我们可以建立人员之间的组织关系,通过组织关系类型找到上级审批领导。它的聚合根是人员,实体有组织关系(包括组织关系类型和上级审批领导),其中组织关系类型(如项目经理、处长、总经理等)是值对象。上级审批领导来源于人员聚合根,可设计为值对象。人员组织关系聚合将包含以下值对象:组织关系类型、上级审批领导。
综上,我们又可以画出人员组织关系聚合对象关系图了。
<img src="https://static001.geekbang.org/resource/image/b5/76/b56e20cd47d161eccbd86d014f9c6e76.jpg" alt="">
**微服务内的对象清单**
在确定各领域对象的属性后,我们就可以设计各领域对象在代码模型中的代码对象(包括代码对象的包名、类名和方法名),建立领域对象与代码对象的一一映射关系了。根据这种映射关系,相关人员可快速定位到业务逻辑所在的代码位置。在经过以上分析后,我们在微服务内就可以分析出如下图的对象清单。
<img src="https://static001.geekbang.org/resource/image/89/bc/89a65daccac054e97591c7259a60e4bc.jpg" alt="">
### 2. 设计微服务代码结构
根据DDD的代码模型和各领域对象所在的包、类和方法我们可以定义出请假微服务的代码结构设计代码对象。
**应用层代码结构**
应用层包括应用服务、DTO以及事件发布相关代码。在LeaveApplicationService类内实现与聚合相关的应用服务在LoginApplicationService封装外部微服务认证和权限的应用服务。
这里提醒一下:如果应用服务逻辑复杂的话,一个应用服务就可以构建一个类,这样可以避免一个类的代码过于庞大,不利于维护。
<img src="https://static001.geekbang.org/resource/image/ed/03/ed9da0faabb9c756a0067dbd3f75d103.png" alt="">
**领域层代码结构**
领域层包括一个或多个聚合的实体类、事件实体类、领域服务以及工厂、仓储相关代码。一个聚合对应一个聚合代码目录,聚合之间在代码上完全隔离,聚合之间通过应用层协调。
请假微服务领域层包含请假和人员两个聚合。人员和请假代码都放在各自的聚合所在目录结构的代码包中。如果随着业务发展,人员相关功能需要从请假微服务中拆分出来,我们只需将人员聚合代码包稍加改造,独立部署,即可快速发布为人员微服务。到这里,微服务内的领域对象,分层以及依赖关系就梳理清晰了。微服务的总体架构和代码模型也基本搭建完成了。
<img src="https://static001.geekbang.org/resource/image/a7/66/a7fa9314002372f6ddad1c1b54573a66.png" alt="">
## 后续的工作
### 1. 详细设计
在完成领域模型和微服务设计后,我们还需要对微服务进行详细的设计。主要设计以下内容:实体属性、数据库表和字段、实体与数据库表映射、服务参数规约及功能实现等。
### 2. 代码开发和测试
开发人员只需要按照详细的设计文档和功能要求,找到业务功能对应的代码位置,完成代码开发就可以了。代码开发完成后,开发人员要编写单元测试用例,基于挡板模拟依赖对象完成服务测试。
## 总结
今天我们通过在线请假考勤项目把DDD设计过程完整地走了一遍。
DDD战略设计从事件风暴开始然后我们要找出实体等领域对象找出聚合根构建聚合划分限界上下文建立领域模型。
战术设计从事件风暴的命令开始,识别和设计服务,建立各层服务的依赖关系,设计微服务内的实体和值对象,找出微服务中所有的领域对象,并建立领域对象与代码对象的映射关系。
这样就可以很好地指导项目团队进行微服务开发和测试了。总结完毕到这你是否已经清楚DDD全部的设计过程了呢有疑问欢迎留言讨论。
## 思考题
你现在采用的是什么样的微服务设计方法?你认为有什么需要特别注意的事项呢?目前有何难点痛点?分享出来,也许我能给你一些有效的建议。
最后,如果今天的实战项目,可以让你举一反三、有所收获,欢迎分享给你的朋友,邀请他加入学习。

View File

@@ -0,0 +1,147 @@
<audio id="audio" title="19 | 总结(一):微服务设计和拆分要坚持哪些原则?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d0/0a/d023d2a2d3ececee48d8cfeea572200a.mp3"></audio>
你好,我是欧创新。
我们前面已经讲了很多DDD的设计方法和实践案例。虽然DDD的设计思想和方法很好但由于企业发展历程以及企业技术和文化的不同DDD和微服务的实施策略也会有差异。那么面对这种差异我们应该如何落地DDD和微服务呢今天我们就来聊聊微服务的设计原则和演进策略。
## 微服务的演进策略
在从单体向微服务演进时,演进策略大体分为两种:绞杀者策略和修缮者策略。
### 1. 绞杀者策略
绞杀者策略是一种逐步剥离业务能力,用微服务逐步替代原有单体系统的策略。它对单体系统进行领域建模,根据领域边界,在单体系统之外,将新功能和部分业务能力独立出来,建设独立的微服务。新微服务与单体系统保持松耦合关系。
随着时间的推移,大部分单体系统的功能将被独立为微服务,这样就慢慢绞杀掉了原来的单体系统。绞杀者策略类似建筑拆迁,完成部分新建筑物后,然后拆除部分旧建筑物。
### 2. 修缮者策略
修缮者策略是一种维持原有系统整体能力不变,逐步优化系统整体能力的策略。它是在现有系统的基础上,剥离影响整体业务的部分功能,独立为微服务,比如高性能要求的功能,代码质量不高或者版本发布频率不一致的功能等。
通过这些功能的剥离,我们就可以兼顾整体和局部,解决系统整体不协调的问题。修缮者策略类似古建筑修复,将存在问题的部分功能重建或者修复后,重新加入到原有的建筑中,保持建筑原貌和功能不变。一般人从外表感觉不到这个变化,但是建筑物质量却得到了很大的提升。
其实还有第三种策略,就是另起炉灶,顾名思义就是将原有的系统推倒重做。建设期间,原有单体系统照常运行,一般会停止开发新需求。而新系统则会组织新的项目团队,按照原有系统的功能域,重新做领域建模,开发新的微服务。在完成数据迁移后,进行新旧系统切换。
对于大型核心系统我一般不建议采用这种策略,这是因为系统重构后的不稳定性、大量未知的潜在技术风险和新的开发模式下项目团队磨合等不确定性因素,会导致项目实施难度大大增加。
## 不同场景下的领域建模策略
由于企业内情况千差万别,发展历程也不一样,有遗留单体系统的微服务改造,也有全新未知领域的业务建模和系统设计,还有遗留系统局部优化的情况。不同场景下,领域建模的策略也会有差异。下面我们就分几类场景来看看如何进行领域建模。
### 1. 新建系统
新建系统又分为简单和复杂领域建模两种场景。
**简单领域建模**
简单的业务领域,一个领域就是一个小的子域。在这个小的问题域内,领域建模过程相对简单,直接采用事件风暴的方法构建领域模型就可以了。
**复杂领域建模**
对于复杂的业务领域,领域可能需要多级拆分后才能开始领域建模。领域拆分为子域,甚至子域还需要进一步拆分。比如:保险它需要拆分为承保、理赔、收付费和再保等子域,承保子域再拆分为投保、保单管理等子子域。复杂领域如果不做进一步细分,由于问题域太大,领域建模的工程量会非常浩大。你不太容易通过事件风暴,完成一个很大的领域建模,即使勉强完成,效果也不一定好。
**对于复杂领域,我们可以分三步来完成领域建模和微服务设计。**
第一步,拆分子域建立领域模型
根据业务领域的特点,参考流程节点边界或功能聚合模块等边界因素。结合领域专家和项目团队的讨论,将领域逐级分解为大小合适的子域,针对子域采用事件风暴,划分聚合和限界上下文,初步确定子域内的领域模型。
第二步,领域模型微调
梳理领域内所有子域的领域模型,对各子域领域模型进行微调。微调的过程重点考虑不同领域模型中聚合的重组。同步考虑领域模型和聚合的边界,服务以及事件之间的依赖关系,确定最终的领域模型。
第三步,微服务的设计和拆分
根据领域模型和微服务拆分原则,完成微服务的拆分和设计。
### 2. 单体遗留系统
如果我们面对的是一个单体遗留系统,只需要将部分功能独立为微服务,而其余仍为单体,整体保持不变,比如将面临性能瓶颈的模块拆分为微服务。我们只需要将这一特定功能,理解为一个简单子领域,参考简单领域建模的方式就可以了。在微服务设计中,我们还要考虑新老系统之间服务和业务的兼容,必要时可引入防腐层。
## DDD使用的误区
很多人在接触微服务后但凡是系统一概都想设计成微服务架构。其实有些业务场景单体架构的开发成本会更低开发效率更高采用单体架构也不失为好的选择。同样虽然DDD很好但有些传统设计方法在微服务设计时依然有它的用武之地。下面我们就来聊聊DDD使用的几个误区。
### 1. 所有的领域都用DDD
很多人在学会DDD后可能会将其用在所有业务域即全部使用DDD来设计。DDD从战略设计到战术设计是一个相对复杂的过程首先企业内要培养DDD的文化其次对团队成员的设计和技术能力要求相对比较高。在资源有限的情况下应聚焦核心域建议你先从富领域模型的核心域开始而不必一下就在全业务域推开。
### 2. 全部采用DDD战术设计方法
不同的设计方法有它的适用环境我们应选择它最擅长的场景。DDD有很多的概念和战术设计方法比如聚合根和值对象等。聚合根利用仓储管理聚合内实体数据之间的一致性这种方法对于管理新建和修改数据非常有效比如在修改订单数据时它可以保证订单总金额与所有商品明细金额的一致但它并不擅长较大数据量的查询处理甚至有延迟加载进而影响效率的问题。
而传统的设计方法可能一条简单的SQL语句就可以很快地解决问题。而很多贫领域模型的业务比如数据统计和分析DDD很多方法可能都用不上或用得并不顺手而传统的方法很容易就解决了。
因此在遵守领域边界和微服务分层等大原则下在进行战术层面设计时我们应该选择最适合的方法不只是DDD设计方法当然还应该包括传统的设计方法。这里要以快速、高效解决实际问题为最佳不要为做DDD而做DDD。
### 3. 重战术设计而轻战略设计
很多DDD初学者学习DDD的主要目的可能是为了开发微服务因此更看重DDD的战术设计实现。殊不知DDD是一种从领域建模到微服务落地的全方位的解决方案。
战略设计时构建的领域模型是微服务设计和开发的输入它确定了微服务的边界、聚合、代码对象以及服务等关键领域对象。领域模型边界划分得清不清晰领域对象定义得明不明确会决定微服务的设计和开发质量。没有领域模型的输入基于DDD的微服务的设计和开发将无从谈起。因此我们不仅要重视战术设计更要重视战略设计。
### 4. DDD只适用于微服务
DDD是在微服务出现后才真正火爆起来的很多人会认为DDD只适用于微服务。在DDD沉默的二十多年里其实它一直也被应用在单体应用的设计中。
具体项目实施时要吸取DDD的核心设计思想和理念结合具体的业务场景和团队技术特点多种方法组合灵活运用用正确的方式解决实际问题。
## 微服务设计原则
微服务设计原则中,如高内聚低耦合、复用、单一职责等这些常见的设计原则在此就不赘述了,我主要强调下面这几条:
**第一条:要领域驱动设计,而不是数据驱动设计,也不是界面驱动设计。**
微服务设计首先应建立领域模型,确定逻辑和物理边界以及领域对象后,然后才开始微服务的拆分和设计。而不是先定义数据模型和库表结构,也不是前端界面需要什么,就去调整核心领域逻辑代码。在设计时应该将外部需求从外到内逐级消化,尽量降低对核心领域层逻辑的影响。
**第二条:要边界清晰的微服务,而不是泥球小单体。**
微服务上线后其功能和代码也不是一成不变的。随着需求或设计变化,领域模型会迭代,微服务的代码也会分分合合。边界清晰的微服务,可快速实现微服务代码的重组。微服务内聚合之间的领域服务和数据库实体原则上应杜绝相互依赖。你可通过应用服务编排或者事件驱动,实现聚合之间的解耦,以便微服务的架构演进。
**第三条:要职能清晰的分层,而不是什么都放的大箩筐。**
分层架构中各层职能定位清晰,且都只能与其下方的层发生依赖,也就是说只能从外层调用内层服务,内层通过封装、组合或编排对外逐层暴露,服务粒度也由细到粗。应用层负责服务的组合和编排,不应有太多的核心业务逻辑,领域层负责核心领域业务逻辑的实现。各层应各司其职,职责边界不要混乱。在服务演进时,应尽量将可复用的能力向下层沉淀。
**第四条要做自己能hold住的微服务而不是过度拆分的微服务。**
微服务过度拆分必然会带来软件维护成本的上升比如集成成本、运维成本、监控和定位问题的成本。企业在微服务转型过程中还需要有云计算、DevOps、自动化监控等能力而一般企业很难在短时间内提升这些能力如果项目团队没有这些能力将很难hold住这些微服务。
如果在微服务设计之初按照DDD的战略设计方法定义好了微服务内的逻辑边界做好了架构的分层其实我们不必拆分太多的微服务即使是单体也未尝不可。随着技术积累和能力提升当我们有了这些能力后由于应用内有清晰的逻辑边界我们可以随时轻松地重组出新的微服务而这个过程不会花费太多的时间和精力。
## 微服务拆分需要考虑哪些因素?
理论上一个限界上下文内的领域模型可以被设计为微服务,但是由于领域建模主要从业务视角出发,没有考虑非业务因素,比如需求变更频率、高性能、安全、团队以及技术异构等因素,而这些非业务因素对于领域模型的系统落地也会起到决定性作用,因此在微服务拆分时我们需要重点考虑它们。我列出了以下主要因素供你参考。
**1. 基于领域模型**
基于领域模型进行拆分,围绕业务领域按职责单一性、功能完整性拆分。
**2. 基于业务需求变化频率**
识别领域模型中的业务需求变动频繁的功能,考虑业务变更频率与相关度,将业务需求变动较高和功能相对稳定的业务进行分离。这是因为需求的经常性变动必然会导致代码的频繁修改和版本发布,这种分离可以有效降低频繁变动的敏态业务对稳态业务的影响。
**3. 基于应用性能**
识别领域模型中性能压力较大的功能。因为性能要求高的功能可能会拖累其它功能,在资源要求上也会有区别,为了避免对整体性能和资源的影响,我们可以把在性能方面有较高要求的功能拆分出去。
**4. 基于组织架构和团队规模**
除非有意识地优化组织架构否则微服务的拆分应尽量避免带来团队和组织架构的调整避免由于功能的重新划分而增加大量且不必要的团队之间的沟通成本。拆分后的微服务项目团队规模保持在1012人左右为宜。
**5. 基于安全边界**
有特殊安全要求的功能,应从领域模型中拆分独立,避免相互影响。
**6. 基于技术异构等因素**
领域模型中有些功能虽然在同一个业务域内,但在技术实现时可能会存在较大的差异,也就是说领域模型内部不同的功能存在技术异构的问题。由于业务场景或者技术条件的限制,有的可能用.NET有的则是Java有的甚至大数据架构。对于这些存在技术异构的功能可以考虑按照技术边界进行拆分。
## 总结
相信你在微服务落地的时候会有很多的收获和感悟。对于DDD和微服务我想总结的就是深刻理解DDD的设计思想和内涵把握好边界和分层这个大原则结合企业文化和技术特点灵活运用战术设计方法选择最适合的技术和方法解决实际问题切勿为了DDD而做DDD
## 思考题
谈谈你在用DDD实践过程中踩过什么样的坑有什么好的建议可以分享给大家期待你的留言

View File

@@ -0,0 +1,135 @@
<audio id="audio" title="20 | 总结分布式架构关键设计10问" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2d/f6/2dd7bc98801b5b46a97dbe714a89fff6.mp3"></audio>
你好,我是欧创新。
前面我们重点讲述了领域建模、微服务设计和前端设计方法,它们组合在一起就可以形成中台建设的整体解决方案。而中台大多基于分布式微服务架构,这种企业级的数字化转型有很多地方值得我们关注和思考。
我们不仅要关注企业商业模式、业务边界以及前中台的融合,还要关注数据技术体系、微服务设计、多活等多领域的设计和协同。结合实施经验和思考,今天我们就来聊聊分布式架构下的几个关键问题。
## 一、选择什么样的分布式数据库?
分布式架构下的数据应用场景远比集中式架构复杂,会产生很多数据相关的问题。谈到数据,首先就是要选择合适的分布式数据库。
分布式数据库大多采用数据多副本的方式,实现数据访问的高性能、多活和容灾。目前主要有三种不同的分布式数据库解决方案。它们的主要差异是数据多副本的处理方式和数据库中间件。
### 1. 一体化分布式数据库方案
它支持数据多副本、高可用。多采用Paxos协议一次写入多数据副本多数副本写入成功即算成功。代表产品是OceanBase和高斯数据库。
### 2. 集中式数据库+数据库中间件方案
它是集中式数据库与数据库中间件结合的方案通过数据库中间件实现数据路由和全局数据管理。数据库中间件和数据库独立部署采用数据库自身的同步机制实现主副本数据的一致性。集中式数据库主要有MySQL和PostgreSQL数据库基于这两种数据库衍生出了很多的解决方案比如开源数据库中间件MyCat+MySQL方案TBase基于PostgreSQL但做了比较大的封装和改动等方案。
### 3. 集中式数据库+分库类库方案
它是一种轻量级的数据库中间件方案分库类库实际上是一个基础JAR包与应用软件部署在一起实现数据路由和数据归集。它适合比较简单的读写交易场景在强一致性和聚合分析查询方面相对较弱。典型分库基础组件有ShardingSphere。
**小结:**这三种方案实施成本不一样,业务支持能力差异也比较大。一体化分布式数据库主要由互联网大厂开发,具有超强的数据处理能力,大多需要云计算底座,实施成本和技术能力要求比较高。集中式数据库+数据库中间件方案,实施成本和技术能力要求适中,可满足中大型企业业务要求。第三种分库类库的方案可处理简单的业务场景,成本和技能要求相对较低。在选择数据库的时候,我们要考虑自身能力、成本以及业务需要,从而选择合适的方案。
## 二、如何设计数据库分库主键?
选择了分布式数据库,第二步就要考虑数据分库,这时分库主键的设计就很关键了。
与客户接触的关键业务我建议你以客户ID作为分库主键。这样可以确保同一个客户的数据分布在同一个数据单元内避免出现跨数据单元的频繁数据访问。跨数据中心的频繁服务调用或跨数据单元的查询会对系统性能造成致命的影响。
将客户的所有数据放在同一个数据单元,对客户来说也更容易提供客户一致性服务。而对企业来说,“以客户为中心”的业务能力,首先就要做到数据上的“以客户为中心”。
当然,你也可以根据业务需要用其它的业务属性作为分库主键,比如机构、用户等。
## 三、数据库的数据同步和复制
在微服务架构中,数据被进一步分割。为了实现数据的整合,数据库之间批量数据同步与复制是必不可少的。数据同步与复制主要用于数据库之间的数据同步,实现业务数据迁移、数据备份、不同渠道核心业务数据向数据平台或数据中台的数据复制、以及不同主题数据的整合等。
传统的数据传输方式有ETL工具和定时提数程序但数据在时效性方面存在短板。分布式架构一般采用基于数据库逻辑日志增量数据捕获CDC技术它可以实现准实时的数据复制和传输实现数据处理与应用逻辑解耦使用起来更加简单便捷。
现在主流的PostgreSQL和MySQL数据库外围有很多数据库日志捕获技术组件。CDC也可以用在领域事件驱动设计中作为领域事件增量数据的获取技术。
## 四、跨库关联查询如何处理?
跨库关联查询是分布式数据库的一个短板,会影响查询性能。在领域建模时,很多实体会分散到不同的微服务中,但很多时候会因为业务需求,它们之间需要关联查询。
关联查询的业务场景包括两类:第一类是基于某一维度或某一主题域的数据查询,比如基于客户全业务视图的数据查询,这种查询会跨多个业务线的微服务;第二类是表与表之间的关联查询,比如机构表与业务表的联表查询,但机构表和业务表分散在不同的微服务。
**如何解决这两类关联查询呢?**
对于第一类场景,由于数据分散在不同微服务里,我们无法跨多个微服务来统计这些数据。你可以建立面向主题的分布式数据库,它的数据来源于不同业务的微服务。采用数据库日志捕获技术,从各业务端微服务将数据准实时汇集到主题数据库。在数据汇集时,提前做好数据关联(如将多表数据合并为一个宽表)或者建立数据模型。面向主题数据库建设查询微服务。这样一次查询你就可以获取客户所有维度的业务数据了。你还可以根据主题或场景设计合适的分库主键,提高查询效率。
对于第二类场景,对于不在同一个数据库的表与表之间的关联查询场景,你可以采用小表广播,在业务库中增加一张冗余的代码副表。当主表数据发生变化时,你可以通过消息发布和订阅的领域事件驱动模式,异步刷新所有副表数据。这样既可以解决表与表的关联查询,还可以提高数据的查询效率。
## 五、如何处理高频热点数据?
对于高频热点数据,比如商品、机构等代码类数据,它们同时面向多个应用,要有很高的并发响应能力。它们会给数据库带来巨大的访问压力,影响系统的性能。
常见的做法是将这些高频热点数据从数据库加载到如Redis等缓存中通过缓存提供数据访问服务。这样既可以降低数据库的压力还可以提高数据的访问性能。
另外对需要模糊查询的高频数据你也可以选用ElasticSearch等搜索引擎。
缓存就像调味料一样,投入小、见效快,用户体验提升快。
## 六、前后序业务数据的处理
在微服务设计时你会经常发现,某些数据需要关联前序微服务的数据。比如:在保险业务中,投保微服务生成投保单后,保单会关联前序投保单数据等。在电商业务中,货物运输单会关联前序订单数据。由于关联的数据分散在业务的前序微服务中,你无法通过不同微服务的数据库来给它们建立数据关联。
**如何解决这种前后序的实体关联呢?**
一般来说,前后序的数据都跟领域事件有关。你可以通过领域事件处理机制,按需将前序数据通过领域事件实体,传输并冗余到当前的微服务数据库中。
你可以将前序数据设计为实体或者值对象,并被当前实体引用。在设计时你需要关注以下内容:如果前序数据在当前微服务只可整体修改,并且不会对它做查询和统计分析,你可以将它设计为值对象;当前序数据是多条,并且需要做查询和统计分析,你可以将它设计为实体。
这样,你可以在货物运输微服务,一次获取前序订单的清单数据和货物运输单数据,将所有数据一次反馈给前端应用,降低跨微服务的调用。如果前序数据被设计为实体,你还可以将前序数据作为查询条件,在本地微服务完成多维度的综合数据查询。只有必要时才从前序微服务,获取前序实体的明细数据。这样,既可以保证数据的完整性,还可以降低微服务的依赖,减少跨微服务调用,提升系统性能。
## 七、数据中台与企业级数据集成
分布式微服务架构虽然提升了应用弹性和高可用能力,但原来集中的数据会随着微服务拆分而形成很多数据孤岛,增加数据集成和企业级数据使用的难度。你可以通过数据中台来实现数据融合,解决分布式架构下的数据应用和集成问题。
**你可以分三步来建设数据中台。**
第一,按照统一数据标准,完成不同微服务和渠道业务数据的汇集和存储,解决数据孤岛和初级数据共享的问题。
第二,建立主题数据模型,按照不同主题和场景对数据进行加工处理,建立面向不同主题的数据视图,比如客户统一视图、代理人视图和渠道视图等。
第三,建立业务需求驱动的数据体系,支持业务和商业模式创新。
数据中台不仅限于分析场景,也适用于交易型场景。你可以建立在数据仓库和数据平台上,将数据平台化之后提供给前台业务使用,为交易场景提供支持。
## 八、BFF与企业级业务编排和协同
企业级业务流程往往是多个微服务一起协作完成的,每个单一职责的微服务就像积木块,它们只完成自己特定的功能。那如何组织这些微服务,完成企业级业务编排和协同呢?
你可以在微服务和前端应用之间增加一层BFF微服务Backend for Frontends。**BFF主要职责是处理微服务之间的服务组合和编排**,微服务内的应用服务也是处理服务的组合和编排,那这二者有什么差异呢?
BFF位于中台微服务之上主要职责是微服务之间的服务协调**应用服务主要处理微服务内的服务组合和编排。**在设计时我们应尽可能地将可复用的服务能力往下层沉淀,在实现能力复用的同时,还可以避免跨中心的服务调用。
BFF像齿轮一样来适配前端应用与微服务之间的步调。它通过Façade服务适配不同的前端通过服务组合和编排组织和协调微服务。BFF微服务可根据需求和流程变化与前端应用版本协同发布避免中台微服务为适配前端需求的变化而频繁地修改和发布版本从而保证微服务核心领域逻辑的稳定。
如果你的BFF做得足够强大它就是一个集成了不同中台微服务能力、面向多渠道应用的业务能力平台。
## 九、分布式事务还是事件驱动机制?
分布式架构下,原来单体的内部调用,会变成分布式调用。如果一个操作涉及多个微服务的数据修改,就会产生数据一致性的问题。数据一致性有强一致性和最终一致性两种,它们实现方案不一样,实施代价也不一样。
对于实时性要求高的强一致性业务场景,你可以采用分布式事务,但分布式事务有性能代价,在设计时我们需平衡考虑业务拆分、数据一致性、性能和实现的复杂度,尽量避免分布式事务的产生。
领域事件驱动的异步方式是分布式架构常用的设计方法,它可以解决非实时场景的数据最终一致性问题。基于消息中间件的领域事件发布和订阅,可以很好地解耦微服务。通过削峰填谷,可以减轻数据库实时访问压力,提高业务吞吐量和处理能力。你还可以通过事件驱动实现读写分离,提高数据库访问性能。对最终一致性的场景,我建议你采用领域事件驱动的设计方法。
## 十、多中心多活的设计
分布式架构的高可用主要通过多活设计来实现,多中心多活是一个非常复杂的工程,下面我主要列出以下几个关键的设计。
1.选择合适的分布式数据库。数据库应该支持多数据中心部署,满足数据多副本以及数据底层复制和同步技术要求,以及数据恢复的时效性要求。
2.单元化架构设计。将若干个应用组成的业务单元作为部署的基本单位,实现同城和异地多活部署,以及跨中心弹性扩容。各单元业务功能自包含,所有业务流程都可在本单元完成;任意单元的数据在多个数据中心有副本,不会因故障而造成数据丢失;任何单元故障不影响其它同类单元的正常运行。单元化设计时我们要尽量避免跨数据中心和单元的调用。
3.访问路由。访问路由包括接入层、应用层和数据层的路由,确保前端访问能够按照路由准确到达数据中心和业务单元,准确写入或获取业务数据所在的数据库。
4.全局配置数据管理。实现各数据中心全局配置数据的统一管理,每个数据中心全局配置数据实时同步,保证数据的一致性。
## 总结
企业级分布式架构的实施是一个非常复杂的系统工程涉及到非常多的技术体系和方法。今天我罗列了10个关键的设计领域每个领域其实都非常复杂需要很多的投入和研究。在实施的时候你和你的公司要结合自身情况来选择合适的技术组件和实施方案。
## 思考题
在分布式架构设计时,你遇到过哪些问题?有什么样的解决方案?一起来分享一下。

View File

@@ -0,0 +1,98 @@
<audio id="audio" title="开篇词 | 学好了DDD你能做什么" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ab/1e/abaf5e849113aa3abdaf0f311dda0b1e.mp3"></audio>
你好,我是欧创新,人保高级架构师,一名奋斗在软件架构一线十余年的技术人。
目前热衷于采用领域驱动设计DDD实现中台业务建模专注基于DDD的微服务设计和开发等。另外我也正在深入探索传统企业中台数字化转型的技术和方法体系。很高兴在这个专栏和你见面
## 我与DDD
说起DDD的实践那就不得不提微服务了。2015年我刚开始接触微服务那时候和别人去介绍微服务的设计理念接受度并不高毕竟大家普遍采用的还是集中式架构。
但即便是在四年前,业务的日渐复杂也是可以预见的,微服务的价值确确实实存在。我就从那个时候开始深入研究,作为公司的高级架构师,我也一直处于公司中台转型和微服务建设的一线。
这个过程中,我和我的技术团队踩过不少坑,最尖锐的一个问题是:“微服务到底怎么拆分和设计才算合理,拆多小才叫微服务?”而微服务的边界历来也是最容易产生争议的地方。
紧接着,阿里巴巴成功完成了中台战略转型。于是,很多大型公司也开启了中台数字化战略转型,中型公司也根据自身需求跃跃欲试。但也有很多公司由于历史原因,存在着大量系统重复建设的问题。
作为中台,需要将通用的可复用的业务能力沉淀到中台业务模型,实现企业级能力复用。因此中台面临的首要问题就是中台领域模型的重构。而中台落地时,依然会面临微服务设计和拆分的问题。这两个问题一前一后,放在任何一家公司,我想都是一个不小的挑战。
这也是我一直在探索和解决的问题。这两年,中台越来越火,微服务越来越热,参与的人越来越多。那是否有好的方法来指导中台和微服务的设计呢?
一次偶然的机会我接触到了DDD深入研究后我发现运用DDD设计思想实现的微服务边界确实清晰很多业务领域划分也十分合理。后来我和我的伙伴们用DDD做了很多的微服务实践。
## 关于专栏
有了这样一个基础我开始尝试将我对DDD的理解和一些实践经验沉淀并写了几篇文章发表在了 [InfoQ](https://www.infoq.cn/article/s_LFUlU6ZQODd030RbH9) 上。在读者的留言中我发现很多人对DDD是有一定的了解的但由于DDD的知识点多且较为抽象体系庞大又缺少实践经验和案例指导很多人对DDD的应用都有这样那样的困惑哪怕知道DDD的好处但是也感到无从下手。
收到极客时间的邀请后,我就开始全力打造课程大纲,力求干货充盈,理论与实践并重。这做起来并不是一件轻松的事儿,专栏从确定主题到和你见面,已经耗时三个多月了。
DDD虽然历史很久了但它与微服务和中台设计的结合却是一片很新的领域。早在2003年就诞生的DDD怎么来指导“迟到”近20年才大热的微服务设计呢
我认为要想应用DDD首要任务就是要吃透DDD的核心设计思想搞清楚DDD、微服务和中台之间的关系。中台本质是业务模型微服务是业务模型的系统落地DDD是一种设计思想它可以同时指导中台业务建模和微服务设计它们之间就是这样的一个铁三角关系。DDD强调领域模型和微服务设计的一体性先有领域模型然后才有微服务而不是脱离领域模型来谈微服务设计。
其次,就是通过战略设计,建立领域模型,划分微服务边界。这步是关键,你可以借助专栏中的一些经验。
最后,通过战术设计,我们会从领域模型转向微服务设计和落地。此时,边界清晰、可持续演进的微服务架构雏形就在你面前了。
遵循以上过程,这门课的设计思路也就诞生了。
## 关于课程设计
如果你以往对DDD的了解并不深入甚至是第一次接触你一定会觉得DDD的术语非常多且非常陌生这些术语之间的关系都算是个“拦路虎”。
搞懂这些之后呢,怎么应用它们,从何下手来设计领域模型等等这些问题又接踵而至。
如果你对DDD有过研究在学会怎么用之后你可能还会反过来想“我费了这么大劲儿去搞懂它那它到底会让我的系统变成什么样呢可以解决什么具体问题是不是真有大家说得那么好
这些都将是这个专栏要交付给你的内容。总结一下的话,我希望这个专栏能带给你这样几点收获:
1. 用浅显易懂的案例带你了解DDD必知必会的10大核心概念深入设计思想厘清各知识域之间的关系
1. 用DDD分层架构带你弄懂微服务架构各层之间的关系并完成微服务分层和代码模型设计
1. 用DDD战略设计和事件风暴带你完成领域建模和企业级中台业务建模
1. 用一个典型的案例带你完整走一遍DDD战略设计和战术设计的全流程学习DDD在领域模型和微服务设计过程中的技术要点
1. 带你深化微服务架构设计原则和注意事项,建立适应你公司技术能力和文化的微服务,建立演进式的微服务架构。
希望这些收获能够给正在从事或者有兴趣深入了解微服务设计和中台的你,提供一些实质性的帮助。
在具体的课程设计上,我将内容分为了三大部分:基础篇、进阶篇和实战篇。下面我来逐一介绍一下。
### 基础篇
基础篇主要讲解DDD的核心知识体系具体包括领域、子域、核心域、通用域、支撑域、限界上下文、实体、值对象、聚合和聚合根等概念。我会用浅显易懂的案例带你理解它们以及它们之间的合作、依赖关系。
<img src="https://static001.geekbang.org/resource/image/dc/66/dc32e8e4a317fe00121ce18adc407c66.jpg" alt="">
### 进阶篇
进阶篇主要讲解领域事件、DDD分层架构、几种常见的微服务架构模型以及中台设计思想等内容。具体包括
- 如何通过领域事件实现微服务解耦?
- 怎样进行微服务分层设计?
- 如何实现层与层之间的服务协作?
- 通过几种微服务架构模型的对比分析,让你了解领域模型和微服务分层的作用和价值。
- 另外我还会介绍中台设计的核心思想和你探讨如何实现前中后台的协同和融合如何利用DDD进行中台设计
### 实战篇
实战篇是我们专栏课程的重点,我准备了多个实战小项目。
- 中台和领域建模的实战带你了解如何用DDD设计思想构建企业级可复用的中台业务模型了解事件风暴以及用事件风暴构建领域模型的过程。
- 微服务设计实战带你了解如何用DDD设计微服务代码模型如何从领域模型完成微服务设计建立领域模型与微服务代码模型的映射关系如何完成微服务的架构演进等。
然后我会用一个典型的案例将DDD所有的知识点串联在一起带你深入了解如何用DDD的设计思想来完成领域建模和微服务设计的全流程。
最后,我还会补充分享一个前端的最新设计思想,带你了解如何借鉴微服务的设计思想来设计前端应用,实现前端应用的解耦。同时,我还为你总结了微服务设计原则以及分布式架构设计的关键注意事项。
<img src="https://static001.geekbang.org/resource/image/3c/9c/3c7264cb91ead783c7ec3094ce2add9c.jpg" alt="">
最后我想说DDD看似复杂可学习起来并不困难多动手参与几次DDD事件风暴工作坊你就能很快理解DDD的核心设计思想和设计过程成功进阶了。
如果你的公司尚不能给你提供实战的机会,你可以在这里小试牛刀。
相信这个专栏会帮助你掌握一套完整而系统的基于DDD的微服务设计和拆分方法明确从战略设计到战术设计的微服务标准设计过程使得你的微服务设计思路能够更加清晰设计过程更加规范让你的中台和微服务落地如虎添翼。
最后感谢你的关注。我也很想借此认识一下你了解一下你对DDD都有哪些认知是否有机会应用学习过程中遇到过哪些困难对这门课又有怎样的期待欢迎你留言和我交流。
现在就让我们一同开启DDD之旅吧

View File

@@ -0,0 +1,8 @@
你好,我是欧创新。
《DDD实战课》这个专栏已经完结有段时间了很庆幸啊依然能收到很多留言与我交流技术。为认真学习的你点赞也很感谢你的支持
为了让你更好地检测自己的学习成果我特意做了一套期末测试题。题目共有20道满分为100分快来检测一下吧
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=137&amp;exam_id=295)

View File

@@ -0,0 +1,47 @@
<audio id="audio" title="结束语 | 所谓高手,就是跨过坑和大海!" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/20/a8/204f1e6d332e0e24dc298a5cf3e8cea8.mp3"></audio>
你好,我是欧创新。
这是本专栏的最后一讲了,非常感谢你这两个月的陪伴,也非常感谢你的意见和建议。加上前期的专栏筹备,前前后后也有半年了,这半年其实也是自我提升的过程,通过专栏,我将原来不成体系的经验、方法和设计思想,整理成了中台和微服务设计的系统的理论和知识体系。
在撰写专栏时我站在架构师的角度尽力将我在实践过程中的经验、思考和体会以及原创案例等全面详细地呈现给你。希望能够对你的DDD实践和架构设计有所帮助也希望你能快速成长为具有企业级战略视角的架构师和DDD设计大师。
那说到成长,相信我们每个人的轨迹都是独特的,但有一点,你一定和我有同样的体会。那就是“所谓高手,就是跨过坑和大海!”每一步都是积累,每一步都是经验,每一步都算数!所以啊,在本专栏的最后,我还是要分享一些干货给你,也是我曾经踩过的一些坑。
很多人接触DDD可能是从DDD战术设计开始的因此不知道如何开始DDD实践。这个专栏开启后咱们就可以从领域建模开始了。有了领域模型我们就可以划分出合理的微服务的逻辑和物理边界也是因为有了它我们才能识别出微服务内各关键对象并建立它们之间的依赖关系然后开始微服务的设计和开发。
而很多DDD和微服务设计的书籍大多侧重于讲述DDD战术设计或者一些通用的微服务设计模式。这些书籍大多没有告诉我们如何从业务领域开始去构建领域模型如何用DDD的思想来指导中台和微服务设计如何将领域模型作为输入来设计和拆分微服务如何将DDD知识体系组合起来应用到中台和微服务的设计和开发中......
这也是本专栏与这些书籍的不同点。当然,我并不是说它们不好,只是各有侧重。在真正实践的时候,强大的知识基础自然也是刚需,你可以把专栏和书籍结合起来学习,从而发挥最大效能。
**下面是我推荐的几本书这些内容是可以和本专栏互补的如果你有意愿进一步学习DDD它们是非常好的学习资料。**
<img src="https://static001.geekbang.org/resource/image/8a/42/8a90eb7bb3a80baa917cef282b7ff042.jpg" alt="">
DDD是一个相对复杂的方法体系它与传统的软件开发模式或者流程存在一定的差异。在实践DDD时你可能会遇到一些困难。企业需要在研发模式上有一定的调整同时项目团队也需要提升DDD的设计和技术能力培养适合DDD成长的土壤。拔高一点看的话我觉得你可能会遇到这样三个大坑下面我来说一说我的看法。
## 1. 业务专家或领域专家的问题
传统企业中业务人员是需求的主要提出者,但由于部门墙,他们很少会参与到软件设计和开发过程中。如果研发模式不调整,你不要奢望业务人员会主动加入到项目团队中,一起来完成领域建模。没有业务人员的参与,是不是就会觉得没有领域专家,不能领域建模了呢?其实并不是这样的。
对于成熟业务的领域建模,我们可以从团队需求人员或者经验丰富的设计或开发人员中,挑选出能够深刻理解业务内涵和业务管理要求的人员,担任领域专家完成领域建模。对于同时熟悉业务和面向对象设计的项目人员,这种设计经验尤其重要,他们可以利用面向对象的设计经验,更深刻地理解和识别出领域模型的领域对象和业务行为,有助于推进领域模型的设计。
而对于新的创业企业,他们面对的是从来没人做过的全新的业务和领域,没有任何可借鉴的经验,更不要提什么领域专家。对于这种情况,就需要团队一起经过更多次更细致的事件风暴,才能建立领域模型。当然建模过程离不开产品愿景分析,这个过程是确定和统一系统建设目标以及项目的核心竞争力在哪里。这种初创业务的领域模型往往需要经过多次迭代才能成型,不要奢望一次就可以建立一个完美的领域模型。
## 2. 团队DDD的理念和技术能力问题
完成领域建模和微服务设计后就要投入开发和测试了。这时你可能会发现一些开发人员并不理解DDD设计方法不知道什么是聚合、分层以及边界也不知道服务的依赖以及层与层之间的职责边界是什么
这样容易出现设计很精妙而开发很糟糕的状况。遇到这种情况除了要在项目团队普及DDD的知识和设计理念外你还要让所有的项目成员尽早地参与到领域建模中事件风暴的过程除了统一团队语言外还可以让团队成员提前了解领域模型、设计要点和注意事项。
## 3. DDD设计原则问题
DDD基于各种考虑有很多的设计原则也用到了很多的设计模式。条条框框多了很多人可能就会被束缚住总是担心或犹豫这是不是原汁原味的DDD。其实我们不必追求极致的DDD这样做反而会导致过度设计增加开发复杂度和项目成本。
DDD的设计原则或模式是考虑了很多具体场景或者前提的。有的是为了解耦如仓储服务、边界以及分层有的则是为了保证数据一致性如聚合根管理等。在理解了这些设计原则的根本原因后有些场景你就可以灵活把握设计方法了你可以突破一些原则不必受限于条条框框大胆选择最合适的方法。
**以上就是我对这三个问题的理解了。**
用好DDD的关键首先要领悟DDD的核心设计思想和理念了解它为什么适合微服务架构然后慢慢体会、消化、吸收和实践。DDD体系虽然复杂但也是有矩可循的照着样例多做几个事件风暴完成领域建模和微服务设计体会DDD的整个设计过程。相信你很快就能领悟到DDD的核心设计理念了这样就可以做到收放自如趟出一条适合自己的DDD实践之路。
好了,到了该说再见的时候了。再次感谢你的陪伴,期待再相遇!愿我们都能跨过坑和大海,开辟出一片广阔新天地!

View File

@@ -0,0 +1,146 @@
<audio id="audio" title="06 | 领域事件:解耦微服务的关键" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c8/03/c875221a1ec671cee3417b26e8b0e903.mp3"></audio>
你好我是欧创新。今天我们来聊一聊“领域事件Domain Event”。
在事件风暴Event Storming我们发现除了命令和操作等业务行为以外还有一种非常重要的事件这种事件发生后通常会导致进一步的业务操作在DDD中这种事件被称为领域事件。
这只是最简单的定义,并不能让我们真正理解它。那到底什么是领域事件?领域事件的技术实现机制是怎样的?这一讲,我们就重点解决这两个大的问题。
## 领域事件
领域事件是领域模型中非常重要的一部分,用来表示领域中发生的事件。一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环。
举例来说的话,领域事件可以是业务流程的一个步骤,比如投保业务缴费完成后,触发投保单转保单的动作;也可能是定时批处理过程中发生的事件,比如批处理生成季缴保费通知单,触发发送缴费邮件通知操作;或者一个事件发生后触发的后续动作,比如密码连续输错三次,触发锁定账户的动作。
**那如何识别领域事件呢?**
很简单,和刚才讲的定义是强关联的。在做用户旅程或者场景分析时,我们要捕捉业务、需求人员或领域专家口中的关键词:“如果发生……,则……”“当做完……的时候,请通知……”“发生……时,则……”等。在这些场景中,如果发生某种事件后,会触发进一步的操作,那么这个事件很可能就是领域事件。
那领域事件为什么要用最终一致性而不是传统SOA的直接调用的方式呢
我们一起回顾一下 [[第05讲]](https://time.geekbang.org/column/article/154547) 讲到的聚合的一个设计原则:在边界之外使用最终一致性。一次事务最多只能更改一个聚合的状态。如果一次业务操作涉及多个聚合状态的更改,应采用领域事件的最终一致性。
领域事件驱动设计可以切断领域模型之间的强依赖关系,事件发布完成后,发布方不必关心后续订阅方事件处理是否成功,这样可以实现领域模型的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性。
回到具体的业务场景,我们发现有的领域事件发生在微服务内的聚合之间,有的则发生在微服务之间,还有两者皆有的场景,一般来说跨微服务的领域事件处理居多。在微服务设计时不同领域事件的处理方式会不一样。
### 1. 微服务内的领域事件
当领域事件发生在微服务内的聚合之间,领域事件发生后完成事件实体构建和事件数据持久化,发布方聚合将事件发布到事件总线,订阅方接收事件数据完成后续业务操作。
微服务内大部分事件的集成都发生在同一个进程内进程自身可以很好地控制事务因此不一定需要引入消息中间件。但一个事件如果同时更新多个聚合按照DDD“一次事务只更新一个聚合”的原则你就要考虑是否引入事件总线。但微服务内的事件总线可能会增加开发的复杂度因此你需要结合应用复杂度和收益进行综合考虑。
微服务内应用服务,可以通过跨聚合的服务编排和组合,以服务调用的方式完成跨聚合的访问,这种方式通常应用于实时性和数据一致性要求高的场景。这个过程会用到分布式事务,以保证发布方和订阅方的数据同时更新成功。
### 2. 微服务之间的领域事件
跨微服务的领域事件会在不同的限界上下文或领域模型之间实现业务协作,其主要目的是实现微服务解耦,减轻微服务之间实时服务访问的压力。
领域事件发生在微服务之间的场景比较多,事件处理的机制也更加复杂。跨微服务的事件可以推动业务流程或者数据在不同的子域或微服务间直接流转。
跨微服务的事件机制要总体考虑事件构建、发布和订阅、事件数据持久化、消息中间件,甚至事件数据持久化时还可能需要考虑引入分布式事务机制等。
微服务之间的访问也可以采用应用服务直接调用的方式,实现数据和服务的实时访问,弊端就是跨微服务的数据同时变更需要引入分布式事务,以确保数据的一致性。分布式事务机制会影响系统性能,增加微服务之间的耦合,所以我们还是要尽量避免使用分布式事务。
## 领域事件相关案例
我来给你介绍一个保险承保业务过程中有关领域事件的案例。
一个保单的生成,经历了很多子域、业务状态变更和跨微服务业务数据的传递。这个过程会产生很多的领域事件,这些领域事件促成了保险业务数据、对象在不同的微服务和子域之间的流转和角色转换。
在下面这张图中,我列出了几个关键流程,用来说明如何用领域事件驱动设计来驱动承保业务流程。
<img src="https://static001.geekbang.org/resource/image/23/93/23e38df6e78c0a10ddf27f8a254c0093.jpg" alt="">
**事件起点:客户购买保险-业务人员完成保单录入-生成投保单-启动缴费动作。**
1.投保微服务生成缴费通知单,发布第一个事件:缴费通知单已生成,将缴费通知单数据发布到消息中间件。收款微服务订阅缴费通知单事件,完成缴费操作。缴费通知单已生成,领域事件结束。
2.收款微服务缴费完成后,发布第二个领域事件:缴费已完成,将缴费数据发布到消息中间件。原来的订阅方收款微服务这时则变成了发布方。原来的事件发布方投保微服务转换为订阅方。投保微服务在收到缴费信息并确认缴费完成后,完成投保单转成保单的操作。缴费已完成,领域事件结束。
3.投保微服务在投保单转保单完成后,发布第三个领域事件:保单已生成,将保单数据发布到消息中间件。保单微服务接收到保单数据后,完成保单数据保存操作。保单已生成,领域事件结束。
4.保单微服务完成保单数据保存后,后面还会发生一系列的领域事件,以并发的方式将保单数据通过消息中间件发送到佣金、收付费和再保等微服务,一直到财务,完后保单后续所有业务流程。这里就不详细说了。
总之,通过领域事件驱动的异步化机制,可以推动业务流程和数据在各个不同微服务之间的流转,实现微服务的解耦,减轻微服务之间服务调用的压力,提升用户体验。
## 领域事件总体架构
领域事件的执行需要一系列的组件和技术来支撑。我们来看一下这个领域事件总体技术架构图,领域事件处理包括:事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等。下面我们逐一讲一下。
<img src="https://static001.geekbang.org/resource/image/b2/8f/b221ed4011c23720ebe9f48ba8eee38f.jpg" alt="">
### 1. 事件构建和发布
事件基本属性至少包括:事件唯一标识、发生时间、事件类型和事件源,其中事件唯一标识应该是全局唯一的,以便事件能够无歧义地在多个限界上下文中传递。事件基本属性主要记录事件自身以及事件发生背景的数据。
另外事件中还有一项更重要,那就是业务属性,用于记录事件发生那一刻的业务数据,这些数据会随事件传输到订阅方,以开展下一步的业务操作。
事件基本属性和业务属性一起构成事件实体,事件实体依赖聚合根。领域事件发生后,事件中的业务数据不再修改,因此业务数据可以以序列化值对象的形式保存,这种存储格式在消息中间件中也比较容易解析和获取。
为了保证事件结构的统一,我们还会创建事件基类 DomainEvent参考下图子类可以扩充属性和方法。由于事件没有太多的业务行为实现方法一般比较简单。
<img src="https://static001.geekbang.org/resource/image/33/ec/3331c2d87d4dc0e68ec99fc9e02b44ec.jpg" alt="">
事件发布之前需要先构建事件实体并持久化。事件发布的方式有很多种,你可以通过应用服务或者领域服务发布到事件总线或者消息中间件,也可以从事件表中利用定时程序或数据库日志捕获技术获取增量事件数据,发布到消息中间件。
### 2. 事件数据持久化
事件数据持久化可用于系统之间的数据对账,或者实现发布方和订阅方事件数据的审计。当遇到消息中间件、订阅方系统宕机或者网络中断,在问题解决后仍可继续后续业务流转,保证数据的一致性。
事件数据持久化有两种方案,在实施过程中你可以根据自己的业务场景进行选择。
- 持久化到本地业务数据库的事件表中,利用本地事务保证业务和事件数据的一致性。
- 持久化到共享的事件数据库中。这里需要注意的是:业务数据库和事件数据库不在一个数据库中,它们的数据持久化操作会跨数据库,因此需要分布式事务机制来保证业务和事件数据的强一致性,结果就是会对系统性能造成一定的影响。
### 3. 事件总线(EventBus)
事件总线是实现微服务内聚合之间领域事件的重要组件,它提供事件分发和接收等服务。事件总线是进程内模型,它会在微服务内聚合之间遍历订阅者列表,采取同步或异步的模式传递数据。事件分发流程大致如下:
- 如果是微服务内的订阅者(其它聚合),则直接分发到指定订阅者;
- 如果是微服务外的订阅者,将事件数据保存到事件库(表)并异步发送到消息中间件;
- 如果同时存在微服务内和外订阅者,则先分发到内部订阅者,将事件消息保存到事件库(表),再异步发送到消息中间件。
### 4. 消息中间件
跨微服务的领域事件大多会用到消息中间件实现跨微服务的事件发布和订阅。消息中间件的产品非常成熟市场上可选的技术也非常多比如KafkaRabbitMQ等。
### 5. 事件接收和处理
微服务订阅方在应用层采用监听机制,接收消息队列中的事件数据,完成事件数据的持久化后,就可以开始进一步的业务处理。领域事件处理可在领域服务中实现。
## 领域事件运行机制相关案例
这里我用承保业务流程的缴费通知单事件,来给你解释一下领域事件的运行机制。这个领域事件发生在投保和收款微服务之间。发生的领域事件是:缴费通知单已生成。下一步的业务操作是:缴费。
<img src="https://static001.geekbang.org/resource/image/89/11/89321072afd996c6a90fa9774f769e11.jpg" alt="">
**事件起点:出单员生成投保单,核保通过后,发起生成缴费通知单的操作。**
1.投保微服务应用服务调用聚合中的领域服务createPaymentNotice和createPaymentNoticeEvent分别创建缴费通知单、缴费通知单事件。其中缴费通知单事件类PaymentNoticeEvent继承基类DomainEvent。
2.利用仓储服务持久化缴费通知单相关的业务和事件数据。为了避免分布式事务,这些业务和事件数据都持久化到本地投保微服务数据库中。
3.通过数据库日志捕获技术或者定时程序,从数据库事件表中获取事件增量数据,发布到消息中间件。这里说明:事件发布也可以通过应用服务或者领域服务完成发布。
4.收款微服务在应用层从消息中间件订阅缴费通知单事件消息主题,监听并获取事件数据后,应用服务调用领域层的领域服务将事件数据持久化到本地数据库中。
5.收款微服务调用领域层的领域服务PayPremium完成缴费。
6.事件结束。
提示:缴费完成后,后续流程的微服务还会产生很多新的领域事件,比如缴费已完成、保单已保存等等。这些后续的事件处理基本上跟 16 的处理机制类似。
## 总结
今天我们主要讲了领域事件以及领域事件的处理机制。领域事件驱动是很成熟的技术在很多分布式架构中得到了大量的使用。领域事件是DDD的一个重要概念在设计时我们要重点关注领域事件用领域事件来驱动业务的流转尽量采用基于事件的最终一致降低微服务之间直接访问的压力实现微服务之间的解耦维护领域模型的独立性和数据一致性。
除此之外领域事件驱动机制可以实现一个发布方N个订阅方的模式这在传统的直接服务调用设计中基本是不可能做到的。
## 思考题
思考一下你公司有哪些业务场景可以采用领域事件驱动的设计方式?
欢迎留言和我分享你的思考,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,137 @@
<audio id="audio" title="07 | DDD分层架构有效降低层与层之间的依赖" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a3/25/a3eaba495b572cce3d747d7d81580925.mp3"></audio>
你好我是欧创新。前面我们讲了DDD的一些重要概念以及领域模型的设计理念。今天我们来聊聊“DDD分层架构”。
微服务架构模型有好多种例如整洁架构、CQRS和六边形架构等等。每种架构模式虽然提出的时代和背景不同但其核心理念都是为了设计出“高内聚低耦合”的架构轻松实现架构演进。而DDD分层架构的出现使架构边界变得越来越清晰它在微服务架构模型中占有非常重要的位置。
那DDD分层架构到底长什么样DDD分层架构如何推动架构演进我们该怎么转向DDD分层架构这就是我们这一讲重点要解决的问题。
## 什么是DDD分层架构
DDD的分层架构在不断发展。最早是传统的四层架构后来四层架构有了进一步的优化实现了各层对基础层的解耦再后来领域层和应用层之间增加了上下文环境Context五层架构DCI就此形成了。
<img src="https://static001.geekbang.org/resource/image/d6/e1/d6abc3e4f5837cd51b689d01433cace1.jpg" alt="">
我们看一下上面这张图在最早的传统四层架构中基础层是被其它层依赖的它位于最核心的位置那按照分层架构的思想它应该就是核心但实际上领域层才是软件的核心所以这种依赖是有问题的。后来我们采用了依赖倒置Dependency inversion principle,DIP的设计优化了传统的四层架构实现了各层对基础层的解耦。
我们今天讲的DDD分层架构就是优化后的四层架构。在下面这张图中从上到下依次是用户接口层、应用层、领域层和基础层。那DDD各层的主要职责是什么呢下面我来逐一介绍一下。
<img src="https://static001.geekbang.org/resource/image/d0/9d/d02e92626dd8d6b077002ab6b977159d.png" alt="">
1.**用户接口层**
用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等。
2.**应用层**
应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。但应用层又位于领域层之上,因为领域层包含多个聚合,所以它可以协调多个聚合的服务和领域对象完成服务编排和组合,协作完成业务操作。
此外,应用层也是微服务之间交互的通道,它可以调用其它微服务的应用服务,完成微服务之间的服务组合和编排。
这里我要提醒你一下:在设计和开发时,不要将本该放在领域层的业务逻辑放到应用层中实现。因为庞大的应用层会使领域模型失焦,时间一长你的微服务就会演化为传统的三层架构,业务逻辑会变得混乱。
另外应用服务是在应用层的它负责服务的组合、编排和转发负责处理业务用例的执行顺序以及结果的拼装以粗粒度的服务通过API网关向前端发布。还有应用服务还可以进行安全认证、权限校验、事务控制、发送或订阅领域事件等。
3.**领域层**
领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念、业务状态和业务规则。
领域层包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。
这里我要特别解释一下其中几个领域对象的关系,以便你在设计领域层的时候能更加清楚。首先,领域模型的业务逻辑主要是由实体和领域服务来实现的,其中实体会采用充血模型来实现所有与之相关的业务功能。其次,你要知道,实体和领域服务在实现业务逻辑上不是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马,它可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。
4.**基础层**
基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。
基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。
比如说,在传统架构设计中,由于上层应用对数据库的强耦合,很多公司在架构演进中最担忧的可能就是换数据库了,因为一旦更换数据库,就可能需要重写大部分的代码,这对应用来说是致命的。那采用依赖倒置的设计以后,应用层就可以通过解耦来保持独立的核心业务逻辑。当数据库变更时,我们只需要更换数据库基础服务就可以了,这样就将资源变更对应用的影响降到了最低。
### DDD分层架构最重要的原则是什么
在《实现领域驱动设计》一书中DDD分层架构有一个重要的原则每层只能与位于其下方的层发生耦合。
而架构根据耦合的紧密程度又可以分为两种严格分层架构和松散分层架构。优化后的DDD分层架构模型就属于严格分层架构任何层只能对位于其直接下方的层产生依赖。而传统的DDD分层架构则属于松散分层架构它允许某层与其任意下方的层发生依赖。
那我们怎么选呢?综合我的经验,为了服务的可管理,我建议你采用严格分层架构。
在严格分层架构中,领域服务只能被应用服务调用,而应用服务只能被用户接口层调用,服务是逐层对外封装或组合的,依赖关系清晰。而在松散分层架构中,领域服务可以同时被应用层或用户接口层调用,服务的依赖关系比较复杂且难管理,甚至容易使核心业务逻辑外泄。
试想下,如果领域层中的某个服务发生了重大变更,那该如何通知所有调用方同步调整和升级呢?但在严格分层架构中,你只需要逐层通知上层服务就可以了。
## DDD分层架构如何推动架构演进
领域模型不是一成不变的,因为业务的变化会影响领域模型,而领域模型的变化则会影响微服务的功能和边界。那我们该如何实现领域模型和微服务的同步演进呢?
1.**微服务架构的演进**
通过基础篇的讲解,我们知道:领域模型中对象的层次从内到外依次是:值对象、实体、聚合和限界上下文。
实体或值对象的简单变更,一般不会让领域模型和微服务发生大的变化。但聚合的重组或拆分却可以。这是因为聚合内业务功能内聚,能独立完成特定的业务逻辑。那聚合的重组或拆分,势必就会引起业务模块和系统功能的变化了。
这里我们可以以聚合为基础单元,完成领域模型和微服务架构的演进。聚合可以作为一个整体,在不同的领域模型之间重组或者拆分,或者直接将一个聚合独立为微服务。
<img src="https://static001.geekbang.org/resource/image/7f/7c/7f1e3891c9c11abce96020e0bf20d67c.jpg" alt="">
我们结合上图以微服务1为例讲解下微服务架构的演进过程
- 当你发现微服务1中聚合a的功能经常被高频访问以致拖累整个微服务1的性能时我们可以把聚合a的代码从微服务1中剥离出来独立为微服务2。这样微服务2就可轻松应对高性能场景。
- 在业务发展到一定程度以后你会发现微服务3的领域模型有了变化聚合d会更适合放到微服务1的领域模型中。这时你就可以将聚合d的代码整体搬迁到微服务1中。如果你在设计时已经定义好了聚合之间的代码边界这个过程不会太复杂也不会花太多时间。
- 最后我们发现在经历模型和架构演进后微服务1已经从最初包含聚合a、b、c演进为包含聚合b、c、d的新领域模型和微服务了。
你看,好的聚合和代码模型的边界设计,可以让你快速应对业务变化,轻松实现领域模型和微服务架构的演进。你可能还会想,那怎么实现聚合代码快速重组呢?别急,后面实战篇会详细讲解,这里我们先感知下大的实现流程。
2.**微服务内服务的演进**
在微服务内部,实体的方法被领域服务组合和封装,领域服务又被应用服务组合和封装。在服务逐层组合和封装的过程中,你会发现这样一个有趣的现象。
<img src="https://static001.geekbang.org/resource/image/9a/76/9a602b741c222b19c7cc4780da79cf76.jpg" alt="">
我们看下上面这张图。在服务设计时你并不一定能完整预测有哪些下层服务会被多少个上层服务组装因此领域层通常只提供一些原子服务比如领域服务a、b、c。但随着系统功能增强和外部接入越来越多应用服务会不断丰富。有一天你会发现领域服务b和c同时多次被多个应用服务调用了执行顺序也基本一致。这时你可以考虑将b和c合并再将应用服务中b、c的功能下沉到领域层演进为新的领域服务b+c。这样既减少了服务的数量也减轻了上层服务组合和编排的复杂度。
你看,这就是服务演进的过程,它是随着你的系统发展的,最后你会发现你的领域模型会越来越精炼,越来越能适应需求的快速变化。
## 三层架构如何演进到DDD分层架构
综合前面的讲解相信DDD分层架构的优势你心里也有个谱了。我们不妨总结一下最最重要两点。
首先由于层间松耦合我们可以专注于本层的设计而不必关心其它层也不必担心自己的设计会影响其它层。可以说DDD成功地降低了层与层之间的依赖。
其次,分层架构使得程序结构变得清晰,升级和维护更加容易。我们修改某层代码时,只要本层的接口参数不变,其它层可以不必修改。即使本层的接口发生变化,也只影响相邻的上层,修改工作量小且错误可以控制,不会带来意外的风险。
**那我们该怎样转向DDD分层架构呢不妨看看下面这个过程。**
传统企业应用大多是单体架构,而单体架构则大多是三层架构。三层架构解决了程序内代码间调用复杂、代码职责不清的问题,但这种分层是逻辑概念,在物理上它是中心化的集中式架构,并不适合分布式微服务架构。
DDD分层架构中的要素其实和三层架构类似只是在DDD分层架构中这些要素被重新归类重新划分了层确定了层与层之间的交互规则和职责边界。
<img src="https://static001.geekbang.org/resource/image/16/a1/1680723ca91aa57d719d5cdbc1d910a1.jpg" alt="">
我们看一下上面这张图分析一下从三层架构向DDD分层架构演进的过程。
首先你要清楚三层架构向DDD分层架构演进主要发生在业务逻辑层和数据访问层。
DDD分层架构在用户接口层引入了DTO给前端提供了更多的可使用数据和更高的展示灵活性。
DDD分层架构对三层架构的业务逻辑层进行了更清晰的划分改善了三层架构核心业务逻辑混乱代码改动相互影响大的情况。DDD分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化领域层实现领域模型的能力。
另外一个重要的变化发生在数据访问层和基础层之间。三层架构数据访问采用DAO方式DDD分层架构的数据库等基础资源访问采用了仓储Repository设计模式通过依赖倒置实现各层对基础资源的解耦。
仓储又分为两部分仓储接口和仓储实现。仓储接口放在领域层中仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config等通用的公共的资源类统一放到了基础层。
最后我想说传统三层架构向DDD分层架构的演进体现的正是领域驱动设计思想的演进。希望你也感受到了并尝试将其应用在自己的架构设计中。
## 总结
今天我们主要讲了DDD的分层架构它作为微服务的核心框架我想怎么强调其重要性都是不过分的。
DDD分层架构包含用户接口层、应用层、领域层和基础层。通过这些层次划分我们可以明确微服务各层的职能划定各领域对象的边界确定各领域对象的协作方式。这种架构既体现了微服务设计和架构演进的需求又很好地融入了领域模型的概念二者无缝结合相信会给你的微服务设计带来不一样的感觉。
## 思考题
请结合你的业务场景中,思考一下领域层会有哪些领域对象,应用层会有哪些领域对象?
欢迎留言和我分享你的思考,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。
<img src="https://static001.geekbang.org/resource/image/7b/33/7b1a917aff0ec923b78a54e81ed90733.jpg" alt="unpreview">

View File

@@ -0,0 +1,134 @@
<audio id="audio" title="08 | 微服务架构模型:几种常见模型的对比和分析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/05/7e/056e3777014519d51340309d94330c7e.mp3"></audio>
你好,我是欧创新。
在上一讲中我重点介绍了DDD分层架构同时我也提到了微服务架构模型其实还有好多种不知道你注意到了没这些架构模型在我们的实际应用中都具有很高的借鉴价值。
那么今天我们就把DDD分层架构详情介绍如有遗忘可回看 [[第07讲]](https://time.geekbang.org/column/article/156849) )、整洁架构、六边形架构这三种架构模型放到一起,对比分析,看看如何利用好它们,帮助我们设计出高内聚低耦合的中台以及微服务架构。
## 整洁架构
整洁架构又名“洋葱架构”。为什么叫它洋葱架构?看看下面这张图你就明白了。整洁架构的层就像洋葱片一样,它体现了分层的设计思想。
在整洁架构里,同心圆代表应用软件的不同部分,从里到外依次是领域模型、领域服务、应用服务和最外围的容易变化的内容,比如用户界面和基础设施。
整洁架构最主要的原则是依赖原则,它定义了各层的依赖关系,越往里依赖越低,代码级别越高,越是核心能力。外圆代码依赖只能指向内圆,内圆不需要知道外圆的任何情况。
<img src="https://static001.geekbang.org/resource/image/fc/42/fc8208d9f4cfadb7949d6e98a8c18442.png" alt="">
在洋葱架构中,各层的职能是这样划分的:
- 领域模型实现领域内核心业务逻辑,它封装了企业级的业务规则。领域模型的主体是实体,一个实体可以是一个带方法的对象,也可以是一个数据结构和方法集合。
- 领域服务实现涉及多个实体的复杂业务逻辑。
- 应用服务实现与用户操作相关的服务组合与编排,它包含了应用特有的业务流程规则,封装和实现了系统所有用例。
- 最外层主要提供适配的能力,适配能力分为主动适配和被动适配。主动适配主要实现外部用户、网页、批处理和自动化测试等对内层业务逻辑访问适配。被动适配主要是实现核心业务逻辑对基础资源访问的适配,比如数据库、缓存、文件系统和消息中间件等。
- 红圈内的领域模型、领域服务和应用服务一起组成软件核心业务能力。
## 六边形架构
六边形架构又名“端口适配器架构”。追溯微服务架构的渊源,一般都会涉及到六边形架构。
六边形架构的核心理念是应用是通过端口与外部进行交互的。我想这也是微服务架构下API网关盛行的主要原因吧。
也就是说在下图的六边形架构中红圈内的核心业务逻辑应用程序和领域模型与外部资源包括APP、Web应用以及数据库资源等完全隔离仅通过适配器进行交互。它解决了业务逻辑与用户界面的代码交错问题很好地实现了前后端分离。六边形架构各层的依赖关系与整洁架构一样都是由外向内依赖。
<img src="https://static001.geekbang.org/resource/image/85/6c/85fb9fb2782b343d45b4ca18c8f21e6c.png" alt="">
六边形架构将系统分为内六边形和外六边形两层,这两层的职能划分如下:
- 红圈内的六边形实现应用的核心业务逻辑;
- 外六边形完成外部应用、驱动和基础资源等的交互和访问对前端应用以API主动适配的方式提供服务对基础资源以依赖倒置被动适配的方式实现资源访问。
六边形架构的一个端口可能对应多个外部系统,不同的外部系统也可能会使用不同的适配器,由适配器负责协议转换。这就使得应用程序能够以一致的方式被用户、程序、自动化测试和批处理脚本使用。
## 三种微服务架构模型的对比和分析
虽然DDD分层架构、整洁架构、六边形架构的架构模型表现形式不一样但你不要被它们的表象所迷惑这三种架构模型的设计思想正是微服务架构高内聚低耦合原则的完美体现而它们身上闪耀的正是以领域模型为中心的设计思想。
<img src="https://static001.geekbang.org/resource/image/b2/71/b2e4dad1040857b5aedf0b1675ae4171.png" alt="">
我们看下上面这张图,结合图示对这三种架构模型做一个分析。
请你重点关注图中的红色线框,它们是非常重要的分界线,这三种架构里面都有,它的作用就是将核心业务逻辑与外部应用、基础资源进行隔离。
红色框内部主要实现核心业务逻辑,但核心业务逻辑也是有差异的,有的业务逻辑属于领域模型的能力,有的则属于面向用户的用例和流程编排能力。按照这种功能的差异,我们在这三种架构中划分了应用层和领域层,来承担不同的业务逻辑。
领域层实现面向领域模型,实现领域模型的核心业务逻辑,属于原子模型,它需要保持领域模型和业务逻辑的稳定,对外提供稳定的细粒度的领域服务,所以它处于架构的核心位置。
应用层实现面向用户操作相关的用例和流程对外提供粗粒度的API服务。它就像一个齿轮一样进行前台应用和领域层的适配接收前台需求随时做出响应和调整尽量避免将前台需求传导到领域层。应用层作为配速齿轮则位于前台应用和领域层之间。
可以说,这三种架构都考虑了前端需求的变与领域模型的不变。需求变幻无穷,但变化总是有矩可循的,用户体验、操作习惯、市场环境以及管理流程的变化,往往会导致界面逻辑和流程的多变。但总体来说,不管前端如何变化,在企业没有大的变革的情况下,核心领域逻辑基本不会大变,所以领域模型相对稳定,而用例和流程则会随着外部应用需求而随时调整。把握好这个规律,我们就知道该如何设计应用层和领域层了。
架构模型通过分层的方式来控制需求变化从外到里对系统的影响,从外向里受需求影响逐步减小。面向用户的前端可以快速响应外部需求进行调整和发布,灵活多变,应用层通过服务组合和编排来实现业务流程的快速适配上线,减少传导到领域层的需求,使领域层保持长期稳定。
这样设计的好处很明显了,就是可以保证领域层的核心业务逻辑不会因为外部需求和流程的变动而调整,对于建立前台灵活、中台稳固的架构很有帮助。
看到这里,你是不是已经猜出中台和微服务设计的关键了呢?我给出的答案是:领域模型和微服务的合理分层设计。那么你的答案呢?
## 从三种架构模型看中台和微服务设计
结合这三种微服务架构模型的共性,下面我来谈谈中台和微服务设计的一些心得体会。
中台本质上是领域的子域它可能是核心域也可能是通用域或支撑域。通常大家认为阿里的中台对应DDD的通用域将通用的公共能力沉淀为中台对外提供通用共享服务。
中台作为子域还可以继续分解为子子域在子域分解到合适大小通过事件风暴划分限界上下文以后就可以定义微服务了微服务用来实现中台的能力。表面上看DDD、中台、微服务这三者之间似乎没什么关联实际上它们的关系是非常紧密的组合在一起可以作为一个理论体系用于你的中台和微服务设计。
### 1. 中台建设要聚焦领域模型
中台需要站在全企业的高度考虑能力的共享和复用。
中台设计时我们需要建立中台内所有限界上下文的领域模型DDD建模过程中会考虑架构演进和功能的重新组合。领域模型建立的过程会对业务和应用进行清晰的逻辑和物理边界微服务划分。领域模型的结果会影响到后续的系统模型、架构模型和代码模型最终影响到微服务的拆分和项目落地。
因此,在中台设计中我们首先要聚焦领域模型,将它放在核心位置。
### 2. 微服务要有合理的架构分层
微服务设计要有分层的设计思想,让各层各司其职,建立松耦合的层间关系。
不要把与领域无关的逻辑放在领域层实现,保证领域层的纯洁和领域逻辑的稳定,避免污染领域模型。也不要把领域模型的业务逻辑放在应用层,这样会导致应用层过于庞大,最终领域模型会失焦。如果实在无法避免,我们可以引入防腐层,进行新老系统的适配和转换,过渡期完成后,可以直接将防腐层代码抛弃。
微服务内部的分层方式我们已经清楚了,那微服务之间是否也有层次依赖关系呢?如何实现微服务之间的服务集成?
有的微服务可以与前端应用集成,一起完成特定的业务,这是项目级微服务。而有的则是某个职责单一的中台微服务,企业级的业务流程需要将多个这样的微服务组合起来才能完成,这是企业级中台微服务。两类微服务由于复杂度不一样,集成方式也会有差异。
**项目级微服务**
项目级微服务的内部遵循分层架构模型就可以了。领域模型的核心逻辑在领域层实现服务的组合和编排在应用层实现通过API网关为前台应用提供服务实现前后端分离。但项目级的微服务可能会调用其它微服务你看在下面这张图中比如某个项目级微服务B调用认证微服务A完成登录和权限认证。
通常项目级微服务之间的集成发生在微服务的应用层由应用服务调用其它微服务发布在API网关上的应用服务。你看下图中微服务B中红色框内的应用服务B它除了可以组合和编排自己的领域服务外还可以组合和编排外部微服务的应用服务。它只要将编排后的服务发布到API网关供前端调用这样前端就可以直接访问自己的微服务了。
<img src="https://static001.geekbang.org/resource/image/b4/9e/b4231550cfbd56c15ccb3795d1062f9e.png" alt="">
**企业级中台微服务**
企业级的业务流程往往是多个中台微服务一起协作完成的,那跨中台的微服务如何实现集成呢?
企业级中台微服务的集成不能像项目级微服务一样,在某一个微服务内完成跨微服务的服务组合和编排。
我们可以在中台微服务之上增加一层,你看下面这张图,增加的这一层就位于红色框内,它的主要职能就是处理跨中台微服务的服务组合和编排,以及微服务之间的协调,它还可以完成前端不同渠道应用的适配。如果再将它的业务范围扩大一些,我可以将它做成一个面向不同行业和渠道的服务平台。
我们不妨借用BFF服务于前端的后端Backend for Frontends这个词暂且称它为BFF微服务。BFF微服务与其它微服务存在较大的差异就是它没有领域模型因此这个微服务内也不会有领域层。BFF微服务可以承担应用层和用户接口层的主要职能完成各个中台微服务的服务组合和编排可以适配不同前端和渠道的要求。
<img src="https://static001.geekbang.org/resource/image/ee/3b/eeb66579c1725817d0e9185161f1843b.png" alt="">
### 3. 应用和资源的解耦与适配
传统以数据为中心的设计模式,应用会对数据库、缓存、文件系统等基础资源产生严重依赖。
正是由于它们之间的这种强依赖的关系,我们一旦更换基础资源就会对应用产生很大的影响,因此需要为应用和资源解耦。
在微服务架构中,应用层、领域层和基础层解耦是通过仓储模式,采用依赖倒置的设计方法来实现的。在应用设计中,我们会同步考虑和基础资源的代码适配,那么一旦基础设施资源出现变更(比如换数据库),就可以屏蔽资源变更对业务代码的影响,切断业务逻辑对基础资源的依赖,最终降低资源变更对应用的影响。
## 总结
今天我们详细讲解了整洁架构和六边形架构并对包括DDD分层架构在内的三种微服务架构模进行对比分析总结出了它们的共同特征并从共性出发梳理出了中台建模和微服务架构设计的几个要点我们后面还会有更加详细的有关设计落地的讲述。
**那从今天的内容中我们不难看出:**DDD分层架构、整洁架构、六边形架构都是以领域模型为核心实行分层架构内部核心业务逻辑与外部应用、资源隔离并解耦。请务必记好这个设计思想今后会有大用处。
## 思考题
在系统设计时,你是如何避免外部需求对核心业务逻辑的影响的?有什么具体方法可以分享给大家吗?
欢迎留言分享,你也可以把今天所学分享给身边的朋友,邀请他一同交流、打卡。

View File

@@ -0,0 +1,137 @@
<audio id="audio" title="09 | 中台:数字转型后到底应该共享什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/03/cb/03fa222d5317007746f78dec5e8e06cb.mp3"></audio>
你好,我是欧创新。
在上一讲中我们了解了分层架构的设计思想,并提到了这种设计思想对中台建设十分有利,那么今天我就来讲一讲中台。
中台是数字化转型的一个热门话题。继阿里提出中台概念后,很多人又提出了各种各样的中台。今天我们主要讨论业务中台和数据中台。作为企业数字化中台转型的整体,我也会顺带聊一聊前台和后台的一些设计思路。
不少企业其实在很多年前就有了建大平台的实践经验,那在中台被热议时,我相信你一定听过很多质疑声。比如,有人说:“中台就是个怪名词,它不就是已经做了好多年的平台吗?”确实,中台源于平台,但它的战略高度要比平台高很多。
学完这一讲,你就会清楚地知道平台与中台的差异在什么地方?中台到底是什么?传统企业的中台建设方式是否应该和阿里一样…
## 平台到底是不是中台?
阿里提出中台战略后,很多企业开始拿着自己的系统与阿里的中台对标。有的企业在十多年前就完成了大一统的集中式系统拆分,实现了从传统大单体应用向大平台的演进,他们将公共能力和核心能力分开建设,解决了公共模块重复投入和重复建设的问题。
那这是不是阿里所说的中台呢?在回答这个问题之前,我们不妨先了解一下阿里的中台到底是什么样的。
阿里业务中台的前身是共享平台,而原来的共享平台更多的被当作资源团队,他们承接各业务方的需求,并为业务方在基础服务上做定制开发。 阿里业务中台的目标是把核心服务链路(会员、商品、交易、营销、店铺、资金结算等)整体当作一个平台产品来做,为前端业务提供的是业务解决方案,而不是彼此独立的系统。
下面我们分析一下传统企业大平台战略和阿里中台战略的差异。
平台只是将部分通用的公共能力独立为共享平台。虽然可以通过API或者数据对外提供公共共享服务解决系统重复建设的问题但这类平台并没有和企业内的其它平台或应用实现页面、业务流程和数据从前端到后端的全面融合并且没有将核心业务服务链路作为一个整体方案考虑各平台仍然是分离且独立的。
平台解决了公共能力复用的问题,但离中台的目标显然还有一段差距!
## 中台到底是什么?
“一千个读者就有一千个哈姆雷特”,这句话形容技术圈对中台的定义再合适不过了,说法很多。
先看一下阿里自己人对中台的定义:“中台是一个基础的理念和架构,我们要把所有的基础服务用中台的思路建设,进行联通,共同支持上端的业务。业务中台更多的是支持在线业务,数据中台提供了基础数据处理能力和很多的数据产品给所有业务方去用。业务中台、数据中台、算法中台等等一起提供对上层业务的支撑。”
再看一下思特沃克对中台的定义:“中台是企业级能力复用平台。”
综上,我们可以提炼出几个关于中台的关键词:共享、联通、融合和创新。联通是前台以及中台之间的联通,融合是前台流程和数据的融合,并以共享的方式支持前端一线业务的发展和创新。
我认为,中台首先体现的是一种企业级的能力,它提供的是一套企业级的整体解决方案,解决小到企业、集团,大到生态圈的能力共享、联通和融合问题,支持业务和商业模式创新。通过平台联通和数据融合为用户提供一致的体验,更敏捷地支撑前台一线业务。
中台来源于平台,但中台和平台相比,它更多体现的是一种理念的转变,它主要体现在这三个关键能力上:对前台业务的快速响应能力;企业级复用能力;从前台、中台到后台的设计、研发、页面操作、流程服务和数据的无缝联通、融合能力。
其中最关键的是快速响应能力和企业级的无缝联通和融合能力,尤其是对于跨业经营的超大型企业来说至关重要。
## 数字化转型中台应该共享什么?
相对互联网企业而言传统企业的渠道应用更多样化有面向内部人员的门店类应用、面向外部用户的互联网电商以及移动APP类应用。这些应用面向的用户和场景可能不同但其功能类似基本涵盖了核心业务能力。此外传统企业也会将部分核心应用的页面或API服务能力开放给生态圈第三方相互借力发展。
为了适应不同业务和渠道的发展过去很多企业的做法是开发很多独立的应用或APP。但由于IT系统建设初期并没有企业级的整体规划平台之间融合不好就导致了用户体验不好最关键的是用户并不想装那么多 APP。
为了提升用户体验实现统一运营很多企业开始缩减APP的数量开始通过一个APP集成企业内的所有能力联通前台所有的核心业务链路。
由于传统企业的商业模式和IT系统建设发展的历程与互联网企业不是完全一样的因此传统企业的中台建设策略与阿里中台战略也应该有所差异需要共享的内容也不一样。
<img src="https://static001.geekbang.org/resource/image/09/45/09d70aae8b66092bc692ac30510f9145.jpg" alt="">
由于渠道多样化传统企业不仅要将通用能力中台化以实现通用能力的沉淀、共享和复用这里的通用能力对应DDD的通用域或支撑域传统企业还需要将核心能力中台化以满足不同渠道的核心业务能力共享和复用的需求避免传统核心和互联网不同渠道应用出现“后端双核心、前端两张皮”的问题这里的核心能力对应DDD的核心域。
这就属于业务中台的范畴了,我们需要解决核心业务链路的联通和不同渠道服务共享的问题。除此之外,我们还需要解决系统微服务拆分后的数据孤岛、数据融合和业务创新等问题,这就属于数据中台的范畴了,尤其是当我们采用分布式架构以后,我们就更应该关注微服务拆分后的数据融合和共享问题了。
综上,在中台设计和规划时,我们需要整体考虑企业内前台、中台以及后台应用的协同,实现不同渠道应用的前端页面、流程和服务的共享,还有核心业务链路的联通以及前台流程和数据的融合、共享,支持业务和商业模式的创新。
## 如何实现前中后台的协同?
企业级能力往往是前中后台协同作战能力的体现。
如果把业务中台比作陆军、火箭军和空军等专业军种的话,它主要发挥战术专业能力。前台就是作战部队,它需要根据前线的战场需求,对业务中台的能力进行调度,实现能力融合和效率最大化。而数据中台就是信息情报中心和联合作战总指挥部,它能够汇集各种数据、完成分析,制定战略和战术计划。后台就是后勤部队,提供技术支持。下面我们分别来说说。
### 1. 前台
传统企业的早期系统有不少是基于业务领域或组织架构来建设的,每个系统都有自己的前端,相互独立,用户操作是竖井式,需要登录多个系统才能完成完整的业务流程。
<img src="https://static001.geekbang.org/resource/image/76/91/76c677ccc83912dbc4d09d62c259b391.jpg" alt="">
中台后的前台建设要有一套综合考虑业务边界、流程和平台的整体解决方案,以实现各不同中台前端操作、流程和界面的联通、融合。不管后端有多少个中台,前端用户感受到的就是只有一个前台。
<img src="https://static001.geekbang.org/resource/image/ba/2b/ba2049c9a3696bbebbd9d60e496df72b.jpg" alt="">
在前台设计中我们可以借鉴微前端的设计思想,在企业内不仅实现前端解耦和复用,还可以根据核心链路和业务流程,通过对微前端页面的动态组合和流程编排,实现前台业务的融合。
前端页面可以很自然地融合到不同的终端和渠道应用核心业务链路中,实现前端页面、流程和功能复用。
### 2. 中台
传统企业的核心业务大多是基于集中式架构开发的而单体系统存在扩展性和弹性伸缩能力差的问题因此无法适应忽高忽低的互联网业务场景。而数据类应用也多数通过ETL工具抽取数据实现数据建模、统计和报表分析功能但由于数据时效和融合能力不够再加上传统数据类应用本来就不是为前端而生的因此难以快速响应前端一线业务。
业务中台的建设可采用领域驱动设计方法,通过领域建模,将可复用的公共能力从各个单体剥离,沉淀并组合,采用微服务架构模式,建设成为可共享的通用能力中台。
同样的,我们可以将核心能力用微服务架构模式,建设成为可面向不同渠道和场景的可复用的核心能力中台。 业务中台向前台、第三方和其它中台提供API服务实现通用能力和核心能力的复用。
<img src="https://static001.geekbang.org/resource/image/26/81/261b05056ae6056ff406f7dadf7ac081.jpg" alt="">
但你需要记住这一点:在将传统集中式单体按业务职责和能力细分为微服务,建设中台的过程中,会产生越来越多的独立部署的微服务。这样做虽然提升了应用弹性和高可用能力,但由于微服务的物理隔离,原来一些系统内的调用会变成跨微服务调用,再加上前后端分离,微服务拆分会导致数据进一步分离,增加企业级应用集成的难度。
如果没有合适的设计和指导思想,处理不好前台、中台和后台的关系,将会进一步加剧前台流程和数据的孤岛化、碎片化。
**数据中台的主要目标是打通数据孤岛,实现业务融合和创新,包括三大主要职能:**
- 一是完成企业全域数据的采集与存储,实现各不同业务类别中台数据的汇总和集中管理。
- 二是按照标准的数据规范或数据模型,将数据按照不同主题域或场景进行加工和处理,形成面向不同主题和场景的数据应用,比如客户视图、代理人视图、渠道视图、机构视图等不同数据体系。
- 三是建立业务需求驱动的数据体系,基于各个维度的数据,深度萃取数据价值,支持业务和商业模式的创新。
**相应的,数据中台的建设就可分为三步走:**
- 第一步实现各中台业务数据的汇集,解决数据孤岛和初级数据共享问题。
- 第二步实现企业级实时或非实时全维度数据的深度融合、加工和共享。
- 第三步萃取数据价值,支持业务创新,加速从数据转换为业务价值的过程。
数据中台不仅限于分析型场景,也适用于交易型场景。它可以建立在数据仓库或数据平台之上,将数据服务化之后提供给业务系统。基于数据库日志捕获的技术,使数据的时效性大大提升,这样就可以为交易型场景提供很好的支撑。
综上,数据中台主要完成数据的融合和加工,萃取数据业务价值,支持业务创新,对外提供数据共享服务。
### 3. 后台
很多人提到中台时自然会问:“既然有前台和中台,那是否有后台,后台的职责又是什么?”
我们来看一下阿里对前台、中台和后台的定位。
前台主要面向客户以及终端销售者实现营销推广以及交易转化中台主要面向运营人员完成运营支撑后台主要面向后台管理人员实现流程审核、内部管理以及后勤支撑比如采购、人力、财务和OA等系统。
那对于后台,为了实现内部的管理要求,很多人习惯性将这些管理要求嵌入到核心业务流程中。而一般来说这类内控管理需求对权限、管控规则和流程等要求都比较高,但是大部分管理人员只是参与了某个局部业务环节的审核。这类复杂的管理需求,会凭空增加不同渠道应用前台界面和核心流程的融合难度以及软件开发的复杂度。
在设计流程审核和管理类功能的时候我们可以考虑按角色或岗位进行功能聚合将复杂的管理需求从通用的核心业务链路中剥离参考小程序的建设模式通过特定程序入口嵌入前台APP或应用中。
管理需求从前台核心业务链路剥离后前台应用将具有更好的通用性它可以更加容易地实现各渠道前台界面和流程的融合。一个前台应用或APP可以无差别地同时面向外部互联网用户和内部业务人员从而促进传统渠道与互联网渠道应用前台的融合。
## 总结
今天我们主要讨论了中台建设的一些思路。企业的中台转型不只是中台的工作,我们需要整体考虑前台、中台和后台的协同、共享、联通和融合。
前台通过页面和流程共享实现不同渠道应用之间的前台融合中台通过API实现服务共享。而前台、业务中台和数据中台的融合可以实现传统应用与互联网应用的融合从而解决“后端双核心、前端两张皮”的问题。能力复用了前台流程和数据融合了才能更好地支持业务的融合和商业模式的创新。
## 思考题
你的公司是如何进行前后端设计和融合的呢?
欢迎留言分享,你也可以把今天所学分享给身边的朋友,邀请他一同交流、打卡。

View File

@@ -0,0 +1,103 @@
<audio id="audio" title="10 | DDD、中台和微服务它们是如何协作的" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2b/34/2b42aa1876fd04527f1ed99addd31834.mp3"></audio>
你好我是欧创新。今天我一起来聊聊DDD、中台和微服务的关系。
DDD和微服务来源于西方而中台诞生于中国的阿里巴巴。DDD在二十多年前提出后一直默默前行中台和微服务的理念近几年才出现提出后就非常火爆。这三者看似风马牛不相及实则缘分匪浅。中台是抽象出来的业务模型微服务是业务模型的系统实现DDD作为方法论可以同时指导中台业务建模和微服务建设三者相辅相成完美结合。
你可能会问凭什么DDD可以指导中台和微服务建设究竟起到了什么作用呢
DDD有两把利器那就是它的战略设计和战术设计方法。
中台在企业架构上更多偏向业务模型形成中台的过程实际上也是业务领域不断细分的过程。在这个过程中我们会将同类通用的业务能力进行聚合和业务重构再根据限界上下文和业务内聚的原则建立领域模型。而DDD的战略设计最擅长的就是领域建模。
那在中台完成领域建模后我们就需要通过微服务来完成系统建设。此时DDD的战术设计又恰好可以与微服务的设计完美结合。可以说中台和微服务正是DDD实战的最佳场景。
## DDD的本质
我们先简单回顾一下DDD领域、子域、核心域、通用域和支撑域等概念后面会用到。
在研究和解决业务问题时DDD会按照一定的规则将业务领域进行细分领域细分到一定的程度后DDD会将问题范围限定在特定的边界内并在这个边界内建立领域模型进而用代码实现该领域模型解决相应的业务问题。领域可分解为子域子域可继续分为子子域一直到你认为适合建立领域模型为止。
子域还会根据自身重要性和功能属性划分为三类子域,它们分别是核心域、支撑域和通用域。关于这三类子域更为详细的讲解,你可以回看[[第02讲]](https://time.geekbang.org/column/article/149945)。
<img src="https://static001.geekbang.org/resource/image/99/29/991b8a5b685d8b0b3e172f38c0460a29.png" alt="">
接下来我们一起看下上面这张图我选择了保险的几个重要领域进行了高阶的领域划分。当然每个企业的领域定位和职责会有些不一样那在核心域的划分上肯定会有一定差异。因此当你去做领域划分的时候请务必结合企业战略这恰恰也体现了DDD领域建模的重要性。
通过领域划分和进一步的子域划分我们就可以区分不同子域在企业内的功能属性和重要性进而采取不同的资源投入和建设策略这在企业IT系统的建设过程中十分重要并且这样的划分还可以帮助企业进行中台设计。
## 中台的本质
中台来源于阿里的中台战略详见《企业IT架构转型之道阿里巴巴中台战略思想与架构实战》钟华编著。2015年年底阿里巴巴集团对外宣布全面启动中台战略构建符合数字时代的更具创新性、灵活性的“大中台、小前台”组织机制和业务机制即作为前台的一线业务会更敏捷、更快速地适应瞬息万变的市场而中台将集合整个集团的运营数据能力、产品技术能力对各前台业务形成强力支撑。
中台的本质其实就是提炼各个业务板块的共同需求,进行业务和系统抽象,形成通用的可复用的业务模型,打造成组件化产品,供前台部门使用。前台要做什么业务,需要什么资源,可以直接找中台,不需要每次都去改动自己的底层。
## DDD、中台和微服务的协作模式
我们在 [[第09讲]](https://time.geekbang.org/column/article/159580) 已经说过了传统企业和阿里中台战略的差异,那实际上更多的企业还是会聚焦在传统企业中台建设的模式,也就是将通用能力与核心能力全部中台化,以满足不同渠道核心业务能力的复用,那么接下来我们就还是把重点放在传统企业上。
传统企业可以将需要共享的公共能力进行领域建模,建设可共享的**通用中台**。除此之外,传统企业还会将核心能力进行领域建模,建设面向不同渠道的可复用的**核心中台**。
而这里的通用中台和核心中台都属于我们上一讲讲到的业务中台的范畴。
DDD的子域分为核心域、通用域和支撑域。划分这几个子域的主要目的是为了确定战略资源的投入一般来说战略投入的重点是核心域因此后面我们就可以暂时不严格区分支撑域和通用域了。
领域、中台以及微服务虽然属于不同层面的东西但我们还是可以将他们分解对照整理出来它们之间的关系。你看下面这张图我是从DDD领域建模和中台建设这两个不同的视角对同一个企业的业务架构进行分析。
<img src="https://static001.geekbang.org/resource/image/c6/df/c647561ff910f97b8500b75de70281df.png" alt="">
如果将企业内整个业务域作为一个问题域的话企业内的所有业务就是一个领域。在进行领域细分时从DDD视角来看子域可分为核心域、通用域和支撑域。从中台建设的视角来看业务域细分后的业务中台可分为核心中台和通用中台。
从领域功能属性和重要性对照来看通用中台对应DDD的通用域和支撑域核心中台对应DDD的核心域。从领域的功能范围来看子域与中台是一致的。领域模型所在的限界上下文对应微服务。建立了这个映射关系我们就可以用DDD来进行中台业务建模了。
**我们这里还是以保险领域为例。**保险域的业务中台分为两类:第一类是提供保险核心业务能力的核心中台(比如营销、承保和理赔等业务);第二类是支撑核心业务流程完成保险全流程的通用中台(比如订单、支付、客户和用户等)。
这里我要提醒你一下根据DDD首先要建立通用语言的原则在将DDD的方法引入中台设计时我们要先建立中台和DDD的通用语言。这里的子域与中台是一致的那我们就可以将子域统一为中台。
中台通过事件风暴可以进一步细分,最终完成业务领域建模。中台业务领域的功能不同,限界上下文的数量和大小就会不一样,领域模型也会不一样。
当完成业务建模后我们就可以采用DDD战术设计设计出聚合、实体、领域事件、领域服务以及应用服务等领域对象再利用分层架构模型完成微服务的设计。
以上就是DDD、中台和微服务在应用过程中的协作模式。
## 中台如何建模?
看完了三者的协作模式,我们就顺着上面的话题,接着来聊聊中台如何建模。
中台业务抽象的过程就是业务建模的过程对应DDD的战略设计。系统抽象的过程就是微服务的建设过程对应DDD的战术设计。下面我们就结合DDD领域建模的方法讲一下中台业务建模的过程。
**第一步:**按照业务流程(通常适用于核心域)或者功能属性、集合(通常适用于通用域或支撑域),将业务域细分为多个中台,再根据功能属性或重要性归类到核心中台或通用中台。核心中台设计时要考虑核心竞争力,通用中台要站在企业高度考虑共享和复用能力。
**第二步:**选取中台,根据用例、业务场景或用户旅程完成事件风暴,找出实体、聚合和限界上下文。依次进行领域分解,建立领域模型。
由于不同中台独立建模,某些领域对象或功能可能会重复出现在其它领域模型中,也有可能本该是同一个聚合的领域对象或功能,却分散在其它的中台里,这样会导致领域模型不完整或者业务不内聚。这里先不要着急,这一步我们只需要初步确定主领域模型就可以了,在第三步中我们还会提炼并重组这些领域对象。
**第三步:**以主领域模型为基础,扫描其它中台领域模型,检查并确定是否存在重复或者需要重组的领域对象、功能,提炼并重构主领域模型,完成最终的领域模型设计。
**第四步:**选择其它主领域模型重复第三步,直到所有主领域模型完成比对和重构。
**第五步:**基于领域模型完成微服务设计,完成系统落地。
<img src="https://static001.geekbang.org/resource/image/3c/47/3caae85ef680eb7cbc2ffb5c6a603f47.png" alt="">
结合上面这张图你可以大致了解到DDD中台设计的过程。DDD战略设计包括上述的第一步到第四步主要为业务域分解为中台对中台归类完成领域建模建立中台业务模型。DDD战术设计是第五步领域模型映射为微服务完成中台建设。
<img src="https://static001.geekbang.org/resource/image/45/71/45de1af6a4b5dd6cf54921ff9f422571.png" alt="">
那么如果还是以保险领域为例的话,完成领域建模后,里面的数据我们就可以填上了。这里我选取了通用中台的用户、客户和订单三个中台来做示例。客户中台提炼出了两个领域模型:客户信息和客户视图模型。用户中台提炼出了三个领域模型:用户管理、登录认证和权限模型。订单中台提炼出了订单模型。
这就是中台建模的全流程,当然看似简单的背后,若是遇上复杂的业务总会出现各种各样的问题,不然应用起来也不会有那么多的困难。如果你在按照以上流程实施的过程中遇到什么问题,欢迎在留言区和我讨论。
## 总结
今天我们主要讨论了传统企业中台建设的一些思路梳理了DDD、中台和微服务的关系。DDD的战略设计可用于中台业务建模战术设计可指导中台微服务设计。相信DDD与中台的完美结合可以让你的中台建设如虎添翼
另外,这一讲只是开一个头,在下一讲中我还会以一个传统核心业务的中台建设案例,详细讲解中台的设计过程。
## 思考题
你的企业是否在做中台现在是用什么方法做中台业务建模呢和DDD的设计方法相比你觉得孰优孰劣
欢迎留言分享,你也可以把今天所学分享给身边的朋友,邀请他一同交流、打卡。

View File

@@ -0,0 +1,59 @@
<audio id="audio" title="答疑有关3个典型问题的讲解" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fc/4b/fcdf97141fa0ce1200643c3ae2e6fb4b.mp3"></audio>
你好,我是欧创新。
截至今天这一讲我们的基础篇和进阶篇的内容就结束了。在这个过程中我一直有关注大家提的问题。那在实战篇正式开始之前啊我想针对3个比较典型的问题做一个讲解希望你也能同步思考调动自己已学过的内容这对我们后面实战篇的学习也是有一定帮助的。
**问题1有关于领域可以划分为核心域、通用域和支撑域以及子域和限界上下文关系的话题还有是否有边界划分的量化标准**
我在 [[第 02 讲]](https://time.geekbang.org/column/article/149945) 中讲到了,在领域不断划分的过程中,领域会被细分为不同的子域,这个过程实际上是将问题范围不断缩小的过程。
借用读者“密码123456”的总结他认为“对于领域问题来说可以理解为对一个问题不断地划分直到划分为我们熟悉的、能够快速处理的小问题。然后再对小问题的处理排列一个优先级。”
这个理解是很到位的。在领域细分到一定的范围后,我们就可以对这个子域进行事件风暴,为这个子域划分限界上下文,建立领域模型,然后就可以基于领域模型进行微服务设计了。
虽然DDD没有明确说明子域和限界上下文的关系。我个人认为子域的划分是一种比较粗的领域边界的划分它不考虑子域内的领域对象、对象之间的关系和结构。子域的划分往往按照业务阶段或者功能模块边界进行粗分其目的就是为了让你能够在一个相对较小的问题空间内比较方便地用事件风暴来梳理业务场景。
而限界上下文本质上也是子域,限界上下文是在明确的子域内,用事件风暴划分出来的。它体现的是一种详细的设计过程。这个过程设计出了领域模型,明确了领域对象以及领域对象的依赖等关系,有了领域模型,你就可以直接进行微服务设计了。
关于核心域、通用域和支撑域划分这三个不同类型子域的主要目的是为了区分业务域的优先级确定IT战略投入。我们会将重要的资源投入在核心域上确保好钢用在刀刃上。每个企业由于商业模式或者战略方向不一样核心域会有一些差异不要用固定的眼光看待不同企业的核心域。
核心域、通用域和支撑域都是业务领域只不过重要性和功能属性不一样。采用的DDD设计方法和过程是没有差异的。
从目前来看还没有可以量化的领域以及限界上下文的划分标准。它主要依赖领域专家经验以及和项目团队在事件风暴过程中不断地权衡和分析。不要奢望一次迭代就能够给复杂的业务建立一个完美的领域模型。领域模型很多时候也需要多次迭代才能成型它也需要不断地演进。但如果是用DDD设计出来的领域模型的边界和微服务内聚合的边界非常清晰的话这个演进过程相对来说会简单很多所需的时间成本也会很低。
**问题2关于聚合设计的问题领域层与基础层为什么要依赖倒置DIP**
聚合主要实现核心业务逻辑,里面有很多的领域对象,这些领域对象之间需要通过聚合根进行统一的管理,以确保数据的一致性。
在聚合设计时我们会用到两个重要的设计模式工厂Factory模式和仓储Repository模式。如果你有兴趣详细了解的话推荐你阅读《实现领域驱动设计》一书的第11章和第12章。
那为什么要引入工厂模式呢?
这是因为有些聚合内可能含有非常多的实体和值对象,我们需要确保聚合根以及所有被依赖的对象实例同时被创建。如果都通过聚合根来构造,将会非常复杂。因此我们可以通过工厂模式来封装复杂对象的创建过程,但并不是所有对象的构造都需要用到工厂,如果构造过程不复杂,只是单一对象的构造,你用简单的构造方法就足够了。
又为什么要引入仓储模式?解答这个问题的同时,我也一起将依赖倒置的问题解答一下。
在传统的DDD四层架构中所有层都是依赖基础层的。这样做有什么不好的地方呢如果应用逻辑对基础层依赖太大的话基础层中与资源有关的代码可能会渗透到应用逻辑中。而现在技术组件的更新频率是很快的一旦出现基础组件的变更且基础组件的代码被带入到了应用逻辑中这样会对上层的应用逻辑产生致命的影响。
为了解耦应用逻辑和基础资源,在基础层和上层应用逻辑之间会增加一层,这一层就是仓储层。一个聚合对应一个仓储,仓储实现聚合内数据的持久化。聚合内的应用逻辑通过接口来访问基础资源,仓储实现在基础层实现。这样应用逻辑和基础资源的实现逻辑是分离的。如果变更基础资源组件,只需要替换仓储实现就可以了,不会对应用逻辑产生太大的影响,这样就实现了应用逻辑与基础资源的解耦,也就实现了依赖倒置。
关于聚合设计过程中的一些原则问题。大部分的业务场景我们都可以通过事件风暴找到聚合根建立聚合划分限界上下文建立领域模型。但也有部分场景比如数据计算、统计以及批处理业务场景所有的实体都是独立无关联的找不到聚合根也无法建立领域模型。但是它们之间的业务关系是非常紧密的在业务上是高内聚的。我们也可以将这类场景作为一个聚合处理除了不考虑聚合根的设计方法外其它诸如DDD分层架构相关的设计方法都是可以采用的。
一些业务场景如果复杂度并不高而用DDD设计会带来不必要的麻烦的话比如增加复杂度有些原则也是可以突破的不要为做DDD而做DDD。即使采用传统的方式也是没有关系的最终以解决实际问题为最佳。但必须记住一点如果采用传统的设计方式一定要保证领域模型的边界以及微服务内聚合的逻辑边界清晰这样的话以后微服务的演进就不会太复杂。
**问题3领域事件采用消息异步机制发布方和订阅方数据如何保证一致性微服务内聚合之间领域事件是否一定要用事件总线**
在领域事件设计中,为了解耦微服务,微服务之间数据采用最终一致性原则。由于发布方是在消息总线发布消息以后,并不关心数据是否送达,或者送达后订阅方是否正常处理,因此有些技术人会担心发布方和订阅方数据一致性的问题。
那在对数据一致性要求比较高的业务场景,我们是有相关的设计考虑的。也就是发送方和订阅方的事件数据都必须落库,发送方除了保存业务数据以外,在往消息中间件发布消息之前,会先将要发布的消息写入本地库。而接收方在处理消息之前,需要先将收到的消息写入本地库。然后可以采用定期对发布方和订阅方的事件数据对账的操作,识别出不一致的数据。如果数据出现异常或不一致的情况,可以启动定时程序再次发送,必要时可以转人工操作处理。
关于事件总线的问题。由于微服务内的逻辑都在一个进程内,后端数据库也是一个,微服务内的事务相比微服务之间会好控制一些。在处理微服务内的领域事件时,如果引入事件总线,会增加开发的复杂度,那是否引入事件总线,就需要你来权衡。
个人感觉如果你的场景中,不会出现导致聚合之间数据不一致的情况,就可以不使用事件总线。另外,通过应用服务也可以实现聚合之间的服务和数据协调。
以上就是3个典型问题的答案了不知你先前是否有同样的疑惑这些答案又是否与你不谋而合呢如果你还有其它问题欢迎在留言区提出我会一一解答。
今天的内容就到这了,如果有所收获,也可以分享给你的朋友,我们实战篇见!