第4章 开发架构设计

开发架构设计阶段,首先,通过整体归纳出各个模块的技术共性,看都有哪些共性的需求;然后,从全局角度去思考整个软件的顶层架构。

在开发架构设计阶段,架构师主要完成以下工作:

1、系统规划

2、接口定义

3、系统分层

4、技术选型

5、代码规范

4.1 系统规划与接口定义

规划一个系统首先站在全局的角度把整个系统规划成几个大的模块或子系统,准确定义出它们的功能与范围,把相互之间的边界划分清楚。然后在此基础上,将各个功能落实到这些模块中。从而可以把整个系统设计的工作分配给多个团队,让彼此独立地去工作。(在各团队独立工作之前,还需要分析清楚彼此之间的联系,落实各模块间的接口。)

接口的定义要以流程为主线,定义各个系统之间的协作、接口及相关规范。

4.2 系统分层与整洁架构

在制订开发规范过程中,需要制订统一的分层架构、技术路线、模块划分与代码规范。

1、系统为什么要分层呢?

答:系统分层就是从横向对系统进行划分。这样划分后,系统要进行哪个方面的变更 ,就去修改哪个层次的代码,从而降低系统变更的维护成本。

2、为什么一变更就需要修改许多层次的代码?

答:

首先,是层与层之间的编码不正确。

层与层之间的边界定义不准确,或者架构师没有清晰地将分层的规范传达给各团队成员,或者团队成员编码时没有严格遵照架构师的规范,那么层与层之间就会产生耦合。

其次,就是数据访问层的设计。

该层本来的职责是完成对不同类型的数据库的访问,然而,一些项目在DAO中加入了很多的业务逻辑。

3、如何解耦?

答:解耦就是在上层业务代码与底层技术框架之间建立“接口层”,从而解决业务代码与底层技术框架间的耦合。

4、什么是整洁构架?

整洁架构是Robert C. Martin(大名鼎鼎的 Uncle Bob)于2012年在他的一篇博客中发表的观点,并在一些会议上做了关于该架构的演讲。

它以圆环的形式把系统分成了几个不同的层次,因此又被称为洋葱架构。在整洁架构的中心是业务实体与业务应用,业务实体就是那些核心业务逻辑,而业务应用就是面向用户的服务。它们合起来组成了业务领域层,也就是通过领域模型形成的业务代码的实现。

整洁代码的精华在于其中间的适配器层,它通过适配器将核心的业务代码与外围的技术框架进行解耦。

(1)依赖规则(The Dependency Rule)

同心圆代表的是不同层级的软件代码。通常当你更深一步思考构造你的系统的时候,你的系统就会在更高的层级。最外层的圈代表的是机制级别的系统。最内层的代表的是策略级别的系统。

最重要的一条规则是依赖规则(The Dependency Rule)。这条规则说的是:代码依赖只能使由外向内。换句话说,内层结构的代码不能包含有任何外层结构的信息。尤其是一些外层结构的名称不应该被内层结构的代码提到,比如函数名,类名,变量名,或者其他的系统实体的名称。

同样的,外层的数据结构不应该被内层代码使用,特别是那些由外部框架生成的数据结构。我们并不希望外部结构的任何东西会影响到内部结构。

(2)实体层(Entities)

实体是用来封装公司的业务规则的。一个实体可以是一个带方法的对象,也可以是一些数据结构和函数。只要实体能被公司的不同业务逻辑部件使用,实体的具体表现形式是无所谓的。

或许你并不是想写企业级的架构,而只是想写一个简单的应用,那么这里实体就是指的应用的业务逻辑对象。它们封装了最通用的规则,并且当外部环境变化的时候,这些实体是最不需要被变化的。举例来说,比如在增加翻页需求或者是安全需求的时候,这些实体是最不应该被改变的。没有任何具体的应用需要改变实体层。

(3)用户实例层(Use Cases)

这一层的软件结构包含了具体的应用业务逻辑。它实现了所有的用户实例。这些用户的实例由流入实体的数据流和流出实体的数据流实现,这些用户实例使得内层的实体能依靠实体内定义的业务逻辑规则来完成系统的用户需求。

