Controle de versão de serviço

Após a implantação inicial e, potencialmente, várias vezes durante sua vida útil, os serviços (e os pontos finais que expõem) podem precisar ser alterados por vários motivos, como a mudança das necessidades de negócios, os requisitos de tecnologia da informação ou para resolver outros problemas. Cada alteração introduz uma nova versão do serviço. Este tópico explica como considerar o controle de versão no Windows Communication Foundation (WCF).

Quatro categorias de alterações de serviço

As alterações aos serviços que possam ser necessárias podem ser classificadas em quatro categorias:

  • Alterações de contrato: por exemplo, uma operação pode ser adicionada ou um elemento de dados em uma mensagem pode ser adicionado ou alterado.

  • Alterações de endereço: por exemplo, um serviço é movido para um local diferente onde os pontos de extremidade têm novos endereços.

  • Alterações de vinculação: por exemplo, um mecanismo de segurança é alterado ou suas configurações são alteradas.

  • Alterações na implementação: por exemplo, quando uma implementação de método interno muda.

Algumas dessas mudanças são chamadas de "breaking" e outras são "nonbreaking". Uma alteração é ininterrupta se todas as mensagens que teriam sido processadas com êxito na versão anterior forem processadas com êxito na nova versão. Qualquer alteração que não cumpra esse critério é uma mudança de rutura .

Orientação ao serviço e controle de versão

Um dos princípios da orientação para o serviço é que os serviços e os clientes são autónomos (ou independentes). Entre outras coisas, isso implica que os desenvolvedores de serviços não podem assumir que controlam ou mesmo sabem sobre todos os clientes de serviço. Isso elimina a opção de reconstruir e reimplantar todos os clientes quando um serviço muda de versão. Este tópico pressupõe que o serviço adere a este princípio e, portanto, deve ser alterado ou "versionado" independentemente de seus clientes.

Nos casos em que uma alteração de quebra é inesperada e não pode ser evitada, um aplicativo pode optar por ignorar esse princípio e exigir que os clientes sejam reconstruídos e reimplantados com uma nova versão do serviço.

Controle de versão de contrato

Os contratos utilizados por um cliente não têm de ser iguais ao contrato utilizado pelo serviço; só precisam de ser compatíveis.

Para contratos de serviço, compatibilidade significa que novas operações expostas pelo serviço podem ser adicionadas, mas as operações existentes não podem ser removidas ou alteradas semanticamente.

Para contratos de dados, compatibilidade significa que novas definições de tipo de esquema podem ser adicionadas, mas as definições de tipo de esquema existentes não podem ser alteradas de maneiras diferentes. As alterações significativas podem incluir a remoção de membros de dados ou a alteração do tipo de dados de forma incompatível. Esta funcionalidade permite ao serviço alguma latitude na alteração da versão dos seus contratos sem quebrar clientes. As próximas duas seções explicam as alterações ininterruptas que podem ser feitas nos dados do WCF e nos contratos de serviço.

Controle de versão de contrato de dados

Esta seção trata do controle de versão de dados ao usar as DataContractSerializer classes e DataContractAttribute .

Controle de versão rigoroso

Em muitos cenários, quando a alteração de versões é um problema, o desenvolvedor do serviço não tem controle sobre os clientes e, portanto, não pode fazer suposições sobre como eles reagiriam às alterações no XML ou esquema da mensagem. Nesses casos, você deve garantir que as novas mensagens serão validadas em relação ao esquema antigo, por dois motivos:

  • Os clientes antigos foram desenvolvidos com o pressuposto de que o esquema não mudará. Eles podem falhar ao processar mensagens para as quais nunca foram projetados.

  • Os clientes antigos podem executar a validação do esquema real em relação ao esquema antigo antes mesmo de tentar processar as mensagens.

A abordagem recomendada nesses cenários é tratar os contratos de dados existentes como imutáveis e criar novos contratos com nomes qualificados XML exclusivos. O desenvolvedor de serviços adicionaria novos métodos a um contrato de serviço existente ou criaria um novo contrato de serviço com métodos que usam o novo contrato de dados.

Muitas vezes, um desenvolvedor de serviços precisa escrever alguma lógica de negócios que deve ser executada em todas as versões de um contrato de dados, além de código de negócios específico da versão para cada versão do contrato de dados. O apêndice no final deste tópico explica como as interfaces podem ser usadas para satisfazer essa necessidade.

