Dela via


Versionshantering av tjänster

Efter den inledande distributionen, och eventuellt flera gånger under deras livslängd, kan tjänster (och slutpunkterna de exponerar) behöva ändras av flera olika orsaker, till exempel ändrade affärsbehov, krav på informationsteknik eller för att åtgärda andra problem. Varje ändring introducerar en ny version av tjänsten. Det här avsnittet beskriver hur du överväger versionshantering i Windows Communication Foundation (WCF).

Fyra kategorier av tjänständringar

De ändringar av tjänster som kan krävas kan klassificeras i fyra kategorier:

  • Kontraktsändringar: En åtgärd kan till exempel läggas till, eller så kan ett dataelement i ett meddelande läggas till eller ändras.

  • Adressändringar: Till exempel flyttas en tjänst till en annan plats där slutpunkter har nya adresser.

  • Bindningsändringar: Till exempel ändras en säkerhetsmekanism eller dess inställningar ändras.

  • Implementeringsändringar: Till exempel när en intern metodimplementering ändras.

Vissa av dessa ändringar kallas "icke-bakåtkompatibla" och andra är "icke-inbrytande". En ändring går inte att bryta om alla meddelanden som skulle ha bearbetats i den tidigare versionen bearbetas korrekt i den nya versionen. Alla ändringar som inte uppfyller det kriteriet är en icke-bakåtkompatibel ändring.

Tjänstorientering och versionshantering

En av grundsatserna för tjänstorientering är att tjänster och klienter är autonoma (eller oberoende). Detta innebär bland annat att tjänstutvecklare inte kan anta att de kontrollerar eller ens känner till alla tjänstklienter. Detta eliminerar möjligheten att återskapa och distribuera om alla klienter när en tjänst ändrar versioner. Det här avsnittet förutsätter att tjänsten följer den här grundsats och därför måste ändras eller "versionshanteras" oberoende av sina klienter.

I fall där en icke-bakåtkompatibel ändring är oväntad och inte kan undvikas kan ett program välja att ignorera den här grundsats och kräva att klienter återskapas och distribueras om med en ny version av tjänsten.

Versionshantering av kontrakt

Kontrakt som används av en klient behöver inte vara samma som det kontrakt som används av tjänsten. de behöver bara vara kompatibla.

För tjänstkontrakt innebär kompatibilitet att nya åtgärder som exponeras av tjänsten kan läggas till, men befintliga åtgärder kan inte tas bort eller ändras semantiskt.

För datakontrakt innebär kompatibilitet att nya schematypsdefinitioner kan läggas till, men befintliga schematypsdefinitioner kan inte ändras på olika sätt. Icke-bakåtkompatibla ändringar kan vara att ta bort datamedlemmar eller ändra deras datatyp inkompatibelt. Den här funktionen gör att tjänsten kan ändra versionen av sina kontrakt utan att bryta klienter. I de följande två avsnitten förklaras icke-inbrytnings- och icke-bakåtkompatibla ändringar som kan göras i WCF-data- och tjänstkontrakt.

Versionshantering av datakontrakt

Det här avsnittet handlar om dataversionshantering när du använder klasserna DataContractSerializer och DataContractAttribute .

Strikt versionshantering

I många scenarier när det är problem med att ändra versioner har tjänstutvecklaren inte kontroll över klienterna och kan därför inte göra antaganden om hur de skulle reagera på ändringar i meddelandets XML eller schema. I dessa fall måste du garantera att de nya meddelandena verifieras mot det gamla schemat av två skäl:

  • De gamla klienterna utvecklades med antagandet att schemat inte kommer att ändras. De kan misslyckas med att bearbeta meddelanden som de aldrig har utformats för.

  • De gamla klienterna kan utföra faktisk schemavalidering mot det gamla schemat innan de ens försöker bearbeta meddelandena.

Den rekommenderade metoden i sådana scenarier är att behandla befintliga datakontrakt som oföränderliga och skapa nya med unika XML-kvalificerade namn. Tjänstutvecklaren skulle sedan antingen lägga till nya metoder i ett befintligt tjänstkontrakt eller skapa ett nytt tjänstkontrakt med metoder som använder det nya datakontraktet.

