架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)

Kotlin 开发者社区 

 

领域模型与领域驱动设计(DDD)

领域模型(Domain Model)

解决什么问题

  • 问题域

  • 需求分析

  • 分析理解复杂业务领域问题

  • 准确反映业务语言

是什么

  • 商业建模

  • 企业的业务模型

  • 行业的业务模型

  • 业务中涉及到的实体及其相互之间的关系

领域驱动设计(Domain-Driven Design)

概述

  • 将复杂的问题域划分为单独的块(称为有界上下文),虽然有很多方式如:不同的心智Mental模式,组织政治,域语言学等也是这样做,但是DDD建立了一个有界的心智mental模式,这样商务人士也可以理解,程序员也可以很容易地在代码中实现。

CQRS,作为一种战术办法,是实现DDD建模领域的最佳途径之一。

"血量"多少

  • 失血模型

    • 传统J2EE或Spring+Hibernate等事务性编程模型只关心数据,这些数据对象除了简单setter/getter方法外,没有任何业务方法,被比喻成失血模型

  • 贫血模型

  • 充血模型

    • 让过去被肢解被黑crack的业务模型回归正常

"世界观"

  • 接触到需求第一步就是考虑领域模型,而不是将其切割成数据和行为,然后数据用数据库实现,行为使用服务实现,最后造成需求的首肢分离。DDD让你首先考虑的是业务语言,而不是数据。重点不同导致编程世界观不同。

  • "演员们"

    • 业务实体

    • 业务实体属性

    • 业务实体的关系

    • 服务

    • 服务与实体的关系

落地实现

  • in-memory缓存

    • 当建立一个大型Java应用时,引起性能问题大部分是延迟,延迟是指请求和响应之间的时间差,在一个分布式Java系统中引起延迟的原因有:

    • 问题背景

从磁盘上加装数据的IO延迟 跨网络加装数据的IO延迟 在分布式锁上的资源争夺 垃圾回收引起的暂停 - 内存缓存 - 内存是快的: 为了高性能,你需要在内存中处理数据。 网络是慢的: 通过网络传输数据会严重影响性能,包括数据库连接池。 在许多应用中,应用的快速性能与数据实时更新需要寻找一个平衡点 - 内存缓存原来作用是提高数据库访问性能。但是缓存不是数据库遮羞布,架构上缓存引入有着重要意义:状态对象:数据库的替代者。

缓存实际是内存,将状态置于内存而不是数据库,不但性能提升,还提高软件的可伸缩性和扩展性,直至轻松发展为分布式系统或云计算,这种缓存称为内存缓存(in-memory cache)或称 数据网格In-Memory-Data-Grid (IMDG) - 当我们将DDD领域模型加载到内存中以后,我们就不再面向关系数据库中数据表编程,而是真正直接面向模型对象编程。

  • CQRS

    • 数据的查询

    • 数据的新增修改删除等动作

    • 命令查询的责任分离Command Query Responsibility Segregation (简称CQRS)

    • 能够使改变模型的状态的命令和模型状态的查询实现分离

    • Command

    • Query

  • DCI

    • DCI可以说是函数式functional编程比如Scala带来的一个理念.

    • 面向组合编程

    • 数据Data 场景Context 交互Interactions的简称,DCI是一种特别关注行为的模式(可以对应GoF行为模式),而MVC模式是一种结构性模式,MVC模式由于结构化,而可能忽视了行为事件。

    • Data: 对象的数据

    • Context: 对象使用的场景

    • Interaction: 对象的交互行为

    • 将“是什么”和“做什么”进行分离

    • 桥模式

    • https://www.artima.com/articles/dci_vision.html

    • Use Case scenario

    • 是什么

    • 理念起源

  • EDA 或 Event Source

    • 事件驱动有以下特征:

    • 以事件为驱动的编程模型

    • 以事件为媒介,实现组件或服务之间解耦

    • 传统面向接口编程是以接口为媒介,实现调用接口者和接口实现者之间的解耦,但是这种解耦程度不是很高,如果接口发生变化,双方代码都需要变动,而事件驱动则是调用者和被调用者互相不知道对方,两者只和中间消息队列耦合。

    • 是什么

    • 特征

