Melhores práticas: Controle de versão de contrato de dados

Este tópico lista as melhores práticas para criação de contratos de dados que podem evoluir com facilidade ao longo do tempo. Para obter mais informações sobre contratos de dados, confira os tópicos em Como usar contratos de dados.

Observação sobre a validação de esquema

Ao discutir o controle de versão do contrato de dados, é importante observar que não há suporte para controle de versão no esquema de contrato de dados exportado pelo WCF (Windows Communication Foundation), além do fato de que os elementos são marcados como opcionais por padrão.

Isso significa que mesmo o cenário de controle de versão mais comum, como a adição de um novo membro de dados, não pode ser implementado de uma forma que seja perfeita em relação a determinado esquema. As versões mais recentes de um contrato de dados (com um novo membro de dados, por exemplo) não validam o uso do esquema antigo.

No entanto, há muitos cenários em que a conformidade estrita do esquema não é necessária. Muitas plataformas de serviços Web, incluindo serviços Web WCF e XML criados com o ASP.NET, não executam a validação de esquema por padrão e, portanto, toleram elementos extras que não são descritos pelo esquema. Ao trabalhar com essas plataformas, muitos cenários de controle de versão são mais fáceis de serem implementados.

Portanto, há dois conjuntos de diretrizes de controle de versão do contrato de dados: um conjunto para cenários em que a validade estrita do esquema é importante e outro definido para cenários em que ela não é importante.

Controle de versão quando a validação de esquema é necessária

Se a validade estrita do esquema for necessária em todas as direções (nova para antiga e antiga para nova), os contratos de dados deverão ser considerados imutáveis. Se o controle de versão for necessário, um novo contrato de dados deverá ser criado, com um nome ou um namespace diferente, e o contrato de serviço que usa o tipo de dados deverá ter o controle de versão de acordo.

Por exemplo, um contrato de serviço de processamento de ordem de compra chamado PoProcessing com uma operação PostPurchaseOrder usa um parâmetro que está em conformidade com um contrato de dados PurchaseOrder. Se o contrato PurchaseOrder precisar ser alterado, crie um contrato de dados, ou seja, PurchaseOrder2, que inclua as alterações. Em seguida, você precisa lidar com o controle de versão no nível do contrato de serviço. Por exemplo, criando uma operação PostPurchaseOrder2 que use o parâmetro PurchaseOrder2 ou criando um contrato de serviço PoProcessing2 em que a operação PostPurchaseOrder use um contrato de dados PurchaseOrder2.

Observe que as alterações em contratos de dados referenciados por outros contratos de dados também se estendem para a camada do modelo de serviço. Por exemplo, no cenário anterior, o contrato de dados PurchaseOrder não precisa ser alterado. No entanto, ele contém um membro de dados de um contrato de dados Customer, que, por sua vez, continha um membro de dados do contrato de dados Address, que precisa ser alterado. Nesse caso, você precisará criar um contrato de dados Address2 com as alterações necessárias, um contrato de dados Customer2 que contenha o membro de dados Address2 e um contrato de dados PurchaseOrder2 que contenha um membro de dados Customer2. Como no caso anterior, o contrato de serviço também precisará ter o controle de versão.

Embora nestes exemplos os nomes sejam alterados (acrescentando um "2"), a recomendação é alterar os namespaces em vez dos nomes acrescentando novos namespaces com um número de versão ou uma data. Por exemplo, o contrato de dados http://schemas.contoso.com/2005/05/21/PurchaseOrder será alterado para o contrato de dados http://schemas.contoso.com/2005/10/14/PurchaseOrder.

Para obter mais informações, confira Melhores práticas: Controle de versão do serviço.

