以消费者为导向的合同作为开发服务的一种方式


-供应商成功的秘诀是为消费者提供优质的产品……哦,就是服务。 好吧,同样重要的是不要沉迷于所有向后兼容的行为。
沃尔特·怀特


来自翻译


这是什么


这是描述消费者驱动的合同(CDC)模板的文章的翻译。
原件Ian Robinson在Martin Fowler的网站上发布。


那为什么


在微服务架构中,服务之间的依赖关系是问题的根源。 CDC模板以适合服务开发人员及其使用者的方式帮助解决这些问题。 Fowler在有关微服务架构的关键文章中引用了消费者驱动的合同:微服务。


是给谁的


本文对于为同一组织内的多个消费者开发服务的团队以及使用此类服务​​的团队特别有用。


关于条款


  • 我没有将“ 消费者驱动的合同”翻译成俄语-我们也没有在对话中翻译“测试驱动的开发”。 如有必要,可以使用选项“ 面向客户的合同”
  • 提供者合同消费者合同被翻译成供应商合同消费者合同 -它们在俄语中具有可持续使用性。
  • 服务的开发演化 (服务演化)-服务功能的完善,扩展。
  • 服务社区(服务社区)-供应商以及该服务的所有消费者。

前言


本文讨论了服务的开发人员和使用者之间出现的问题,例如,当供应商更改其合同的一部分时,尤其是文档布局时。 描述了两种应对它们的策略:


  1. 添加扩展点。
  2. 收到消息的“足够”验证。

两种策略都有助于保护消费者免受合同变更的影响,但不能使服务提供商获得洞察力:


  • 如何最好地使用这些策略;
  • 服务发展时需要做什么。

本文介绍了“ 消费者驱动的合同”模板,该模板使供应商可以更好地了解其客户,并将服务的开发重点放在消费者的需求上。


服务演进示例


为了说明我们在开发服务时遇到的一些问题,请考虑一个简单的ProductSearch,它使您可以在我们的产品目录中进行搜索。 搜索结果具有以下结构:



图1:搜索结果图


具有搜索结果的示例文档如下:


<?xml version="1.0" encoding="utf-8"?> <Products xmlns="urn:example.com:productsearch:products"> <Product> <CatalogueID>101</CatalogueID> <Name>Widget</Name> <Price>10.99</Price> <Manufacturer>Company A</Manufacturer> <InStock>Yes</InStock> </Product> <Product> <CatalogueID>300</CatalogueID> <Name>Fooble</Name> <Price>2.00</Price> <Manufacturer>Company B</Manufacturer> <InStock>No</InStock> </Product> </Products> 

当前有两个应用程序使用ProductSearch:一个内部营销应用程序和一个外部经销商Web应用程序。 两个客户端都使用XSD来验证收到的文档,然后再处理它们。 内部应用程序使用CatalogIDID,名称,价格和制造商字段。 外部应用程序-字段CatalogIDID,名称和价格。 他们中没有一个使用InStock字段:尽管考虑将其用于市场营销应用程序,但它在开发的早期阶段就被淘汰了。


开发服务的最常见方法之一是为一个或多个使用者将字段添加到文档中。 根据客户端和服务器部分的实现方式,即使是像这样的简单更改也会对业务及其合作伙伴造成高成本的后果。


在我们的示例中,ProductSearch服务运行了一段时间后,另一个转销商想要使用它,但是要求将Description字段添加到每个产品。 由于安排客户的方式,这种变化对供应商和现有客户均造成重大且代价高昂的后果。 它们每个的成本取决于我们实施更改的方式。 我们可以通过至少两种方式在“服务社区”成员之间分摊变更成本。 首先,我们可以更改原始架构,并要求每个使用者更新其架构副本以正确检查搜索结果。 更改系统的成本在供应商之间分配,面对这样的更改请求,供应商仍会进行一些更改; 以及对更新功能不感兴趣的消费者。 另一方面,我们可以为新客户添加第二个操作和方案,并为现有客户保留原始操作和方案。 现在,所有改进都在供应商一方,但是服务的复杂性和支持成本在增加。