生产者producer发生实时事件 推送通知 生产者发射即完成fire-and -orget 消费者consumer立即响应 事件与命令是有区别的

分层架构

三层模型

 

 

  • 表现层

  • 业务层

    • 负责表达业务领域概念、业务状态以及业务规则

    • OO设计原则和设计模式

    • 负责完成功能,并且协调丰富的领域对象来实现功能,不能包括业务规则,无业务状态

    • 应用层

    • 领域层

  • 持久层

层与层之间的规则

  • 每个层都是内聚的,并且只依赖它的下层,为了实现各层的最大解耦,IOC/DI容器是当前Java业务层的最好选择 。

 

架构整洁之道

  又称干净的架构The Clean Architecture,这是著名软件工程大师Robert C Martin提出的一种架构整洁清晰之道,也是当前各种语言开发的目标架构。干净、清晰、整洁的架构应该只包含单向的依赖关系,这样才可以在逻辑上形成一种向上的抽象系统。

我们经常听说过如下各种架构:

  • 六边形架构Hexagonal Architecture (也称为 端口和适配器) 这是由Alistair Cockburn 提出,被Steve Freeman和 Nat Pryce在他们的书籍Growing Object Oriented Software中采取的。

 

多年来,软件应用程序的一个重要问题是将业务逻辑渗透到用户界面代码中。这导致的问题有三个:

 

首先,系统无法用自动化测试套件进行整齐测试,因为需要测试的部分逻辑依赖于不断变化的视觉细节,如字段大小和按钮位置;

 

出于同样的原因,不可能从人为驱动的系统使用转变为批量运行系统;

出于同样的原因,当程序变得有吸引力时,很难或不可能允许程序由另一程序驱动。

 

在许多组织中重复使用的尝试解决方案是在架构中创建一个新层,并承诺这一次,真实而真实地,没有业务逻辑将被放入新层。但是,由于没有机制来检测违反该承诺的时间,组织几年后发现新层混乱了业务逻辑并且旧问题再次出现。

 

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第1张图片

 

 

Hexagonal Architecture(六角形或六边形) 于2005年由Alistair Cockburn撰写,是一个具有许多优势的软件架构,自2015年以来又重新引起了人们的兴趣。

六边架构的初衷是:

允许应用程序同样由用户,程序,自动化测试或批处理脚本驱动,并与最终的运行时设备和数据库隔离开发和测试。

六角形架构允许隔离应用程序的核心业务,并自动测试其行为,而不依赖于其他任何事情。这可能是该架构引起域驱动设计(DDD)从业者关注的原因。但要小心,DDD和六边形结构是两个相当不同的概念,它们可以相互加强,但不一定一起使用。

最后,这种架构设置起来并不复杂。它基于一些简单的规则和原则。让我们探索这些原则,看看它们在实践中的含义。

六角架构原理

六边形体系结构基于三个原则和技术:

  • 明确区分应用程序,领域和基础结构三个层

  • 依赖关系是从应用程序和基础结构再到领域

  • 我们使用端口和适配器隔离它们的边界

1. 原则:独立的应用程序,域和基础结构三个层

第一个原则是明确地将代码分成三个大层。

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第2张图片

 

左侧Application是应用程序端

这是用户或外部程序与应用程序交互的一面。它包含允许这些交互的代码。通常,您的用户界面代码,API的HTTP路由,以及使用您的应用程序的程序的JSON序列化都在这里。(banq注:Spring Boot的控制器)

这里也是Actor角色驱动领域所在。注意:Alistair Cockburn谈的是应用程序方面的左侧或用户侧。

 

领域层Domain中心位置

