Controle de versão de contrato de dados

À medida que os aplicativos evoluem, talvez você também precise alterar os contratos de dados usados pelos serviços. Este tópico explica como versionar contratos de dados. Este tópico descreve os mecanismos de versão do contrato de dados. Para obter uma visão geral completa e orientação prescritiva sobre controle de versão, consulte Práticas recomendadas: controle de versão de contrato de dados.

Alterações interruptiva e não interruptiva

As alterações em um contrato de dados podem ser interruptiva e não interruptiva. Quando um contrato de dados é alterado de forma não interruptiva, um aplicativo que usa a versão mais antiga do contrato pode se comunicar com um aplicativo que usa a versão mais recente, e um aplicativo que usa a versão mais recente do contrato pode se comunicar com um aplicativo que usa a versão mais antiga. Por outro lado, uma mudança interruptiva impede a comunicação em uma ou ambas as direções.

Quaisquer alterações em um tipo que não afetem como ele é transmitido e recebido são não interruptivas. Tais alterações não alteram o contrato de dados, apenas o tipo subjacente. Por exemplo, você pode alterar o nome de um campo de maneira não interruptiva se definir a propriedade Name do DataMemberAttribute para o nome da versão mais antiga. O código a seguir mostra a versão 1 de um contrato de dados.

// Version 1
[DataContract]
public class Person
{
    [DataMember]
    private string Phone;
}
' Version 1
<DataContract()> _
Public Class Person
    <DataMember()> _
    Private Phone As String
End Class

O código a seguir mostra uma alteração não interruptiva.

// Version 2. This is a non-breaking change because the data contract
// has not changed, even though the type has.
[DataContract]
public class Person
{
    [DataMember(Name = "Phone")]
    private string Telephone;
}
' Version 2. This is a non-breaking change because the data contract 
' has not changed, even though the type has.
<DataContract()> _
Public Class Person
    <DataMember(Name:="Phone")> _
    Private Telephone As String
End Class

Algumas alterações modificam os dados transmitidos, mas podem ou não ser interruptivas. As seguintes mudanças são sempre interruptivas:

  • Alterar o valor Name ou Namespace de um contrato de dados.

  • Alterar a ordem dos membros de dados usando a propriedade Order do DataMemberAttribute.

  • Renomear um membro de dados.

  • Alterar o contrato de dados de um membro de dados. Por exemplo, alterar o tipo de membro de dados de um inteiro para uma cadeia de caracteres ou de um tipo com um contrato de dados chamado "Cliente" para um tipo com um contrato de dados chamado "Pessoa".

As seguintes alterações também são possíveis.

Adicionar e remover membros de dados

Na maioria dos casos, adicionar ou remover um membro de dados não é uma alteração importante, a menos que você exija validade de esquema estrita (novas instâncias validadas em relação ao esquema antigo).

Quando um tipo com um campo extra é desserializado em um tipo com um campo ausente, as informações extras são ignoradas. (Ele também pode ser armazenado para fins de ida e volta; para obter mais informações, consulte Contratos de dados compatíveis com o futuro).

Quando um tipo com um campo ausente é desserializado em um tipo com um campo extra, o campo extra é deixado em seu valor padrão, geralmente zero ou null. (O valor padrão pode ser alterado; para obter mais informações, consulte Retornos de chamada de serialização tolerantes à versão).

Por exemplo, você pode usar a classe CarV1 em um cliente e a classe CarV2 em um serviço, ou pode usar a classe CarV1 em um serviço e a classe CarV2 em um cliente.

// Version 1 of a data contract, on machine V1.
[DataContract(Name = "Car")]
public class CarV1
{
    [DataMember]
    private string Model;
}

// Version 2 of the same data contract, on machine V2.
[DataContract(Name = "Car")]
public class CarV2
{
    [DataMember]
    private string Model;

    [DataMember]
    private int HorsePower;
}
' Version 1 of a data contract, on machine V1.
<DataContract(Name:="Car")> _
Public Class CarV1
    <DataMember()> _
    Private Model As String
End Class

' Version 2 of the same data contract, on machine V2.
<DataContract(Name:="Car")> _
Public Class CarV2
    <DataMember()> _
    Private Model As String

    <DataMember()> _
    Private HorsePower As Integer
End Class

O ponto de extremidade da versão 2 pode enviar dados com êxito para o ponto de extremidade da versão 1. A serialização da versão 2 do contrato de dados Car produz um XML semelhante ao seguinte.

<Car>  
    <Model>Porsche</Model>  
    <HorsePower>300</HorsePower>  
</Car>  

O mecanismo de desserialização na V1 não encontra um membro de dados correspondente para o campo HorsePower e descarta esses dados.

Além disso, o ponto de extremidade da versão 1 pode enviar dados para o ponto de extremidade da versão 2. A serialização da versão 1 do contrato de dados Car produz um XML semelhante ao seguinte.

<Car>  
    <Model>Porsche</Model>  
</Car>  