Det är ofta så att en tjänstutvecklare behöver skriva affärslogik som ska köras i alla versioner av ett datakontrakt plus versionsspecifik affärskod för varje version av datakontraktet. I bilagan i slutet av det här avsnittet förklaras hur gränssnitt kan användas för att uppfylla detta behov.

Släpphänt versionshantering

I många andra scenarier kan tjänstutvecklaren anta att lägga till en ny valfri medlem i datakontraktet inte bryter befintliga klienter. Detta kräver att tjänstutvecklaren undersöker om befintliga klienter inte utför schemaverifiering och att de ignorerar okända datamedlemmar. I dessa scenarier är det möjligt att dra nytta av funktioner för datakontrakt för att lägga till nya medlemmar på ett icke-inbrytande sätt. Tjänstutvecklaren kan göra detta med säkerhet om datakontraktsfunktionerna för versionshantering redan användes för den första versionen av tjänsten.

WCF, ASP.NET Web Services och många andra webbtjänststackar har stöd för släpphänt versionshantering: de genererar inte undantag för nya okända datamedlemmar i mottagna data.

Det är lätt att felaktigt tro att lägga till en ny medlem inte kommer att bryta befintliga klienter. Om du är osäker på om alla klienter kan hantera släpphänt versionshantering är rekommendationen att använda strikta riktlinjer för versionshantering och behandla datakontrakt som oföränderliga.

Detaljerade riktlinjer för både släpphänt och strikt versionshantering av datakontrakt finns i Metodtips: Versionshantering av datakontrakt.

Skilja mellan datakontrakt och .NET-typer

En .NET-klass eller -struktur kan projiceras som ett datakontrakt genom att attributet tillämpas DataContractAttribute på klassen. .NET-typen och dess datakontraktsprognoser är två olika saker. Det är möjligt att ha flera .NET-typer med samma projektion av datakontrakt. Den här skillnaden är särskilt användbar för att du ska kunna ändra .NET-typen samtidigt som du underhåller det planerade datakontraktet och därmed upprätthålla kompatibiliteten med befintliga klienter även i ordets strikta bemärkelse. Det finns två saker du alltid bör göra för att upprätthålla den här skillnaden mellan .NET-typ och datakontrakt:

  • Ange en Name och Namespace. Du bör alltid ange namnet och namnområdet för ditt datakontrakt för att förhindra att .NET-typens namn och namnområde exponeras i kontraktet. På så sätt förblir datakontraktet detsamma om du senare bestämmer dig för att ändra .NET-namnområdet eller typnamnet.

  • Ange Name. Du bör alltid ange namnet på dina datamedlemmar för att förhindra att ditt .NET-medlemsnamn exponeras i kontraktet. På så sätt förblir ditt datakontrakt detsamma om du senare bestämmer dig för att ändra .NET-namnet på medlemmen.

Ändra eller ta bort medlemmar

Att ändra namn eller datatyp för en medlem eller ta bort datamedlemmar är en icke-bakåtkompatibel ändring även om släpphänt versionshantering tillåts. Om detta är nödvändigt skapar du ett nytt datakontrakt.

Om tjänstkompatibiliteten är av hög prioritet kan du överväga att ignorera oanvända datamedlemmar i koden och lämna dem på plats. Om du delar upp en datamedlem i flera medlemmar kan du överväga att lämna den befintliga medlemmen på plats som en egenskap som kan utföra den nödvändiga uppdelningen och omaggregeringen för klienter på nednivå (klienter som inte uppgraderas till den senaste versionen).

På samma sätt bryter ändringar i datakontraktets namn eller namnområde ändringar.

Rundturer med okända data

I vissa scenarier finns det ett behov av "tur och retur"-okända data som kommer från medlemmar som lagts till i en ny version. Till exempel skickar en "versionNy"-tjänst data med några nyligen tillagda medlemmar till en "versionOld"-klient. Klienten ignorerar de nyligen tillagda medlemmarna när meddelandet bearbetas, men samma data skickas igen, inklusive de nyligen tillagda medlemmarna, tillbaka till versionenNy tjänst. Det typiska scenariot för detta är datauppdateringar där data hämtas från tjänsten, ändras och returneras.