通过领域层隔离左侧和右侧。它包含所有关注和实现业务逻辑的代码。业务词汇和纯粹的业务逻辑。

 

右侧基础设施层

在这里,我们可以找到您的应用程序需要什么,它驱动哪些组件进行工作。它包含必要的基础结构详细信息,例如与数据库交互的代码,调用文件系统或处理对您所依赖的其他应用程序的HTTP调用的代码(集成)。

 

以下原则将实现在应用程序,域和基础结构之间实现逻辑分层。

这种分离的第一个重要特征是它将问题分开。在任何时候,您都可以选择专注于某个逻辑,几乎独立于其他两个逻辑:应用程序的逻辑,业务的逻辑或基础架构的逻辑。它们在不混合的情况下更容易理解,并且每个逻辑的约束对其他逻辑的影响较小。

另一个特点是我们将业务逻辑放在代码的最前端。它可以在目录或模块中隔离,以使其对所有开发人员都明确。它可以在不承担程序其余部分的认知负荷的情况下进行定义,改进和测试。这很重要,因为最终,开发人员对生产中的业务有了解。

https://www.jdon.com/51678

带适配器的六边形架构

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第3张图片

 

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第4张图片

  • Onion Architecture 作者Jeffrey Palermo

 

写出高质量软件是困难和复杂的:不仅仅是为了满足需求,还应该是健壮的,可维护的,可测试的,并且足够灵活以适应成长和变化。这就是洋葱架构出现的原因,它代表一组优秀的开发实践,用来开发任何的软件应用都是一个不错的方式。

洋葱架构,也成为整洁架构(The Clean Architecture),用来构建具有如下特点的系统:

1.    独立的Frameworks

2.    可测试

3.    独立的UI

4.    独立的数据库

5.    独立的任意外部服务(代理)

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第5张图片

看到这张图,你应该能理解为什么称其为洋葱架构了.

 

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第6张图片

 

依赖原则(The dependency rules)

上面的同心圆代表软件的不同部分。总得来说,越往里面,代码级别越高。外层的圆是(实现)机制,而内层的圆是原则(Policies)。

 

让这个架构起作用的最主要原则是依赖原则。这个原则要求源码依赖只能指向内部。内部的圆不能知道外圆的任何事情。一般来说,外圆的声明(包括方法、类、变量或任何软件实体)不能被内圆引用。

 

同样的,外圆使用的数据格式不能被内圆使用,尤其是外圆中的Framework产生的格式。我们不想让外圆的任何东西影响内圆。

 

 

 

越往里面抽象级别越高,最外层的圆是低级别的具体细节。越往里面内容越抽象,并且封装更高级别的原则(Policies)。最里面的圆是最通用的。

 

Entities

Entities封装了企业级的业务规则。一个Entity可以是一个带方法的对象,也可以是一个数据结构和方法集。Entities可以被用于企业的其他应用。

 

如果你没有加入企业,而是仅仅在写一个简单的应用,那么这些Entities就是这个应用的业务对象。它们封装了最通用、最上层的原则。它们是最不容易改变的,即使外部的东西改变了。例如,你不想让这些对象受到页面导航、安全的影响。应用的任何操作变化都不应该影响Entities Layer。

 

Use Casess

这一层包含了应用特有的业务规则。它封装和实现了系统的所有用例。这些用例协调数据从entities的流入和流出,并且指导entities使用它们的企业级业务规则来达到用例的目标。

 

我们不希望这一层的改变影响到Entities,同时也不希望这一层被外层的改变影响,如外层的数据库,UI或者任何Frameworks的改变,这一层独立于这些关注点。

 

当然,我们确实期望应用的操作变化影响用例层。如果一个用例的细节改变,那么这一层的部分代码确实会受到影响。

 

Interface Adapters

