
- O segredo do sucesso do fornecedor é fornecer aos consumidores produtos de qualidade ... oh, isto é, serviço. Bem, e também é importante não levar a sério a violação da compatibilidade com versões anteriores.
Walter White
Do tradutor
O que é isso
Esta é uma tradução de um artigo que descreve o modelo de contratos orientados a consumidores (CDC).
O original foi publicado no site de Martin Fowler por Ian Robinson .
Por que isso
Em uma arquitetura de microsserviço, dependências entre serviços são uma fonte de problemas. O modelo do CDC ajuda a resolver esses problemas de maneira adequada aos desenvolvedores do serviço e a seus consumidores. Fowler cita Contratos Orientados a Consumidores em seu artigo principal sobre arquitetura de microsserviços: microsserviços.
Para quem é
Este artigo será especialmente útil para equipes que desenvolvem serviços para vários consumidores da mesma organização e equipes que consomem esses serviços.
Sobre termos
- Não traduzi contratos orientados a consumidores para russo - não traduzimos desenvolvimento orientado a testes em uma conversa. Se necessário, você pode usar a opção Contratos orientados ao cliente .
- Contrato de fornecedor e Contrato de consumidor são traduzidos como contrato de fornecedor e contrato de consumidor - eles têm uso sustentável em russo.
- Desenvolvimento ou evolução de um serviço (evolução de serviço) - refinamento, expansão da funcionalidade do serviço.
- A comunidade de serviço (comunidade de serviço) - o fornecedor e todos os consumidores desse serviço.
Preâmbulo
Este artigo discute os problemas que surgem entre desenvolvedores e consumidores de serviços, por exemplo, quando os fornecedores alteram parte de seus contratos, em particular os layouts de documentos. Duas estratégias para lidar com eles são descritas:
- Adicionando pontos de extensão.
- Validação "suficiente" das mensagens recebidas.
Ambas as estratégias ajudam a proteger os consumidores de alterações no contrato, mas não fornecem ao provedor de serviços informações:
- qual a melhor forma de usar essas estratégias;
- o que precisa ser feito à medida que o serviço se desenvolve.
O artigo descreve o modelo de contratos orientados ao consumidor , que permite que os fornecedores entendam melhor seus clientes e concentra o desenvolvimento do serviço naquilo que os consumidores precisam.
Exemplo de evolução de serviço
Para ilustrar alguns dos problemas que encontramos ao desenvolver serviços, considere um simples ProductSearch, que permite pesquisar em nosso catálogo de produtos. O resultado da pesquisa possui a seguinte estrutura:

