龙空技术网

架构设计的弄潮儿——微服务的“技术维度”(一)

Java小陈 200

前言:

此刻小伙伴们对“技术架构图和业务架构图区别”大致比较重视,朋友们都想要学习一些“技术架构图和业务架构图区别”的相关资讯。那么小编也在网上网罗了一些有关“技术架构图和业务架构图区别””的相关文章,希望看官们能喜欢,你们快快来学习一下吧!

引言

近几年来,微服务架构风格逐渐成为软件业的宠儿,并在大多数新兴的业务场景中得到运用,同时,我们发现另外一种设计方法总会与微服务携手同行,这就是领域驱动设计(Domain Driven Design,DDD)。一个是架构设计的弄潮儿,另一个是默默耕耘的经典方法,它们之间是怎么碰撞出“感情火花”的呢?

我们谈论微服务,看重它的“微”带来的灵活、弹性、易维护、可替换等诸多便利;同时却要看到:服务的细粒度分解固然减小了系统的规模,却又会因为显著增加的服务数量使系统结构变得更加繁杂。显然,微服务架构实则是通过牺牲“结构”换来“规模”的红利。

从结构这个维度看,微服务其实一点都不“微”!一个微服务从诞生到最后的消亡,经历了设计、开发、测试、上线、运行到下线贯穿始终的生命周期。当一个系统包含成百乃至上千个微服务时,系统的结构会变得越来越复杂。每个环节都有方方面面的因素需要考量,诸如设计原则的遵守、通信机制的选择、数据一致性的保障、健康状态的监控与跟踪,乃至于服务的配置、测试与运维。这些都是运用和实施微服务的重要关注点,它们更多来自技术层面的考量,我称其为微服务的“技术维度”。

基于“微服务”的定义,在这个架构风格中,每个服务都是独立运行(standalone)的微小服务,这意味着微服务的边界为完全独立的跨进程通信边界。这就带来一个至关重要的核心问题:如何设计和分解微服务?从粒度看,如果服务分解过细,则会增加通信成本和运维成本;如果服务分解过粗,那么又达不到单独伸缩和运维的目的。从职责分配看,由于微服务粒度比单体架构更细,且牵涉跨进程的通信边界,一旦职责分配有误,调整的成本要远远大于单体架构。故而在微服务架构风格下,微服务的设计与分解有可能成为整个系统在未来的“阿喀琉斯之踵”。针对微服务架构,普遍达成的共识是从业务角度驱动服务的识别与分解,我称其为微服务的“业务维度”。

技术维度的关注点,我们交给基础架构的设计者及微服务的框架来保障。至于业务维度的关注点恰好是领域驱动设计所擅长解决的。这就是为什么在谈到微服务时,我们需要领域驱动设计的原因。

领域驱动设计简介

领域驱动设计是由Eric Evans最早提出的综合软件系统分析和设计的面向对象建模方法,如今已经发展为一种针对大型复杂系统的领域建模与分析方法。它完全改变了传统软件开发工程师针对数据库进行的建模方法,从而将要解决的业务概念和业务规则转换为软件系统中的类型及类型的属性与行为,通过合理运用面向对象的封装、继承、多态等设计要素,降低或隐藏整个系统的业务复杂性,并使得系统具有更好的扩展性,应对纷繁多变的现实业务问题。

领域驱动设计本质上是一种方法论(Methodology)。它建立了以领域为核心驱动力的设计体系,因而具有一定的开放性。在这个体系中,你可以使用不限于领域驱动设计提出的任何一种方法来解决这些问题。例如,我们可以使用用例(Use Case)、测试驱动开发(TDD)、用户故事(User Story)帮助我们对领域建立模型;我们可以引入整洁架构思想及六边形架构,帮助我们建立一个层次分明、结构清晰的系统架构;我们可以引入函数式编程思想,利用纯函数与抽象代数结构的不变性及函数的组合性来表达领域模型。这些实践方法与模型已经超越了Eric Evans最初提出的领域驱动设计范畴,但在体系上却是一脉相承的。这也是为什么在领域驱动设计社区,能够不断诞生诸如CQRS模式、事件溯源(Event Sourcing)模式与事件风暴(Event Storming)等新概念的原因。

作为一种软件设计方法论,领域驱动设计贯穿了整个软件开发的生命周期,包括对需求的分析、建模、架构和设计,甚至最终的编码实现、测试与重构。它尤为强调领域模型的重要性,并提倡通过模型驱动设计来保障领域模型与程序设计的一致。领域驱动设计认为:开发团队应该从业务需求中提炼出统一语言(Ubiquitous Language),再基于统一语言建立领域模型;这个领域模型会指导程序设计及编码实现;最后,又通过重构来发现隐式概念,并运用设计模式改进设计与开发质量。

