Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Dopo la distribuzione iniziale, e potenzialmente più volte durante la loro durata, i servizi (e gli endpoint esposti) potrebbero dover essere modificati per diversi motivi, ad esempio la modifica delle esigenze aziendali, i requisiti della tecnologia delle informazioni o per risolvere altri problemi. Ogni modifica introduce una nuova versione del servizio. In questo argomento viene illustrato come prendere in considerazione il controllo delle versioni in Windows Communication Foundation (WCF).
Quattro categorie di modifiche al servizio
Le modifiche ai servizi che possono essere necessarie possono essere classificate in quattro categorie:
Modifiche al contratto: ad esempio, è possibile aggiungere un'operazione o modificare un elemento dati in un messaggio.
Modifiche all'indirizzo: ad esempio, un servizio passa a un percorso diverso in cui gli endpoint hanno nuovi indirizzi.
Modifiche all'associazione: ad esempio, un meccanismo di sicurezza cambia o le sue impostazioni vengono modificate.
Modifiche all'implementazione: ad esempio, quando viene modificata un'implementazione di un metodo interno.
Alcune di queste modifiche sono denominate "breaking" e altre sono "non interrompenti." Una modifica è non interrompente se tutti i messaggi che verrebbero elaborati correttamente nella versione precedente vengono elaborati correttamente nella nuova versione. Qualsiasi modifica che non soddisfa tale criterio è una modifica che causa un'interruzione .
Orientamento del servizio e controllo delle versioni
Uno dei principi dell'orientamento al servizio è che i servizi e i clienti sono autonomi (o indipendenti). Tra le altre cose, ciò implica che gli sviluppatori di servizi non possono presupporre che controllino o addirittura conoscano tutti i client di servizio. In questo modo viene eliminata l'opzione di ricompilazione e ridistribuzione di tutti i client quando un servizio modifica le versioni. In questo argomento si presuppone che il servizio sia conforme a questo principio e pertanto deve essere modificato o "versionato" indipendentemente dai suoi client.
Nei casi in cui una modifica di rilievo è imprevista e non può essere evitata, un'applicazione può scegliere di non tener conto di questo principio e richiedere che i client vengano ricompilati e ridistribuiti con una nuova versione del servizio.
Versionamento del contratto
I contratti utilizzati da un cliente non devono essere uguali al contratto utilizzato dal servizio; devono essere compatibili solo.
Per i contratti di servizio, la compatibilità significa che è possibile aggiungere nuove operazioni esposte dal servizio, ma le operazioni esistenti non possono essere rimosse o modificate semanticamente.
Per i contratti sui dati, la compatibilità significa che è possibile aggiungere nuove definizioni dei tipi di schema, ma le definizioni dei tipi di schema esistenti non possono essere modificate in modo significativo. Le modifiche di rilievo possono includere la rimozione di membri dati o la modifica del tipo di dati in modo incompatibile. Questa funzionalità consente al servizio di modificare la versione dei contratti senza interrompere i client. Le due sezioni successive illustrano le modifiche non modificative e modificative che possono essere apportate ai contratti di dati e di servizio WCF.
Versionamento del contratto di dati
Questa sezione illustra il controllo delle versioni dei dati quando si usano le DataContractSerializer classi e DataContractAttribute .
Controllo rigido delle versioni
In molti scenari quando si modificano le versioni è un problema, lo sviluppatore del servizio non ha il controllo sui client e pertanto non può fare ipotesi su come reagirebbe alle modifiche nel messaggio XML o schema. In questi casi, è necessario garantire che i nuovi messaggi vengano convalidati rispetto allo schema precedente, per due motivi:
I vecchi client sono stati sviluppati presupponendo che lo schema non cambierà. Potrebbero non riuscire a elaborare i messaggi per cui non sono mai stati progettati.
I vecchi client possono eseguire la convalida effettiva dello schema rispetto allo schema precedente prima di tentare di elaborare i messaggi.
L'approccio consigliato in questi scenari consiste nel considerare i contratti dati esistenti come non modificabili e crearne di nuovi con nomi completi XML univoci. Lo sviluppatore del servizio aggiungerà quindi nuovi metodi a un contratto di servizio esistente o creerebbe un nuovo contratto di servizio con metodi che usano il nuovo contratto dati.
Spesso uno sviluppatore di servizi deve scrivere una logica di business che deve essere eseguita all'interno di tutte le versioni di un contratto dati più codice business specifico della versione per ogni versione del contratto dati. L'appendice alla fine di questo argomento illustra come usare le interfacce per soddisfare questa esigenza.
Versionamento flessibile
In molti altri scenari, lo sviluppatore del servizio può fare il presupposto che l'aggiunta di un nuovo membro facoltativo al contratto dati non interrompa i client esistenti. Ciò richiede allo sviluppatore del servizio di verificare se i client esistenti non eseguono la convalida dello schema e che ignorano i membri dati sconosciuti. In questi scenari, è possibile sfruttare le funzionalità del contratto di dati per l'aggiunta di nuovi membri in modo senza interruzioni. Lo sviluppatore di servizi può fare questo presupposto con fiducia se le funzionalità del contratto dati per il controllo delle versioni sono già state usate per la prima versione del servizio.
WCF, ASP.NET Servizi Web e molti altri stack di servizi Web supportano il versionamento flessibile, ovvero non generano eccezioni per i nuovi membri dati sconosciuti nei dati ricevuti.
È facile credere erroneamente che l'aggiunta di un nuovo membro non interromperà i client esistenti. Se non si è certi che tutti i client possano gestire il controllo delle versioni flessibile, è consigliabile usare le rigide linee guida per il controllo delle versioni e trattare i contratti dati come non modificabili.
Per linee guida dettagliate sia sulla versione flessibile che su quella rigida dei contratti di dati, vedere Procedure consigliate: controllo delle versioni del contratto di dati.
Distinzione tra contratti dati e tipi .NET
Una classe o una struttura .NET può essere proiettata come contratto dati applicando l'attributo DataContractAttribute alla classe . Il tipo .NET e le proiezioni del contratto dati sono due aspetti distinti. È possibile avere più tipi .NET con la stessa proiezione del contratto di dati. Questa distinzione è particolarmente utile per consentire di modificare il tipo .NET mantenendo il contratto dati proiettato, mantenendo così la compatibilità con i client esistenti anche nel senso rigoroso della parola. È consigliabile eseguire due operazioni per mantenere questa distinzione tra il tipo .NET e il contratto dati:
Specificare un Name e Namespace. È sempre necessario specificare il nome e lo spazio dei nomi del contratto dati per impedire che il nome e lo spazio dei nomi del tipo .NET vengano esposti nel contratto. In questo modo, se si decide in un secondo momento di modificare lo spazio dei nomi o il nome del tipo .NET, il contratto dati rimane invariato.
Specificare Name. È sempre necessario specificare il nome dei membri dati per impedire che il nome del membro .NET venga esposto nel contratto. In questo modo, se si decide in un secondo momento di modificare il nome .NET del membro, il contratto dati rimane invariato.
Modifica o rimozione di membri
Cambiare il nome o il tipo di dati di un membro, o rimuovere membri dei dati, è una modifica che causa un'interruzione anche se è consentita la gestione delle versioni flessibile. Se necessario, creare un nuovo contratto dati.
Se la compatibilità del servizio è di importanza elevata, è possibile ignorare i membri dati inutilizzati nel codice e lasciarli invariati. Se si divide un membro dati in più membri, è consigliabile lasciare il membro esistente come proprietà in grado di eseguire la suddivisione e la ri-aggregazione necessarie per i client di livello inferiore (client che non vengono aggiornati alla versione più recente).
Analogamente, le modifiche apportate al nome o allo spazio dei nomi del contratto dati causano modifiche di rilievo.
Round-Trips di dati sconosciuti
In alcuni scenari, è necessario effettuare un "round-trip" dei dati sconosciuti provenienti dai membri aggiunti in una nuova versione. Ad esempio, un servizio "versionNew" invia dati con alcuni membri appena aggiunti a un client "versionOld". Il client ignora i membri appena aggiunti durante l'elaborazione del messaggio, ma invia nuovamente gli stessi dati, inclusi i membri appena aggiunti, alla versioneNuovo servizio. Lo scenario tipico è costituito dagli aggiornamenti dei dati in cui i dati vengono recuperati dal servizio, modificati e restituiti.
Per abilitare il round-tripping per un particolare tipo, il tipo deve implementare l'interfaccia IExtensibleDataObject . L'interfaccia contiene una proprietà, ExtensionData, che restituisce il tipo ExtensionDataObject. La proprietà viene utilizzata per archiviare tutti i dati delle versioni future del contratto di dati sconosciute alla versione corrente. Questi dati sono opachi per il client, ma quando l'istanza viene serializzata, il contenuto della ExtensionData proprietà viene scritto con il resto dei dati dei membri del contratto dati.
È consigliabile che tutti i tipi implementino questa interfaccia per contenere membri futuri nuovi e sconosciuti.
Biblioteche dei contratti di dati
Possono essere presenti librerie di contratti dati in cui un contratto viene pubblicato in un repository centrale e gli implementatori di servizi e tipi implementano ed espongono contratti dati da tale repository. In tal caso, quando si pubblica un contratto dati nel repository, non si ha alcun controllo su chi crea tipi che lo implementano. Pertanto, non è possibile modificare il contratto dopo la pubblicazione, rendendolo effettivamente immutabile.
Quando si usa XmlSerializer
Gli stessi principi di controllo delle versioni si applicano quando si usa la XmlSerializer classe . Quando è necessario un controllo delle versioni rigoroso, considerare i contratti dati come non modificabili e creare nuovi contratti dati con nomi univoci e qualificati per le nuove versioni. Quando si è certi che è possibile usare il controllo delle versioni lax, è possibile aggiungere nuovi membri serializzabili in nuove versioni, ma non modificare o rimuovere membri esistenti.
Annotazioni
XmlSerializer utilizza gli attributi XmlAnyElementAttribute e XmlAnyAttributeAttribute per supportare il round-tripping dei dati sconosciuti.
Versionamento del contratto di messaggio
Le linee guida per il versionamento dei contratti di messaggio sono molto simili al versionamento dei contratti dati. Se è necessario un controllo delle versioni rigoroso, non è consigliabile modificare il corpo del messaggio, ma creare un nuovo contratto di messaggio con un nome completo univoco. Se si sa che è possibile usare il versionamento flessibile, è possibile aggiungere nuove parti del corpo del messaggio, ma non modificare o rimuovere quelle esistenti. Queste linee guida si applicano sia ai contratti di messaggio semplici che a quelli incapsulati.
È sempre possibile aggiungere intestazioni di messaggio, anche se è in uso un controllo delle versioni rigoroso. Il flag MustUnderstand può influire sul controllo delle versioni. In generale, il modello di versionamento per gli header in WCF è descritto nella specifica SOAP.
Controllo delle versioni del contratto di servizio
Analogamente al controllo delle versioni del contratto dati, il controllo delle versioni del contratto di servizio comporta anche l'aggiunta, la modifica e la rimozione di operazioni.
Specifica del nome, dello spazio dei nomi e dell'azione
Per impostazione predefinita, il nome di un contratto di servizio è il nome dell'interfaccia. Lo spazio dei nomi predefinito è http://tempuri.orge l'azione di ogni operazione è http://tempuri.org/contractname/methodname. È consigliabile specificare in modo esplicito un nome e uno spazio dei nomi per il contratto di servizio e un'azione per ogni operazione per evitare di usare http://tempuri.org e impedire l'esposizione di nomi di interfaccia e metodi nel contratto del servizio.
Aggiunta di parametri e operazioni
L'aggiunta di operazioni del servizio esposte dal servizio è una modifica che non causa interruzioni perché i client esistenti non devono preoccuparsi di tali nuove operazioni.
Annotazioni
L'aggiunta di operazioni a un contratto di callback duplex è una modifica che causa un'interruzione.
Modifica del parametro dell'operazione o dei tipi restituiti
La modifica dei tipi di parametro o dei tipi restituiti è in genere una modifica significativa, a meno che il nuovo tipo non implementi lo stesso contratto di dati implementato dal tipo precedente. Per apportare tale modifica, aggiungere una nuova operazione al contratto di servizio o definire un nuovo contratto di servizio.
Rimozione delle operazioni
La rimozione delle operazioni è anche una modifica che causa un'interruzione. Per apportare una modifica di questo tipo, definire un nuovo contratto di servizio ed esporlo in un nuovo endpoint.
Contratti di errore
L'attributo FaultContractAttribute consente a uno sviluppatore di contratti di servizio di specificare informazioni sugli errori che possono essere restituiti dalle operazioni del contratto.
L'elenco degli errori descritti nel contratto di un servizio non è considerato esaustivo. In qualsiasi momento, un'operazione può restituire errori non descritti nel contratto. Pertanto, la modifica del set di difetti descritti nel contratto non è considerata una violazione. Ad esempio, l'aggiunta di un nuovo errore al contratto usando FaultContractAttribute o rimuovendo un errore esistente dal contratto.
Librerie del contratto di servizio
Le organizzazioni possono avere librerie di contratti in cui un contratto viene pubblicato in un repository centrale e gli implementatori di servizi implementano contratti da tale repository. In questo caso, quando si pubblica un contratto di servizio nel repository non si ha alcun controllo su chi crea servizi che lo implementano. Pertanto, non è possibile modificare il contratto di servizio dopo la pubblicazione, rendendolo effettivamente non modificabile. WCF supporta l'ereditarietà del contratto, che può essere usata per creare un nuovo contratto che estende i contratti esistenti. Per usare questa funzionalità, definire una nuova interfaccia del contratto di servizio che eredita dall'interfaccia del contratto di servizio precedente, quindi aggiungere metodi alla nuova interfaccia. Modificare quindi il servizio che implementa il contratto precedente per implementare il nuovo contratto e modificare la definizione dell'endpoint "versionOld" per usare il nuovo contratto. Per i client "versionOld", l'endpoint continuerà a essere visualizzato come esposto al contratto "versionOld". ai client "versionNew", l'endpoint apparirà per esporre il contratto "versionNew".
Versionamento di indirizzo e binding
Le modifiche apportate all'indirizzo dell'endpoint e all'associazione sono modifiche significative a meno che i client non siano in grado di individuare dinamicamente il nuovo indirizzo o l'associazione dell'endpoint. Un meccanismo per implementare questa funzionalità consiste nell'usare un registro UDDI (Universal Discovery Description and Integration) e il modello di chiamata UDDI in cui un client tenta di comunicare con un endpoint e, in caso di errore, esegue una query su un registro UDDI noto per i metadati dell'endpoint corrente. Il client usa quindi l'indirizzo e l'associazione da questi metadati per comunicare con l'endpoint. Se la comunicazione ha esito positivo, il client memorizza nella cache l'indirizzo e le informazioni di associazione per un uso futuro.
Servizio di routing e controllo delle versioni
Se le modifiche apportate a un servizio causano modifiche di rilievo ed è necessario avere due o più versioni diverse di un servizio in esecuzione simultaneamente, è possibile usare il servizio di routing WCF per instradare i messaggi all'istanza del servizio appropriata. Il servizio di routing WCF usa il routing basato sul contenuto, in altre parole, usa le informazioni all'interno del messaggio per determinare dove instradare il messaggio. Per altre informazioni sul servizio di routing WCF, vedere Servizio di routing. Per un esempio di come usare il servizio di routing WCF per il controllo delle versioni dei servizi, vedere Procedura: Controllo delle versioni del servizio.
Appendice
Le linee guida generali per il controllo delle versioni dei contratti dati quando è necessario il controllo delle versioni restrittive consiste nel trattare i contratti dati come non modificabili e crearne di nuovi quando sono necessarie modifiche. È necessario creare una nuova classe per ogni nuovo contratto dati, quindi è necessario un meccanismo per evitare di dover accettare codice esistente scritto in termini di classe del contratto dati precedente e riscriverlo in termini di nuova classe del contratto dati.
Un meccanismo di questo tipo consiste nell'usare le interfacce per definire i membri di ogni contratto dati e scrivere codice di implementazione interno in termini di interfacce anziché le classi del contratto dati che implementano le interfacce. Il codice seguente per la versione 1 di un servizio mostra un'interfaccia IPurchaseOrderV1 e un PurchaseOrderV1 oggetto:
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 {...}
}
Anche se le operazioni del contratto di servizio verrebbero scritte in termini di PurchaseOrderV1, la logica di business effettiva sarebbe in termini di IPurchaseOrderV1. Quindi, nella versione 2, ci sarebbe una nuova IPurchaseOrderV2 interfaccia e una nuova PurchaseOrderV2 classe, come illustrato nel codice seguente:
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 { ... }
}
Il contratto di servizio verrà aggiornato per includere nuove operazioni scritte in termini di PurchaseOrderV2. La logica di business esistente scritta in termini di IPurchaseOrderV1 continuerà a funzionare per PurchaseOrderV2 e la nuova logica di business che richiede la OrderDate proprietà verrebbe scritta in termini di IPurchaseOrderV2.