我们不希望用户实例层的任何改变会影响到实体层。我们同样也不希望用户实例层会被外部的结构层,比如UI、数据库或者任何公共的框架,的改变而影响。这层应该是独立于这些概念的。

当然,必然发生的是应用的业务逻辑被修改会影响到用户实例层的代码和结构。如果用户的需求改变了,这层的部分当然会被修改。

(4)接口适配层(Interface Adapters)

这一层的软件结构的目的就是进行数据的转换,将便于用户实例和实体层操作的数据结构变化成为最便于外部结构(比如数据库或者Web)操作的数据结构。比如GUI的MVC结构,表现器、视图器、控制器都是属于这个结构的。这层很可能是通过控制器将数据结构传给用户实例层,并且返回数据给表现器,视图器。

数据在这层会被转换,将便于实体层和用户实例层使用的数据转化成为持久层能使用的数据,比如数据库。这一层的代码并不需要知道任何数据库的信息。如果数据库是SQL数据库,那么,所有的SQL语言应当在这层被限制使用,特别是在这一层中与数据库有交互的代码部分。

当一些外部的服务需要与用户实例层和实体层进行交互的时候,这时候需要的数据转换也理所当然地放在这一层了。

(5)框架和驱动层(Frameworks and Drivers)

最外层是由框架和使用工具组成的。比如数据库,Web框架等。通常你并不需要写很多代码就能达到与内层进行交互的行为。

这层表达的是所有的数据应该具体最终到达的地方。Web是数据的最终到达地,数据库也是数据的最终到达地。我们把这些东西放在最外层,它们几乎对整个系统的架构造不成什么影响。

只有四层?

答案是否定的。这个分层模型是纲要性的。在具体实现中,你会发现你可能需要到比四层更多的层级。并没有任何规定说你必须只能实现这四个层级。但是,依赖规则(The Dependency Rule)是必须遵守的:代码依赖只能使由外向内。越向内,抽象程度越高,越向外,细节信息越多。当你越向内设计的时候,你需要设计越高层面的策略。内部层级比外部层级更抽象。

(6)跨层调用

图片的右下角是一个如何跨层调用的例子。它演示了控制器和表现器如何和用户实例层进行交互。注意下流向,从控制器开始,通过用户实例层,然后向上执行到表现器。这里也需要注意它们的代码依赖,必须是向内依赖的。

我们通常使用依赖倒置原则(Dependency Inversion Principle)来处理这种情况。比如在JAVA中,我们会使用接口或者继承关系来保证代码会在制定的地方依赖倒置。

举例说明,假设用户实例需要调用表现器。但是这种调用是违背了依赖规则的:外部的逻辑名称不应该被内部提到。所以我们让用户实例层调用一个接口(这里叫做用户实例输出端口Use Case Output Port),然后让表现层在外部实现这个接口。

相同的方法可以使用在架构的所有跨层调用上。因此实际上我们能使用多种形式的代码依赖达到我们能让控制流按照依赖规则的反方向流动。

(7)什么数据能跨层调用?

一般跨层调用的数据是简单的数据结构。你可以使用数据结构或者是简单的数据传输流,又或者可以通过函数的参数来进行传递。你也可以将数据封装到一个hashmap结构,或者一个对象中。数据传输最重要的事情是无依赖,简单。我们并不希望跨层传递的数据是实体,或者是数据表的行数据,理由是我们不希望数据有任何形式的违反依赖规则。

例如,许多数据库框架对query查询返回行结构,我们叫它RowStructure。我们并不希望将这个RowStructure传递给内层结构,因为这个违反了依赖规则,它会强制让内层结构了解外层的结构。

总结

遵守这些简单的规则并不难,但是它们会解决你很多头疼的问题。将系统分层,遵守依赖规则,你就会自然写出可测试的系统了,所有附带的好处也会实现了。当任何外部的系统是独立的,比如数据库、web框架,你就能以最小的代价将它们进行替换。

设计软件应当秉承这样的态度:花更多的赶时间去分析设计,让软件设计精简到极致,从而花更少的时间去编码。

4.3 技术中台建设

1、技术中台应当具备什么样的特征?

答:

