领域、子域、限界上下文
DDD(Domain-Drive Design)的概念或者说业界的声音其实可以追溯到几十年前了。最近开始想要系统得整理一下DDD的一些东西。这一篇是一个简单的引子,也是mark一下自己接触到的概念和理解。
对于领域的概念其实很好理解,就如字面意思一样,比如出版书籍领域,广告设计领域。圈定了一定的范畴,并且在这个范畴内,所有团队成员对于某一概念的理解是一致的。比如书籍就是我们需要出版的书籍,而不是比如出版社给员工提供的什么季度奖励书籍。也许这里的例子还是有点偏差,但是当具体设计系统的时候就会去思考真正的业务边界。
我理解的领域驱动设计不是一种设计风格,也不是一种架构模型。而是一种思考方式。在考虑一个项目、需求的时候不会先去思考需要怎样的数据存储结构以及会有哪些行为和操作需要实现。而是去思考业务中一些重要的核心界限以及概念。最重要的就是定义通用语言。如何定义一个限界上下文的通用语言,来保证在具体的系统设计中,也即一个限界上下文中是一个唯一概念是非常重要的。限界上下文包含了模块(领域模型)以及上下文,所以它也是一个显示边界。
限界上下文中的术语一定准确反映通用语言限界上下文可以是我们的一个系统。比如最初,我们要给一个图书馆客户做一个系统,包含了图书馆的书籍管理,入库、借出。然后图书馆又提出,比如某些书籍是专门卖的,然后某些书籍是专门买进来奖励一些会员的奖品。然后我们需要确定,“书”这个概念,是不是已经引起了歧义。当我们的开发人员说出书这个词的时候,可能有的人理解是需要借的书,有的人理解是回馈客户的奖品。这个时候就出现了对于“书”的理解不一致,也就违反了领域模型的通用性。我们的系统边界,应该是完整的表达一个领域模型,或者说是通用语言。这就像音乐家创作的交响乐谱一样,多一个音符不多,少一个音符不少,刚刚好好,完整而优美得可以演奏出来一篇华丽的作品。所以系统设计也是一样,我们所说的领域专家其实就是具备能细致得划分领域边界,以及从复杂的业务中抽象出来通用语言的人。所以刚才的图书馆的例子,在落地到系统中的时候,就需要考虑系统划分了。出售书的系统中的书,不会使用租书系统中的书的实体来处理自己的领域事件。
在限界上下文定义好之后,就可以划定核心域了。比如这里,图书馆的核心业务是租书来收取定期的阅读费用。那么租书上下文中的租书服务就是我们的核心域了。图书馆的场景中,还需要有一个独立的用户系统,来管理用户登录和用户基本信息。租书上下文,售书上下文都会需要调用用户管理上下文。用户管理上下文就是一个通用子域。当然还有其他子域,有些子域可能共享一些状态,是一种协作上下文。
上下文映射
当我们需要设计多个上下文来完成需求的时候,就会要考虑上下文之间是否存在着一些联系。就如上述,用户管理上下文就是一个上游系统,成为其他系统的支撑子域。当我们需要在租书系统中需要用户信息的时候可以有几种做法:
- 共享内核方式:对于模型和代码的共享,但是这需要团队之间协作紧密,始终保持通用语言的一致性
- 客户-供应关系:上游团队的开发需独立于下游,并且也要尽量估计下游团队是需求和时间。
- 防腐层(Anticorruption Layer): 下游系统对上游模型进行翻译。防腐层的接口用来和其他系统交互,在防腐层内部,来实现模型概念的转换。
- 开放主机服务(Open Host Service):定义协议,让其他系统通过协议访问系统服务。
- 发布语言(Published Language):在两个限界上下文之间发布一个公用语言应用于翻译模型中。
在实际的集成中我们可呢过需要结合多种映射方式。比如,如果使用分离内核(和共享内核相对的),可以在上游用户管理上下文中使用开放主机服务和发布语言,然后下游的借书上下文通过防腐层来进行模型的转换。这种上下文的映射保证了通用语言在所有模块的纯洁性。每个上下文的开发团队就只需要专注于自己上下文的领域和统一的语言环境中就可以了。
在实际的实现手段上可以有多种方式,比如开放主机服务,可以使用Rest的方式,或者RPC 机制,或者消息机制。发布语言可以使用XML、Json、protocal buffer等或者消息的形式。防腐层就是把外界的概念翻译成本上下文的对象。在开发过程中也可以用一些概念映射关系来表达通用语言。团队成员共享这些概念,并且时刻提醒自己所关注的领域范围。上游上下文需要把资源不可用显示暴露出来,下游上下文需要能正确处理上游的模型状态。在实施中可以用消息机制,或者做一些异步的探测依赖的上下文的可用状态等。
DDD架构
终于到了正文。后面按照几种比较普遍的分类陈述。
依赖倒置
现在主流的框架中,依赖倒置简言之就是依赖接口、抽象,而不是依赖具体的实现。这种分层的形式,在现在的系统架构中都普遍流行。Java语言的Spring就是一个典型的践行依赖倒置的框架。这种基于抽象的分层,能让应用服务和领域服务很好的解耦。但是有时候过度得设计分层也会倒置贫血模型。贫血模型和反模式这种话题也是若干年没有讨论出一个谁是谁非的话题,这里就不展开了。其实,只要是对领域模型有正确的抽象,能反应出一套统一的通用语言,再根据具体的业务时间去选择自己的架构方式就足够了。
所以这里希望在实践的过程中,需要坚持不要过度依赖应用服务,让应用服务的抽象承担了太多的领域服务职责就可以了。另外,应用服务和领域服务是两个完全不同的概念。应用服务可以和其他上下文进行服务输出、输入、可以处理事务和复杂的业务逻辑。但是领域服务是更加轻量级的,为应用服务提供领域操作的。如果将计算和验证结合在实体中就是充血模式,如果是需要多个领域聚合的就可以再抽象出来一层单独的领域服务层。当然领域服务也不一定要用依赖倒置,或者说,业务领域服务根本不需要接口,因为领域专属的东西,不希望向客户端泄露扩展方式。
六边形
六边形的架构是很适合和领域驱动结合的架构方式。六边形架构的简单架构图如下:
图片来自《实践领域驱动设计》。无论是SOA 或者是RESTful 都很适用六边形的架构。我觉得这里的六边形体现了一种平等性,就像蜂巢一样。每一个窝都是平等的,并且可以很好地交互。新增一个扩展的Client只要新增一个适配器就可以将client的输入转化成系统内部的API的参数模型。系统输出服务也是一样的。所以六便习惯的关键就是每个适配器。每个外界的类型都有一个适配器想对应。外界通过API和系统交互,可以使一个Http请求,也可以是消息机制。适配器将外界的请求或者内部的输出都通过API参数的形式来设计和交互。对于参数映射可以采用很多框架来帮助完成。六边形的内部就是领域模型的应用。通过适配器,能进行很好的系统间防腐,做到外界参数和领域模型的互相转换。Spring 框架对于Restful的参数映射和资源映射也有很好的支持。通过注解的方式,就可以完成适配器应该做的工作。具体的例子可以参看下Spring Annotation Based Controllers。
SOA
SOA 架构图如下:
SOA结构中,服务的边界是在六边形的外层(这里的六边形依然是描述了单个上下文的结构)。在设计SOA 架构的时候,不应该以REST 或者SOAP 或消息类型来决定上下文的大小。这样会导致多个小的限界上下文和领域模型。所以在设计服务导向的上下文的时候需要注意保证上下文所要表达的通用语言的领域模型。
RESTFul Http
RESTFul 的架构中,每个资源都有一个URI,通过资源的方式来向外界提供操作和访问入口。Restful是具有”Presentation”和”State”的。
- 展现的格式可以是xml、json或者html,或者二进制的数据。
- 无状态表示的是一种请求的独立性,提高可上下文的可伸缩性。
对于资源确定之后就可以确定操作接口(如Http的get、post、put、delete等)。但是在设计接口中不应该将领域模型暴露给外界,不能因为领域模型的改变导致系统接口的变化。所以Restful 可以结合六边形架构。用Spring的web.bind服务就可以很好地实现这个上下文边界的设计。
为了和领域驱动很好的结合,还可以和微服的架构思想一起考虑,如果每个上下文都是一个微服,可以独立部署。那么在每个领域的限界上下文之间可以建立一个统一的系统接口层。所有需要多个领域上下文的请求都可以通过这个系统接口层。将核心领域和各个协作上下文的领域解耦。这种是对于各个上下文没有互相依赖的情况下来说是一种很好的服务输出方式,并且还可以用很多异步、并发控制框架来提高请求处理的性能。
对于上下文之间依赖,其实除了RPC,也可以通过事件驱动或者还是用Restful的方式进行集成。但是无论以什么方式或者技术手段来实现,都要注意不要将外部的领域模型暴露给本地系统,或者把本地限界上下文的领域模型暴露给外部。具体的处理方式,依据不同的架构会有不同的实现。如果是Restful 的方式,可以通过一个适配器(Adapter)来实现http 请求的转发,和对本地模型(DTO)映射的处理。然后将模型转化进一步交给翻译器(Translator),翻译器负责将远程对象(其他限界上下文的DTO,而非领域模型)转化为本地DTO,这个过程可以用Spring的RestTemplate(也可以进一步对于template封装一层本地服务的Facade)来帮助对HttpClient 的封装和JSON数据进行处理。
CQRS
CQRS 是命令查询的责任分离Command Query Responsibility Segregation的简称。其实可以简单理解为读(Query 请求)写(Commend请求)服务的分离。CQRS可以很好的解决复杂的界面显示的问题。一个接口或者是获取参数处理命令的,或者是返回数据的。这样的分离可以在读写两个层次上分别抽取出不同的系统服务。C和Q的数据可以通过领域事件的方式进行同步。CQRS一篇很好的总结可以参考一下CQRS架构简介
事件驱动
Event-Driven Architecture(EDA) 通过消息机制可以很好地完成上下文之间的解耦。当然在选择用消息集成的时候,对于可靠性和实时性上的要求需要做好权衡。事件驱动可以结合在六边形的架构和Restful架构中。作为一种辅助的解耦方式。本地的消息可以通过guava的Eventbus,集群的可以通过RabbitMQ来实现。
对于事件源来说,事件发布需要对于领域对象的修改进行跟踪,聚合上的每一次操作都有一个领域事件发布出去,每一个领域事件都需要被保存到一个时间存储中,这样可以保证对于事件发布前后的各个状态都能回溯。在实现最终一致性上,消息机制往往需要更加复杂的处理。订阅方也即Observer中的观察者需要对时间进行存储。当客户端需要查找聚合实例的时候,通过资源库再向事件存储中查找最终需要的聚合实例。当聚合实例发生变化的时候再执行实践的发布。围绕一个聚合的实例就形成了一个生产和消费的闭环。整个事件机制中还要考虑重发机制以及超时时间。订阅方需要幂等得处理事件,并且再状态不一致的时候,可以进行失败补偿。如果允许失败,那就可以直接采用工作流的方式。在具体的实施中可以有很多方式,都需要根据实际的场景和投入产出做具体的衡量。
数据网织
DataFabric 主要是在大数据处理上的一种架构方案,处理DB 性能瓶颈的时候可以将领域模型以序列化的方式放到缓存中,具体的保存方式可以是文本、json格式,也可以是二进制数据。可以通过很多Nosql技术来实现,GemFire、Coherence、redis、Mongo等。
结语
以上的所有架构都可以通过DDD的思想进行实现,在实际的系统架构上,肯定也不止是只应用其中的一种。六边形一般都可以结合Restful或者事件驱动。当然无论采用什么架构或者技术手段,回归到领域驱动的核心就是对于通用语言的定义。限界上下文要保证自己的纯粹性,尽量减少上下文间的遵奉关系或者共享内核。尽量保证系统的自治性,对其他系统的无感知性,不要将系统内部的领域模型暴露给客户端。后面会继续抽空对DDD的实施进行详细的总结和整理。
相关推荐
系统分析与设计是软件开发过程中的关键阶段,它涉及到对现有系统的理解、需求的收集与分析、系统的规划以及设计。本课程笔记主要涵盖了这一领域的...这份“系统分析与设计——课程笔记”将是你探索这一领域的宝贵资源。
【深度学习】是当前教育领域关注的热点,它强调学习者积极主动的探究与思考,旨在培养学生的高阶思维能力。在【小学数学】教学中,深度学习的引入至关重要。教师需要通过【创新教学设计】,激发学生的【自主探究学习...
"安卓Andriod源码——MyWorkText.zip"这个压缩包很可能包含了开发者在研究Android系统或开发应用时所整理的一些文本资料,可能涵盖了从操作系统内核到应用程序框架的各个方面。让我们详细探讨一下Android源码中的...
《决胜B端》概念画布是一份详细梳理B端产品设计和管理的文档,由VIPKID产品总监杨堃撰写,旨在帮助企业数字化转型、应用架构设计、业务中台建设等多个领域提供理论支持和实践指导。这份文档以统一的视图展示B端产品...
在数据治理领域,数据仓库的概念被频繁提及。数据仓库是用于报告和数据分析的数据库系统,它可以对大量数据进行集成和整理,以便进行决策支持。它的建立依赖于数据分层的概念,这通常涉及原始数据层、整合层、服务层...
因此,在提取过程中还利用了领域专业知识来对提取到的大量信息进行分类和整理。Software Landscape可视化工具在此过程中起到了辅助作用,帮助研究人员迭代地查看和优化分类结果。 #### 工具的局限性与改进措施 ...
研究通过构建操作定义和结构模型,结合教育学和心理学领域的相关研究成果,设计了问卷并采取五分制评分制进行测量。问卷中包含一级指标、二级指标,甚至三级指标,如网络信息素养维度包括学习资源的检索与筛选、分析...
### 数据挖掘:概念与技术(中文版) #### 一、数据挖掘概述 **1.1 什么激发数据挖掘?为什么它是重要的?** 随着信息技术的发展,企业和组织积累了大量的数据。然而,这些海量数据的价值并未得到充分利用。数据...
《汇编语言精粹——深度探索8大CHM文件》 汇编语言,作为计算机科学的基础,是直接与机器硬件对话的语言,对于理解和优化程序性能至关重要。本资源库包含了精心整理的8个CHM(Compiled Help Manual)文件,旨在提供...
- **简介**:本书是C++领域的一本经典之作,对C++的设计哲学进行了深入探讨。 - **知识点**: - C++面向对象编程的基本原则 - 设计模式的应用 - 通用编程与模板元编程 - 内存管理的最佳实践 - 异常安全性和资源...
1. **B2B2C基础功能点整理——商家端.jpeg**: 这张图片可能展示了B2B2C电商平台商家端的核心功能,如商品管理、订单处理、库存控制、支付集成、客户服务等。了解这些功能有助于理解商家如何在平台上操作,并为用户...
- 安全领域设计模式。 - 主体、会话管理。 - **MySQL高级视频** - SQL优化技巧。 - 存储引擎深入理解。 - 复制与分区策略。 - **MyBatis视频** - MyBatis框架原理。 - 映射文件详解。 - 动态SQL使用。 - ...
- 设计合理的软件架构对于提高系统的可扩展性和可维护性至关重要。包括模块化设计、分层设计等内容。 - **知识点三:内存操作** - 内存管理是嵌入式编程的基础之一。了解静态内存分配、动态内存分配的区别,以及...
【标题】"大富翁论坛2008离线资料" 涵盖了C++和Delphi编程领域的技术问答,这些资源对于深入理解和实践这两种编程语言至关重要。大富翁论坛,作为一个专业的技术交流平台,汇聚了众多IT爱好者和专家,他们在2008年的...