För att aktivera rund-tripping för en viss typ måste typen implementera IExtensibleDataObject gränssnittet. Gränssnittet innehåller en egenskap som ExtensionData returnerar ExtensionDataObject typen. Egenskapen används för att lagra data från framtida versioner av datakontraktet som är okända för den aktuella versionen. Dessa data är ogenomskinliga för klienten, men när instansen serialiseras skrivs innehållet i ExtensionData egenskapen med resten av datakontraktets medlemmars data.

Vi rekommenderar att alla dina typer implementerar det här gränssnittet för nya och okända framtida medlemmar.

Datakontraktsbibliotek

Det kan finnas bibliotek med datakontrakt där ett kontrakt publiceras till en central lagringsplats och tjänst- och typ-implementerare implementerar och exponerar datakontrakt från den lagringsplatsen. I så fall har du ingen kontroll över vem som skapar typer som implementerar det när du publicerar ett datakontrakt till lagringsplatsen. Därför kan du inte ändra kontraktet när det har publicerats, vilket gör det effektivt oföränderligt.

När du använder XmlSerializer

Samma versionsprinciper gäller när du använder XmlSerializer klassen. När strikt versionshantering krävs behandlar du datakontrakt som oföränderliga och skapar nya datakontrakt med unika, kvalificerade namn för de nya versionerna. När du är säker på att släpphänt versionshantering kan användas kan du lägga till nya serialiserbara medlemmar i nya versioner men inte ändra eller ta bort befintliga medlemmar.

Kommentar

XmlSerializer Använder attributen XmlAnyElementAttribute och XmlAnyAttributeAttribute för att stödja avkörning av okända data.

Versionshantering av meddelandekontrakt

Riktlinjerna för versionshantering av meddelandekontrakt liknar versionshantering av datakontrakt. Om strikt versionshantering krävs bör du inte ändra meddelandetexten utan i stället skapa ett nytt meddelandekontrakt med ett unikt kvalificerat namn. Om du vet att du kan använda släpphänt versionshantering kan du lägga till nya meddelandetextdelar men inte ändra eller ta bort befintliga. Den här vägledningen gäller både för kontrakt med tomma och omslutna meddelanden.

Meddelandehuvuden kan alltid läggas till, även om strikt versionshantering används. Flaggan MustUnderstand kan påverka versionshantering. I allmänhet är versionsmodellen för rubriker i WCF enligt beskrivningen i SOAP-specifikationen.

Versionshantering av tjänstkontrakt

På samma sätt som versionshantering av datakontrakt innebär versionshantering av tjänstkontrakt även att lägga till, ändra och ta bort åtgärder.

Ange namn, namnområde och åtgärd

Som standard är namnet på ett tjänstkontrakt namnet på gränssnittet. Dess standardnamnområde är http://tempuri.org, och varje åtgärds åtgärd är http://tempuri.org/contractname/methodname. Vi rekommenderar att du uttryckligen anger ett namn och namnområde för tjänstkontraktet och en åtgärd för varje åtgärd för att undvika att använda http://tempuri.org och förhindra att gränssnitts- och metodnamn exponeras i tjänstens kontrakt.

Lägga till parametrar och åtgärder

Att lägga till tjänståtgärder som exponeras av tjänsten är en icke-banbrytande ändring eftersom befintliga klienter inte behöver bry sig om de nya åtgärderna.

Kommentar

Att lägga till åtgärder i ett dubbelsidigt återanropskontrakt är en icke-bakåtkompatibel ändring.

Ändra åtgärdsparameter eller returtyper

Att ändra parameter- eller returtyper är vanligtvis en icke-bakåtkompatibel ändring om inte den nya typen implementerar samma datakontrakt som implementerats av den gamla typen. Om du vill göra en sådan ändring lägger du till en ny åtgärd i tjänstkontraktet eller definierar ett nytt tjänstkontrakt.

Ta bort åtgärder

Att ta bort åtgärder är också en icke-bakåtkompatibel ändring. Om du vill göra en sådan ändring definierar du ett nytt tjänstkontrakt och exponerar det på en ny slutpunkt.

Felkontrakt

Attributet FaultContractAttribute gör det möjligt för en tjänstkontraktsutvecklare att ange information om fel som kan returneras från kontraktets åtgärder.

