关于后端:何时使用领域驱动设计

8次阅读

共计 13715 个字符,预计需要花费 35 分钟才能阅读完成。

何时应用畛域驱动设计?其实当你的应用程序架构设计是面向业务的时候,你曾经开始应用畛域驱动设计了。畛域驱动设计既不是架构格调(Architecture Style),也不是架构模式(Architecture Pattern),它也不是一种软件开发方法论,所以,是否应该应用畛域驱动设计,以及什么时候应用畛域驱动设计,这个问题自身就比较复杂(或者说这并不是一个好问题)。或者,更准确的发问形式应该是:“我应该抉择什么样的架构格调来构建我的零碎?”。当初咱们先不急着答复这个问题,还是回到畛域驱动设计的话题上,来回顾一下畛域驱动设计里的基本概念。

畛域驱动设计
很多人都理解测试驱动开发(TDD)、性能驱动开发(FDD)、API 驱动开发(ADD)和行为驱动开发(BDD),那么什么又是畛域驱动设计(DDD)呢?DDD 的第三个 D 为什么是“设计”而不是“开发”呢?畛域驱动设计最开始提出来的目标是为了简化业务人员与开发团队之间的沟通,以保障开发进去的软件产品不仅可能很好地解决业务畛域问题并满足客户的需要,而且还可能简化或解决传统软件开发过程中遇到的各种问题(比方需要变更、横向或纵向扩展性差等等)。因而,通用语言(ubiquitous language)就是畛域驱动设计中最重要最外围的概念:它可能确保代码的组织形式可能间接反映业务模型和业务逻辑,并且在整个业务零碎中,对于同一个业务概念应用雷同的代码表述(比方银行零碎中的 Account 对象)。从通用语言的定义登程,畛域驱动设计对于业务领域建模提供了一些指引,具体表现为引入了实体(Entity)、值对象(Value Object)、服务(Service)、聚合(Aggregate)、聚合根(Aggregate Root)、工厂(Factory)和仓储(Repository)。这里我就不打算深刻探讨这些概念了,就简略回顾一下吧。

领域建模三剑客:实体、值对象和服务
在进行领域建模时,畛域驱动设计引入了三个概念:实体、值对象和服务。实体和值对象都可能反映真实世界中的一个业务概念,两者的区别是,实体通过特定的标识符(ID)来确定一个个体,而值对象则是通过对象自身各个字段的值来确定一个个体。例如,某班的学生信息,学生(Student)就是一个实体,在进行领域建模的时候,个别会应用学号作为学生的 ID,因为没有任何一个或者一组学生身上的属性可能惟一确定一个学生:姓名不行,出生日期不行,身份证号也不行(撇开有可能重号不说,用身份证号来标识学生会带来信息泄露问题);再比方学生的联系地址(Address)则是一个值对象,因为零碎能够通过国家、省份、城市、街道和门牌号这些值的组合来惟一确定一个地址。为实体设计一个正当的标识符(ID)策略,通常状况下并不是一件简略的事件:标识符须要具备全局惟一、生成高效、存储敌对、意义显明这些根本特质,所以,Guid 并不是一个很好的抉择:它全局惟一、生成高效,然而并非存储 / 索引敌对,而且是一串字符加数字和横杠,不代表任何意义。很多利用零碎会有专门的服务来产生满足条件的标识符,比方销售零碎很有可能会有独自的分布式服务来生成一个由订单日期、客户 ID、订单流水号以及校验码组成的一长串字符串来用作订单编号。总而言之,为畛域模型中的实体对象实现一个标识符的生成机制能够有很多种办法,这里也不进一步开展了,然而你会发现,畛域驱动设计在这里只通知你,实体须要一个 ID,如何实现?这不是畛域驱动设计的探讨领域,因而也就答复了下面“第三个 D 为什么是‘设计’而不是‘开发’”的问题。因为畛域模型中的对象都是对业务概念的实在反映,所以,对象不仅会有状态,而且还会有行为,应该尽可能地将业务行为设计到正当的畛域模型对象上,而不是将畛域模型对象全副都设计成 POCO/POJO,而后将所有业务行为都塞到 Transaction Script 里。例如:学生会有写作业的行为,因而,doHomeWork(Homework homework)办法就应该设计在“学生”实体上。然而,有些状况下,某些业务行为很难归结到某个实体或者值对象上,一个经典的例子就是银行业务里的转账(transfer)办法,它并不是某个银行账户(Account)的行为,可能是银行的行为,也可能是用户的行为,在这种状况下,畛域驱动设计引入了服务的概念:在服务上定义从畛域角度无奈归结到任何一种模型对象上的行为。由此可见,服务是领域建模中的一部分,也是畛域模型的重要组成部分。