Versionamento Lax

Em muitos outros cenários, o desenvolvedor de serviços pode supor que adicionar um novo membro opcional ao contrato de dados não interromperá os clientes existentes. Isso requer que o desenvolvedor do serviço investigue se os clientes existentes não estão executando a validação de esquema e se ignoram membros de dados desconhecidos. Nesses cenários, é possível aproveitar os recursos do contrato de dados para adicionar novos membros de forma ininterrupta. O desenvolvedor do serviço pode fazer essa suposição com confiança se os recursos do contrato de dados para controle de versão já foram usados para a primeira versão do serviço.

WCF, ASP.NET Web Services e muitas outras pilhas de serviços Web suportam versionamento lax: ou seja, eles não lançam exceções para novos membros de dados desconhecidos nos dados recebidos.

É fácil acreditar erroneamente que adicionar um novo membro não quebrará os clientes existentes. Se você não tiver certeza de que todos os clientes podem lidar com versionamento frouxo, a recomendação é usar as diretrizes rígidas de controle de versão e tratar os contratos de dados como imutáveis.

Para obter diretrizes detalhadas para o controle de versão laxista e rigoroso de contratos de dados, consulte Práticas recomendadas: controle de versão de contrato de dados.

Distinguindo entre contratos de dados e tipos .NET

Uma classe ou estrutura .NET pode ser projetada como um contrato de dados aplicando o DataContractAttribute atributo à classe. O tipo .NET e suas projeções de contrato de dados são duas questões distintas. É possível ter vários tipos .NET com a mesma projeção de contrato de dados. Essa distinção é especialmente útil para permitir que você altere o tipo .NET enquanto mantém o contrato de dados projetado, mantendo assim a compatibilidade com clientes existentes, mesmo no sentido estrito da palavra. Há duas coisas que você sempre deve fazer para manter essa distinção entre o tipo .NET e o contrato de dados:

  • Especifique a Name e Namespace. Você sempre deve especificar o nome e o namespace do seu contrato de dados para evitar que o nome e o namespace do seu tipo .NET sejam expostos no contrato. Dessa forma, se você decidir posteriormente alterar o namespace .NET ou o nome do tipo, seu contrato de dados permanecerá o mesmo.

  • Especifique Name. Você deve sempre especificar o nome de seus membros de dados para evitar que seu nome de membro .NET seja exposto no contrato. Dessa forma, se você decidir mais tarde alterar o nome .NET do membro, seu contrato de dados permanecerá o mesmo.

Alterar ou remover membros

Alterar o nome ou o tipo de dados de um membro ou remover membros de dados é uma alteração importante, mesmo que o controle de versão laxista seja permitido. Se necessário, crie um novo contrato de dados.

Se a compatibilidade do serviço for de alta importância, você pode considerar ignorar os membros de dados não utilizados em seu código e deixá-los no lugar. Se você estiver dividindo um membro de dados em vários membros, considere deixar o membro existente no lugar como uma propriedade que pode executar a divisão e reagregação necessárias para clientes de nível inferior (clientes que não são atualizados para a versão mais recente).

Da mesma forma, as alterações no nome ou namespace do contrato de dados estão interrompendo as alterações.

Viagens de ida e volta de dados desconhecidos

Em alguns cenários, há a necessidade de "ida e volta" dados desconhecidos que vêm de membros adicionados em uma nova versão. Por exemplo, um serviço "versionNew" envia dados com alguns membros recém-adicionados para um cliente "versionOld". O cliente ignora os membros recém-adicionados ao processar a mensagem, mas reenvia esses mesmos dados, incluindo os membros recém-adicionados, de volta para o serviço versionNew. O cenário típico para isso são atualizações de dados em que os dados são recuperados do serviço, alterados e retornados.

Para habilitar a viagem de ida e volta para um tipo específico, o tipo deve implementar a IExtensibleDataObject interface. A interface contém uma propriedade, ExtensionData que retorna o ExtensionDataObject tipo. A propriedade é usada para armazenar quaisquer dados de versões futuras do contrato de dados que são desconhecidos para a versão atual. Esses dados são opacos para o cliente, mas quando a instância é serializada, o conteúdo da ExtensionData propriedade é gravado com o restante dos dados dos membros do contrato de dados.

Recomenda-se que todos os seus tipos implementem esta interface para acomodar membros novos e futuros desconhecidos.

Bibliotecas de contratos de dados