这一层包含一个adapters set(数据适配器集),它们把适用于Use Casess和entities的数据转换为适用于外部服务(external agency,如Database或Web)的格式。 例如,这一层可以完全包括GUI的MVX架构,Presenters, Views和Controllers都属于这里。Models可能仅仅是从Controllers传到Use Casess的数据结构,然后从Use Casess返回给Presenters和Views。

 

 

 

这一层的数据会被转换,从适用于entities和Use Casess的格式转换到适用于所使用的持久化框架的格式(如数据库)。这个圆以内的代码不应该知道关于数据库的任何东西。如果是一个SQL数据库,那么所有的SQL应该被限制到这一层,并且通常来说是被限制到层中跟数据库有关的部分。

 

 

 

同样,这一层也需要一些其他必要的Adapter来把外部的数据格式(如来自于外部服务的格式),转换为适用于Use Casess和entities的格式。

 

 

 

Frameworks and Drivers.

最外面的一层通常由Frameworks和Tools组成,如Database,Web Framework等。一般来说,除了用于和内层圆交互的连接代码,你不会在这一层写很多代码。

 

这一层是实现所有细节的地方。Web和Database都是需要实现的细节。我们把这些东西放在外面以减轻来自于它们的伤害(即减轻对他们的依赖)。

 

 跨界

 

在图的右下角是一个我们应该如何跨界的例子。它展示了Controllers、Presenters与下一层的Use Casess的交互。注意控制的流向,它开始于Controller,经过Use Casess,最终在Presenter中执行。同时也请注意Source Code依赖,它们每一个都指向内部的Use Casess。

 

 我们通常用依赖倒置原则来解决这个明显的矛盾。比如,在Java这样的语言里,我们可以使用接口和继承关系在合适的地方让源码依赖与控制流反向来跨界。

 

 例如,假设Use Cases需要访问Presenter,当然,不能是直接访问,不然会违反依赖原则,所以我们让内圆的Use Cases访问一个接口(如图中的Use Cases output port),然后外圆的Presenter实现这个接口。

 

在这个架构中,同样的技术也被用于跨越其他的边界。我们利用运行时多态来创建与控制流相反的SourceCode依赖以满足依赖原则,无论控制流是如何流向的。

 

通常跨界的数据都是简单的数据结构。你可以使用简单的结构或数据传输对象(Data Transfer Object)。这个数据可以简单的是方法调用的参数,你也可以把它包装到一个HashMap或者一个对象。最重要的是独立的、简单的数据结构才能跨越边界。不要投机取巧,如传输Entites或者Database rows。我们不想让这个数据结构有任何违反依赖原则的依赖。

 

例如,很多的数据库框架对于query返回一个方便的数据格式,我们可以称之为Row Structure,我们不想向内部传递这个row structure。这会让内圆知道外圆的内容而违反了依赖原则。

 

 

  • Screaming Architecture Bob大叔

  • DCI 由James Coplien和Trygve Reenskaug推动

  • BCE  Ivar Jacobson在他的书籍Object Oriented Software Engineering: A Use-Case Driven Approach提出

 

领域驱动设计参考架构

 

 

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第7张图片

领域驱动设计参考架构

领域驱动设计的架构核心目标是要创建一个富领域模型,其典型特征是它的领域对象具有丰富的业务方法用以处理业务逻辑,而Transaction Script风格的领域对象则仅仅是数据的载体,没有业务方法,这种领域也被称作“贫血的领域对象”(Anemic Domain Objects)。在Service方面,领域驱动设计的架构里Service是非常“薄“的一层,其并不负责处理业务逻辑,而在TransactionScript风格的架构里,Service是处理业务逻辑的主要场所,因而往往非常厚重。

 

 

 

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第8张图片

“传统”架构-贫血领域模型

架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD)_第9张图片

数据与操作分离的Transaction Script风格的架构

虽然这些架构在细节上都略有不同,但他们都非常相似。它们都具有相同的目标,那就是分离关注。他们都通过软件分层来实现这种分离。至少有一个层代表业务规则,而另一个层用于接口。

