Condividi tramite


Serializzazione in Orleans

Esistono sostanzialmente due tipi di serializzazione usati in Orleans:

  • Serializzazione delle chiamate granulari : usata per serializzare gli oggetti passati da e verso grani.
  • Serializzazione dell’archiviazione granulare: usata per serializzare oggetti da e verso sistemi di archiviazione.

Questo articolo è principalmente dedicato alla serializzazione delle chiamate granulari tramite il framework di serializzazione incluso in Orleans. La sezione Serializzatori di archiviazione granulare tratta la serializzazione dell'archiviazione granulare.

Usare la serializzazione Orleans

Orleans include un framework di serializzazione avanzato ed estendibile che può essere definito OrleansSerializzazione. Il framework di serializzazione incluso in Orleans è progettato per soddisfare i seguenti obiettivi:

  • Prestazioni elevate: il serializzatore è progettato e ottimizzato per alte prestazioni. Ulteriori informazioni sono disponibili in questa presentazione.
  • Alta fedeltà: il serializzatore rappresenta fedelmente la maggior parte del sistema di tipi di .NET, compreso il supporto per generics, polimorfismo, gerarchie di ereditarietà, identità degli oggetti e grafi ciclici. I puntatori non sono supportati, poiché non sono portabili tra i processi.
  • Flessibilità: il serializzatore può essere personalizzato per supportare librerie di terzi creando surrogati o delegando a librerie di serializzazione esterne, come System.Text.Json, Newtonsoft.Json e Google.Protobuf.
  • Tolleranza di versione: il serializzatore consente ai tipi di applicazione di evolversi nel tempo, supportando:
    • Aggiunta e rimozione di membri
    • Sottoclassi
    • Ampliamento e restringimento numerico e (ad esempio, int verso/da long, float verso/da double)
    • Ridenominazione dei tipi