Figura 1: Diagrama do resultado da pesquisa
Um documento de exemplo com resultados da pesquisa é o seguinte:
<?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>
No momento, o ProductSearch está sendo usado por dois aplicativos: um aplicativo de marketing interno e um aplicativo da web de revendedor externo. Ambos os clientes usam o XSD para verificar os documentos recebidos antes de processá-los. O aplicativo interno usa os campos CatalogIDID, Nome, Preço e Fabricante; aplicativo externo - campos CatalogIDID, Nome e Preço. Nenhum deles usa o campo InStock: embora tenha sido considerado para um aplicativo de marketing, foi descartado em um estágio inicial de desenvolvimento.
Uma das maneiras mais comuns de desenvolver um serviço é adicionar um campo a um documento para um ou mais consumidores. Dependendo de como as partes do cliente e do servidor foram implementadas, mesmo alterações simples como essa podem ter consequências onerosas para os negócios e seus parceiros.
Em nosso exemplo, depois que o serviço ProductSearch estiver em execução há algum tempo, um segundo revendedor deseja usá-lo, mas pede para adicionar o campo Descrição a cada produto. Devido à forma como os clientes são organizados, essa alteração tem consequências significativas e dispendiosas para o fornecedor e os clientes existentes. O custo de cada um deles depende de como implementamos a mudança. Existem pelo menos duas maneiras pelas quais podemos compartilhar o custo das mudanças entre os membros da "comunidade de serviços". Primeiro, podemos alterar nosso esquema original e exigir que cada consumidor atualize sua cópia do esquema para verificar corretamente os resultados da pesquisa. O custo da alteração do sistema aqui é distribuído entre o fornecedor, que, diante de tal solicitação de alteração, ainda fará algumas alterações; e consumidores que não estão interessados em funcionalidades atualizadas. Por outro lado, poderíamos adicionar uma segunda operação e esquema para um novo cliente e manter a operação e esquema originais para os clientes existentes. Todas as melhorias estão agora do lado do fornecedor, mas a complexidade do serviço e o custo de seu suporte aumentam.
Interlúdio: SOA queimado
As principais vantagens do uso de serviços são:
- Maior flexibilidade organizacional.
- Custos gerais mais baixos para implementar mudanças.
SOA aprimora a flexibilidade, colocando funções de negócios em serviços dedicados e reutilizáveis. Em seguida, esses serviços são usados e orquestrados para executar os principais processos de negócios. Isso reduz o custo das alterações reduzindo as dependências entre os serviços, permitindo que eles sejam reconstruídos e ajustados rapidamente em resposta a alterações ou eventos não planejados.
No entanto, uma empresa pode obter esses benefícios completamente somente se a implementação da SOA permitir que os serviços evoluam de forma independente. Para aumentar a independência, criamos contratos de serviço. Apesar disso, somos obrigados a modificar os consumidores com a mesma frequência que o fornecedor, principalmente porque os consumidores dependem da versão específica do contrato do fornecedor. No final, os fornecedores começam a ser extremamente cautelosos ao modificar qualquer elemento do contrato; em particular, porque eles não têm idéia de como os consumidores implementam esse contrato. Na pior das hipóteses, os consumidores estão vinculados ao fornecedor, descrevendo todo o esquema do documento como parte de sua lógica interna.
Os contratos garantem a independência do serviço; paradoxalmente, eles também podem vincular fornecedores e consumidores de maneiras indesejáveis. Sem pensar na função e no papel dos contratos que implementamos em nossa SOA, expomos nossos serviços a comunicações "secretas". A ausência de informações sobre como os consumidores do serviço implementam o contrato no código, bem como a falta de restrições de implementação (tanto para o fornecedor quanto para o consumidor), minam os benefícios percebidos da SOA. Em suma, uma empresa começa a se cansar de serviços.
Versão do esquema
Podemos começar a pesquisar problemas e dependências do contrato que interferem no nosso serviço ProductSearch, fazendo a versão do esquema. O WC3 Technical Architecture Group (TAG) descreveu várias estratégias de versão que podem nos ajudar a projetar circuitos para nossos serviços de maneira a reduzir os problemas de dependência. Essas estratégias variam de nenhuma liberalmente excessiva, que exige que os serviços não façam distinção entre versões diferentes do esquema e aceite todas as alterações, a um big bang extremamente conservador, que exige que o serviço gere um erro se receber uma versão inesperada da mensagem.
Ambos os extremos criam problemas que não agregam valor aos negócios e aumentam o custo total de propriedade do sistema. Estratégias explícitas e implícitas de "sem versão" levam a sistemas que são imprevisíveis em suas interações, pouco desenvolvidos e com alto custo de melhorias. As estratégias do Big Bang, por outro lado, criam cenários de serviços altamente interconectados, nos quais as mudanças nos circuitos afetam todos os fornecedores e consumidores, prejudicando a disponibilidade, diminuindo o desenvolvimento e diminuindo as oportunidades de lucro.
A comunidade de serviços do nosso exemplo implementa efetivamente a estratégia do Big Bang. Dados os custos de aumentar o valor do sistema para os negócios, é claro que fornecedores e consumidores se beneficiarão de uma estratégia de controle de versão mais flexível - o que o TAG chama de estratégia de compatibilidade . Ele fornece compatibilidade direta e reversa de circuitos. Com o desenvolvimento dos serviços, os esquemas compatíveis com versões anteriores permitem que os consumidores de novos esquemas aceitem instâncias do esquema antigo: um provedor criado para processar novas versões compatíveis com versões anteriores pode, no entanto, aceitar a solicitação no formato de esquema antigo. A compatibilidade direta, por outro lado, permite que os consumidores de esquemas mais antigos processem uma instância de um novo esquema. Este é um ponto importante para os usuários existentes do ProductSearch: se os resultados da pesquisa foram originalmente projetados com compatibilidade direta, os consumidores poderiam processar as respostas da nova versão sem precisar de mais desenvolvimento.
Pontos de extensão
O mapeamento de esquemas com compatibilidade com versões anteriores e posteriores é uma tarefa de design bem compreendida, melhor expressa pelo padrão de extensibilidade Must Ignore (consulte os artigos de David Orchard e Deira Obasanjo ). O modelo Deve Ignorar recomenda que os esquemas incluam pontos de extensão que permitam adicionar elementos a tipos e atributos adicionais para cada elemento. O modelo também recomenda que o XML descreva um modelo de processamento que defina o que os consumidores fazem com as extensões. O modelo mais simples exige que os consumidores ignorem elementos que não reconhecem - daí o nome do modelo. O modelo também pode exigir que os consumidores processem elementos que possuem o sinalizador Must Understanding ou cometer um erro se eles não puderem entendê-los.
Aqui está nosso esboço original do documento de resultados da pesquisa:
<?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>
Agora vamos voltar no tempo e, desde o início, indicaremos um esquema extensível e compatível para o nosso serviço:
<?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>
Esse design inclui um elemento de extensão opcional para cada produto. O próprio elemento de extensão pode conter um ou mais elementos do espaço para nome de destino:

Figura 2: Esquema extensível de resultados de pesquisa
Agora que somos solicitados a adicionar uma descrição do produto, podemos publicar um novo esquema com um elemento Description adicional, que o provedor insere no ponto de extensão. Isso permite que o ProductSearch retorne resultados contendo descrições de produtos, e os consumidores usam o novo esquema para validar todo o documento. Os consumidores que usam o esquema antigo não serão interrompidos, embora não processem o campo Descrição. Um novo documento de resultado de pesquisa é semelhante a este:
<?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>
O esquema revisado é assim:
<?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>
Observe que a primeira versão do esquema extensível é compatível com a segunda e a segunda é compatível com a anterior. No entanto, essa flexibilidade tem o custo de maior complexidade. Esquemas extensíveis nos permitem fazer alterações imprevistas, mas fornecem recursos que talvez nunca sejam necessários; enquanto perdemos:
- expressividade a partir de um design simples
- apresentação clara dos dados, introduzindo elementos de meta-informações do contêiner.
Além disso, não discutiremos esquemas extensíveis. Basta dizer que os pontos de expansão nos permitem fazer alterações diretas e retrocompatíveis nos diagramas e documentos, sem afetar os fornecedores e consumidores do serviço. No entanto, expandir o esquema não nos ajuda a controlar a evolução do sistema quando precisamos fazer uma alteração que viola o contrato.
Quebrando a compatibilidade