这些架构产生的系统特点是:

  1. 独立的框架. 这样的架构并不依赖与应用软件的具体库包,这样可以将框架作为工具,而不必将你的系统都胡乱混合在一起。

  2. 可测试. 业务规则能够在没有UI和数据库 或Web服务器的情况下被测试。

  3. UI的独立性. UI改变变得容易,不必改变系统的其余部分,一个Web UI能被一个控制台或专门的图形UI替代, 这些读不必更改业务核心规则。

  4. 数据库的独立性. 你能够在Oracle或SQL Server Mongo, BigTable, CouchDB,或之间切换, . 你的业务规则不会和数据库绑定

  5. 独立的外部代理,其实你的业务规则可以对其外面的技术世界毫无所知,比如是否使用了MVC或DCI都可以不关心。

这种干净的架构图如下:

 

依赖规则Dependency Rule

上图中同心圆代表各种不同领域的软件。一般来说,越深入代表你的软件层次越高。外圆是战术实现机制,内圆的是战略核心策略。

使此体系架构能够工作的关键是依赖规则。这条规则规定源代码只能向内依赖,在最里面的部分对外面一点都不知道,也就是内部不依赖外部,而外部依赖内部。这种依赖包含代码名称,或类的函数,变量或任何其他命名软件实体。

同样,在外面圈中使用的数据格式不应被内圈中使用,特别是如果这些数据格式是由外面一圈的框架生成的。我们不希望任何外圆的东西会影响内圈层。

实体Entities

实体封装的是企业业务规则,一个实体能是一个带有方法的对象,或者是一系列数据结构和函数,只要这个实体能够被不同的应用程序使用即可。

如果你没有编写企业软件,只是编写简单的应用程序,这些实体就是应用的业务对象,它们封装着最普通的高级别业务规则,你不能希望这些实体对象被一个页面的分页导航功能改变,也不能被安全机制改变,操作实现层面的任何改变不能影响实体层,只有业务需求改变了才可以改变实体。

用例Use Cases

在这个层的软件包含应用指定的业务规则,它封装和实现系统的所有用例,这些用例会混合各种来自实体的各种数据流程,并且指导这些实体使用企业规则来完成用例的功能目标。

我们并不期望改变这层会影响实体层. 我们也不期望这层被更外部如数据库 UI或普通框架影响,这层也是因为关注而外部分离的。

我们期望应用层面的技术操作都不能影响用例层,如果需求中用例发生改变,这个层的代码才会发生改变。

接口适配器Interface Adapters

这一层的软件基本都是一些适配器,主要用于将用例和实体中的数据转换为外部系统如数据库或Web使用的数据,在这个层次,可以包含一些GUI的MVC架构,表现视图 控制器都属于这个层,模型Model是从控制器传递到用例或从用例传递到视图的数据结构。

通常在这个层数据被转换,从用例和实体使用的数据格式转换到持久层框架使用的数据,主要是为了存储到数据库中,这个圈层的代码是一点和数据库没有任何关系,如果数据库是一个SQL数据库, 这个层限制使用SQL语句以及任何和数据库打交道的事情。

框架和驱动

最外面一圈通常是由一些框架和工具组成,如数据库Database, Web框架等. 通常你不必在这个层不必写太多代码,而是写些胶水性质的代码与内层进行粘结通讯。这个层是细节所在,Web技术是细节,数据库是细节,我们将这些实现细节放在外面以免它们对我们的业务规则造成影响伤害。

只有四个圈层吗?

这个圆圈图是示意性的。您可能会发现您需要的不仅仅是这四个。也没有规定说你必须始终只有这四个。然而,依赖规则始终适用。源代码的依赖关系总是由外向内。当你越向内时,抽象水平越高。而最外面的一圈是低层次的具体细节。当你越向内时软件变得越为抽象,封装了更高层次的策略。

跨边界流程