(1)简单易用、快速便捷

(2)易于技术架构演化

(3)支持领域驱动与微服务的技术架构

2、命令与查询职责分享(CQRS)模式?

答:

(来源网络)命令查询的责任分离Command Query Responsibility Segregation (简称CQRS)模式是一种架构体系模式,能够使改变模型状态的命令和模型状态的查询实现分离。这属于DDD应用领域的一个模式,主要解决DDD在数据库报表输出上处理方式。

在基于 CQRS 的系统中,命令(写操作)和查询(读操作)所使用的数据模型是有区别的。命令模型用于有效地执行写/更新操作,而查询模型用于有效地支持各种读模式。通过领域事件或其他各种机制将命令模型中的变更传播到查询模型中,让两个模型之间的数据保持同步。

 

如果你觉得它们看起来就像是两个不同的微服务,那么我来说一说它们之间的一个细微区别。从物理实现层面来看,这两个数据模型可以作为两个独立的微服务,甚至可以用一个命令模型来支持多个查询模型。但是,微服务架构的一个关键构造是两个微服务通常代表两个独立的领域,而在 CQRS 中,无论运行时架构是怎样的,命令模型和查询模型都属于同一逻辑领域。如果查询模型对命令模型一无所知,就无法发挥作用。这里的耦合是预期的,不同于微服务之间的解耦行为。

CQRS 并没有规定这两个模型如何保持同步。同步可以通过同时更新两个模型来同步实现,也可以通过消息代理(如 Kafka)将命令从命令模型传输到查询模型来异步实现。后一种比较常用,因为它让系统更加可伸缩,尽管它需要在写操作和读操作的最终一致性方面做出权衡。

这不就是缓存吗?

只用于读取的数据模式看起来就像是一个缓存。事实上,查询模型可以使用 Redis 这样的缓存技术来实现。但是,CQRS 不只是为了分离数据的写入和读取,它的根本目的是为了实现数据的多重表示,每一种表示都能够满足某些用户的需求。CQRS 可能会有多种查询模式,每个模式可能使用不同的物理实现。有些可能使用数据库,有些可能使用 Redis,等等。

什么时候应该使用 CQRS

对于一部分场景,CQRS 是一种非常有用的架构模式。

第一个是我在前面已经提到过的。如果同一个数据模型不能有效地满足系统的读和写模式,那么通过应用 CQRS 来解耦读写是很有意义的。解耦后的数据模型可以满足特定的需求。CQRS 有效地将单个数据表示变成任意数量的(读)表示,所有这些表示都与负责处理所有更新的核心表示保持一致。

适用 CQRS 的第二个场景是将读负载与写负载分开。前面我讲了缓存和 CQRS 的区别,缓存并不是应用 CQRS 的目的。但是,通过分离命令模式和查询模式,就有了对单个模式进行伸缩的可能性。查询模型可以有自己的数据库和缓存,可以使用最适合某些特定场景的技术来实现。但不管怎样,命令模型的伸缩都不会受制于查询模型。我在这里需要重申的是,它们不是独立的系统,尽管它们之间有深度的耦合,但这不是问题。

什么时候不该使用 CQRS

在系统中使用 CQRS 会带来显著的认知负担和复杂性。开发人员必须面对至少两个数据模型和多种技术选择,所有这些都是不可忽略的负担。

第二个问题是如何保持命令模型和查询模型的数据同步。如果选择了异步方式,那么整个系统就要承担最终一致性所带来的后果。这可能非常麻烦,特别是当用户希望系统能够立即反映出他们的操作时,即使是单个一致性要求也会危及整个系统的设计。

如果我们选择让模型在任何时候都保持一致,就会有 CAP 和两阶段提交问题。如果两个模型使用同一个支持 ACID 的数据库,我们可以通过事务来保持它们的一致性,但 CQRS 的很多可伸缩性优势就发挥不出来了。如果要支持多个查询模型,写操作将会越来越慢,因为需要更新所有的查询模型。

因为这两个问题的存在,在选择是否使用 CQRS 时就要十分谨慎。如果使用得当,它可以极大提升应用程序的伸缩性。但是,支持多个数据模型并不是件容易的事,所以应该只在没有其他方法可以满足要求时才考虑这么做。