O desserializador da versão 2 não sabe como definir o campo HorsePower porque não há dados correspondentes no XML de entrada. Em vez disso, o campo é definido com o valor padrão de 0.

Membros de dados obrigatórios

Um membro de dados pode ser marcado como obrigatório definindo a propriedade IsRequired de DataMemberAttribute como true. Se os dados necessários estiverem ausentes durante a desserialização, uma exceção será lançada em vez de definir o membro de dados para seu valor padrão.

Adicionar um membro de dados obrigatório é uma alteração interruptiva. Ou seja, o tipo mais recente ainda pode ser enviado para pontos de extremidade com o tipo mais antigo, mas não o contrário. A remoção de um membro de dados que foi marcado como necessário em qualquer versão anterior também é uma alteração interruptiva.

Alterar o valor da propriedade IsRequired de true para false não é interruptivo, mas alterá-lo de false para true pode ser interruptivo se alguma versão anterior do tipo não tiver o membro de dados em questão.

Observação

Embora a propriedade IsRequired esteja definida como true, os dados de entrada podem ser nulos ou zero, e um tipo deve ser preparado para lidar com essa possibilidade. Não use IsRequired como um mecanismo de segurança para proteção contra dados de entrada incorretos.

Valores padrão omitidos

É possível (embora não recomendado) definir a propriedade EmitDefaultValue no atributo DataMemberAttribute como false, conforme descrito em Valores padrão do membro de dados. Se esta configuração for false, o membro de dados não será emitido se estiver definido com seu valor padrão (geralmente nulo ou zero). Isso não é compatível com membros de dados necessários em versões diferentes de duas maneiras:

  • Um contrato de dados com um membro de dados necessário em uma versão não pode receber dados padrão (nulos ou zero) de uma versão diferente na qual o membro de dados tenha EmitDefaultValue definido como false.

  • Um membro de dados obrigatório que tenha EmitDefaultValue definido como false não pode ser usado para serializar seu valor padrão (nulo ou zero), mas pode receber tal valor na desserialização. Isso cria um problema de ida e volta (os dados podem ser lidos, mas os mesmos dados não podem ser gravados). Portanto, se IsRequired for true e EmitDefaultValue for false em uma versão, a mesma combinação deve ser aplicada a todas as outras versões, de modo que nenhuma versão do contrato de dados possa produzir um valor que não resulte em uma viagem de ida e volta.

Considerações do esquema

Para obter uma explicação de qual esquema é produzido para tipos de contrato de dados, consulte Referência de esquema de contrato de dados.

O esquema que o WCF produz para tipos de contrato de dados não faz provisões para controle de versão. Ou seja, o esquema exportado de uma determinada versão de um tipo contém apenas os membros de dados presentes nessa versão. A implementação da interface IExtensibleDataObject não altera o esquema de um tipo.

Os membros de dados são exportados para o esquema como elementos opcionais por padrão. Ou seja, o valor minOccurs (atributo XML) é definido como 0. Os membros de dados necessários são exportados com minOccurs definido como 1.

Muitas das mudanças consideradas não interruptiva estão, na verdade, é interruptiva se for necessária uma adesão estrita ao esquema. No exemplo anterior, uma instância CarV1 com apenas o elemento Model seria validada em relação ao esquema CarV2 (que tem Model e Horsepower, mas ambos são opcionais). No entanto, o inverso não é verdadeiro: uma instância CarV2 falharia na validação em relação ao esquema CarV1.

A viagem de ida e volta também envolve algumas considerações adicionais. Para obter mais informações, consulte a seção "Considerações do esquema" em Contratos de dados compatíveis com o futuro.

Outras alterações permitidas

A implementação da interface IExtensibleDataObject é uma mudança ininterrupta. No entanto, o suporte de ida e volta não existe para versões do tipo anteriores à versão em que IExtensibleDataObject foi implementado. Para obter mais informações, consulte Contratos de dados compatíveis por encaminhamento.

Enumerações

Adicionar ou remover um membro de enumeração é uma alteração interruptiva. A alteração do nome de um membro de enumeração está sendo interrompida, a menos que seu nome de contrato seja mantido igual ao da versão antiga usando o atributo EnumMemberAttribute. Para obter mais informações, consulte Tipos de enumeração em contratos de dados.

Coleções

A maioria das alterações de coleção são ininterruptas porque a maioria dos tipos de coleção são intercambiáveis entre si no modelo de contrato de dados. No entanto, personalizar uma coleção não personalizada ou vice-versa é uma mudança interruptiva. Além disso, alterar as configurações de personalização da coleção é uma alteração interruptiva; ou seja, alterar o nome e o namespace do contrato de dados, repetindo o nome do elemento, o nome do elemento-chave e o nome do elemento de valor. Para obter mais informações sobre personalização de coleção, consulte Tipos de coleção em contratos de dados.
Naturalmente, alterar o contrato de dados do conteúdo de uma coleção (por exemplo, mudar de uma lista de inteiros para uma lista de cadeia de caracteres) é uma alteração interruptiva.

Confira também