插曲:烧毁SOA


使用服务的主要优点是:


  1. 增加了组织的灵活性。
  2. 降低实施变更的总成本。

SOA通过将业务功能置于专用的可重用服务中来增强灵活性。 然后,这些服务将被使用和编排以执行主要业务流程。 通过减少服务之间的依赖性,这可以降低更改的成本,从而使服务可以响应于更改或计划外事件而快速重建和调整。


但是,只有SOA的实施允许服务独立发展,企业才能充分实现这些好处。 为了增加独立性,我们创建了服务合同。 尽管如此,我们还是被迫与供应商一样频繁地修改消费者,这主要是因为消费者依赖于供应商合同的特定版本。 最后,供应商在修改合同的任何内容时都变得极为谨慎。 特别是因为他们不知道消费者如何执行此合同。 在最坏的情况下,消费者与供应商联系在一起,将文档的整个大纲描述为其内部逻辑的一部分。


合同确保服务独立性; 矛盾的是,它们也可能以不良的方式束缚供应商和消费者。 在不考虑我们在SOA中实现的合同的功能和作用的情况下,我们将服务公开给“隐蔽”通信。 缺少有关服务的消费者如何在代码中实施合同的任何信息,以及(对供应商和消费者而言)都缺乏实施限制,这些都会破坏SOA的可感知收益。 简而言之,企业开始厌倦服务。


模式版本控制


我们可以通过对模式进行版本控制来开始研究会干扰我们ProductSearch服务的合同问题和依赖关系。 WC3技术架构小组(TAG)描述了许多版本控制策略,这些策略可以帮助我们以减少依赖问题的方式设计服务电路。 这些策略的范围从过于宽松的none (要求服务不区分方案的不同版本并接受所有更改)到极其保守的大爆炸 (要求服务在接收到意外版本的消息时引发错误)之间。


两种极端情况都会产生无法为业务带来价值的问题,并会增加系统的总拥有成本。 显式和隐式的“无版本”策略导致系统的交互无法预测,开发欠佳且改进成本很高。 另一方面,“大爆炸”战略创造了高度相互联系的服务格局,在这种格局中,电路变化会影响所有供应商和消费者,削弱可用性,减慢发展速度,并减少获利机会。


我们示例中的服务社区有效地实施了“大爆炸”策略。 鉴于增加业务系统价值的成本,很明显,供应商和消费者将从更灵活的版本控制策略(TAG称为兼容性策略)中受益。 它提供电路的直接和向后兼容性。 随着服务的发展,向后兼容的电路允许新电路的使用者接受旧电路的实例:创建用于处理向后兼容的新版本的提供程序可以接受旧电路的格式的请求。 另一方面,直接兼容性允许旧模式的使用者处理新模式的实例。 这是现有ProductSearch用户的关键点:如果最初设计搜索结果时就考虑到直接兼容性,那么消费者就可以处理新版本的响应而无需进一步开发。


延伸点


具有向前和向后兼容性的方案映射是一个很好理解的设计任务,最好用“ 必须忽略”可扩展性模式来表达(请参阅David OrchardDeira Obasanjo的文章 )。 “ 必须忽略”模板建议方案包含扩展点,这些扩展点允许您将元素添加到类型中,并为每个元素添加其他属性。 该模板还建议XML描述一个处理模型,该模型定义使用者对扩展的处理方式。 最简单的模型要求使用者忽略他们无法识别的元素-因此是模板的名称。 该模型还可能要求使用者处理具有“必须理解”标志的元素,或者如果他们无法理解则给出错误。


