领域驱动设计金融基础技术与业务架构

DDD领域驱动设计的项目实践

2019-05-09  本文已影响112人  小猫无痕

Domain-Driven Design

DDD的概念众所周知,各个文章中的基本概念也都大同小异。这里就不再累述了。DDD最大的好处是使用领域通用语言(UBIQUITOUS LANGUAGE)将原先晦涩难懂的业务通过领域概念清晰的显性化表达出来。写这篇文章的目的在于学习怎么使用DDD来降低应用的复杂度,增加框架的可扩展性。本文主要阐述了我在项目中的思考过程和架构实现。

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

我们从以下一些方面进行些讨论以帮助我们完成DDD的具体实践。

1.DDD分层架构

分层架构
分层 描述
UserInterface UI层,负责数据接收的消息处理、页面展示的数据封装。
Application 展示层与领域层的中间层,定义软件要完成的所有任务,提炼总结表现层的需求,不应随着上层频繁变化而变化。向下调用领域层(领域对象或领域服务)协调领域层进行工作,应用层不包含业务逻辑,但包含流程控制逻辑。
Domain 业务逻辑的核心层,领域模型都处在这一层 。领域层的重点应该为如何表达领域模型。
Infrastructure 为其他层提供支持,Repository,Config,Common和message都在这一层,通过基础设施层对上游提供功能、屏蔽实现细节。

  分层最大的意义是在架构的层面上来进行职责的拆分,分离关注点,每一层都确定独立的职责,分层内部保持高度内聚性,让每一层只解决该层关注的问题,从而将复杂的问题简化,它们只对下层进行依赖,实现高内聚低耦合、职责分离的思想。
  应用层代表。
那么应用层代表什么,就是代表系统;所以, 我们要明白人与系统的关系;人使用系统,系统提供外部功能,系统内部有领域模型;

在分层架构中,我们将领域模型和业务逻辑分离出来,并减少对基础设施、用户界面甚至应用层逻辑的依赖,因为它们不属于业务逻。将一个复杂的系统分为不同的层,每层都应该具有良好的内聚性,并且只依赖于比其自身更低的层。   --Evans

由于DDD分层架构这种向下依赖的模式使得整个框架对于Infrastructure层过度依赖,这不是我们所期望的,我们希望能在Application和Domain层更多的决定领域的功能和流程。所以根据DIP(依赖倒置)原则, 高层模块和低层模块都应该依赖于抽象,为了让模型的逻辑定义更为内聚,我们可以把抽象放在上层(Application、Domain),Infrastructure提供实现。

例如在现在的项目中,我们在Domain层中定义IRepository、ISender、IListener等行为接口,在Frameworks and Drivers层(The Clean Architecture)实现接口,通过注入的方式提供给Domain层。这样能够使依赖的核心层更加内聚,同时对外层的扩展预留出了足够的空间。

(同样的操作我们还尝试的用在对领域层中,将领域对象抽象化,使领域服务依赖于领域对象的抽象,将真正的的领域对象交给更外层去实现,来满足我们架构对于相似业务的扩张能力。但这种做法是极具风险的,不同业务的差异化和领域对象抽象的能力都是极难把握的。总的来说,我们所期望的方向是:1.抽象能够涵盖所有业务。2.业务的差异化只在抽象的实现层被应用。只有满足以上2点,我们的想法才有可能被实现。)

Any problem in computer science can be solved with another layer of indirection. But that usually will create another problem.    --David Wheeler


聊完了高低层模块的依赖,我们再来聊下同层之间的依赖。

分层架构的一个重要原则是每层只能与位于其下方的层发生耦合,有些书中还有严格分层架构和松散分层架构两种区分。两者的区别在于能否对非直接下层发生耦合。但有一点是相同的,同分层之间的耦合都是不被允许的。但问题是有些时候,实际情况使它并不是容易被实现的。
例如在我们的项目中,为了满足高吞吐的业务需求,我们选用了Actor模型和AKKA多线程框架来构建我们的架构。这种响应式的异步模式上层无法及时获得下层的反馈,这使上层对下层的协调、分配变得异常艰难。所以我们需要在不同的分层中都创建Actor进行交互,通过对sender的回件来进行解耦。(TODO:图)

Actors and DDD: A Perfect Match

To quote Alan Kay, "The Actor model retained more of what I thought were the good features of the object idea."
 Also from Kay: "The big idea is messaging." 
In creating a ubiquitous language, developers can focus on the actors as being the objects or the components, the elements of the domain model, and the messages sent between them.

One reactive service is no service; reactive services come in systems. 
And so what developers are ultimately trying to accomplish is to build full systems, not just single services.
By emitting domain events to other bounded context or microservices, developers can accomplish this more easily.

Actor模型对于DDD的使用还是有很多帮助的,他们都有相同的对象理念,同时,这种响应式架构使领域事件到其他的边界上下文或微服务变得更容易。


The Clean Architecture

经过一些分层、抽象,The Clean Architecture是我们项目期望的目标。
关于整洁架构...只能先待续了...
The Clean Code Blog by Robert C. Martin (Uncle Bob)

2.领域建模