3、DDD领域驱动设计?

(引自J道)Eric Evans的“Domain-Driven Design领域驱动设计”简称DDD,Evans DDD是一套综合软件系统分析和设计的面向对象建模方法。

过去系统分析和系统设计都是分离的,正如我们国家“系统分析师” 和“系统设计师” 两种职称考试一样,这样割裂的结果导致,需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。

DDD则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。见下面DDD与传统CRUD或过程脚本或者面向数据表等在开发效率上比较:

服务器后端发展三个阶段:

UI+DataBase的两层架构,这种面向数据库的架构(上图table module )没有灵活性。

UI+Service+DataBase的多层SOA架构,这种服务+表模型的架构易使服务变得囊肿,难于维护拓展,伸缩性能差,见这里讨论或Spring Web 应用的最大败笔或垂直切片的烟囱式故事已经一去不复返了

DDD+SOA微服务的事件驱动的CQRS读写分离架构,应付复杂业务逻辑,以聚合模型替代数据表模型,以并发的事件驱动替代串联的消息驱动。真正实现以业务实体为核心的灵活拓展。

DDD革命性在于:领域模型准确反映了业务语言,而传统J2EE或Spring+Hibernate等事务性编程模型只关心数据,这些数据对象除了简单setter/getter方法外,没有任何业务方法,被比喻成失血模型,那么领域模型这种带有业务方法的充血模型到底好在哪里?

以比赛Match为案例,比赛有“开始”和“结束”等业务行为,但是传统经典的方式是将“开始”和“结束”行为放在比赛的服务Service中,而不是放在比赛对象本身之中。我们不能因为用了计算机,用了数据库,用了框架,业务模型反而被技术框架给绑架,就像人虽然是由母亲生的,但是人的吃喝拉撒母亲不能替代,更不能以母爱名义肢解人的正常职责行为,如果是这样,这个人就是被母爱绑架了。

提倡充血模型,实际就是让过去被肢解被黑crack的业务模型回归正常,当然这也会被一些先入为主或被洗过脑的程序员看成反而不正常,这更是极大可悲之处。看到领域模型代码,就看到业务需求,没有翻译没有转换,保证软件真正实现“拷贝不走样”。

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

 DDD是解决复杂中大型软件的一套行之有效方式,在国外已经成为主流。DDD认为很多原因造成软件的复杂性,我们不可能避免这些复杂性,能做的是对复杂的问题进行控制。而一个好的领域模型是控制复杂问题的关键。领域模型的价值在于提供一种通用的语言,使得领域专家、产品经理和软件技术人员联系在一起,沟通无歧义。

  DDD落地实现离不开Clean架构、六边形架构、 CQRS、Event Source几大大相关领域。下图是传统以数据库为中心的架构与使用DDD实现以领域为中心架构的区别。

DDD专门为解决复杂性而诞生,因此解决思路完全不同于传统的CRUD,但是DDD本身掌握起来并不会感觉复杂,从程序员角度看,DDD其实是研究将包含业务逻辑的ifelse语句放在哪里的学问。

4、支持DDD与微服务的技术中台应当具备什么能力?

答:

(1)能解决当前微服务架构的技术不确定性,使得微服务项目可以以更低的成本应对日后技术架构的更迭

(2)可以更容易地将领域驱动设计应用到微服务架构中,包括领域建模、限界上下文的微服务拆分、事件通知机制等

(3)能够实现微服务间的数据装配,以便于通过仓库与工厂装配领域对象的过程中将本地查询替换为远程接口调用

微服务架构设计最大的难题是微服务的合理拆分,拆分要体现单一职责原则,微服务间低耦合,微服务内高内聚。最佳的实践无疑是基于领域的设计,即先按照领域业务建模,然后基于限办上下文进行微服务拆分。

4.4 技术选型与技术规范

在开发架构这个设计阶段,除了给系统进行整体的规划,就是给各团队、各模块制定规范。

在技术选型时,一方面尽量选择主流框架,另一方面要多询问使用过这些框架的朋友、同行,从而获得直观的第一手资料。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值