应用程序
领域模型通常位于程序的中心位置,应用程序通过用户界面向外展示领域模型的概念,并且允许用户在模型上执行各种操作。用户界面使用应用服务来协调用例任务,管理事务,并执行一些必要的安全授权
用户界面通过应用服务实现业务功能,应用服务把操作委托给领域模型(实体、值对像、聚合)、领域服务(可能不存在)、资源库。领域模型、领域服务、资源库是领域要素,是通用语言的表达,是接口定义。而真正的技术实现都由基础设施实现,基础设施屏蔽领域概念与技术实现,基础设施直接跟技术组件打交道,比如存储的DB、消息中间件等,领域模型(包括领域服务和资源库)是不直接跟技术组件打交道的。领域服务和资源库,会使用和组合领域模型(主要是聚合),分别完成业务逻辑和聚合存取。
用户界面
渲染领域对象
很多时候,除了操作所需数据之外,还会向用户界面提供一些额外的数据,因为这些数据可以对用户操作起到作用,这些额外数据还会包含一些选项数据.因此用户界面通常都需要渲染多个聚合
渲染数据传输对象
可以通过数据传输对象(Data Transfer Object,DTO)或领域负载对象(Domain Payload Object,DPO)来组装多个聚合实例,DTO直接拷贝属性,类似深拷贝,DPO只拷贝聚合实体引用,所以前者适合需要序列化的场景(如RPC),后者适合单虚拟机应用架构中。
使用调听者发布聚合的内部状态
应用服务
应用服务是领域模型的直接客户。应用服务应该做成很薄的一层,并且只使用它们来协调对模型的任务操作。
应用服务负责用例流的任务协调,每个用例流对应着一个应用服务方法。
应用服务管理着事务、安全和任务委派等操作,把操作委派给领域模型。
应用服务方法参数可以直接使用领域对象吗?建议不使用,入参使用命令对象,命令对象属性使用基本数据类型,输出使用DTO。虽然会增加很多对象的生成和释放消耗。
应用服务可以使用独立接口,也可以把接口和实现定义在一个类中。应用服务一般不需要使用基础设施来实现。
基础设施
基础设施就是为领域模型(包括领域服务和资源库)提供技术实现。如前面应用程序定义的图形,基础设施实现了领域服务、资源库的接口乃至用户界面,直接于技术组件打交道。
基础设施的实现可以依赖注入或服务工厂来完成接口实现的查找。
多个限界上下文组合
图中 需要将多个模型组合成为一个单一的展现 三个上下文都属于外部模型
多个限界上下文一节的问题是:UI需要组合多个模型,而三个模型位于三个限界上下文。如果使用一个应用服务来组合多个模型,因为是集成多个限界上下文的,所以这时候应用服务其实需要内建一个防腐层,并且“映射”出新的领域模型,这些新领域模型更多是运载数据属性的需要,所以容易产生贫血领域对象。如果是创建一些新的、清晰的限界上下文?这也增加不少复杂度,特别是会导致用户接口层对多个应用层的依赖。那到底该选择哪种方式呢?书中也没给出答案,只能根据自己的实际情况做决定。
在微服务的思路下,服务进程拆得很细,可能一个聚合或一个资源库实现就会独立成一个服务进程,如果这时候把一个服务进程都当成一个上下文,那会使得应用程序的领域概念很复杂,所以下面提出一个思路,限界上下文是按领域来划分按通用语言的表达,而不是服务进程:多服务进程的上下文左图中的每一个非内嵌框图都是代表一个独立服务进程的微服务
实战
知识总结
- 界限上下文:代表一个系统、一个应用程序或者一种业务服务。限界上下文所包含的领域模型概念应该恰如其分,不多也不少。
- 通用语言:作用于某个“限界上下文”,在一个特定的限界上下文中只使用一套通用语言,并且保证它的清晰性(避免一个概念在同一个界限上下文中的二义性)和简洁性。
举个例子:像京东和天猫这样的B2C系统中会用到系统的人有2种,买家和卖家,对于系统来说都可以称为用户,但是这样破坏了清晰性的特点。如果使用一个类似Type的枚举来区分,破坏了简洁性。所以对于这种场景,就应该直接设计2个对象:买家和卖家。
- 领域:从大了看,领域代表整个公司的运作一切。从小了看,是每个组织运作中的一切。所以领域的概念必然与公司的组织架构所承担的职责有一定的关系。
- 子域:一个领域内可以包含1个或者多个子域。理论上一个子域对应一个限界上下文是最优也是最理想的情况,但是有时又要考虑到业务关联度需要做出权衡。子域又分核心域、支撑子域、通用子域。
- 核心域:它是整个业务领域的一部分,也是业务成功的主要促成因素。从战略层面上讲,企业应该在核心域上胜人一筹。我们应该给予核心域最高的优先级、最资深的领域专家和最优秀的开发团队。在实施DDD的过程中将主要关注核心域。
- 支撑子域:对应着业务的某些重要方面,但却不是核心,那么它便是一个支撑子域。
- 通用子域:某个支撑子域的运用范围是整个系统,那么这个子域便是通用子域。
- 上下文映射图:由多个界限上下文和子域组成的表示当前单个领域或者多个领域之间的集成关系图。
DDD的核心。
这里再把这3个概念重新梳理一下。
Entity(实体): 每个实体是唯一的,并且可以相当长的一段时间内持续地变化。我们可以对实体做多次修改,故一个实体对象可能和它先前的状态大不相同。但是,由于它们拥有相同的身份标识,他们依然是同一个实体。
ValueObject(值对象):值对象用于度量和描述事物,当你只关心某个对象的属性时,该对象便可作为一个值对象。实体与值对象的区别在于唯一的身份标识和可变性。
Aggregate(聚合):聚合类是实体的升级,是由一组与生俱来就密切相关实体和值对象组合而成的,这整个组合的最上层实体就是聚合。
操作层面点
操作层面就是《实现领域驱动设计》里提到的9种组织模式和集成模式。
①合作关系(Partnership):如果2个限界上下文的团队要么一起成功,要么一起失败,此时就是这种关系。应该为相互关联的软件功能制定好计划表,这样可以确保这些功能在同一个发布中完成。
②共享内核(Shared Kernel):对模型和代码的共享将产生一种紧密的依赖性,对于设计来说,这种依赖性可好可坏。我们需要为共享的部分模型指定一个显式边界,并保持共享内核的小型化。共享内核具有特殊的状态,在没有与另一个团队协商的情况下,这种状态是不能改变的。我们应该引入一种持续集成过程来保证共享内核与通用语言的一致性。【简单的说就是数据库共享】
③客户方——供应方(Customer-Supplier Development):当2个团队处于一种上游——下游关系时,上游团队可能独立于下游团队完成开发,此时下游团队的开发可能会受到很大的影响。因此,在上游团队的计划中,我们应该顾及到下游团队的需求。
④遵奉者(Conformist):在存在上游——下游关系的2个团队中,如果上游团队已经没有动力提供下游团队之需,下游团队便孤军无助了。处于利他主义,上游团队可能向下游团队做出种种承诺,但是有很大的可能是:这些承诺是无法实现的。下游团队只能盲目地使用上游团队模型。
⑤防腐层(Anticorruption Layer):在集成2个设计良好的限界上下文时,翻译层可能很简单,甚至可以很优雅的实现。但是,当共享内核,合作关系或客户方——供应方关系无法顺利实现时,此时的翻译将变得复杂。对于下游客户来说,你需要根据自己的领域模型创建一个单独的层,该层作为上游系统的委派向你的系统提供功能。防腐层通过已有的接口与其他系统交互,而其他系统只需要做很小的修改,甚至无需修改。在防腐层内部,它在你自己的模型和他方模型之间进行翻译转换。【为每个防腐层定义相应的领域服务】
⑥开放主机服务(Open Host Service):定义一种协议,让你的子系统通过该协议来访问你的服务。并且需要将协议公开。
⑦发布语言(Published Language):在2个限界上下文之间翻译模型需要一种公用的语言。此时你应该使用一种发布出来的共享语言来完成集成交流。发布语言通常与开放主机服务一起使用。
⑧另谋他路(SeparateWay):在确定需求时,我们应该做到坚持彻底。如果2套功能没有显著的关系,那么它们是可以被完全解耦的。集成总是昂贵的,有时带给你的好处也不大。声明2个限界上下文之间不存在任何关系,这样使得开发者去另外寻找简单的、专门的方法来解决问题。
⑨大泥球(Big Ball of Mud):当我们检查已有系统时,经常会发现系统中存在混杂在一起的模型,它们之间的边界是非常模糊的。此时你应该为整个系统绘制一个边界,然后将其归纳在大泥球范围之列。在这个边界内,不要试图使用复杂的建模手段来化解问题。同时,这样的系统有可能会向其他系统蔓延,应该对此保持警觉
以soa系统的简单商场系统分析(毕设)
源码:
- 作为一个商场系统,最重要的就是销售 也就是卖越多钱越好
所以销售子域就是最核心的 也叫作核心域
- 除了销售核心域之外,还有诸如订单 用户 支付之类的子域,跟销售子域没有交集
- 对于其他子域比如订单子域,可能只有一个订单上下文
- 对于销售子域而言 可能里面包括购买 利润等上下文
- 对于这些上下文而言 他们有如上所说的各种关系
- 对于其他子域而言 订单是最基础的 也就是通用子域, 那么其他就是支撑 也就是上下游关系。比如 销售必须依托订单才能完成 所以销售是上游u 订单是下游p
基础架构
在学习ddd之前 我们一直学习的都是mvc,而这里我们学习的事ddd
Application:这层的职责是对接收到的数据做一些非业务性验证,事务的控制,最重要的是协调多个聚合之间的操作。这里应该可以清晰的表达出整个操作所做的事情,并且与通用语言是一致的。
Domain:这一层是DDD设计的核心,这里不但需要精确合理的表达出通用语言的每一个细节,另外如何把对象合理的定义为聚合、实体、值对象也是重中之重。这里不但关系着整个项目的复杂度,也是战术建模的体现,任何的一行代码都是对业务的准确定义,应该是恰到好处。一个清晰简洁的战术建模才可以应对后续的快速变化。
Infrastructure:这里是辅助性的一层,也是整个项目的基础。好比这里存放着一砖一瓦,最终建造什么模样的高楼在于用它的地方。主要包括,仓储的实现(我们存放数据的地方)、一些通用的支撑性类库。
六边形架构
从上图中看出为了保证领域模型所在的应用程序的干净简洁和自治性,各种适配器作为"防腐层(在上篇中有提到)"在整个程序的最外层保护着当前的“界限上下文(在上篇中有提到)”不受外部入侵。
所以在我们的整个设计中需要注意对涉及到外部系统交互的地方的抽象,通过面向接口、依赖注入等方式做到外部的变化对自身系统的影响最小化。
初级架构
将后台分成controller,service,dao,domain四个层次
controller 负责处理web请求service 负责业务逻辑处理dao 负责与持久层的数据库打交道domain 领域实体
controller接收外部传过来的实体(也可能是dto),然后将实体交给对应的service 进行处理,
service 对domain进行处理后交给dao层进行持久化;
这里展示一个ddd落地的样子(参考搭建)
Mall.Application:按模块分别定义不同的ApplicationService来讲述每一个操作下的“故事”。
Mall.Application.DomainEventSubscribers:所有的领域事件订阅者。
Mall.Domain:这里存放着战术建模的结晶,Entity、Aggregate、ValueObject。(下面会具体讲述下)
Mall.Domain.Events:所有的领域事件,这层也可以合并到Domain中,给它新建一个文件夹。
Mall.Domain.IRepositories:所有的仓储(资源库)接口。类似于三层中的IDAL。
Mall.DomainService:领域服务,存放着那些不适合放在聚合/值对象上的无状态的操作方法,用于实现特定某个领域的任务。
Mall.Infrastructure:存放着一些通用类库
Mall.Infrastructure.Repositories:所有仓储(资源库)的实现。
Mall.Infrastructure.Translators:翻译层,也就是与外部系统沟通的桥梁,主要的职责就是做好“反腐层”的重任。
cqrs是什么 与ddd的关系
如上是一个普通的curd系统
这里基本上是围绕关系数据库构建而成的“创建、读取、更新、删除”系统(即CRUD系统)
此类系统在一些业务逻辑简单的项目中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能问题。
我们经常用到的解决方案就是对数据库进行读写分离。让主数据库处理事务性的增、删、改操作,让从数据库处理查询操作,然后主从数据库之间进行同步。
但是这只是从DB角度处理了读写分离,从业务或者系统层面上来说,读和写的逻辑仍然是存放在一起的,他们都是操作同一个实体对象。
cqrs
简单的说,CQRS(Command Query Responsibility Segration)就是一个系统,从架构上把 CRUD 系统拆分为两部分:命令(Command)处理和查询(Query)处理。其中命令处理包括增、删、改。
然后命令与查询两边可以用不同的架构实现,以实现CQ两端(即Command Side,简称C端;Query Side,简称Q端)的分别优化。
CQRS 实现方式
CQRS 可以有两种实现方式。
- CQ 两端数据库共享,只是在上层代码上分离。
这样做的好处是可以让我们的代码读写分离,更容易维护,而且不存在 CQ 两端的数据一致性问题,
因为是共享一个数据库的。这种架构是非常实用的(也就是我上面画的那种)
- CQ 两端不仅代码分离,数据库也分离,然后Q端数据由C端同步过来。
同步方式有两种:同步或异步,如果需要 CQ 两端的强一致性,则需要用同步;如果能接受 CQ 两端数据的最终一致性,则可以使用异步。
C端可以采用Event Sourcing(简称ES)模式,所有C端的最新数据全部用 Domain Event 表达即可
而要查询显示用的数据,则从Q端的 ReadDB(关系型数据库)查询即可(
cqrs与ddd的关联
https://blog.csdn.net/abchywabc/article/details/80879514