- Sim, nossa equipe violou a compatibilidade com versões anteriores na versão mais recente! Eles simplesmente não resistiram à leve otimização do protocolo ... Bem, não se ofenda, querida!
Carla Borin
Nosso serviço ProductSearch inclui nos resultados da pesquisa um campo indicando a disponibilidade deste produto. O serviço preenche esse campo usando uma chamada cara para um sistema de inventário antigo - uma dependência que é cara de manter. O fornecedor gostaria de remover essa dependência, limpar o design e melhorar o desempenho geral do sistema. E, de preferência, sem afetar os consumidores. Ao se comunicar com os consumidores, a equipe de fornecedores descobre que nenhum dos aplicativos de consumidores realmente faz alguma coisa com esse campo; isto é, sendo caro, é redundante.
Infelizmente, na situação atual, se removermos o campo InStock do nosso esquema extensível, interromperemos os consumidores existentes. Para consertar o provedor, precisamos consertar todo o sistema: quando removemos a funcionalidade do fornecedor e publicamos um novo contrato, cada aplicativo de consumidor precisará ser reinstalado com um novo esquema, e a interação entre os serviços será exaustivamente testada. O serviço ProductSearch a esse respeito não pode ser desenvolvido independentemente dos consumidores: o fornecedor e os consumidores devem "pular ao mesmo tempo".
Nossa comunidade de serviços está decepcionada com sua evolução, porque cada consumidor possui uma dependência "oculta", que reflete todo o contrato do fornecedor na lógica interna do consumidor. Os consumidores, usando a validação XSD e, em menor grau, a ligação estática ao esquema de documentos no código, aceitam implicitamente todo o contrato do fornecedor, independentemente de sua intenção de usar apenas parte.
David Orchard fornece algumas pistas sobre como podemos evitar esse problema, referindo-se ao princípio da confiabilidade : "A implementação deve ser conservadora em seu comportamento e liberal no que aceita dos outros". Podemos fortalecer esse princípio no contexto do desenvolvimento de serviços, declarando que os destinatários da mensagem devem realizar verificação "suficiente": ou seja, devem processar apenas os dados que usam e devem executar verificação explicitamente limitada ou direcionada dos dados que recebem - em oposição à validação implicitamente ilimitada Tudo ou nada inerente ao processamento XSD.
Schematron
Uma maneira de configurar a validação do lado do consumidor é usar máscaras ou expressões regulares para caminhos para documentar elementos, possivelmente usando uma linguagem de validação de estrutura em árvore, como o Schematron . Usando o Schematron, cada usuário do ProductSearch pode especificar o que espera encontrar nos resultados da pesquisa:
<?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>
As implementações do Schematron geralmente convertem um esquema do Schematron, como este, em uma transformação XSLT que o destinatário da mensagem pode aplicar ao documento para validar.
Observe que este diagrama não contém suposições sobre elementos no documento de origem que não são necessários pelo aplicativo consumidor. Assim, a validação apenas dos elementos requeridos é explicitamente descrita. Alterações no esquema do documento de origem não serão rejeitadas durante a validação se não violarem as expectativas explícitas descritas no esquema Schematron, mesmo que ele remova os elementos necessários anteriormente.
Aqui está uma solução relativamente fácil para nossos problemas de contrato e dependência, e isso não exige que adicionemos elementos implícitos de meta-informação ao documento. Então, vamos reverter novamente no tempo e restaurar o circuito simples descrito no início do artigo. Mas desta vez, insistiremos também que os consumidores são livres em seu comportamento, validam e processam apenas as informações de que precisam (usando esquemas Schematron, não XSD para verificar as mensagens recebidas). Agora que um fornecedor adiciona uma descrição para um produto, ele pode publicar um esboço revisado sem afetar os clientes existentes. Da mesma forma, se considerar que o campo InStock não é verificado ou processado por nenhum consumidor, o serviço pode revisar o diagrama de resultados da pesquisa - novamente, sem afetar os consumidores.
No final desse processo, o esquema de resposta do ProductSearch se parece com o seguinte:
<?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>
Contratos orientados ao consumidor
O uso do Schematron no exemplo acima leva a algumas conclusões interessantes sobre contratos entre fornecedores e consumidores que vão além da validação de documentos. Nesta seção, destacamos e resumimos algumas dessas idéias e as expressamos em termos do modelo que chamamos de contratos do consumidor .
A primeira coisa a observar é que os esquemas de documentos são apenas parte do que um provedor de serviços deve oferecer aos consumidores para que eles possam usar sua funcionalidade. Chamamos essas informações terceirizadas de contrato de fornecedor .
Contratos com Fornecedores
O contrato do fornecedor descreve a funcionalidade do serviço como um conjunto de elementos exportados necessários para usar essa funcionalidade. Em termos de desenvolvimento de serviço, um contrato é um contêiner para um conjunto de elementos exportados que descrevem as funções de negócios. Uma lista desses itens inclui:
- Esquemas de documentos . Já discutimos detalhadamente esquemas de documentos. Juntamente com as interfaces, os esquemas de documentos fazem parte do contrato do fornecedor, cuja mudança é mais provável à medida que o serviço se desenvolve; mas, talvez por isso, elas também sejam as partes com as quais temos mais experiência em implementação com várias estratégias de desenvolvimento de serviços, como pontos de extensão e uso de máscaras como caminhos para documentar elementos.
- Interfaces Na sua forma mais simples, as interfaces do provedor incluem um conjunto de assinaturas de transação exportadas que um consumidor pode usar para controlar o comportamento do fornecedor. Os sistemas de mensagens geralmente exportam assinaturas de transação relativamente simples e colocam o conteúdo comercial nas mensagens que eles trocam. Nesses sistemas, as mensagens recebidas são processadas de acordo com a semântica codificada no cabeçalho ou no corpo da mensagem. 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 , . , , .
, :
Implementação
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. , ; . — — , " " .
, : , . , , , , .
Referências
- Ian Robinson. Consumer-Driven Contracts ( )
- Martin Fowler. Microservices , Decentralized Governance
- . , " "
- .