La rappresentazione di tipi ad alta fedeltà è piuttosto rara per i serializzatori; è quindi necessario elaborare ulteriormente alcuni punti:

  1. Tipi dinamici e polimorfismo arbitrario: Orleans non applica restrizioni ai tipi che possono essere passati nelle chiamate di granularità e mantengono la natura dinamica del tipo di dati effettivo. Ciò significa, ad esempio, che se il metodo nelle interfacce granulari accetta IDictionary ma in fase di runtime e il mittente passa SortedDictionary<TKey,TValue>, il ricevitore otterrà effettivamente SortedDictionary (anche se l'interfaccia "contratto statico"/granulare non ha specificato questo comportamento).

  2. Mantenimento dell'identità dell’oggetto: se lo stesso oggetto viene passato a più tipi negli argomenti di una chiamata granulare o punta indirettamente più volte dagli argomenti, Orleans lo serializzerà una sola volta. Sul lato ricevitore, Orleans ripristinerà correttamente tutti i riferimenti in modo che due puntatori allo stesso oggetto puntino allo stesso oggetto anche dopo la deserializzazione. È importante preservare l’identità degli oggetti in scenari analoghi al seguente. Si supponga che la granularità A invii un dizionario con 100 voci alla granularità B e che 10 delle chiavi nel dizionario puntino allo stesso oggetto, obj, sul lato A. Senza preservare l'identità dell'oggetto, B riceverebbe un dizionario di 100 voci con tali 10 chiavi puntate verso 10 cloni diversi di obj. Preservando l’identità dell'oggetto, il dizionario sul lato B è identico a quello di A, con le 10 chiavi puntate verso un singolo oggetto obj. Notare che poiché le implementazioni predefinite del codice hash di stringa in .NET sono casuali per processo, l'ordinamento dei valori nei dizionari e nei set di hash (ad esempio) potrebbe non essere preservato.

Per supportare la tolleranza di versione, il serializzatore richiede che gli sviluppatori indichino esplicitamente quali tipi e membri sono serializzati. Abbiamo cercato di rendere il processo più indolore possibile. È necessario contrassegnare tutti i tipi serializzabili con Orleans.GenerateSerializerAttribute per indicare a Orleans di generare codice serializzatore per il tipo. Dopo aver completato questa operazione, è possibile usare la correzione del codice inclusa per aggiungere il necessario Orleans.IdAttribute ai membri serializzabili nei tipi, come illustrato di seguito:

An animated image of the available code fix being suggested and applied on the GenerateSerializerAttribute when the containing type doesn't contain IdAttribute's on its members.

Di seguito è riportato un esempio di tipo serializzabile in Orleans, che illustra come applicare gli attributi.

[GenerateSerializer]
public class Employee
{
    [Id(0)]
    public string Name { get; set; }
}

Orleans supporta l'ereditarietà e serializzerà i singoli livelli della gerarchia separatamente, consentendo loro di avere ID membro distinti.

[GenerateSerializer]
public class Publication
{
    [Id(0)]
    public string Title { get; set; }
}

[GenerateSerializer]
public class Book : Publication
{
    [Id(0)]
    public string ISBN { get; set; }
}

Notare come nel codice precedente, sia Publication che Book hanno membri con [Id(0)], anche se Book deriva da Publication. Questa è la procedura consigliata in Orleans, poiché gli identificatori dei membri hanno come ambito il livello di ereditarietà, non il tipo nel suo complesso. I membri possono essere indipendentemente aggiunti e rimossi da Publication e Book, ma non è possibile inserire una nuova classe di base nella gerarchia dopo la distribuzione dell'applicazione senza particolari considerazioni.

Orleans supporta anche la serializzazione dei tipi con membri internal, private e readonly, come questo esempio di tipo:

[GenerateSerializer]
public struct MyCustomStruct
{
    public MyCustom(int intProperty, int intField)
    {
        IntProperty = intProperty;
        _intField = intField;
    }

    [Id(0)]
    public int IntProperty { get; }

    [Id(1)] private readonly int _intField;
    public int GetIntField() => _intField;

    public override string ToString() => $"{nameof(_intField)}: {_intField}, {nameof(IntProperty)}: {IntProperty}";
}

Per impostazione predefinita, Orleans serializzerà il tipo codificandone il nome completo. È possibile eseguire l'override di questa operazione aggiungendo un Orleans.AliasAttribute. Così facendo, il tipo viene serializzato usando un nome resiliente alla ridenominazione della classe sottostante o allo spostamento tra assembly. Gli alias di tipo hanno ambito globale e due alias con lo stesso valore non possono coesistere in un'applicazione. Per tipi generici, il valore alias deve includere il numero di parametri generici preceduto da un apice inverso; ad esempio, MyGenericType<T, U> potrebbe avere l'alias [Alias("mytype`2")].

Serializzazione dei tipi record

Per impostazione predefinita, i membri definiti nel costruttore primario di un record hanno ID impliciti. In altre parole, Orleans supporta la serializzazione dei tipi record. Ciò significa che non è possibile modificare l'ordine dei parametri per un tipo già distribuito, poiché ciò interromperebbe la compatibilità con le versioni precedenti dell'applicazione (nel caso di un aggiornamento in sequenza) e con istanze serializzate di tale tipo in archiviazione e flussi. I membri definiti nel corpo di un tipo di record non condividono le identità con i parametri del costruttore primario.

[GenerateSerializer]
public record MyRecord(string A, string B)
{
    // ID 0 won't clash with A in primary constructor as they don't share identities
    [Id(0)]
    public string C { get; init; }
}

Se non si desidera che i parametri del costruttore primario vengano inclusi automaticamente come campi serializzabili, usare [GenerateSerializer(IncludePrimaryConstructorParameters = false)].

Surrogati per la serializzazione di tipi esterni

Talvolta potrebbe essere necessario passare tipi tra grani sui quali non si ha completo controllo. In questi casi, la conversione manuale verso e da tipi definiti dall’utente all’interno del codice dell’applicazione potrebbe risultare poco pratico. Orleans offre una soluzione per queste situazioni sotto forma di tipi surrogati. I surrogati vengono serializzati al posto del tipo di destinazione e dispongono di funzionalità per la conversione da e verso il tipo di destinazione. Si consideri il seguente esempio di un tipo esterno e un surrogato e un convertitore corrispondenti:

// This is the foreign type, which you do not have control over.
public struct MyForeignLibraryValueType
{
    public MyForeignLibraryValueType(int num, string str, DateTimeOffset dto)
    {
        Num = num;
        String = str;
        DateTimeOffset = dto;
    }

    public int Num { get; }
    public string String { get; }
    public DateTimeOffset DateTimeOffset { get; }
}

// This is the surrogate which will act as a stand-in for the foreign type.
// Surrogates should use plain fields instead of properties for better performance.
[GenerateSerializer]
public struct MyForeignLibraryValueTypeSurrogate
{
    [Id(0)]
    public int Num;

    [Id(1)]
    public string String;

    [Id(2)]
    public DateTimeOffset DateTimeOffset;
}

// This is a converter that converts between the surrogate and the foreign type.
[RegisterConverter]
public sealed class MyForeignLibraryValueTypeSurrogateConverter :
    IConverter<MyForeignLibraryValueType, MyForeignLibraryValueTypeSurrogate>
{
    public MyForeignLibraryValueType ConvertFromSurrogate(
        in MyForeignLibraryValueTypeSurrogate surrogate) =>
        new(surrogate.Num, surrogate.String, surrogate.DateTimeOffset);

    public MyForeignLibraryValueTypeSurrogate ConvertToSurrogate(
        in MyForeignLibraryValueType value) =>
        new()
        {
            Num = value.Num,
            String = value.String,
            DateTimeOffset = value.DateTimeOffset
        };
}

Nel codice precedente:

  • MyForeignLibraryValueType non è sotto il controllo dell’utente, ed è definito in una libreria di utilizzo.
  • MyForeignLibraryValueTypeSurrogate è un tipo surrogato che esegue il mapping a MyForeignLibraryValueType.
  • Il RegisterConverterAttribute specifica che il MyForeignLibraryValueTypeSurrogateConverter funge da convertitore per eseguire il mapping verso e da i due tipi. La classe è un'implementazione dell'interfaccia IConverter<TValue,TSurrogate>.

Orleans supporta la serializzazione dei tipi all’interno di gerarchie di tipo (tipi che derivano da altri tipi). Nel caso in cui un tipo esterno appaia in una gerarchia di tipi (ad esempio, come classe di base per uno dei propri tipi), è necessario implementare anche l'interfaccia Orleans.IPopulator<TValue,TSurrogate>. Si consideri l'esempio seguente:

// The foreign type is not sealed, allowing other types to inherit from it.
public class MyForeignLibraryType
{
    public MyForeignLibraryType() { }

    public MyForeignLibraryType(int num, string str, DateTimeOffset dto)
    {
        Num = num;
        String = str;
        DateTimeOffset = dto;
    }

    public int Num { get; set; }
    public string String { get; set; }
    public DateTimeOffset DateTimeOffset { get; set; }
}

// The surrogate is defined as it was in the previous example.
[GenerateSerializer]
public struct MyForeignLibraryTypeSurrogate
{
    [Id(0)]
    public int Num;

    [Id(1)]
    public string String;

    [Id(2)]
    public DateTimeOffset DateTimeOffset;
}

// Implement the IConverter and IPopulator interfaces on the converter.
[RegisterConverter]
public sealed class MyForeignLibraryTypeSurrogateConverter :
    IConverter<MyForeignLibraryType, MyForeignLibraryTypeSurrogate>,
    IPopulator<MyForeignLibraryType, MyForeignLibraryTypeSurrogate>
{
    public MyForeignLibraryType ConvertFromSurrogate(
        in MyForeignLibraryTypeSurrogate surrogate) =>
        new(surrogate.Num, surrogate.String, surrogate.DateTimeOffset);

    public MyForeignLibraryTypeSurrogate ConvertToSurrogate(
        in MyForeignLibraryType value) =>
        new()
    {
        Num = value.Num,
        String = value.String,
        DateTimeOffset = value.DateTimeOffset
    };

    public void Populate(
        in MyForeignLibraryTypeSurrogate surrogate, MyForeignLibraryType value)
    {
        value.Num = surrogate.Num;
        value.String = surrogate.String;
        value.DateTimeOffset = surrogate.DateTimeOffset;
    }
}

// Application types can inherit from the foreign type, assuming they're not sealed
// since Orleans knows how to serialize it.
[GenerateSerializer]
public sealed class DerivedFromMyForeignLibraryType : MyForeignLibraryType
{
    public DerivedFromMyForeignLibraryType() { }

    public DerivedFromMyForeignLibraryType(
        int intValue, int num, string str, DateTimeOffset dto) : base(num, str, dto)
    {
        IntValue = intValue;
    }

    [Id(0)]
    public int IntValue { get; set; }
}

Regole di controllo delle versioni

La tolleranza di versione è supportata, a condizione che lo sviluppatore segua una serie di norme durante la modifica dei tipi. Se lo sviluppatore ha familiarità con sistemi come Google Protocol Buffers (Protobuf), queste norme risulteranno familiari.

Tipi composti (class e struct)

  • L'ereditarietà è supportata, ma la modifica della gerarchia di ereditarietà di un oggetto non è supportata. La classe base di una classe non può essere aggiunta, modificata a un'altra classe o rimossa.
  • Ad eccezione di alcuni tipi numerici, descritti nella sezioneNumerici seguente, i tipi di campo non possono essere modificati.
  • I campi possono essere aggiunti o rimossi in qualsiasi punto di una gerarchia di ereditarietà.
  • Non è possibile modificare gli ID campo.
  • Gli ID campo devono necessariamente essere univoci per ogni livello in una gerarchia di tipi, ma possono essere riutilizzati tra classi base e sottoclassi. Ad esempio, la classe Base può dichiarare un campo con ID 0, e un altro campo può essere dichiarato da Sub : Base con lo stesso ID, 0.

Valori numerici

  • Non è possibile modificare la firma di un campo numerico.
    • Le conversioni tra int e uint non sono valide.
  • È possibile modificare la larghezza di un campo numerico.
    • Ad esempio, le conversioni da int a long o da ulong a ushort sono supportate.
    • Le conversioni che restringono la larghezza genereranno un’eccezione se il valore di runtime di un campo potrebbe provocare un overflow.
      • La conversione da ulong a ushort è supportata solo se il valore in fase di runtime è minore di ushort.MaxValue.
      • Le conversioni da double a float sono supportate solo se il valore di runtime è compreso tra float.MinValue e float.MaxValue.
      • decimal, il quale ha un intervallo più ristretto di e double float, funziona analogamente.

Copiatrici

Per impostazione predefinita, Orleans promuove la sicurezza. Ciò include la sicurezza di alcune classi di bug di concorrenza. In particolare, Orleans copierà immediatamente gli oggetti passati nelle chiamate granulari per impostazione predefinita. Questa operazione di copiatura è facilitata dalla serializzazione Orleans, e quando Orleans.CodeGeneration.GenerateSerializerAttribute viene applicata a un tipo, anche Orleans genererà copie per tale tipo. Orleans eviterà di copiare tipi o singoli membri contrassegnati usando ImmutableAttribute. Per ulteriori dettagli, consultare Serializzazione di tipi non modificabili in Orleans.

Procedure consigliate per la serializzazione

  • Assegnare gli alias dei tipi usando l'attributo [Alias("my-type")]. I tipi con alias possono essere rinominati senza compromettere la compatibilità.

  • Non modificare un record in un normale class o viceversa. Record e classi non sono rappresentati allo stesso identico modo poiché i record hanno membri del costruttore primari oltre ai membri normali, e pertanto i due non sono intercambiabili.

  • Non aggiungere nuovi tipi a una gerarchia di tipi esistente per un tipo serializzabile. Non aggiungere mai una nuova classe di base a un tipo esistente. È possibile aggiungere in sicurezza una nuova sottoclasse a un tipo esistente.

  • Sostituire gli utilizzi di SerializableAttribute con GenerateSerializerAttribute e le dichiarazioni IdAttribute corrispondenti.

  • Impostare tutti gli ID membro su zero all’avvio per ogni tipo. Gli ID in una sottoclasse e la relativa classe di base possono essere sovrapposti in sicurezza. Entrambe le proprietà nel seguente esempio hanno ID equivalenti a 0.

    [GenerateSerializer]
    public sealed class MyBaseClass
    {
        [Id(0)]
        public int MyBaseInt { get; set; }
    }
    
    [GenerateSerializer]
    public sealed class MySubClass : MyBaseClass
    {
        [Id(0)]
        public int MyBaseInt { get; set; }
    }
    
  • Ampliare i tipi di membri numerici in base alle esigenze. È possibile ampliare sbyte a short aint a long.

    • È possibile restringere i tipi di membri numerici; tuttavia, tale operazione genererà un'eccezione di runtime se i valori osservati non possono essere rappresentati correttamente dal tipo ristretto. Ad esempio, int.MaxValue non può essere rappresentato da un campo short; quindi, il restringimento di un campo int a short potrebbe risultare in un'eccezione di runtime se tale valore è stato rilevato.
  • Non modificare la firma di un membro di tipo numerico. Ad esempio, non modificare il tipo di un membro da uint a int o un int a un uint.

Serializzatori di archiviazione granulari

Orleans include un modello di persistenza per grani supportato dal provider, accessibile tramite la proprietà State o inserendo uno o più valori IPersistentState<TState> nella granularità. Prima di Orleans 7.0, ciascun provider seguiva un meccanismo diverso per la configurazione della serializzazione. In Orleans 7.0 è ora disponibile un'interfaccia generica per la serializzatore dello stato di granularità (IGrainStorageSerializer), la quale offre un metodo coerente per personalizzare la serializzazione dello stato per ogni provider. I provider di archiviazione supportati implementano un modello che prevede che la proprietà IStorageProviderSerializerOptions.GrainStorageSerializer sia impostata sulla classe opzioni del provider, ad esempio:

Per impostazione predefinita, la serializzazione dell'archiviazione granulare è Newtonsoft.Json per la serializzazione dello stato. È possibile sostituirla modificando la proprietà in fase di configurazione. Il seguente esempio illustra questa operazione usando OptionsBuilder<TOptions>:

siloBuilder.AddAzureBlobGrainStorage(
    "MyGrainStorage",
    (OptionsBuilder<AzureBlobStorageOptions> optionsBuilder) =>
    {
        optionsBuilder.Configure<IMySerializer>(
            (options, serializer) => options.GrainStorageSerializer = serializer);
    });

Per ulteriori informazioni, consultare OptionsBuilder API.

Orleans dispone di un framework di serializzazione avanzato ed estendibile. Orleans serializza i tipi di dati passati nei messaggi di richiesta e risposta granulari, nonché negli oggetti di stato persistente granulari. Come parte di questo framework, Orleans genera automaticamente il codice di serializzazione per tali tipi di dati. Oltre a generare una serializzazione/deserializzazione più efficiente per i tipi che sono già serializzabili da .NET, Orleans tenta anche di generare serializzatori per i tipi usati nelle interfacce granulari non serializzabili in NET. Il framework include anche un set di efficienti serializzatori integrati per tipi di uso frequente: elenchi, dizionari, stringhe, primitive, matrici e così via.

Due importanti funzionalità del serializzatore Orleans lo differenziano da molti altri framework di serializzazione di terzi: tipi dinamici/polimorfismo arbitrario e identità degli oggetti.

  1. Tipi dinamici e polimorfismo arbitrario: Orleans non applica restrizioni ai tipi che possono essere passati nelle chiamate di granularità e mantengono la natura dinamica del tipo di dati effettivo. Ciò significa, ad esempio, che se il metodo nelle interfacce granulari accetta IDictionary ma in fase di runtime e il mittente passa SortedDictionary<TKey,TValue>, il ricevitore otterrà effettivamente SortedDictionary (anche se l'interfaccia "contratto statico"/granulare non ha specificato questo comportamento).

  2. Mantenimento dell'identità dell’oggetto: se lo stesso oggetto viene passato a più tipi negli argomenti di una chiamata granulare o punta indirettamente più volte dagli argomenti, Orleans lo serializzerà una sola volta. Sul lato ricevitore, Orleans ripristinerà correttamente tutti i riferimenti in modo che due puntatori allo stesso oggetto puntino allo stesso oggetto anche dopo la deserializzazione. È importante preservare l’identità degli oggetti in scenari analoghi al seguente. Si supponga che la granularità A invii un dizionario con 100 voci alla granularità B e che 10 delle chiavi nel dizionario puntino allo stesso oggetto, obj, sul lato A. Senza preservare l'identità dell'oggetto, B riceverebbe un dizionario di 100 voci con tali 10 chiavi puntate verso 10 cloni diversi di obj. Preservando l’identità dell'oggetto, il dizionario sul lato B è identico a quello di A, con le 10 chiavi puntate verso un singolo oggetto obj.

I due comportamenti precedentemente descritti sono forniti dal serializzatore binario .NET standard e consideriamo quindi importante supportare questo comportamento standard e comune anche in Orleans.

Serializzatori generati

Orleans usa le seguenti regole per decidere quali serializzatori generare. Le regole sono le seguenti:

  1. Analizza tutti i tipi in tutti gli assembly che fanno riferimento alla libreria principale Orleans.
  2. Da tali assembly, generare serializzatori per tipi a cui si fa diretto riferimento in firme di metodo di interfacce granulari, firme di classe di stato, o per tutti i tipi contrassegnati da SerializableAttribute.
  3. Inoltre, un'interfaccia granulare o un progetto di implementazione può puntare a tipi arbitrari per la generazione della serializzazione aggiungendo attributi KnownTypeAttribute o KnownAssemblyAttribute a livello di assembly allo scopo di indicare al generatore di codice di generare serializzatori per tipi specifici o per tutti i tipi idonei all'interno di un assembly. Per ulteriori informazioni sugli attributi a livello di assembly, consultare Applicare attributi a livello di assembly.

Serializzazione di fallback

Orleans supporta la trasmissione di tipi arbitrari in fase di esecuzione e pertanto il generatore di codice integrato non può determinare l'intero set di tipi che verranno trasmessi in anticipo. Inoltre, non è possibile generare serializzatori per alcuni tipi poiché inaccessibili (ad esempio, private) o con campi inaccessibili (ad esempio, readonly). Di conseguenza, la serializzazione just-in-time risulta necessaria per tipi imprevisti o per cui non è possibile generare serializzatori in anticipo. Il serializzatore responsabile di questi tipi è denominato serializzatore di fallback. Orleans viene fornito con due serializzatori di fallback:

Il serializzatore di fallback può essere configurato usando la proprietà FallbackSerializationProvider sia su ClientConfiguration nel client che su GlobalConfiguration nei silo.

// Client configuration
var clientConfiguration = new ClientConfiguration();
clientConfiguration.FallbackSerializationProvider =
    typeof(FantasticSerializer).GetTypeInfo();

// Global configuration
var globalConfiguration = new GlobalConfiguration();
globalConfiguration.FallbackSerializationProvider =
    typeof(FantasticSerializer).GetTypeInfo();

In alternativa, il provider di serializzazione di fallback può essere specificato nella configurazione XML:

<Messaging>
    <FallbackSerializationProvider
        Type="GreatCompany.FantasticFallbackSerializer, GreatCompany.SerializerAssembly"/>
</Messaging>

BinaryFormatterSerializer è il serializzatore di fallback predefinito.

Serializzazione delle eccezioni

Le eccezioni vengono serializzate usando il serializzatore di fallback. Quando si usa la configurazione predefinita, BinaryFormatter è il serializzatore di fallback; pertanto, è necessario seguire il criterio ISerializable per garantire la corretta serializzazione di tutte le proprietà in un tipo di eccezione.

Riportato di seguito è un esempio di tipo di eccezione con serializzazione implementata correttamente:

[Serializable]
public class MyCustomException : Exception
{
    public string MyProperty { get; }

    public MyCustomException(string myProperty, string message)
        : base(message)
    {
        MyProperty = myProperty;
    }

    public MyCustomException(string transactionId, string message, Exception innerException)
        : base(message, innerException)
    {
        MyProperty = transactionId;
    }

    // Note: This is the constructor called by BinaryFormatter during deserialization
    public MyCustomException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        MyProperty = info.GetString(nameof(MyProperty));
    }

    // Note: This method is called by BinaryFormatter during serialization
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue(nameof(MyProperty), MyProperty);
    }
}

Procedure consigliate per la serializzazione

La serializzazione svolge due principali funzioni in Orleans:

  1. Funge da formato di collegamento per la trasmissione di dati tra grani e client in fase di esecuzione.
  2. Funge da formato di archiviazione per dati persistenti di lunga durata da recuperare in seguito.

I serializzatori generati da Orleans sono adatti per la prima funzione grazie alla loro flessibilità, prestazioni e versatilità. Non sono adatti alla seconda funzione, poiché non possiedono esplicitamente tolleranza di versione. È consigliabile per gli utenti configurare un serializzatore con tolleranza di versione, ad esempio buffer di protocollo per i dati persistenti. I buffer di protocollo sono supportati tramite Orleans.Serialization.ProtobufSerializer dal pacchetto Microsoft.Orleans.OrleansGoogleUtilsNuGet. È consigliabile usare le procedure suggerite per il serializzatore di scelta al fine di garantire la tolleranza di versione. I serializzatori di terzi possono essere configurati usando la proprietà di configurazioneSerializationProviders, come descritto in precedenza.