这是我们搜索结果文档的原始大纲:


 <?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns="urn:example.com:productsearch:products" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:example.com:productsearch:products" id="Products"> <xs:element name="Products" type="Products" /> <xs:complexType name="Products"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="Product" /> </xs:sequence> </xs:complexType> <xs:complexType name="Product"> <xs:sequence> <xs:element name="CatalogueID" type="xs:int" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Price" type="xs:double" /> <xs:element name="Manufacturer" type="xs:string" /> <xs:element name="InStock" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:schema> 

现在让我们回到过去,从一开始我们将为我们的服务指明一个兼容的,可扩展的方案:


 <?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns="urn:example.com:productsearch:products" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:example.com:productsearch:products" id="Products"> <xs:element name="Products" type="Products" /> <xs:complexType name="Products"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="Product" /> </xs:sequence> </xs:complexType> <xs:complexType name="Product"> <xs:sequence> <xs:element name="CatalogueID" type="xs:int" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Price" type="xs:double" /> <xs:element name="Manufacturer" type="xs:string" /> <xs:element name="InStock" type="xs:string" /> <xs:element minOccurs="0" maxOccurs="1" name="Extension" type="Extension" /> </xs:sequence> </xs:complexType> <xs:complexType name="Extension"> <xs:sequence> <xs:any minOccurs="1" maxOccurs="unbounded" namespace="##targetNamespace" processContents="lax" /> </xs:sequence> </xs:complexType> </xs:schema> 

该设计包括每个产品的可选扩展元素。 扩展元素本身可以包含来自目标名称空间的一个或多个元素:



图2:可扩展的搜索结果架构


现在,我们被要求添加一个产品描述,我们可以发布一个带有附加Description元素的新模式,提供商将其插入扩展点。 这使ProductSearch可以返回包含产品说明的结果,并且消费者可以使用新方案来验证整个文档。 使用旧方案的使用者不会中断,尽管他们不会处理“说明”字段。 新的搜索结果文档如下所示:


 <?xml version="1.0" encoding="utf-8"?> <Products xmlns="urn:example.com:productsearch:products"> <Product> <CatalogueID>101</CatalogueID> <Name>Widget</Name> <Price>10.99</Price> <Manufacturer>Company A</Manufacturer> <InStock>Yes</InStock> <Extension> <Description>Our top of the range widget</Description> </Extension> </Product> <Product> <CatalogueID>300</CatalogueID> <Name>Fooble</Name> <Price>2.00</Price> <Manufacturer>Company B</Manufacturer> <InStock>No</InStock> <Extension> <Description>Our bargain fooble</Description> </Extension> </Product> </Products> 

修改后的方案如下所示:


 <?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns="urn:example.com:productsearch:products" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:example.com:productsearch:products" id="Products"> <xs:element name="Products" type="Products" /> <xs:complexType name="Products"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="Product" /> </xs:sequence> </xs:complexType> <xs:complexType name="Product"> <xs:sequence> <xs:element name="CatalogueID" type="xs:int" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Price" type="xs:double" /> <xs:element name="Manufacturer" type="xs:string" /> <xs:element name="InStock" type="xs:string" /> <xs:element minOccurs="0" maxOccurs="1" name="Extension" type="Extension" /> </xs:sequence> </xs:complexType> <xs:complexType name="Extension"> <xs:sequence> <xs:any minOccurs="1" maxOccurs="unbounded" namespace="##targetNamespace" processContents="lax" /> </xs:sequence> </xs:complexType> <xs:element name="Description" type="xs:string" /> </xs:schema> 

请注意,可扩展架构的第一个版本与第二个兼容,而第二个版本与第一个向后兼容。 但是,这种灵活性是以增加复杂性为代价的。 可扩展的模式使我们能够进行无法预料的更改,但是它们提供了可能永远不需要的功能。 虽然我们输了:


  • 简单设计的表现力
  • 通过引入容器的元信息元素来清晰地呈现数据。