Pode haver bibliotecas de contratos de dados em que um contrato é publicado num repositório central e os implementadores de serviços e tipos implementam e expõem contratos de dados desse repositório. Nesse caso, quando você publica um contrato de dados no repositório, não tem controle sobre quem cria os tipos que o implementam. Assim, não é possível modificar o contrato uma vez publicado, tornando-o efetivamente imutável.

Ao usar o XmlSerializer

Os mesmos princípios de controle de versão se aplicam ao usar a XmlSerializer classe. Quando for necessário um controle de versão rigoroso, trate os contratos de dados como imutáveis e crie novos contratos de dados com nomes exclusivos e qualificados para as novas versões. Quando tiver certeza de que o controle de versão lax pode ser usado, você pode adicionar novos membros serializáveis em novas versões, mas não alterar ou remover membros existentes.

Nota

O XmlSerializer usa os XmlAnyElementAttribute atributos e XmlAnyAttributeAttribute para dar suporte à viagem de ida e volta de dados desconhecidos.

Controle de versão de contrato de mensagem

As diretrizes para o controle de versão de contratos de mensagens são muito semelhantes aos contratos de controle de versão de dados. Se for necessário um controle de versão rigoroso, você não deve alterar o corpo da mensagem, mas criar um novo contrato de mensagem com um nome qualificado exclusivo. Se você sabe que pode usar o controle de versão lax, pode adicionar novas partes do corpo da mensagem, mas não alterar ou remover as existentes. Esta orientação aplica-se a contratos de mensagens nuas e encapsuladas.

Os cabeçalhos de mensagem sempre podem ser adicionados, mesmo se o controle de versão estrito estiver em uso. O sinalizador MustUnderstand pode afetar o controle de versão. Em geral, o modelo de controle de versão para cabeçalhos no WCF é conforme descrito na especificação SOAP.

Controle de versão de contrato de serviço

Semelhante ao controle de versão de contrato de dados, o controle de versão de contrato de serviço também envolve adicionar, alterar e remover operações.

Especificando nome, namespace e ação

Por padrão, o nome de um contrato de serviço é o nome da interface. Seu namespace padrão é http://tempuri.org, e a ação de cada operação é http://tempuri.org/contractname/methodname. É recomendável especificar explicitamente um nome e namespace para o contrato de serviço e uma ação para cada operação para evitar o uso http://tempuri.org e impedir que nomes de interface e método sejam expostos no contrato de serviço.

Adicionando parâmetros e operações

Adicionar operações de serviço expostas pelo serviço é uma alteração ininterrupta porque os clientes existentes não precisam se preocupar com essas novas operações.

Nota

Adicionar operações a um contrato de retorno de chamada duplex é uma alteração importante.

Alterando o parâmetro de operação ou os tipos de retorno

Alterar os tipos de parâmetro ou retorno geralmente é uma alteração de quebra, a menos que o novo tipo implemente o mesmo contrato de dados implementado pelo tipo antigo. Para fazer essa alteração, adicione uma nova operação ao contrato de serviço ou defina um novo contrato de serviço.

Removendo operações

A remoção de operações também é uma mudança de rutura. Para fazer essa alteração, defina um novo contrato de serviço e exponha em um novo ponto de extremidade.

Contratos de Falha

O FaultContractAttribute atributo permite que um desenvolvedor de contrato de serviço especifique informações sobre falhas que podem ser retornadas das operações do contrato.

A lista de defeitos descrita no contrato de prestação de serviços não é considerada exaustiva. A qualquer momento, uma operação pode devolver falhas que não estão descritas no seu contrato. Portanto, alterar o conjunto de falhas descritas no contrato não é considerado quebra. Por exemplo, adicionar uma nova falha ao contrato usando o FaultContractAttribute ou removendo uma falha existente do contrato.

Bibliotecas de contratos de serviços

As organizações podem ter bibliotecas de contratos em que um contrato é publicado num repositório central e os implementadores de serviços implementam contratos a partir desse repositório. Nesse caso, quando você publica um contrato de serviço no repositório, não tem controle sobre quem cria os serviços que o implementam. Portanto, não é possível modificar o contrato de serviço uma vez publicado, tornando-o efetivamente imutável. O WCF oferece suporte à herança de contrato, que pode ser usada para criar um novo contrato que estende os contratos existentes. Para usar esse recurso, defina uma nova interface de contrato de serviço que herda da interface de contrato de serviço antiga e, em seguida, adicione métodos à nova interface. Em seguida, altere o serviço que implementa o contrato antigo para implementar o novo contrato e altere a definição de ponto de extremidade "versionOld" para usar o novo contrato. Para clientes "versionOld", o ponto de extremidade continuará a aparecer como expondo o contrato "versionOld"; para clientes "versionNew", o ponto de extremidade aparecerá para expor o contrato "versionNew".