领域驱动设计的两个阶段

然而,当我们面对规模庞大、业务复杂的软件系统时,如果从一开始就要深入每个功能点进行需求分析和领域建模,则可能会面临高复杂度的挑战。因此,领域驱动设计将整个设计过程划分为两个阶段:战略设计阶段与战术设计阶段。

1.战略设计阶段

也从两个方面去考虑:

问题域方面:引入核心领域(Core Domain)与子领域(SubDomain)来划分问题域,然后通过限界上下文(Bounded Context)和上下文映射(Context Map)给出这些问题域的解决方案,在减小领域模型规模的同时,维持领域概念的一致性。架构方面:通过分层架构来隔离关注点,尤其是将领域独立出来,可以更利于领域模型的单一性与稳定性;引入六边形架构清晰地表达领域与技术基础设施的边界;CQRS模式则分离了查询场景和命令场景,针对不同场景选择使用同步或异步操作,提高架构的低延迟性与高并发能力。

Eric Evans 提出战略设计的初衷是要保持模型的完整性。限界上下文的边界可以保护上下文内部和其他上下文之间的领域概念互不冲突。然而,如果我们将领域驱动设计的战略设计模式引入架构过程,就会发现限界上下文不仅限于对领域模型的控制,还在于分离关注点之后,使得整个上下文可以成为独立部署的设计单元,这就是“微服务”的概念,上下文映射的诸多模式则对应了微服务之间的协作。因此在战略设计阶段,微服务扩展了领域驱动设计的内容,反过来领域驱动设计又能够保证良好的微服务设计。

一旦确立了限界上下文的边界,尤其是作为物理边界,分层架构就不再针对整个软件系统,而是针对粒度更小的限界上下文。此时,限界上下文定义了技术实现的边界,对当前上下文的领域与技术实现进行了封装,我们只需要关心对外暴露的接口与集成方式即可。显然,限界上下文是整个战略设计阶段的核心要素。

2.战术设计阶段

整个软件系统被分解为多个限界上下文后,我们就可以分而治之,对每个限界上下文进行战术设计。领域驱动设计提倡用领域模型来表达复杂的领域知识,构成模型的要素包括:

值对象(Value Object);实体(Entity);领域服务(Domain Service);领域事件(Domain Event);资源库(Repository);工厂(Factory);聚合(Aggregate);应用服务(Application Service)。

战术设计诸要素之间的关系图:

领域驱动设计围绕领域模型进行设计,通过分层架构(Layered Architecture)将领域独立出来。表示领域模型的对象包括实体、值对象、领域服务和领域事件。领域逻辑都应该封装在这些对象中。这一严格的设计原则可以避免业务逻辑渗透到领域层之外,导致技术实现与业务逻辑的混淆。

什么是聚合?

聚合是一种边界,它可以封装一到多个实体与值对象,并维持该边界范围之内的业务完整性。在聚合中,至少包含一个实体,且只有实体才能作为聚合根(Aggregate Root)。注意,在领域驱动设计中,聚合代表的是边界概念,而非领域概念。极端情况下,一个聚合可能有且只有一个实体。

什么是工厂和资源库?

工厂和资源库都是对领域对象生命周期的管理。前者负责领域对象的创建,往往用于封装复杂或可能变化的创建逻辑。后者则负责从存放资源的位置(数据库、内存或其他Web资源)获取、添加、删除或修改领域对象。领域模型中的资源库不应该暴露访问领域对象的技术实现细节。

3.演进的领域驱动设计过程

战略设计会控制和分解战术设计的边界与粒度,战术设计则以实证角度验证领域模型的有效性、完整性与一致性,进而以演进的方式对之前的战略设计阶段进行迭代,从而形成一种螺旋式上升的迭代设计过程,如图:

面对客户的业务需求,由领域专家与开发团队展开充分的交流,经过需求分析与知识提炼,获得清晰的问题域。通过对问题域进行分析和建模,识别限界上下文,利用它划分相对独立的领域,再通过上下文映射建立它们之间的关系,辅以分层架构与六边形架构划分系统的逻辑边界与物理边界,界定领域与技术之间的界限。之后,进入战术设计阶段,深入限界上下文内对领域进行建模,并以领域模型指导程序设计与编码实现。在实现过程中,若发现领域模型存在重复、错位或缺失,就需要对已有模型进行重构,甚至重新划分限界上下文。

两个不同阶段的设计目标是保持一致的,它们是一个连贯的过程,彼此之间又相互指导与规范,并最终保证一个有效的领域模型和一个富有表达力的实现同时演进。

标签: #技术架构图和业务架构图区别 #java微服务开源框架