Part 1: API设计和策略
软件系统的复杂性是一个很痛苦的问题,而且无法避免。Fred Brooks将复杂性描述为,软件系统解决业务问题所固有的本质复杂性,以及实施该解决方案所带来的偶发复杂性。
随着与采用“API优先”工程实践和微服务架构的组织进行更密切的合作,我发现这种描述越来越有用。首先,通过这种方式分离复杂性,它允许架构师和工程师专注于最小化系统的偶发复杂性。
与微服务相关的技术创新有很大帮助:容器,自动化工具和API管理平台。但我真正喜欢这种复杂性划分的原因是,它帮助我们认清一个事实:任何技术都不能降低系统的本质复杂性。
“开发软件产品的许多经典问题源于这种本质的复杂性,其非线性随着规模的增加而增加。” – 出自Fred Brooks著《No Silver Bullet—Essence & Accident in Software Engineering》
但是,我们能做的至少是理解和记录一个系统的本质复杂性。对于大多数技术人员来说,有效完成这一任务很重要,因为系统的本质复杂性不包括解决方案中使用的底层技术。
系统本质复杂性定义的本质要素是系统执行的任务和功能,以及执行所需的对象和交互。为了优化这种定义的认知负荷,将此定义表达为视觉模型也是很自然的。即使在可视化的情况下,软件架构师仍然努力保持模型中的元素不受技术限制,并且保持一致。
Eric Evans的领域驱动设计(Domain-Driven Design ,DDD)方法论突出的原因是,它提供了一种可视化定义软件系统本质复杂性的方法。特别是,显示域,子域,有界上下文及其相互关系的上下文映射,与我见过的以有效方式说明本质复杂性的任何方法都非常接近。 UML意在提供一种用于定义与技术无关的应用程序描述的手段,但它的许多模型自然偏向于面向对象的根源。事实上,如果你深入DDD工具箱,我认为你会遇到同样的偏见。
我在定义一个互连微服务系统时,使用了DDD上下文映射的派生。将在未来的文章中详细介绍这种方法。
示例上下文映射
为什么理解和记录软件系统的本质复杂性很重要,这与微服务有什么关系?随着微服务系统的发展和演变,其许多好处来自于服务本身的凝聚力。PhilCalçado在他的博客文章“微服务与分布式对象的第一定律(Microservices & the First Law of Distributed Objects)”中强调了这一需求。凝聚力是确定服务边界的产物,这是模拟系统本质复杂性的必要步骤。
有趣的是,与Brooks所描述的复杂性似乎是矛盾的,微服务系统的技术无关上下文映射在某种程度上与它的实现的拓扑结构是同构的,远远超过一整套单体应用的拓扑。这可能是一个巧合,也表明我们正在采用这种微服务架构方法。
Part 2:设计微服务系统
最近我一直在与许多大型组织合作,帮助他们理解并实现Web API和微服务体系结构的价值。在这项工作中,我看到这些组织一直在努力于,如何定义企业里微服务之间的最佳边界。这是一个已知的问题,但没有解决。虽然关于凝聚式服务(cohesive services)的价值和有界上下文已经写了很多,但如何实际识别这些内容似乎没有指导意义。
这个问题的一个原因是,试图确定服务边界的人是技术人员,他们天生就在寻找技术解决方案,但是定义凝聚力和能力一致性的服务边界(cohesive, capability-aligned service boundaries)却需要领域专家。从根本上说,这应该是一种建模练习,它独立于任何技术的覆盖。然而,即使那些有意识地采用了技术无关建模方法的组织,也一直在努力定义服务边界。
发生这种情况的部分原因是,最流行的软件建模方法往往存在实施偏差。例如,领域驱动设计(DDD)倾向于面向对象的编程,而UML偏向数据建模的角度。除此之外,业界还没有就如何直观地描绘微服务系统模型达成共识,诸如应该描述哪些组件,应该如何表示关系,应该使用什么术语,更不用说这其中的任何工具。
不过,所有的希望都不会丢失。 DDD的一些概念,最明显的是“有界上下文”( bounded contexts)的概念,已经获得了普及,并启动了关于服务边界定义的行业对话。重要的是,DDD方法论中更高层次的抽象,包括域,子域,有界上下文,聚合(aggregates),上下文映射,都是技术不可知和模型为中心的。
理解系统的方法是关注组件之间的关系,如果想要一个微服务系统的基本表示,不需要比上下文映射更深入。因此,也许我们可以使用DDD上下文映射作为可视化微服务系统的起点。
即使在上下文映射层面,大型组织的完整服务系统也是不可理解的。因此,为了使上下文映射有用,为上下文映射本身设置上下文非常重要。这种上下文可以是特定整体应用程序的分解,或特定计划的服务交互。连贯地构建一个大型组织的微服务系统的唯一方法就是,逐条地,上下文地进行。
微服务上下文映射示例
来看一个例子。一家大型零售银行希望引入以客户为中心的服务,以实现留住客户的战略目标。它希望提供一种新的以客户为中心的支付解决方案,允许客户根据他们与银行的关系(他们的账户,投资,资产,交互模式)进行购买,而不是将授权决策基于一个特定帐户。
零售银行业务本身就是一个复杂的业务领域。客户,产品和服务渠道(分支机构,网上银行,销售点)等实体之间存在相互关系,以及与财务透明度和数据隐私相关的强制性规定。因此,尽管这种新的支付服务背后的想法很直观,但它却呈现出一个复杂的问题空间。那么从哪里开始呢?
继DDD方法松散之后,我们可以首先将零售银行业务领域划分为子域。作为一个起点,并遵循康威法则的必然性,可以从组成零售银行的组织单位开始,并与以客户为中心的支付解决方案相关。这些是:
这些子域可以通过以下方式进行可视化描述:
这给了我们一个考虑如何分类创建以客户为中心的支付解决方案服务的起点。自助银行和客户与卡片管理子域名突出显示,因为它们已被确定为解决方案的关键子域名。使用DDD的“语言边界”概念,显然有一些有界上下文出现在子域中。
在自助银行内部,有两种截然不同的有界上下文。网上银行渠道与手机银行渠道有共同的语言,他们都提供个性化的客户体验,而支持移动渠道的应用程序则是由网络渠道构建和共享组件。我们将这种情况称为context Online Banking。另一方面,销售点具有独特的语言,更多地集中在设备管理和商家关系上。我们将称之为销售点(context Point of Sale)。
客户与卡片管理甚至更加分散。核心客户信息,包括银行客户的权威列表,他们的联系信息和产品库,形成了一个独特的词汇表,我们称之为客户信息有界的上下文。另一种不同的语言被用来描述卡功能,这主要与支付活动有关。这是“消费者支付和交易”上下文。最后,还有一个客户身份上下文专注于客户安全和访问控制。
产品子域名对于此付款解决方案并不重要,但值得注意的是零售贷款和贷款子域划分为单独的PLC账户和信用卡上下文。完整的相关有界上下文如下所示:
在确定了有界上下文之后,现在可以考虑顾客将在其中使用哪些服务。如前所述,网上银行有两种服务消费者:网上银行web App和手机银行App。新的支付解决方案也将通过销售点的POS网络收到的消息请求所消耗。
客户身份上下文必须提供客户身份验证服务,以确保只有合适的请求者才能访问新的付款服务。客户信息背景包含客户和产品持有人之间的交叉参考,因此需要核心客户信息服务来为新的支付解决方案提供这些数据。
除此之外,跟踪客户财务活动,与其产品持有相关的财务事件,将有助于做出授权决策。因此,客户活动分析服务可以在客户信息上下文中创建。
消费者支付和交易环境是为新的以客户为中心的支付解决方案建立核心服务的场所。首先,客户需要注册新产品并设置其帐户和产品偏好。为此,引入了以客户为中心的支付管理服务。
对于非功能性原因(例如性能,安全性和可用性),我们将创建一个单独的授权支付服务,称为以客户为中心的支付授权服务。最后,由于向客户帐户发布交易可以晚于授权决定发布,因此我们将创建与授权服务分离的交易发布服务(Transaction Posting Service)。
对于产品子域,我们只会在每个有界的上下文中引用单个同名服务。例如,存款账户上下文只包含一个存款账户服务。整个服务系统现在看起来像这样:
这个设计过程的最后一步是注释将发生在服务之间的交互。事实上,人们可能想要模拟有界上下文之间的相互作用,以便梳理出单个服务,但为了覆盖视觉效果,我们将最后描述这一步骤。在这个阶段举例说明所有可能的交互会造成视觉混淆,因此只会关注一些能够帮助理解系统如何运作的关键信息。这里有些例子:
客户使用网上银行Web应用程序通过以客户为中心的支付管理服务,注册并设置新支付解决方案的偏好;以客户为中心的支付管理服务从客户信息服务中检索客户的产品组合;客户活动分析服务使用来自产品系统的信息构建客户财务状况;客户通过移动银行应用程序执行以客户为中心的支付,该应用程序称为以客户为中心的支付授权服务;以客户为中心的支付授权服务,依次使用来自以客户为中心的支付管理服务和客户活动分析服务的信息构建授权配置文件;一旦获得授权,支付将被转发至交易发布服务完成,然后调用相应的产品服务;
关于微服务系统有几点需要注意。首先,不要假定在为最终用户活动提供服务时,这些交互中的每一个都会实时发生。例如,尽管以客户为中心的授权服务需要来自其他一些服务的信息,但可以使用基于事件的方法结合缓存来确保其处理是独立的,以便实时服务客户的授权请求。
其次,可以在有界上下文之间建立防腐层(ACL),以提供松散的语义耦合。最后,虽然这是一个相当复杂的图片,但没有实现细节。换句话说,这种模式可以使用许多不同的技术来实现,包括语言,主机环境和网络协议。
最终的上下文映射(包括交互)如下所示:
最终,此系统设计过程的目的是,帮助企业如何在复杂解决方案中定义微服务的服务边界进行最佳猜测。希望这里的方法既能为服务建立一个良好的初始模型,又能更容易地重新绘制服务边界。
Part 3:微服务设计画布
微服务有自身的起源,通常从现有单体应用程序中涌现出来,以满足眼前的需求。提高交付速度的愿望推动了微服务的采用,开发人员通常采取“代码优先,问题排后”的方法,并重复实现有用的结果。这可行,但它是最佳的吗?回答这个问题会带来另一个问题:在开发微服务之前应该考虑哪些设计因素?正如软件架构师Simon Brown所说,“前期做大设计是愚蠢的,但没有前期的设计更愚蠢。”
借鉴精益画布(lean canvas )方法设计商业模型,介绍一下微服务设计画布。这个画布是一个工具,可以帮助企业在构建服务之前捕获服务中最重要的前端属性。画布采用了一种外部方法,它可以很好地将您的界面与其底层实现松散耦合。
你可以先在底部框中填写服务的自由格式“说明”。从这里开始,你应该完成“消费者任务”框,记录服务消费者需要执行的任务。列举服务的消费者以及他们需要执行的任务有助于明确服务的目的,并提供设计界面所需的材料输入。这些信息可以帮助填写“界面”框。在这里,消费者任务可以细分为与服务接口的特定交互。根据模式(查询,命令,事件)对交互进行分类,将有助于形成底层服务实现,并最终推动API的设计。
除了服务的任务和交互之外,还必须考虑服务的非功能方面。可以使用“质量(Qualities)”框来确定服务属性,例如可用性和性能级别,可扩展性方法和安全性期望。这将有助于消费者进一步了解服务,并影响其实施。总而言之,消费者的任务,界面和品质定义了服务的“表面”。这对系统的设计至关重要,因为它捕获了系统中其他利益相关者所需的最重要的信息。
在表面之下,还有一些关键的考虑因素。 “逻辑/规则”和“数据”框为服务设计人员提供了一个位置,以记录这些领域的关键因素。抵制在这个阶段太深的诱惑。没有必要为服务的工作编写一个完整的内部系统设计,但是可能需要感觉独特和支持表面属性所需的项目。最后,应列出服务“依赖关系”,以便调出服务需要的任务。对于具有相当数量业务逻辑的任务繁重的微服务,需要与更多面向数据的服务进行交互是很自然的。但是,本着微服务架构的精神,目标是最大限度地减少这些依赖关系。
来看几个示例画布。首先是以客户为中心的支付管理服务画布:
请注意,该服务看起来好像在交互中很直接,并且与其数据相对独立。 从非功能角度来看,这不是关键任务,也不需要最高级别的事务完整性。 因此,它的实现可能相当简单。
接下来是以客户为中心的支付授权服务的画布:
这项服务有一些更复杂的考虑因素。首先,它预计将是高容量和低延迟。这对服务的工程设计产生了明确的影响,需要复杂的授权信息缓存。其次,由于它的交互涉及金钱的流动,交易完整性和审计至关重要。最后,有一些事件交互意味着接口将超越典型的RESTful API。事实上,看到这些非功能性差异就清楚地说明,为什么将以客户为中心的支付解决方案的授权和管理功能,分解为单独的服务是一个很好的设计决策。在系统视图和服务视图之间迭代将有助于定义服务边界。
希望这个微服务设计画布可以成为着手微服务部署的企业的有用工具。它清楚地与API设计联系在一起。
作者:Matt McLarty
原文链接:
1、Visualizing Microservices: API Design and Strategy
https://dzone.com/articles/visualizing-microservices-api-design-and-strategy
2、Visualizing Microservices: Designing a Microservice System
https://dzone.com/articles/visualizing-microservices-designing-a-microservice
3、Visualizing Microservices: The Microservice Design Canvas
https://dzone.com/articles/visualizing-microservices-the-microservice-design