Serviceversiebeheer
Na de eerste implementatie en mogelijk meerdere keren tijdens hun levensduur moeten services (en de eindpunten die ze beschikbaar maken) om verschillende redenen worden gewijzigd, zoals het wijzigen van bedrijfsbehoeften, vereisten voor informatietechnologie of het oplossen van andere problemen. Elke wijziging introduceert een nieuwe versie van de service. In dit onderwerp wordt uitgelegd hoe u versiebeheer in Windows Communication Foundation (WCF) kunt overwegen.
Vier servicecategorieën
De wijzigingen in services die mogelijk vereist zijn, kunnen worden geclassificeerd in vier categorieën:
Contractwijzigingen: een bewerking kan bijvoorbeeld worden toegevoegd of een gegevenselement in een bericht worden toegevoegd of gewijzigd.
Adreswijzigingen: een service wordt bijvoorbeeld verplaatst naar een andere locatie waar eindpunten nieuwe adressen hebben.
Bindingswijzigingen: een beveiligingsmechanisme wordt bijvoorbeeld gewijzigd of de instellingen worden gewijzigd.
Implementatiewijzigingen: bijvoorbeeld wanneer een interne methode wordt geïmplementeerd.
Sommige van deze wijzigingen worden 'breken' genoemd en andere zijn 'vastlopend'. Een wijziging is onbreekbaar als alle berichten die met succes in de vorige versie zouden zijn verwerkt, in de nieuwe versie worden verwerkt. Elke wijziging die niet aan dat criterium voldoet, is een belangrijke wijziging.
Servicestand en versiebeheer
Een van de servicerichtingsrichtingen is dat services en clients autonoom (of onafhankelijk) zijn. Dit impliceert onder andere dat serviceontwikkelaars niet kunnen aannemen dat ze alle serviceclients beheren of zelfs weten. Dit elimineert de optie om alle clients opnieuw te bouwen en opnieuw te implementeren wanneer een service versies wijzigt. In dit onderwerp wordt ervan uitgegaan dat de service aan dit tenet voldoet en daarom moet worden gewijzigd of 'versiebeheer' onafhankelijk van de clients.
In gevallen waarin een belangrijke wijziging onverwacht is en niet kan worden vermeden, kan een toepassing ervoor kiezen om dit tenet te negeren en te vereisen dat clients opnieuw worden opgebouwd en opnieuw worden geïmplementeerd met een nieuwe versie van de service.
Contractversiebeheer
Contracten die door een klant worden gebruikt, hoeven niet hetzelfde te zijn als het contract dat door de service wordt gebruikt; ze hoeven alleen compatibel te zijn.
Voor servicecontracten betekent compatibiliteit dat nieuwe bewerkingen die door de service worden weergegeven, kunnen worden toegevoegd, maar bestaande bewerkingen kunnen niet semantisch worden verwijderd of gewijzigd.
Voor gegevenscontracten betekent compatibiliteit dat nieuwe schematypedefinities kunnen worden toegevoegd, maar bestaande schematypedefinities kunnen niet worden gewijzigd op belangrijke manieren. Belangrijke wijzigingen kunnen het verwijderen van gegevensleden of het wijzigen van hun gegevenstype incompatibly zijn. Met deze functie kan de service enige breedtegraad hebben bij het wijzigen van de versie van de contracten zonder clients te breken. In de volgende twee secties worden niet-brekende en belangrijke wijzigingen uitgelegd die kunnen worden aangebracht in WCF-gegevens- en servicecontracten.
Versiebeheer van gegevenscontract
In deze sectie wordt het gebruik van gegevensversies besproken wanneer u de DataContractSerializer en DataContractAttribute klassen gebruikt.
Strikte versiebeheer
In veel scenario's wanneer het wijzigen van versies een probleem is, heeft de serviceontwikkelaar geen controle over de clients en kan daarom geen veronderstellingen maken over hoe ze zouden reageren op wijzigingen in de bericht-XML of het schema. In deze gevallen moet u garanderen dat de nieuwe berichten om twee redenen worden gevalideerd op basis van het oude schema:
De oude clients zijn ontwikkeld met de veronderstelling dat het schema niet zal veranderen. Ze kunnen berichten waarvoor ze nooit zijn ontworpen, niet verwerken.
De oude clients kunnen de daadwerkelijke schemavalidatie uitvoeren op basis van het oude schema voordat ze zelfs proberen de berichten te verwerken.
De aanbevolen aanpak in dergelijke scenario's is het behandelen van bestaande gegevenscontracten als onveranderbaar en het maken van nieuwe contracten met unieke XML-gekwalificeerde namen. De serviceontwikkelaar voegt vervolgens nieuwe methoden toe aan een bestaand servicecontract of maakt een nieuw servicecontract met methoden die gebruikmaken van het nieuwe gegevenscontract.
Het komt vaak voor dat een serviceontwikkelaar bepaalde bedrijfslogica moet schrijven die moet worden uitgevoerd in alle versies van een gegevenscontract plus versiespecifieke bedrijfscode voor elke versie van het gegevenscontract. In de bijlage aan het einde van dit onderwerp wordt uitgelegd hoe interfaces kunnen worden gebruikt om aan deze behoefte te voldoen.
Lax-versiebeheer
In veel andere scenario's kan de serviceontwikkelaar ervan uitgaan dat het toevoegen van een nieuw, optioneel lid aan het gegevenscontract bestaande clients niet zal breken. Hiervoor moet de serviceontwikkelaar onderzoeken of bestaande clients geen schemavalidatie uitvoeren en dat ze onbekende gegevensleden negeren. In deze scenario's is het mogelijk om te profiteren van functies voor gegevenscontract voor het toevoegen van nieuwe leden op een onbreekbare manier. De serviceontwikkelaar kan deze aanname met vertrouwen doen als de functies van het gegevenscontract voor versiebeheer al zijn gebruikt voor de eerste versie van de service.
WCF, ASP.NET Web Services en vele andere webservicestacks bieden ondersteuning voor laxversiebeheer: dat wil gezegd: ze genereren geen uitzonderingen voor nieuwe onbekende gegevensleden in ontvangen gegevens.
Het is gemakkelijk om per ongeluk te geloven dat het toevoegen van een nieuw lid bestaande clients niet zal breken. Als u niet zeker weet dat alle clients laxversiebeheer kunnen verwerken, is het raadzaam de strikte richtlijnen voor versiebeheer te gebruiken en gegevenscontracten als onveranderbaar te behandelen.
Zie Best Practices: Data Contract Versioning voor gedetailleerde richtlijnen voor zowel lax- als strikte versiebeheer van gegevenscontracten.
Onderscheid maken tussen gegevenscontract- en .NET-typen
Een .NET-klasse of -structuur kan worden geprojecteerd als een gegevenscontract door het DataContractAttribute kenmerk toe te passen op de klasse. Het .NET-type en de projecties van het gegevenscontract zijn twee verschillende zaken. Het is mogelijk om meerdere .NET-typen met dezelfde gegevenscontractprojectie te hebben. Dit onderscheid is vooral handig bij het wijzigen van het .NET-type terwijl u het projected gegevenscontract onderhoudt, waardoor de compatibiliteit met bestaande clients zelfs in de strikte zin van het woord behouden blijft. Er zijn twee dingen die u altijd moet doen om dit onderscheid tussen het .NET-type en het gegevenscontract te behouden:
Geef een Name en Namespace. U moet altijd de naam en naamruimte van uw gegevenscontract opgeven om te voorkomen dat de naam en naamruimte van uw .NET-type worden weergegeven in het contract. Op deze manier blijft uw gegevenscontract hetzelfde als u later besluit om de .NET-naamruimte of typenaam te wijzigen.
Geef Name op. U moet altijd de naam van uw gegevensleden opgeven om te voorkomen dat uw .NET-lidnaam wordt weergegeven in het contract. Op deze manier blijft uw gegevenscontract hetzelfde als u later besluit de .NET-naam van het lid te wijzigen.
Leden wijzigen of verwijderen
Het wijzigen van de naam of het gegevenstype van een lid of het verwijderen van gegevensleden is een belangrijke wijziging, zelfs als laxversiebeheer is toegestaan. Als dit nodig is, maakt u een nieuw gegevenscontract.
Als servicecompatibiliteit van groot belang is, kunt u overwegen ongebruikte gegevensleden in uw code te negeren en deze te laten staan. Als u een gegevenslid opsplitst in meerdere leden, kunt u overwegen om het bestaande lid op te slaan als een eigenschap die de vereiste splitsing en heraggregatie voor clients op down-level kan uitvoeren (clients die niet worden bijgewerkt naar de nieuwste versie).
Op dezelfde manier worden wijzigingen in de naam of naamruimte van het gegevenscontract gewijzigd.
Retouren van onbekende gegevens
In sommige scenario's is het nodig om onbekende gegevens te 'retourneren' die afkomstig zijn van leden die zijn toegevoegd in een nieuwe versie. Een service versionNew verzendt bijvoorbeeld gegevens met enkele nieuw toegevoegde leden naar een versionOld-client. De client negeert de zojuist toegevoegde leden bij het verwerken van het bericht, maar deze verzendt dezelfde gegevens, inclusief de nieuw toegevoegde leden, terug naar de versionNew-service. Het gebruikelijke scenario hiervoor is gegevensupdates waarbij gegevens worden opgehaald uit de service, gewijzigd en geretourneerd.
Als u roundtripping voor een bepaald type wilt inschakelen, moet het type de IExtensibleDataObject interface implementeren. De interface bevat één eigenschap die ExtensionData het ExtensionDataObject type retourneert. De eigenschap wordt gebruikt voor het opslaan van gegevens uit toekomstige versies van het gegevenscontract die onbekend is met de huidige versie. Deze gegevens zijn ondoorzichtig voor de client, maar wanneer het exemplaar wordt geserialiseerd, wordt de inhoud van de ExtensionData eigenschap geschreven met de rest van de gegevens van de gegevenscontractleden.
Het wordt aanbevolen dat al uw typen deze interface implementeren voor nieuwe en onbekende toekomstige leden.
Gegevenscontractbibliotheken
Er kunnen bibliotheken van gegevenscontracten zijn waarin een contract wordt gepubliceerd naar een centrale opslagplaats, en service- en typei implementeerfuncties gegevenscontracten uit die opslagplaats implementeren en beschikbaar maken. In dat geval hebt u bij het publiceren van een gegevenscontract naar de opslagplaats geen controle over wie typen maakt die het implementeren. U kunt het contract dus niet wijzigen zodra het is gepubliceerd, waardoor het effectief onveranderbaar wordt.
Wanneer u de XmlSerializer gebruikt
Dezelfde versiebeheerprincipes zijn van toepassing wanneer u de XmlSerializer klasse gebruikt. Wanneer strikte versiebeheer is vereist, behandelt u gegevenscontracten als onveranderbaar en maakt u nieuwe gegevenscontracten met unieke, gekwalificeerde namen voor de nieuwe versies. Wanneer u zeker weet dat lax-versiebeheer kan worden gebruikt, kunt u nieuwe serialiseerbare leden toevoegen in nieuwe versies, maar bestaande leden niet wijzigen of verwijderen.
Notitie
Hierbij XmlSerializer worden de XmlAnyElementAttribute en XmlAnyAttributeAttribute kenmerken gebruikt ter ondersteuning van round-tripping van onbekende gegevens.
Versiebeheer van berichtcontract
De richtlijnen voor versiebeheer van berichtencontracten zijn vergelijkbaar met versiebeheer van gegevenscontracten. Als strikte versiebeheer is vereist, moet u de berichttekst niet wijzigen, maar in plaats daarvan een nieuw berichtcontract maken met een unieke gekwalificeerde naam. Als u weet dat u laxversiebeheer kunt gebruiken, kunt u nieuwe berichttekstonderdelen toevoegen, maar bestaande onderdelen niet wijzigen of verwijderen. Deze richtlijnen zijn zowel van toepassing op bare als verpakte berichtcontracten.
Berichtkoppen kunnen altijd worden toegevoegd, zelfs als strikte versiebeheer wordt gebruikt. De vlag MustUnderstand kan van invloed zijn op versiebeheer. Over het algemeen is het versiebeheermodel voor headers in WCF zoals beschreven in de SOAP-specificatie.
Versiebeheer van servicecontract
Net als bij versiebeheer van gegevenscontracten omvat versiebeheer voor servicecontracten ook het toevoegen, wijzigen en verwijderen van bewerkingen.
Naam, naamruimte en actie opgeven
Standaard is de naam van een servicecontract de naam van de interface. De standaardnaamruimte is http://tempuri.org
, en de actie van elke bewerking is http://tempuri.org/contractname/methodname
. Het wordt aanbevolen om expliciet een naam en naamruimte op te geven voor het servicecontract, en een actie voor elke bewerking om te voorkomen http://tempuri.org
dat interface- en methodenamen worden weergegeven in het contract van de service.
Parameters en bewerkingen toevoegen
Het toevoegen van servicebewerkingen die door de service worden weergegeven, is een vaste wijziging, omdat bestaande clients zich geen zorgen hoeven te maken over deze nieuwe bewerkingen.
Notitie
Het toevoegen van bewerkingen aan een dubbelzijdig callback-contract is een belangrijke wijziging.
Bewerkingsparameter of retourtypen wijzigen
Het wijzigen van parameter- of retourtypen is doorgaans een belangrijke wijziging, tenzij het nieuwe type hetzelfde gegevenscontract implementeert dat door het oude type is geïmplementeerd. Als u een dergelijke wijziging wilt aanbrengen, voegt u een nieuwe bewerking toe aan het servicecontract of definieert u een nieuw servicecontract.
Bewerkingen verwijderen
Het verwijderen van bewerkingen is ook een belangrijke wijziging. Als u een dergelijke wijziging wilt aanbrengen, definieert u een nieuw servicecontract en maakt u dit beschikbaar op een nieuw eindpunt.
Foutcontracten
Met FaultContractAttribute het kenmerk kan een ontwikkelaar van een servicecontract informatie opgeven over fouten die kunnen worden geretourneerd door de bewerkingen van het contract.
De lijst met fouten die in het contract van een service worden beschreven, wordt niet als volledig beschouwd. Op elk gewenst moment kan een bewerking fouten retourneren die niet in het contract worden beschreven. Daarom wordt het wijzigen van de set fouten die in het contract worden beschreven, niet beschouwd als fouten die fouten veroorzaken. Bijvoorbeeld het toevoegen van een nieuwe fout aan het contract met behulp van de FaultContractAttribute of het verwijderen van een bestaande fout uit het contract.
Servicecontractbibliotheken
Organisaties kunnen bibliotheken hebben van contracten waarbij een contract wordt gepubliceerd naar een centrale opslagplaats en service-implementeerfuncties contracten vanuit die opslagplaats implementeren. In dit geval hebt u, wanneer u een servicecontract publiceert naar de opslagplaats, geen controle over wie services maakt die het implementeren. Daarom kunt u het servicecontract niet meer wijzigen nadat het is gepubliceerd, waardoor het effectief onveranderbaar wordt. WCF ondersteunt overname van contracten, die kan worden gebruikt om een nieuw contract te maken dat bestaande contracten uitbreidt. Als u deze functie wilt gebruiken, definieert u een nieuwe servicecontractinterface die overkomt van de oude servicecontractinterface en voegt u vervolgens methoden toe aan de nieuwe interface. Vervolgens wijzigt u de service waarmee het oude contract wordt geïmplementeerd om het nieuwe contract te implementeren en wijzigt u de eindpuntdefinitie versionOld om het nieuwe contract te gebruiken. Voor 'versionOld'-clients wordt het eindpunt weergegeven als het 'versionOld'-contract; voor clients met versionNew wordt het eindpunt weergegeven om het contract versionNew weer te geven.
Versiebeheer voor adressen en bindingen
Wijzigingen in eindpuntadres en binding zijn belangrijke wijzigingen, tenzij clients het nieuwe eindpuntadres of de nieuwe binding dynamisch kunnen detecteren. Een mechanisme voor het implementeren van deze mogelijkheid is met behulp van een UDDI-register (Universal Discovery Description and Integration) en het UDDI-aanroeppatroon waarbij een client probeert te communiceren met een eindpunt en bij fouten een query uitvoert op een bekend UDDI-register voor de huidige eindpuntmetagegevens. De client gebruikt vervolgens het adres en de binding van deze metagegevens om te communiceren met het eindpunt. Als deze communicatie slaagt, slaat de client het adres en de bindingsgegevens in de cache op voor toekomstig gebruik.
Routeringsservice en versiebeheer
Als de wijzigingen in een service fouten veroorzaken en u twee of meer verschillende versies van een service tegelijk wilt uitvoeren, kunt u de WCF-routeringsservice gebruiken om berichten naar het juiste service-exemplaar te routeren. De WCF-routeringsservice maakt gebruik van routering op basis van inhoud, met andere woorden, er wordt informatie in het bericht gebruikt om te bepalen waar het bericht moet worden gerouteerd. Zie Routeringsservice voor meer informatie over de WCF-routeringsservice. Zie Instructies voor serviceversiebeheer voor een voorbeeld van het gebruik van de WCF-routeringsservice.
Bijlage
De algemene richtlijnen voor het versiebeheer van gegevenscontracten wanneer strikte versiebeheer nodig is, is het verwerken van gegevenscontracten als onveranderbaar en het maken van nieuwe contracten wanneer wijzigingen vereist zijn. Er moet een nieuwe klasse worden gemaakt voor elk nieuw gegevenscontract, dus er is een mechanisme nodig om te voorkomen dat bestaande code moet worden gebruikt die is geschreven in termen van de oude gegevenscontractklasse en deze opnieuw schrijft in termen van de nieuwe gegevenscontractklasse.
Een dergelijk mechanisme is om interfaces te gebruiken om de leden van elk gegevenscontract te definiëren en interne implementatiecode te schrijven in termen van de interfaces in plaats van de gegevenscontractklassen die de interfaces implementeren. De volgende code voor versie 1 van een service toont een IPurchaseOrderV1
interface en een 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 {...}
}
Hoewel de activiteiten van het servicecontract zouden worden geschreven in termen van PurchaseOrderV1
, zou de werkelijke bedrijfslogica in termen van IPurchaseOrderV1
. In versie 2 zou er dan een nieuwe IPurchaseOrderV2
interface en een nieuwe PurchaseOrderV2
klasse zijn, zoals wordt weergegeven in de volgende code:
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 { ... }
}
Het servicecontract wordt bijgewerkt met nieuwe bewerkingen die zijn geschreven in termen van PurchaseOrderV2
. Bestaande bedrijfslogica die is geschreven in termen van IPurchaseOrderV1
zou blijven werken voor PurchaseOrderV2
en nieuwe bedrijfslogica die de OrderDate
eigenschap nodig heeft, worden geschreven in termen van IPurchaseOrderV2
.