在图的右下方是我们如何越过圆边界的例子。它显示控制器和界面之间是如何和用例进行通信的。注意控制流程。它开始于控制器,通过用例,然后在界面处执行。还要注意源代码的依赖关系。他们中的每一个点都是指向内部用例。我们通常使用依赖注入来实现这种依赖。

那么数据如何跨层流动呢?

通常跨层的数据是简单的数据结构。如果你喜欢你可以使用基本结构或简单的数据传输对象DTO。或可以函数可以调用数据参数。或者你可以打包到哈希表中,或为它建构一个对象。最重要是跨层传递是孤立的、 简单的数据结构。

我们不想让这个数据结构是一个实体或数据库记录,因为我们不希望它们有任何的依赖关系,这会违反了依赖规则。例如,许多数据库框架在查询响应中返回一个方便的数据格式。我们可能会要求这对这个记录重构,因为我们不想要跨层向内传递数据库记录。这就违反了依赖规则,它会迫使内圈要知道关于外圈的东西。所以当我们跨层传递数据,它总是以对内圈最方便的形式。

总结

符合这些简单的规则将会节省您大量的头痛开发。通过将软件分离到各种层,并符合依赖规则,这样您创建一个系统本质上是可测试,这意味着很多好处。

by Robert C. Martin (Uncle Bob) 原文:https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

 

 

Over the last several years we’ve seen a whole range of ideas regarding the architecture of systems. These include:

  • Hexagonal Architecture (a.k.a. Ports and Adapters) by Alistair Cockburn and adopted by Steve Freeman, and Nat Pryce in their wonderful book Growing Object Oriented Software

  • Onion Architecture by Jeffrey Palermo

  • Screaming Architecture from a blog of mine last year

  • DCI from James Coplien, and Trygve Reenskaug.

  • BCE by Ivar Jacobson from his book Object Oriented Software Engineering: A Use-Case Driven Approach

Though these architectures all vary somewhat in their details, they are very similar. They all have the same objective, which is the separation of concerns. They all achieve this separation by dividing the software into layers. Each has at least one layer for business rules, and another for interfaces.

Each of these architectures produce systems that are:

  1. Independent of Frameworks. The architecture does not depend on the existence of some library of feature laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their limited constraints.

  2. Testable. The business rules can be tested without the UI, Database, Web Server, or any other external element.

  3. Independent of UI. The UI can change easily, without changing the rest of the system. A Web UI could be replaced with a console UI, for example, without changing the business rules.

  4. Independent of Database. You can swap out Oracle or SQL Server, for Mongo, BigTable, CouchDB, or something else. Your business rules are not bound to the database.

  5. Independent of any external agency. In fact your business rules simply don’t know anything at all about the outside world.

The diagram at the top of this article is an attempt at integrating all these architectures into a single actionable idea.

The Dependency Rule

The concentric circles represent different areas of software. In general, the further in you go, the higher level the software becomes. The outer circles are mechanisms. The inner circles are policies.

The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle. That includes, functions, classes. variables, or any other named software entity.

By the same token, data formats used in an outer circle should not be used by an inner circle, especially if those formats are generate by a framework in an outer circle. We don’t want anything in an outer circle to impact the inner circles.

Entities

Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise.

If you don’t have an enterprise, and are just writing a single application, then these entities are the business objects of the application. They encapsulate the most general and high-level rules. They are the least likely to change when something external changes. For example, you would not expect these objects to be affected by a change to page navigation, or security. No operational change to any particular application should affect the entity layer.

Use Cases

The software in this layer contains application specific business rules. It encapsulates and implements all of the use cases of the system. These use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise widebusiness rules to achieve the goals of the use case.

We do not expect changes in this layer to affect the entities. We also do not expect this layer to be affected by changes to externalities such as the database, the UI, or any of the common frameworks. This layer is isolated from such concerns.

We do, however, expect that changes to the operation of the application will affect the use-cases and therefore the software in this layer. If the details of a use-case change, then some code in this layer will certainly be affected.