Ocasionalmente, você precisa garantir a conformidade estrita do esquema para mensagens enviadas pelo seu aplicativo, mas não pode contar com as mensagens de entrada para estar estritamente em conformidade com o esquema. Nesse caso, há o perigo de que uma mensagem de entrada possa conter dados incorretos. Os valores incorretos são armazenados e retornados pelo WCF e, portanto, resultam no envio de mensagens inválidas para esquema. Para evitar esse problema, o recurso de viagem de ida e volta deve ser desativado. Há duas maneiras de fazer isso.

Para obter mais informações sobre a viagem de ida e volta, confira Contratos de dados compatíveis com versões posteriores.

Controle de versão quando a validação de esquema não é necessária

A conformidade estrita do esquema raramente é necessária. Muitas plataformas toleram elementos extras não descritos por um esquema. Desde que isso seja tolerado, o conjunto completo de recursos descritos em Controle de versão do contrato de dados e Contratos de dados compatíveis com versões posteriores pode ser usado. As diretrizes a seguir são recomendadas.

Algumas das diretrizes precisam ser seguidas exatamente para o envio de novas versões de um tipo, em que uma mais antiga é esperada, ou para o envio de uma antiga, em que a nova é esperada. Outras diretrizes não são estritamente necessárias, mas estão listadas aqui porque podem ser afetadas pelo futuro do controle de versão do esquema.

  1. Não tente fazer o controle de versão dos contratos de dados por herança de tipo. Para criar versões posteriores, altere o contrato de dados em um tipo existente ou crie um tipo não relacionado.

  2. O uso da herança com os contratos de dados é permitido, desde que a herança não seja usada como um mecanismo de controle de versão e que determinadas regras sejam seguidas. Se um tipo derivar de determinado tipo base, não o faça derivar de um tipo base diferente em uma versão futura (a menos que ele tenha o mesmo contrato de dados). Há uma exceção a isso: você pode inserir um tipo na hierarquia entre um tipo de contrato de dados e o tipo base, mas somente se ele não contém membros de dados com os mesmos nomes de outros membros em qualquer versão possível dos outros tipos na hierarquia. Em geral, o uso de membros de dados com os mesmos nomes em diferentes níveis da mesma hierarquia de herança pode levar a sérios problemas de controle de versão e deve ser evitado.

  3. Começando com a primeira versão de um contrato de dados, sempre implemente IExtensibleDataObject para habilitar a viagem de ida e volta. Para obter mais informações, consulte Contratos de dados compatíveis por encaminhamento. Se você tiver lançado uma ou mais versões de um tipo sem implementar essa interface, implemente-a na próxima versão do tipo.

  4. Em versões posteriores, não altere o nome nem o namespace do contrato de dados. Se você alterar o nome ou o namespace do tipo subjacente ao contrato de dados, preserve o nome e o namespace do contrato de dados usando os mecanismos apropriados, como a propriedade Name do DataContractAttribute. Para obter mais informações sobre a nomeação, confira Nomes de contratos de dados.

  5. Em versões posteriores, não altere os nomes de nenhum membro de dados. Se você alterar o nome do campo, da propriedade ou do evento subjacente ao membro de dados, use a propriedade Name do DataMemberAttribute para preservar o nome do membro de dados existente.

  6. Em versões posteriores, não altere o tipo de nenhum campo, propriedade ou evento subjacente a um membro de dados, de modo que o contrato de dados resultante desse membro de dados seja alterado. Tenha em mente que os tipos de interface são equivalentes a Object para fins de determinar o contrato de dados esperado.

  7. Em versões posteriores, não altere a ordem dos membros de dados existentes ajustando a propriedade Order do atributo DataMemberAttribute.

  8. Em versões posteriores, novos membros de dados podem ser adicionados. Eles devem sempre seguir estas regras:

    1. A propriedade IsRequired deve sempre ser mantida com o valor padrão de false.

    2. Se um valor padrão igual a null ou zero do membro for inaceitável, um método de retorno de chamada deverá ser fornecido usando o OnDeserializingAttribute para fornecer um padrão razoável, caso o membro não esteja presente no fluxo de entrada. Para obter mais informações sobre o retorno de chamada, confira Retornos de chamada de serialização tolerantes à versão.

    3. A DataMemberAttribute.Order propriedade deve ser usada para garantir que todos os membros de dados recém-adicionados apareçam após os membros de dados existentes. A maneira recomendada de fazer isso é a seguinte: nenhum dos membros de dados na primeira versão do contrato de dados deve ter a propriedade Order definida. Todos os membros de dados adicionados na versão 2 do contrato de dados devem ter sua Order propriedade definida como 2. Todos os membros de dados adicionados na versão 3 do contrato de dados devem ter a Order definida como 3 etc. É permitido ter mais de um membro de dados definido com o mesmo número de Order.

  9. Não remova os membros de dados em versões posteriores, mesmo que a propriedade IsRequired tenha sido mantida como a propriedade padrão de false em versões anteriores.

  10. Não altere a propriedade IsRequired em nenhum membro de dados existente de versão para versão.

  11. Para os membros de dados necessários (em que IsRequired é true), não altere a propriedade EmitDefaultValue de versão para versão.

  12. Não tente criar hierarquias de controle de versão ramificadas. Ou seja, sempre deve haver um caminho em, pelo menos, uma direção de qualquer versão para qualquer outra versão usando apenas as alterações permitidas por essas diretrizes.

    Por exemplo, se a versão 1 de um contrato de dados Person contiver apenas o membro de dados Name, você não deverá criar a versão 2a do contrato adicionando apenas o membro Age e a versão 2b adicionando apenas o membro Address. Ir de 2a a 2b envolverá a remoção de Age e a adição de Address. Ir na outra direção implicará a remoção de Address e a adição de Age. A remoção de membros não é permitida por essas diretrizes.

  13. Em geral, você não deve criar subtipos de tipos de contrato de dados existentes em uma nova versão do aplicativo. Da mesma forma, você não deve criar contratos de dados que são usados no lugar de membros de dados declarados como Object ou como tipos de interface. A criação dessas classes só é permitida quando você sabe que pode adicionar os novos tipos à lista de tipos conhecidos de todas as instâncias do aplicativo antigo. Por exemplo, na versão 1 do aplicativo, você pode ter o tipo de contrato de dados LibraryItem com os subtipos de contrato de dados Book e Newspaper. LibraryItem terá então uma lista de tipos conhecidos que contém Book e Newspaper. Suponha que agora você adicione um tipo Magazine na versão 2 que é um subtipo de LibraryItem. Se você enviar uma instância de Magazine da versão 2 para a versão 1, o contrato de dados Magazine não será encontrado na lista de tipos conhecidos e uma exceção será gerada.

  14. Você não deve adicionar nem remover membros de enumeração entre versões. Você também não deve renomear membros de enumeração, a menos que use a propriedade Name no atributo EnumMemberAttribute para manter os nomes deles no modelo de contrato de dados iguais.

  15. As coleções são intercambiáveis no modelo de contrato de dados, conforme descrito em Tipos de coleta em contratos de dados. Isso permite um amplo grau de flexibilidade. No entanto, lembre-se de não alterar inadvertidamente um tipo de coleção de uma forma não intercambiável de versão para versão. Por exemplo, não o altere de uma coleção não personalizada (ou seja, sem o atributo CollectionDataContractAttribute) para uma coleção personalizada ou de uma coleção personalizada para uma não personalizada. Além disso, não altere as propriedades no CollectionDataContractAttribute de versão para a versão. A única alteração permitida será adicionar uma propriedade Name ou Namespace se o nome ou o namespace do tipo de coleção subjacente tiver sido alterado e você precisar tornar o nome do contrato de dados e o namespace da mesma forma que em uma versão anterior.

Algumas das diretrizes listadas aqui poderão ser ignoradas com segurança quando circunstâncias especiais se aplicarem. Entenda por completo os mecanismos de serialização, de desserialização e de esquema envolvidos antes de se desviar das diretrizes.

Confira também