生命周期双子星:工厂和仓储
有了畛域对象,天然就须要治理对象的生命周期,在介绍工厂和仓储之前,先看一下与畛域对象相干的两个抽象概念:聚合与聚合根。聚合是可能表白一个残缺的畛域概念(或者说业务概念)的实体和值对象的组合,如果用 UML 类图来示意聚合,应该抉择应用组合模式。不难理解,聚合里的所有实体和值对象都有雷同的生命周期,它们被同时创立,也被同时销毁。对于每一个聚合,必然有一个实体其自身就代表了整个聚合的业务意义,比方“销售订单”聚合能够由“销售订单”实体、“销售订单明细”实体以及“联系地址”值对象组成,而其中的“销售订单”实体就代表了整个聚合的业务意义,像这样的实体,咱们称之为聚合根。当然,有些聚合仅蕴含一个实体,而这个聚合的聚合根就是这个实体自身。所有与生命周期相干的操作都应该产生在聚合根上。在畛域驱动设计中,工厂负责创立聚合,而仓储负责聚合的长久化、激活以及销毁,这些操作都是利用在聚合根上。同样,畛域驱动设计并没有探讨工厂和仓储应该如何实现,然而基于它们自身的特点,在理论中咱们更多地会抉择一些创立型模式来实现工厂,而抉择一些数据长久化机制(比方数据库)来实现仓储。就仓储的实现而言,咱们基本上会联合底层的数据存储技术选型来决定仓储的设计,甚至会将其形象成仓储设计模式。在不同的架构格调下,仓储的职责也会有所不同:传统分层架构下,仓储是有查问职责的,因为它须要基于聚合根来重建整个聚合,然而,在基于事件的 CQRS 架构中,仓储的查问职责变得十分单薄,这是因为读写拆散造成的。以上基本上对畛域驱动设计的基础性内容进行了回顾,如果你的我的项目正在,或者将要遵循下面的这些概念和指引进行业务剖析与领域建模,或者在进行需要剖析的时候,你的团队也在不停地思考如何在软件中设计你所要面对的这些业务对象,并且在不停地梳理相干的畛域常识,那么祝贺你,你曾经步入了畛域驱动设计的正规。当然,在畛域模型建设的过程中,你会发现很多问题,比方你会发现,银行账户与互联网登录账户都叫“账户”,但它们却是齐全不同的货色;你甚至会发现,尽管都是“银行账户”,但在不同的场景下它所表述的意义齐全不同(例如用于领取的领取账户与用户的定期账户是两码事),对于这些问题,畛域驱动设计也提出了相应的解决方案,比方引入“界定上下文(Bounded Context)”的概念,而这一概念也刚好符合了目前最风行的软件架构格调:微服务架构格调,下文再深刻探讨。接下来你能够思考本文刚开始的问题:我应该抉择什么样的架构格调来构建我的零碎。