Interface Adapters

The software in this layer is a set of adapters that convert data from the format most convenient for the use cases and entities, to the format most convenient for some external agency such as the Database or the Web. It is this layer, for example, that will wholly contain the MVC architecture of a GUI. The Presenters, Views, and Controllers all belong in here. The models are likely just data structures that are passed from the controllers to the use cases, and then back from the use cases to the presenters and views.

Similarly, data is converted, in this layer, from the form most convenient for entities and use cases, into the form most convenient for whatever persistence framework is being used. i.e. The Database. No code inward of this circle should know anything at all about the database. If the database is a SQL database, then all the SQL should be restricted to this layer, and in particular to the parts of this layer that have to do with the database.

Also in this layer is any other adapter necessary to convert data from some external form, such as an external service, to the internal form used by the use cases and entities.

Frameworks and Drivers.

The outermost layer is generally composed of frameworks and tools such as the Database, the Web Framework, etc. Generally you don’t write much code in this layer other than glue code that communicates to the next circle inwards.

This layer is where all the details go. The Web is a detail. The database is a detail. We keep these things on the outside where they can do little harm.

Only Four Circles?

No, the circles are schematic. You may find that you need more than just these four. There’s no rule that says you must always have just these four. However, The Dependency Rule always applies. Source code dependencies always point inwards. As you move inwards the level of abstraction increases. The outermost circle is low level concrete detail. As you move inwards the software grows more abstract, and encapsulates higher level policies. The inner most circle is the most general.

Crossing boundaries.

At the lower right of the diagram is an example of how we cross the circle boundaries. It shows the Controllers and Presenters communicating with the Use Cases in the next layer. Note the flow of control. It begins in the controller, moves through the use case, and then winds up executing in the presenter. Note also the source code dependencies. Each one of them points inwards towards the use cases.

We usually resolve this apparent contradiction by using the Dependency Inversion Principle. In a language like Java, for example, we would arrange interfaces and inheritance relationships such that the source code dependencies oppose the flow of control at just the right points across the boundary.

For example, consider that the use case needs to call the presenter. However, this call must not be direct because that would violate The Dependency Rule: No name in an outer circle can be mentioned by an inner circle. So we have the use case call an interface (Shown here as Use Case Output Port) in the inner circle, and have the presenter in the outer circle implement it.

The same technique is used to cross all the boundaries in the architectures. We take advantage of dynamic polymorphism to create source code dependencies that oppose the flow of control so that we can conform to The Dependency Rule no matter what direction the flow of control is going in.

What data crosses the boundaries.

Typically the data that crosses the boundaries is simple data structures. You can use basic structs or simple Data Transfer objects if you like. Or the data can simply be arguments in function calls. Or you can pack it into a hashmap, or construct it into an object. The important thing is that isolated, simple, data structures are passed across the boundaries. We don’t want to cheat and pass Entities or Database rows. We don’t want the data structures to have any kind of dependency that violates The Dependency Rule.

For example, many database frameworks return a convenient data format in response to a query. We might call this a RowStructure. We don’t want to pass that row structure inwards across a boundary. That would violate The Dependency Rule because it would force an inner circle to know something about an outer circle.

So when we pass data across a boundary, it is always in the form that is most convenient for the inner circle.

Conclusion

Conforming to these simple rules is not hard, and will save you a lot of headaches going forward. By separating the software into layers, and conforming to The Dependency Rule, you will create a system that is intrinsically testable, with all the benefits that implies. When any of the external parts of the system become obsolete, like the database, or the web framework, you can replace those obsolete elements with a minimum of fuss.


Kotlin 开发者社区

 

国内第一Kotlin 开发者社区公众号,主要分享 Kotlin  / Java、Spring Boot、Android、React.js/Node.js、函数式编程、架构设计等相关主题。

 

你可能感兴趣的:(架构整洁之道 (Clean Architecture )与领域模型与领域驱动设计(DDD))