我曾经担任某央企千万级生产系统交付方的首席架构师,负责整个系统的架构设计和架构管理。整个项目主要由三家公司、四个团队构成(各个团队下还有各自的子团队),团队人数规模大约100人。
交付模式
整个项目的交付过程以我定义的所谓“大瀑布+小敏捷”模式开展,该模式的具体形式如下图所示:
之所以采用这一方式,在于项目的每个里程碑节点必须按照合同约定的时间交付,例如在系统设计阶段结束时,必须交付概要设计说明书等交付物,然而,项目组又不可能完全依据瀑布方式开展工作,否则交付风险为显著增加,就需要有选择地进行敏捷迭代开发。
如何将瀑布流程与敏捷流程有机地结合起来,确实是项目组必须面临的挑战。我之所以定义这一混合模式,固然是为了各取所长,但其起因还在于项目的交付模式。甲方客户作为一家大型央企,必然重视流程管理与合同履约,该交付项目规模庞大,属于甲方客户极为重视的战略项目,客户的IT部门必须重度参与到管理流程中,通过跟踪项目计划的制订与执行来跟踪进度。
实际验证,“大瀑布+小敏捷”相结合的混合模式在面对这样的客户和项目时,确实发挥了巨大的作用。我想,若有机会,我会撰写单独的文章详细阐释我提出来的这一方法。
架构管理
作为首席架构师,除了主导整个项目的整体架构设计,并对关键问题做出技术决策之外,还需要负责技术管理。为此,我组建了架构师团队,团队成员同时担任各个团队的技术负责人。我还建立了架构CoP(Community Of Practice)机制,通过定期召开架构CoP会议,跟踪项目组面临的技术问题,识别技术风险并给出相应的解决方案。
四个团队分别位于成都、北京、杭州,这样的分布式团队需要通过线上会议进行沟通,沟通成本极大。架构CoP在运行一段时间后,我发现相当多争执不休乃至悬而未决的问题出在对应用边界的定义上,这一问题在微服务架构之上体现得尤为明显。
微服务架构
整个系统采用了如下所示的微服务架构:
微服务架构采用了前后端分离的整体架构,后端的微服务层按照领域边界划分子领域,在各个子领域内部,则根据业务的边界识别微服务,对外提供客户端需要的业务能力。每个微服务都是一个相互独立完整的业务单元,其逻辑边界也包括支撑该业务所必须的外部资源,包括数据库、文件或消息队列等。
客户端层根据UI呈现方式与载体的不同分为Web前端、APP前端、其他智能终端等。原则上,设计时需要保证前端的轻量级,尽量避免在前端编写业务逻辑,目的在于:
- 前端资源有限,不利于处理复杂的业务逻辑
- 不同前端需要支撑的业务逻辑可能相同,如果在前端编写业务逻辑,会导致业务实现的重复
遵循“轻前端、重后端”的思路,在进行产品设计与业务分析时,应确保前端逻辑与后端逻辑的边界,并尽可能识别多个前端可以重用的业务逻辑,将其沉淀到后端的微服务层。
然而,客户端层的前端与后端的微服务层往往不能做到完全的一一对应,因为前端的划分依据是根据前端类型与用户体验进行划分的,后端的划分依据则是从领域维度与业务能力。
设计微服务公开的接口时,应从业务能力的角度定义,如此才能保证服务接口的重用粒度。根据这一设计原则定义的服务接口可能无法满足前端UI的呈现目标,当后端微服务还需要支持不同的前端时,这一矛盾就更加突出。
微服务的服务接口既然是为了满足业务能力设计的,就需要遵循“单一职责原则”,保证每个服务是正交的。对于前端的呈现逻辑而言,可能需要汇聚多个业务方向的数据,以降低用户操作的复杂度。对于前端的用户体验而言,后端微服务定义的单个服务接口可能无法满足前端的一次调用需求。
要解决这两个问题,在架构上的做法就是在微服务层与客户端层之间再引入一个间接层,它所处的位置属于后端,但设计的接口却完全是为前端服务的,因而被称为“BFF(backend for frontend)层”。它的主要功能包括:
- UI适配:调用后端微服务,将微服务返回的响应消息转换为支持多个前端呈现的视图模型
- 服务聚合:定义一个外观服务,内部发起对多个微服务接口的调用,然后将它们各自返回的数据聚合为一个整体的视图模型
BFF层与API网关共同组成微服务应用架构的边缘层。
康威定律
一个好的开发团队与设计良好的架构应该遵循“康威定律”,也就是一个设计良好的系统,其架构的组织应该与开发它的团队组织保持一致。因此,要与微服务的应用架构相对应,就应该建立如下所示的团队组织:
遵循前后端分离的原则,通常需要为前端和后端建立不同的团队。在后端的微服务层中,各个子领域项目可以根据微服务的边界与团队规模,以微服务为边界组建特性团队。之所以子领域的边界没有贯穿前端团队,主要在于前端的边界划分并没有遵循子领域的边界,前端对后端微服务的调用,存在跨子领域调用的场景。
如果遵循康威定律,BFF层(边缘层的API网关属于微服务组件的范畴,无需单独开发,故而不考虑在内)也需要建立一个单独的团队。但是在实际情况下,该团队的建立需要考虑两个因素:
- 交流成本
- 技能要求
从交流的频率看,BFF与前端团队的交流更加频繁,为了降低交流成本,可考虑将BFF层的开发职责放到前端团队;从技能要求看,前端团队主要掌握JS技术栈,除非BFF的实现选择NodeJS,否则仍然需要将实现交给后端团队,这实际上是技术实现对边界的影响。
多团队协作的职责边界
组建团队时,团队的边界既是业务的边界,又是职责的边界。由于整个项目的参与方牵涉到三家公司的四个团队,且这些团队参与到项目的时间并不一致,要规避应用边界的问题,就必须为各个团队定义清晰的职责。
由于我们采用领域驱动设计方法定义子领域,这四个粒度较大的团队实际对应于整个应用架构的子领域,因此,可以结合子领域的类型与范围为团队规定各自的职责范围:
每个团队都有自己的前端(或微前端)与后端,共同实现垂直领域的完整业务功能。
多团队协作的数据边界
除业务边界外,组建团队时还需要考虑数据的边界,下图体现了数据存储和管理的范围:
由上图可知,移动App对应的移动子领域并未存储和管理业务主数据,它如果需要获得数据,需要调用业务A和业务B对外公开的服务。
应用边界设计原则
应用架构的边界受到业务边界、数据边界、团队边界、技术边界多个方面的影响,必须控制边界,否则会带来设计与开发的混乱,影响团队之间的协作。由此,这是我在参与多次架构CoP会议得到的亲身感受。
应用边界设计原则
为了避免大量类似问题的重复出现,也为了减少不必要的工作纠纷,我根据微服务的设计原则与团队的组建原则,结合项目的实际情况,确定了如下应用边界设计原则。
业务的边界与数据的边界尽可能保持一致,应保证“谁拥有数据,即由谁访问数据”。例如,数据平台拥有全域分析数据,则由数据平台提供分析业务功能;业务A与业务B分别提供与其业务数据对应的业务功能,移动App没有业务数据,不提供访问业务数据的业务功能。
业务的边界与职责和能力的边界尽可能保持一致,一个业务功能的边界可以参考团队职责范围对其进行界定。例如,短信通知业务属于跨领域的公共服务,应交由业务A团队负责;数据平台的监控功能属于数据平台的管理服务,应交由数据平台团队负责;扫描二维码是移动APP才具备的能力,应交由移动App团队负责。
BFF层UI适配和服务聚合功能的划分取决于它所服务的前端。倘若它仅服务于移动APP和智能终端前端,原则上应由移动App团队负责;反之,应根据业务边界和职责边界,确定由除移动子领域之外的其他子领域团队负责。
服务接口设计原则
接口设计的不确定性也会影响到应用边界。每个服务接口都有服务提供方和消费方,若能定义一些普适性且具有实证主义的服务接口设计原则,就能清晰地规定各自职责,避免提供方和消费方的相互推诿。以下是我给出的服务接口设计原则。
微服务对外定义的查询接口应确保其扩展性,即只需为服务接口提供一个实现以支持不同的查询条件与排序条件。原则上,查询接口在返回列表数据时,应支持分页能力。
服务接口的设计应遵循单一职责原则,对外提供高内聚的业务能力,保证服务接口彼此之间是正交的。如果调用者需要多个业务能力支持,以返回一个粗粒度的聚合数据,那么该服务属于服务聚合的范畴,应定义为一个对客户端公开的外观服务,放在BFF层。
服务接口的通用性优于专有性。例如,设计查询某资源的服务接口时,应考虑到多个客户端的调用请求,提供尽可能全面的资源信息,服务的调用者可以根据自身要求选择使用返回的对应数据,如此就可以通过增加服务接口的粒度来减少接口的数量。
开发团队在确定业务功能的应用边界时,应遵循以上原则,保证各个开发团队能够各司其职,以良好协作的方式完成业务功能的开发。如果存在业务功能的边界划分不适用于以上任何原则,或者对原则的适用存在分歧,则由架构师团队召集相关团队负责人进行讨论,根据具体的应用场景确定最佳的解决方案。