Listan över fel som beskrivs i en tjänsts kontrakt anses inte vara fullständig. När som helst kan en åtgärd returnera fel som inte beskrivs i avtalet. Därför anses det inte gå att ändra den uppsättning fel som beskrivs i kontraktet. Du kan till exempel lägga till ett nytt fel i kontraktet med hjälp av FaultContractAttribute eller ta bort ett befintligt fel från kontraktet.

Tjänstkontraktsbibliotek

Organisationer kan ha bibliotek med kontrakt där ett kontrakt publiceras till en central lagringsplats och tjänst implementerar kontrakt från den lagringsplatsen. I det här fallet har du ingen kontroll över vem som skapar tjänster som implementerar det när du publicerar ett tjänstkontrakt till lagringsplatsen. Därför kan du inte ändra tjänstkontraktet när det har publicerats, vilket gör det effektivt oföränderligt. WCF stöder arv av kontrakt, som kan användas för att skapa ett nytt kontrakt som utökar befintliga kontrakt. Om du vill använda den här funktionen definierar du ett nytt tjänstkontraktsgränssnitt som ärver från det gamla tjänstkontraktsgränssnittet och lägger sedan till metoder i det nya gränssnittet. Sedan ändrar du den tjänst som implementerar det gamla kontraktet för att implementera det nya kontraktet och ändrar slutpunktsdefinitionen "versionOld" för att använda det nya kontraktet. För "versionOld"-klienter fortsätter slutpunkten att visas som exponerar "versionOld"-kontraktet. till "versionNya" klienter visas slutpunkten för att exponera "versionNyt" kontrakt.

Adress- och bindningsversioner

Ändringar av slutpunktsadress och bindning bryter mot ändringar om inte klienterna kan identifiera den nya slutpunktsadressen eller bindningen dynamiskt. En mekanism för att implementera den här funktionen är att använda ett UDDI-register (Universal Discovery Description and Integration) och UDDI-anropsmönstret där en klient försöker kommunicera med en slutpunkt och vid fel frågar ett välkänt UDDI-register för aktuella slutpunktsmetadata. Klienten använder sedan adressen och bindningen från dessa metadata för att kommunicera med slutpunkten. Om den här kommunikationen lyckas cachelagrar klienten adress- och bindningsinformationen för framtida användning.

Routningstjänst och versionshantering

Om ändringarna i en tjänst bryter mot ändringarna och du behöver ha två eller flera olika versioner av en tjänst som körs samtidigt kan du använda WCF-routningstjänsten för att dirigera meddelanden till rätt tjänstinstans. WCF-routningstjänsten använder innehållsbaserad routning, med andra ord använder den information i meddelandet för att avgöra var meddelandet ska dirigeras. Mer information om WCF-routningstjänsten finns i Routningstjänsten. Ett exempel på hur du använder WCF-routningstjänsten för tjänstversioner finns i How To: Service Versionsing (Så här gör du: Tjänstversioner).

Bilaga

Den allmänna vägledningen för versionshantering av datakontrakt när strikt versionshantering behövs är att behandla datakontrakt som oföränderliga och skapa nya när ändringar krävs. En ny klass måste skapas för varje nytt datakontrakt, så det krävs en mekanism för att undvika att behöva ta befintlig kod som skrevs i termer av den gamla datakontraktsklassen och skriva om den i termer av den nya datakontraktsklassen.

En sådan mekanism är att använda gränssnitt för att definiera medlemmarna i varje datakontrakt och skriva intern implementeringskod när det gäller gränssnitten i stället för de datakontraktsklasser som implementerar gränssnitten. Följande kod för version 1 av en tjänst visar ett IPurchaseOrderV1 gränssnitt och en 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 {...}  
}  

Även om servicekontraktets åtgärder skulle skrivas i termer av PurchaseOrderV1, skulle den faktiska affärslogik vara i termer av IPurchaseOrderV1. I version 2 skulle det sedan finnas ett nytt IPurchaseOrderV2 gränssnitt och en ny PurchaseOrderV2 klass enligt följande kod:

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 { ... }  
}  

Tjänstkontraktet uppdateras så att det innehåller nya åtgärder som skrivs i termer av PurchaseOrderV2. Befintlig affärslogik som skrivits i termer av IPurchaseOrderV1 skulle fortsätta att fungera för PurchaseOrderV2 och ny affärslogik som behöver OrderDate egenskapen skulle skrivas i termer av IPurchaseOrderV2.

Se även