领域建模是整个DDD中比较核心的步骤,大部分DDD的工程都是基于领域建模展开的。这里同样不再对实体,值对象等做过多的名词解释,只来记录一次领域建模的过程。


DDD领域建模过程 .png

这是一个关于交易平台报价行情系统的部分需求:
1.交易员提交报价 2.交易员选择[群组]提交报价
3.交易核心处理报价 4.交易核心下发[报价报告] 5.交易核心下发[报价行情消息]
6.行情系统订阅交易核心相关消息
7.行情系统收到[全市场][报价报告],开始计算报价行情:
a.根据[报价报告][计算]出[公有报价行情]。b.根据[公有报价行情][聚合]出[公有聚合报价行情]。c.根据[公有报价行情]为[机构]生成[私有报价行情]。d.根据[私有报价行情][聚合]出[私有聚合报价行情]。
8.行情系统收到[群组][报价报告],开始计算报价行情:
a.根据[报价报告]为[群组]包含的[机构][计算]出[私有报价行情]。b.根据[私有报价行情][聚合]出[私有聚合报价行情]。
9.行情系统收到[公有报价行情消息],开始计算报价行情:
a.将[公有报价行情消息][转化]成[公有报价行情]。b.根据[公有报价行情][聚合]出[公有聚合报价行情]。
10.行情系统收到[私有报价行情消息],开始计算报价行情:
a.将[私有报价行情消息][转化]成[私有报价行情]。b.根据[私有报价行情][聚合]出[私有聚合报价行情]。

第一步:分析需求,定义关键类,描述业务流程
1.从需求中抽象出我们所关心的类
Book:报价簿,报价行情形象话的表现
Page:报价簿中记录内容的抽象类
Institution:.........

2.建立类的大致内容、行为和关系


报价行情类图.png

上图只是对类的简单定义,项目中最后落地一定不可能如此简单,需要进行反复的细化工作。

3.描述大致的业务活动图


报价行情计算流程活动图.png

第二步:划分主要构成
我们必须在这步中识别出模型中的实体、值对象,定义出核心域、子域并理清关系,划分应用层和服务层,确定限界上下文边界。
本需求寻找核心有两种方向,一种以行情计算和聚合为核心,机构、群组等作为独立子域;一种以行情拥有对象(全市场、机构)为核心,行情计算、聚合作为支撑子域;第一种偏向横向分层,第二种偏向纵向分层。按实际情况,我们选择了第二种方式,围绕Owner(全市场和机构)展开它们的行情计算、价格预警、价格变化、单机构等等。同时,上面的类图、流程图都需要调整,使依赖尽量向核心(Owner)偏移。

核心域:Owner。生成报价行情的对象,报价行情的所有者。
子域:报价簿子域、资产子域、群组子域、预警子域、监控子域、报价明细子域。
实体:机构、群组、全市场、报价单、报价簿、报价明细、资产信息。
值对象:报价簿规则、报价簿ID。
领域服务:机构服务、群组服务、行情计算服务、行情聚合服务、报价预处理服务、仓库服务。

当领域中的某个操作过程或转换过程不是实体或值对象的职责时,我们便应该将该操作放在一个单独的接口中,即领域服务。请确保该服务和通用语言时一致的;并且保证它是无状态的。

限界上下文

一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以,只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。

组织架构决和整体架构定了行情系统在一个限界上下文中。

第三步:聚合
根据前面的识别出来的各类对象初步构造出模型。

报价行情构建模型.png
第四步:细化模型
反复考虑业务,基于性能、扩展等方面因素,进行模型的细化工作,反复迭代。
报价行情类图 -2.png

3.CQRS和EDA


DDD的设计落地

待续....



Ticket

DDD + Actor的一些思考

Actor是Actor.class模型的核心,怎样在DDD的架构中使用Actor?

Actor的能力和Service相似,服务是无状态的,但状态却是Actor模型的特点。一个无状态的领域服务一般使用单例模式,而当一个类有了状态,就好像一个物体有了生命,“我是谁?我在哪?”这些也都成了它所需要关心的问题。我们需要维护服务列表、制定路由规则、实现服务发现功能...写到这,我们可以发现这些和微服务的服务治理非常相似。所以,我们可以建立我们的“注册中心”封装select,create,actorof等Akka的操作来实现服务发现,还能在里面提供限流、熔断、合并等等扩展。

Bad programmers worry about the code. Good programmers worry about data structures and their relationships.    --Linus Torvalds


面向对象
DDD是一种方法论,它的本质还是面向对象的思想。DDD在OOAD的基础上提炼演进出了一套架构设计理论。帮助我们,使我们能更容易的以面对对象的思想来设计工程。DDD的设计也需要遵循OOAD的SOLID原则。

有之以为利,无之以为用。

可能几天或者几周后,看现在的这篇博文就像一坨屎一样

参考资料

Effective Aggregate Design by Vaughn Vernon
The Clean Code Blog by Robert C. Martin (Uncle Bob)
Reactive-Systems-Akka-Actors-DomainDrivenDesign
复杂度应对之道 - COLA应用架构

上一篇 下一篇

猜你喜欢

热点阅读