软件系统架构格调
通常状况下,咱们会抉择一种软件架构格调来实现软件系统,而在开发的过程中,咱们还会利用很多开发模式并且引入一些开发方法论,比方在模型长久化局部,咱们会抉择仓储模式,而在构建畛域对象模型时,又有可能用到访问者模式,咱们还会抉择应用麻利开发方法论来领导咱们的日常开发工作等等。由此可见,软件系统架构格调并非是一种模式,简略地说,架构格调决定了零碎将由哪些组件组成,以及这些组件之间的关系如何,而架构模式则表述了如何实现这些组件以及解决它们之间的关系。在《面向模式的软件体系结构(卷一):模式零碎》一书中,将软件设计模式分为三种:体系结构模式、设计模式以及习用法。体系结构模式也就是架构模式,常见的有黑板模式、分层模式、MVC、发布者 / 订阅者、Proactor/Reactor、命令查问职责拆散(CQRS)等等。这些模式的独特特点是,它们对软件系统的根本组织进行形容,这包含各种组件以及组件之间、组件与环境之间的互相关系的定义,并决定了软件系统设计与演进的准则。设计模式更多的是在组件外部,对于对象及其之间的关系以及它们之间的行为与合作提供肯定的设计准则,从而使得组件的设计满足面向对象的 SOLID 准则。习用法令是与特定编程语言相干的一种罕用模式,比方在 C#中,对于单例模式(Singleton)有它本人的独特的实现形式,这种形式依赖于 C# 中动态字段是线程平安的语言个性,而这种实现形式却并不能用在 C ++ 中。与架构模式相比,架构格调并不关怀真正的业务畛域是什么,以及软件系统须要解决什么样的业务问题。无论你是开发 ERP 零碎,还是开发购物网站,你都能够抉择微服务架构,只是不同畛域所须要的微服务不同罢了。常见的软件系统架构格调有:经典分层架构(N-Tier)、事件驱动架构(EDA)以及微服务架构(Microservices)。随着云计算的遍及和推动,也衍生出了一些与云计算、人工智能以及大数据处理相干的架构格调,比方基于微软 Azure 云平台的 Web-Queue-Worker 架构、Big data 架构以及 Big Compute 架构。那么,我到底应该抉择什么样的架构格调呢?在不同的架构格调下,畛域驱动设计又如何使用呢?上面就对比拟常见和风行的经典分层架构、事件驱动架构以及微服务架构做一些介绍。

经典分层架构(N-Tier Architecture)
这是一种为人熟知的架构格调,基本上所有开发人员都晓得,软件系统须要分层设计。比拟传统的常见的分层形式就是分三层:界面层、业务逻辑层以及数据拜访层,各层之间会有数据传输对象(DTO)实现数据交互,以此隔离不同层外部的实现细节。畛域驱动设计则将利用零碎分为四层:用户界面层、应用层、畛域层和基础设施层:

用户界面层:这一层比拟好了解,就是间接面向用户的这一层,比方前端单页面利用或者基于 MVC 框架开发的前端利用。如果你的利用零碎仅提供 API,那么 API 这一层也属于用户界面层
应用层:依据畛域驱动设计的形容,应用层是很薄的一层,它次要负责协调上层的执行工作,并隔离畛域层与用户界面层。如果你抉择采纳经典分层架构,并开始实际畛域驱动设计,那么在应用层你能够实现一些诸如 Coordinator 或者 Workflow 这样的组件,它们不参加任何畛域或者业务相干的操作,仅仅负责协调。最常见的一种实现就是在应用层引入事务处理,有时候甚至还会跨资源实现分布式事务
畛域层:你的畛域模型所波及的所有对象都会呈现在这一层,如上文所述,畛域层对象须要尽量避免贫血模型,开发团队与领域专家一起实现畛域层的设计与开发工作
基础设施层:所有与技术细节相干的基础设施组件都属于这一层,因而,零碎所依赖的数据库存储以及内部服务,都属于基础设施层。此外还有面向切面(Aspect-Oriented)的组件,比方异样解决模块、缓存模块、平安模块等等,也都属于基础设施层
在早 10 年以前,微软的西班牙团队在 Github 上开源了一套残缺的基于畛域驱动设计实际的分层架构案例:Microsoft NLayerApp,然而十分惋惜的是,这个我的项目目前曾经找不到了,但我依然保留了一些材料,下图就是这个 NLayerApp 的架构图:

上图中红色局部代表的是用户界面层;天蓝色局部代表的是应用层;蓝色局部代表的是畛域层;而绿色局部则代表基础设施层,整个软件的架构是十分清晰的,这就是一个规范的合乎畛域驱动设计思维的分层架构。在这个案例中,设计者引入了很多体系结构模式,比方畛域层的仓储(Repository)模式和规约(Specification)模式、展示层(用户界面层)的 MVC 模式等,还引入了一些开发方法论,比方面向切面的编程(Aspect Oriented Programming, AOP)。从整个构造上看,它自身也就是一种架构模式:如果你抉择分层架构格调,那么你就能够思考应用上图中相似的构造来开发你的软件系统,比方引入畛域模型、仓储模式、查问规约、工作流、MVC 等等。当然,分层架构并不一定非要按上图中的这样去设计,你能够抛开畛域驱动设计思维,本人依据我的项目或者产品的特点来实现分层,这是齐全没有问题的,只有可能在肯定的老本下,满足业务畛域的需要就能够了。在分层架构中应用领域驱动设计也是须要通过严格斟酌和思考的,比方在上图中,仓储模式的实现,为什么 Repository Contracts(也就是咱们平时所说的仓储接口)是设计在畛域层,而 Repository Implementations 则是放在根底结构层?起因很简略:一方面,依据上文所述,仓储的概念就是治理畛域聚合的生命周期,因而它是一个畛域模型中的概念,而另一方面,在理论实现当中,仓储是须要间接拜访数据长久化机制的,而数据长久化机制又是与基础设施相干的组件,所以,仓储的实现局部是须要设计在基础设施层的。于是,畛域模型层以及其下层的组件通过仓储接口拜访仓储实例,而仓储实例则是在应用程序启动的时候通过依赖注入的模式提供。Microsoft NLayer App 曾经不存在了,不过你也能够参考我在很早以前写的一个合乎畛域驱动设计的多层分布式架构案例:Byteart Retail,尽管目前看起来它所应用的技术绝对比拟老,然而整个零碎的架构和各层组织构造还是十分清晰的,基本上能够比对上图的架构去浏览理解。至此,你应该对畛域驱动设计是如何在分层架构中使用曾经有了肯定的理解,你会发现,即便是在绝对简略的分层架构中,要正确使用畛域驱动设计的思维也不是一件容易的事件。你能够退而求其次,依然抉择应用分层架构,在对业务畛域、研发团队、我的项目流程、市场反馈等等各方面进行了综合评估之后,如果你依然抉择了分层架构,而并不感觉它是一种不那么风行的架构格调的话,那么祝贺你,你或者做出了一个正确的抉择。总结起来,分层架构是绝对比较简单比拟容易了解的一种架构格调,实际技术也都十分成熟,有极为成熟的案例能够参考,如果你的软件系统业务自身并不简单,而且在未来的一段时间内业务扩大不会特地大(比方为学校图书馆开发一套图书馆管理系统),而你的团队对于分层架构也更为相熟的话,它确实是一个不错的抉择。然而,如果你的软件所要解决的业务比较复杂,而且今后业务会一直扩大变大,那么宏大的业务体量将会使得你的业务逻辑层变得臃肿简单,从而引起零碎难以保护、代码构建工夫过长、组件关联盘根错节、零碎性能逐步升高等等一系列问题,在这种状况下,你或者更应该抉择微服务架构格调。但不管怎么选,由畛域驱动设计所领导的领域建模实际以及相干的体系结构模式,都能够应用在(或者不应用在)你所抉择的软件架构之中。分层架构大抵就介绍这么多吧,接下来介绍一下一种比拟风行的架构格调:事件驱动型架构。

事件驱动型架构(Event-Driven Architecture)
事件驱动型架构通过采纳一种发布者 - 订阅者(Publisher-Subscriber)或者事件流的模型,以异步的模式表白组件之间的关系。在这种架构中,事件产生方生成并公布事件到事件总线(Event Bus),而事件生产方则侦听事件总线并解决它所关怀的事件,事件能够被一个或多个消费者所订阅和生产。因而,在事件驱动型架构中,事件产生方并不依赖于事件生产方,事件生产方之间也没有依赖关系。通常状况下,如果你的软件系统须要执行一些比拟耗时的工作,而同时又要保证系统响应度的状况下,能够思考采纳事件驱动型架构。比方,IoT 零碎通常会采纳这种架构,因为数据采集与剖析都是比拟耗时的操作,客户端能够首先发动一个创立数据处理工作的操作,而后通过轮询的形式取得工作的执行状态。因为在这种架构中,各组件都是互相独立的,因而,这种架构具备很好的延展性(Scalability)和分布式部署的个性;然而,它也有一些实际上的难点,比方:如何确保事件可能被精确、稳固地散发;如何确保事件可能依照肯定的程序被生产方生产;如何确保事件仅被同一生产方生产一次等等。举个例子:在命令查问职责拆散(CQRS)体系结构模式的实际中,当一个聚合须要被创立的时候,比方当须要创立一个 Student 聚合时,从 Command 这一方可能会产生并公布两个事件:StudentCreatedEvent 和 StudentNameChangedEvent,别离示意有一个 Student 聚合曾经被创立,并批改了它的 Name 属性。那么对于事件的订阅方,必定是心愿首先解决 StudentCreatedEvent,而后解决 StudentNameChangedEvent,如果程序反了,那就不对了:Student 还没有被创立进去,又谈何批改它的 Name 属性呢?如果你的音讯订阅方只有一个实例在运行,你或者能够通过事件的工夫戳或者序列号来确定它们的程序,而后引入一些相似无限状态机(FSM)的机制来保障音讯的程序生产。但如果(其实是绝大多数状况下)你的音讯订阅方有多个实例同时运行,那么相似这样的问题就会变得更加简单。再比方,很多事件驱动零碎中,会通过引入成熟的第三方解决方案来确保事件散发的准确性,以保障当生产方没有确切给出一个信号的时候,事件始终都可能被保留在事件总线上以待下一次派发;而对于事件生产方,也会采纳一些幂等设计,来保障事件仅被无效解决一次。接下来咱们看一个案例:一个基于命令查问职责拆散(Command Query Responsibility Seggregation)体系结构模式所实现的分布式事件驱动型架构,在这个案例中,你能够理解到畛域驱动设计是如何领导其设计并被使用在 CQRS 体系结构模式当中。CQRS 体系结构模式最早是由畛域驱动设计先锋 Greg Young 提出,它的架构图大抵如下:

(上图来自 2018 年 1 月我在微软 MVP 论坛上的讲义,主题是《ASP.NET Core 下畛域驱动设计的实际》)

在 CQRS 中,所有的操作都是基于事件的,当客户端发动一个申请须要批改畛域对象中的某个属性时,客户端会将批改属性的命令音讯发送到零碎中,命令处理器接管到命令音讯之后,会依据聚合根的标识符(ID),从仓储中读取该聚合的所有事件,并依据这些事件重建聚合。在批改了属性之后,畛域模型会产生一个事件,而后将这个事件保留到仓储中,与此同时,该事件还会被派送到事件音讯总线。这种事件在 CQRS 模式中称为畛域事件(Domain Events),因为它产生在畛域层。接下来,事件处理器在收到属性批改的畛域事件后,会相应地更新查询数据库;抑或会触发外部的无限状态机,以便在某些状况下当相关联的畛域事件全副被接管之后,可能从新产生一条命令,对畛域模型进行进一步的批改(比方订单在收到用户的领取之后,状态由 WaitForPayment 改为 Paid)。这种读写拆散的架构隔离了畛域模型的批改局部与查问局部,使得它们可能以异构的平台和技术被开发和部署,甚至能够以不同的设计策略和资源分配对这两局部进行独立设计。此外,CQRS 模式存储了整个零碎从运行之初到以后的所有畛域事件,也就是说它记录了整个零碎从运行之初到以后所产生过的所有事件,这就使零碎具备回溯到任何一个状态点的能力,这种机制咱们通常称之为事件溯源(Event Sourcing)。从畛域驱动设计的角度,CQRS 模式中也蕴含畛域模型、仓储等概念,然而,实现形式与分层架构大不相同:

畛域模型中不蕴含规约(Specifications),因为“写”端不具备查问性能
畛域模型中聚合自身的行为(也就是办法)仅蕴含一个职责,就是派发畛域事件(Domain Events)。例如,上面就是批改 User 聚合的 Email 属性的样例代码,从代码上看,它仅仅是派发了一个事件:
而 User 聚合自身也是一个事件订阅者,因而,它在接管到了这个事件后,会更新本人的属性:
我置信你必定会有疑难:这不是多此一举么?在 ChangeEmail 办法中间接设置属性不就行了?然而,答案就是不行,因为当调用方通过 User ID 来向仓储读取 User 聚合的时候,仓储会从数据库中读出与这个 ID 相干的所有事件,而后逐个利用在 User 对象上,此时,下面的由 InlineEventHandler 所标识的 HandleChangeEmailEvent 事件处理办法就会被调用,从而实现对 Email 属性的设置。我置信你还会有疑难:仓储会读出所有的事件,而后逐个利用在 User 对象上?那如果与 User 对象相干的事件特地多,逐个利用这些事件岂不是会影响性能?在 CQRS 中,这一问题是通过快照解决的,基本思路就是在保留畛域事件的时候,每隔肯定数量的畛域事件对聚合做一次快照,比方每 1000 个畛域事件做一次快照,那么当咱们须要复原第 1001 个畛域事件时,只须要读出这个快照,而后利用剩下的那一个畛域事件即可,并不存在性能问题
畛域模型中实体对象的属性都是只读的,因为批改须要通过畛域事件来实现
仓储中不蕴含查询方法,因而,它仅有两个职责:保留聚合、依据聚合根的 ID 来读取聚合:
仓储所依赖的事件存储数据库中仅有一张数据表(或者说一种文档):畛域事件表,大抵蕴含这些信息:序列号、畛域事件所产生的对象类型、畛域事件所产生的对象 ID、畛域事件类型、畛域事件产生工夫以及畛域事件的具体内容
多年前我也基于 CQRS 体系结构模式做了一个绝对残缺的案例:WeText,代码齐全公开在 Github,虽说应用的技术可能有些过期,但整个架构是事件驱动型的,并在肯定水平上实现了 CQRS 体系结构模式以及畛域驱动设计中的基本要素。这个案例的架构图如下,供参考:

(图片起源:自己的开源我的项目 WeText,点击查看大图)

CQRS 模式的实现非常复杂,所以大多数状况下它只会被使用在某个界定上下文(Bounded Context)中,甚至大多数状况下都不会残缺地实现上图所述的整个构造。或者你的业务并不需要保留历史事件,那么你就没必要设计事件存储;或者你的客户端不心愿以异步的模式向零碎收回命令,那么你有可能就不须要命令音讯总线。目前在世界上确实是有残缺实现 CQRS 模式的事件驱动型软件我的项目,但却是百里挑一。同理,在事件驱动型架构中,并不一定须要采纳 CQRS 架构模式(应该说绝大多数状况下不须要),还是那句话,你应该依据我的项目自身的特点以及研发团队的状况来决定应用哪些架构模式来实现事件驱动型架构,有时候你可能只须要一个非常简单的设计就能满足要求。然而,如果你心愿在事件驱动型架构中实际畛域驱动设计,那么 CQRS 应该是你所须要理解并深刻学习的一种架构模式,它能更好地帮忙你了解畛域驱动设计,并在事件驱动型架构中更好地使用它。上面咱们再看看目前最为风行的架构格调:微服务架构。

微服务架构(Microservices Architecture)
在最开始着手软件系统的设计时,你或者不会抉择微服务架构,因为在那个时候,微服务架构并不能帮你解决眼前的设计问题。然而当你的业务畛域变得非常宏大,而分层架构无奈持续撑持你的软件系统时,你可能会思考采纳微服务架构。在微服务架构中,各应用服务之间相互独立,它们能够由不同团队采纳异构的平台和技术,以及应用不同的软件开发办法实现开发,这些服务能够应用不同的数据存储系统,甚至能够是一个仅进行数据实时处理而不存储任何数据的计算服务,微服务实例之间能够以同步或者异步的形式进行通信。不难看出,实际微服务架构的一个难点就是如何去协调各个服务之间的合作,例如如何在分布式的环境中保证数据的一致性;然而,当你真的决定采纳微服务架构时,你所遇到的第一个问题就是:如何划分微服务的边界。在微服务架构的官方网站上给出了四种将应用程序解形成多个微服务的模式:Decompose by business capability、Decompose by subdomain、Self-contained service 以及 Service per team。其中与畛域驱动设计所对应的模式就是 Decompose by subdomain,它要求设计者可能依据软件系统的业务畛域来区分子畛域,而后利用相干模式来确定微服务的划分,大抵流程如下:

对业务畛域进行剖析,通过通用语言来形容业务畛域中的要害概念和业务行为,并确定整个大的业务畛域由哪些子畛域(subdomain)形成
依据这些子畛域来确定界定上下文(Bounded Context),每一个界定上下文会有一套独立的畛域模型对子畛域进行形容,界定上下文中的畛域模型不会存在二义性
在界定上下文中建模,设计好畛域模型以及各畛域对象之间的关系
基于建设好的畛域模型,划分微服务
在畛域驱动设计中,界定上下文(有些文章将其翻译为“有界上下文”,意思雷同)是实现通用语言的重要工具,很多状况下,有些词语或者句子在不同的上下文中会有不同的含意,界定上下文就定义了这样一个边界,它能使得在边界内的词语或者句子具备惟一明确的含意而不存在二义性。例如某公司生产产品而后卖给客户(Customer),而后会有另一个团队为这些客户(Customer)提供售后服务或技术支持。那么在这里咱们有两个“客户”的概念,对于整个公司来说,它们示意的是同一个概念,然而在不同的上下文中,这个“客户”的概念又有所不同:在销售子畛域中,“客户”示意产品销售的对象,因而会更多地关注它对产品的需要以及信用额度、交货形式等等;而在售后服务子畛域中,“客户”示意提供服务的对象,因而会更多关注它的历史订单信息以及历史服务工单。从下面的基于 Decompose by subdomain 的根本流程来看,一旦辨别并确定了整个畛域中的界定上下文,也就基本上确定了利用零碎中大抵会有哪些微服务。从畛域模型上剖析,界定上下文也不是相对独立的,应该说绝大多数状况下不是。畛域驱动设计引入了“上下文映射(Context Mapping)”来解决跨界定上下文的畛域常识的交互。罕用的形式能够是使负责不同子畛域的团队之间达成共识、通过形象伎俩来建设跨多个界定上下文的公共模型(Shared Kernel),或者引入防腐层(Anti-corruption Layer)来达到不同界定上下文之间无缝沟通的目标。这篇文章很好地介绍了这些内容。这里限于文章篇幅,我仅仅简略地介绍了与畛域驱动设计相干的要点,下面探讨的内容中的每一个点都能够持续展开讨论持续剖析钻研。你是不是曾经开始思考是否真的须要微服务架构了吧?因为是否采纳微服务架构格调,以及微服务如何划分,将间接影响到今后你的业务零碎的开发和演进是否真的可能帮你解决宏大的业务畛域体量所带来的软件开发问题,而不是让你的架构变得逐步臃肿不堪错误百出难以保护,给你带来无穷无尽的懊恼。或者你的利用零碎并没有那么大的业务畛域体量,你也曾经将你的业务畛域划分成了多个微服务,那么接下来就是开发技术以及开发流程和团队治理的问题了。微服务架构真的有很多长处:因为整个业务畛域被划分成多个子畛域,由不同的微服务实现,因而这种架构格调具备十分好的延展性,并且能够依据须要来动静调配各个服务的运行资源。另一方面,在微服务架构中,通常都会由不同的团队来负责各个微服务的开发,这些团队能够抉择适合的技术,采纳本人的代码托管与分支策略,应用不同的软件开发过程来发展开发工作。如果团队采纳麻利开发过程,那么一个绝对较小的团队可能更加高效地实际麻利,使得微服务的开发可能一直向前迭代。微服务架构的另一个长处就是对于云平台的反对,尽管各个服务会采纳不同技术运行在不同平台上,然而当初风行的容器化技术能够屏蔽这种应用层技术实现的差别,通过将各个服务封装成容器,使得整个利用零碎能够十分不便地部署到云平台,并且十分不便地调用托管的云服务。因为这种架构上的灵活性和分布式的特点,微服务架构也存在很多挑战:配置管理、服务发现、服务间通信、分布式事务(数据最终一致性的保障)、部署和测试复杂度、安全策略的实现等等,每一个技术难点都有可能成为你成功实践微服务架构的阻力。例如,异步通信是微服务间最为常见的通信机制之一,而大多数状况下,分布式事务就须要依赖于这种异步通信机制,而它个别都是基于事件音讯的,所以,除了根本的事件音讯框架的实现之外,各个微服务还须要思考如何参加到这种分布式事务之中:如何在事务胜利的时候提交变更,以及如何在事务失败的时候进行弥补操作。Saga 体系结构模式就是一种实现跨服务事务的模式,它有两种实现形式:编排式和协调式,前者通过微服务之间互通畛域事件来实现事务,而后者则是由一个中心化的协调器来接管来自各服务的畛域事件,而后依据畛域事件的处理结果来决定整个事务应该被接管还是被驳回。当某个事务参加的微服务比拟少,并且解决逻辑不简单的状况下,采纳编排式的设计会比较简单;但如果参加的微服务和畛域事件比拟多,抉择协调式的设计会使得构造更加清晰,而且不容易出错。目前有一些开发框架曾经很好地实现了或者反对 Saga 模式,比方.NET 下的 NServiceBus 框架,然而因为其过于简单,学习老本比拟高,因而利用范畴也不是特地广。值得一提的是,微服务架构之下各服务之间隔离度越高越好,尽管微服务架构自身并不强制要求每个服务都有本人的数据库,然而 Database per service 依然是一个比拟举荐的做法。前端的实现也是如此,开发团队能够有各自的前端开发人员来开发用于以后微服务的前端界面,而后通过某些微前端框架进行整合。所以,微服务架构看上去比拟先进、时尚,然而要想无效、正确地实际微服务架构却不是一件容易的事件。如果你的业务零碎并没有大到须要拆分成多个子系统来进行设计,或者你的团队没有大到足以应答由这些微服务带来的技术复杂度,那么,你真的应该考虑一下,采纳微服务的架构是否真的利大于弊。架构设计就是如此,没有对错,只有是否正当,整个过程就是均衡与取舍。以下是微软官网的一个残缺的微服务架构的案例:eShopOnContainers,代码开源,其业务畛域是一个电商批发网站。它的架构图如下:

(图片起源:微软 eShopOnContainers 代码库,点击查看大图)

eShopOnContainers 反对挪动客户端、传统的基于 ASP.NET Core MVC 的浏览器客户端以及基于 Angular 的单页面利用(SPA)三种不同的客户端体验;在服务端,eShopOnContainers 实现了面向 mobile 和面向 web 的两套 API 网关(API Gateway),所有的 API 申请都由这两套网关所代理,与后端的不同微服务进行通信。eShopOnContainers 采纳基于 ASP.NET Identity 的由 IdentityServer4 所实现的认证与受权机制,它是一个基于 SQL Server 数据库的传统的 ASP.NET Core 的服务。在基于子畛域的划分上,eShopOnContainers 将其业务畛域分为三个子畛域:用于保护商品信息的 Catalog 子畛域、用于解决订单的 Ordering 子畛域以及用于治理购物篮信息的 Basket 子畛域,因而,对应的微服务也就按子畛域进行划分,各个微服务所采纳的技术也齐全不同:

Catalog 微服务应用传统的 Data Service/CRUD API 模式,将 Entity Framework Core 的 DbContext 以结构器注入的形式注入控制器(Controller),而后在控制器中实现业务操作和数据拜访,后盾采纳 SQL Server 数据库
Ordering 微服务应用 CQRS 体系结构模式,它的运作齐全基于畛域事件,尽管它并非齐全实现 CQRS 模式的所有细节,但曾经足够实现它的业务逻辑,并且它的复杂度也失去了很好的管制,它后盾也是采纳 SQL Server 数据库
Basket 微服务应用基于畛域驱动设计的分层模式,它引入了畛域模型、仓储等概念,并将仓储的实例通过结构器注入的形式注入控制器,而后让控制器充当畛域驱动设计中应用层的角色,实现业务解决和畛域模型的重建和长久化,后盾采纳 Redis 缓存作为数据长久化机制
这些微服务之间通过 RabbitMQ(或者 Azure Service Bus)的事件总线(Event Bus)实现通信,以编排式的 Saga 模式实现了根本的分布式事务,整个后端架构都是容器化的,运行在容器编排集群中(docker-compose 或者 Kubernetes)。由此可见,在微服务的架构格调中,畛域驱动设计可能被更加灵便地使用,因为不同的微服务是由不同的团队负责开发,因而就能够在不同的微服务中,以不同的水平来引入畛域驱动设计的思维以辅助解决业务剖析与零碎开发中的难点,最终达到整个软件架构的良性倒退。软件架构格调大抵就介绍这些吧,波及的内容的确很多,也没有方法在一篇文章里齐全写完,当前有机会再深刻补充吧。

总结
读到这里,你应该曾经大抵理解了什么是畛域驱动设计、软件架构模式与软件架构格调的区别是什么、常见的软件架构格调有哪些,以及在不同的软件架构格调下,畛域驱动设计是如何对软件的架构设计提供指引并领导模式的正当应用。你还会理解到,很多状况下,对于绝大多数我的项目而言,或者一个面向数据的 CRUD 服务曾经齐全可能满足你的利用零碎需要,或者你也只须要一个单体架构(Monolithic)就可能解决你眼下乃至几年内的开发痛点,那么在这些状况下,你须要慎重考虑是否真的须要“赶时髦”地引入过于简单的架构格调和架构模式。然而另一方面,在团队绝对比拟成熟、对畛域驱动设计有肯定认知和认同、老本容许的前提下,可能激励大家尝试实际畛域驱动设计,这也是一件十分好的事件,毕竟有学习有实际才会有提高。所以,何时应用畛域驱动设计?应该抉择什么样的架构格调?还是你本人来决定吧。

如果本文对你有帮忙,别忘记给我个 3 连,点赞,转发,评论,

咱们下期见!学习更多 JAVA 常识与技巧

正文完
 0