此外,我们将不讨论可扩展方案。 可以说扩展点使我们能够在图表和文档中进行直接和向后兼容的更改,而不会影响服务的供应商和消费者。 但是,当我们需要进行违反合同的变更时,扩展方案并不能帮助我们控制系统的发展。


破坏兼容性



-是的,我们的团队在最新版本中违反了向后兼容性! 他们只是无法抗拒一些协议优化...好吧,别生气,亲爱的!
卡拉·鲍林


我们的ProductSearch服务在搜索结果中包含一个字段,指示该产品的可用性。 该服务使用对古老库存系统的昂贵调用来填充此字段-这种依赖维护成本很高。 供应商希望消除这种依赖关系,清理设计并改善整体系统性能。 并且优选地,在不影响消费者的情况下。 与消费者进行沟通后,供应商团队发现没有一个消费者应用程序实际上对该字段有任何作用。 也就是说,价格昂贵,这是多余的。


不幸的是,在当前情况下,如果我们从可扩展模式中删除InStock字段,则会破坏现有的使用者。 要修复提供者,我们必须修复整个系统:当我们从供应商删除功能并发布新合同时,将需要使用新方案重新安装每个消费者应用程序,并且应该对服务之间的交互进行彻底的测试。 在这方面,ProductSearch服务不能独立于消费者而开发:供应商和消费者必须“同时跳跃”。


我们的服务社区对它的发展感到失望,因为每个消费者都有一个“隐藏的”依赖关系,这反映了供应商在消费者内部逻辑中的整个合同。 消费者使用XSD验证,并在较小程度上通过静态绑定到代码中的文档方案,隐含地接受整个供应商合同,无论他们打算只使用一部分。


戴维·奥查德(David Orchard)提到可靠性原则,提供了一些有关如何避免该问题的线索:“实施应在行为上保持保守,在接受他人的基础上保持自由。” 我们可以通过声明消息接收者应该执行“足够”的验证来增强该原则,即消息接收者应该只处理他们使用的数据,并且应该对接收到的数据执行显式有限或有针对性的验证-而不是隐式无限验证XSD处理中固有的全部或全部。


Schematron


我们可以配置用户端验证的一种方法是使用掩码或正则表达式来表示文档元素的路径,可能使用树结构验证语言(例如Schematron) 。 使用Schematron,每个ProductSearch用户都可以指定他们希望在搜索结果中找到的内容:


 <?xml version="1.0" encoding="utf-8" ?> <schema xmlns="http://www.ascc.net/xml/schematron"> <title>ProductSearch</title> <ns uri="urn:example.com:productsearch:products" prefix="p"/> <pattern name="Validate search results"> <rule context="*//p:Product"> <assert test="p:CatalogueID">Must contain CatalogueID node</assert> <assert test="p:Name">Must contain Name node</assert> <assert test="p:Price">Must contain Price node</assert> </rule> </pattern> 

Schematron实现通常将Schematron架构(如该架构)转换为XSLT转换,消息接收者可以将其应用于文档以进行验证。


请注意,此图不包含有关源文档中使用者应用程序不需要的元素的假设。 因此,明确描述了仅对必需元素的验证。 如果源文档的架构更改没有违反Schematron架构中描述的明确期望,则即使在验证过程中删除了先前必需的元素,也不会在验证期间拒绝对源文档架构的更改。


这是解决合同和依存关系问题的相对简单的解决方案,并且不需要我们向文档中添加隐式元信息元素。 因此,让我们及时回滚并恢复本文开头所述的简单电路。 但是这一次,我们还将坚持要求消费者自由行事,只验证和处理他们所需的信息(使用Schematron方案,而不是XSD来检查收到的消息)。 现在,供应商添加了产品说明,便可以发布修订的大纲,而不会影响现有客户。 同样,如果发现InStock字段未由任何使用者检查或处理,则该服务可以再次修改搜索结果图-而不会影响使用者。