Endereço e controle de versão de vinculação

As alterações no endereço do ponto de extremidade e na associação estão quebrando as alterações, a menos que os clientes sejam capazes de descobrir dinamicamente o novo endereço de ponto de extremidade ou ligação. Um mecanismo para implementar esse recurso é usando um registro UDDI (Universal Discovery Description and Integration) e o padrão de invocação UDDI, onde um cliente tenta se comunicar com um ponto de extremidade e, em caso de falha, consulta um registro UDDI conhecido para obter os metadados de ponto de extremidade atuais. Em seguida, o cliente usa o endereço e a associação desses metadados para se comunicar com o ponto de extremidade. Se essa comunicação for bem-sucedida, o cliente armazenará em cache o endereço e as informações de vinculação para uso futuro.

Serviço de roteamento e controle de versão

Se as alterações feitas em um serviço estiverem quebrando alterações e você precisar ter duas ou mais versões diferentes de um serviço em execução simultaneamente, poderá usar o Serviço de Roteamento WCF para rotear mensagens para a instância de serviço apropriada. O Serviço de Roteamento WCF usa roteamento baseado em conteúdo, em outras palavras, ele usa informações dentro da mensagem para determinar para onde encaminhar a mensagem. Para obter mais informações sobre o Serviço de Roteamento WCF, consulte Serviço de Roteamento. Para obter um exemplo de como usar o WCF Routing Service para controle de versão de serviço, consulte Como: Controle de versão de serviço.

Anexo

A orientação geral de controle de versão de contrato de dados quando o controle de versão estrito é necessário é tratar os contratos de dados como imutáveis e criar novos contratos quando forem necessárias alterações. Uma nova classe precisa ser criada para cada novo contrato de dados, portanto, um mecanismo é necessário para evitar ter que pegar o código existente que foi escrito em termos da classe de contrato de dados antiga e reescrevê-lo em termos da nova classe de contrato de dados.

Um desses mecanismos é usar interfaces para definir os membros de cada contrato de dados e escrever código de implementação interno em termos de interfaces, em vez das classes de contrato de dados que implementam as interfaces. O código a seguir para a versão 1 de um serviço mostra uma IPurchaseOrderV1 interface e um PurchaseOrderV1:

public interface IPurchaseOrderV1  
{  
    string OrderId { get; set; }  
    string CustomerId { get; set; }  
}  
  
[DataContract(  
Name = "PurchaseOrder",  
Namespace = "http://examples.microsoft.com/WCF/2005/10/PurchaseOrder")]  
public class PurchaseOrderV1 : IPurchaseOrderV1  
{  
    [DataMember(...)]  
    public string OrderId {...}  
    [DataMember(...)]  
    public string CustomerId {...}  
}  

Enquanto as operações do contrato de serviços seriam escritas em termos de PurchaseOrderV1, a lógica comercial real seria em termos de IPurchaseOrderV1. Então, na versão 2, haveria uma nova IPurchaseOrderV2 interface e uma nova PurchaseOrderV2 classe, como mostrado no código a seguir:

public interface IPurchaseOrderV2  
{  
    DateTime OrderDate { get; set; }  
}

[DataContract(
Name = "PurchaseOrder",  
Namespace = "http://examples.microsoft.com/WCF/2006/02/PurchaseOrder")]  
public class PurchaseOrderV2 : IPurchaseOrderV1, IPurchaseOrderV2  
{  
    [DataMember(...)]  
    public string OrderId {...}  
    [DataMember(...)]  
    public string CustomerId {...}  
    [DataMember(...)]  
    public DateTime OrderDate { ... }  
}  

O contrato de serviços seria atualizado para incluir novas operações que são escritas em termos de PurchaseOrderV2. A lógica de negócios existente escrita em termos de continuaria a funcionar e a nova lógica de negócios que precisa da IPurchaseOrderV1OrderDate propriedade seria escrita em termos de IPurchaseOrderV2.PurchaseOrderV2

Consulte também