在此过程结束时,ProductSearch响应方案如下所示:


 <?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns="urn:example.com:productsearch:products" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:example.com:productsearch:products" id="Products"> <xs:element name="Products" type="Products" /> <xs:complexType name="Products"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="unbounded" name="Product" type="Product" /> </xs:sequence> </xs:complexType> <xs:complexType name="Product"> <xs:sequence> <xs:element name="CatalogueID" type="xs:int" /> <xs:element name="Name" type="xs:string" /> <xs:element name="Price" type="xs:double" /> <xs:element name="Manufacturer" type="xs:string" /> <xs:element name="Description" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:schema> 

消费者驱动的合同


在上面的示例中使用Schematron可以得出一些有趣的结论,这些结论超出了供应商与消费者之间的合同验证范围。 在本节中,我们重点介绍和总结其中的一些想法,并通过我们称为“ 消费者合同 ”的模板来表达它们。


首先要注意的是,文档方案只是服务提供商应为消费者提供的内容的一部分,以便他们可以使用其功能。 我们将此外包信息称为供应商合同


供应商合同


供应商合同将服务功能描述为使用此功能所需的一组导出元素。 在服务开发方面,合同是用于描述业务功能的一组导出元素的容器。 这些项目的列表包括:


  • 文件计划 。 我们已经详细讨论了文件方案。 除接口外,文档计划也是供应商合同的一部分,随着服务的发展,很可能会更改合同。 但是也许正因为如此,它们还是我们在各种服务开发策略(例如扩展点和使用掩码作为文档元素路径的使用)方面拥有最多实施经验的部分。
  • 介面 提供者界面以最简单的形式包括一组导出的交易签名,消费者可以使用这些签名来控制供应商的行为。 消息系统通常会导出相对简单的交易签名,并将业务内容放入它们交换的消息中。 在这样的系统中,接收到的消息是根据在消息的标题或主体中编码的语义来处理的。 RPC- , , - . , , , , .
  • . , , "-" "fire-and-forget". , . , , . , «» , , , "" . , " ", , . , , .
  • . , , , , . , . Web- WS-Policy , WS-SecurityPolicy, " ", .
  • . , , , , . .

, , , , . , : , . , - , , , , . , , WS-Basic, .


, . , , , .


, ? , , (, ) , . , , , , - . .


:




, — , — . Schematron . , , . , , , , "" , . , , , - , .


, /, , ( ). , , , .


, , , , , . , , .



3:


:


  • . . .
  • . , . - , - . , , . , . , ; .
  • . , / .

Consumer-Driven Contracts


. , , , . , , . , . , Consumer-Driven Contracts .


---, . , , . ; , , .


Consumer-Driven Contracts :


  • . , . , / , / .
  • . , , .
  • . . , Consumer-Driven Contract , . , , .


, :


/
/

实作


Consumer-Driven Contracts , Consumer-Driven Contracts. , , , / .


. , -. unit-, , , . Schematron WS-Policy, .


, , . Consumer-Driven Contracts , , , , . / , . , , - .



Consumer-Driven Contracts , . -, . , . Consumer-driven contracts , , — , , . "" , -, . — — , .


, , . Consumer-Driven Contracts . Consumer-driven contracts , , . , , , , . , , .



Consumer-Driven Contracts , , Consumer-Driven Contracts , . , , CDC.


Consumer-Driven Contracts , , : , , , . , , , . .


, , Consumer-Driven Contracts, , . , — : , . , , , . , , , , . , — , deprecated , .


Consumer-Driven Contracts . , , . , , «» , .


, Consumer-Driven Contracts . , - — - — , , , WS-Agreement WSLA, SLA. , ; . — — , " " .


, : , . , , , , .


参考文献


  1. Ian Robinson. Consumer-Driven Contracts ( )
  2. Martin Fowler. Microservices , Decentralized Governance
  3. . , " "
  4. .

Source: https://habr.com/ru/post/zh-CN423803/


All Articles