﻿# Avro Serializer sample

<!-- Version variant: core_9; default: [/samples/serializers/avro/index.md](/samples/serializers/avro/index.md) -->


> [!NOTE]
> This sample showcases how serialization using Avro can work, but it is not a supported serializer and some use cases may not work. See our [serializer documentation for a full list of supported serializers](/nservicebus/serialization/index.md#supported-serializers).

This sample uses [Apache.Avro data serialization system](https://www.nuget.org/packages/apache.avro) to serialize and deserialize the messages.

## Limitations

This sample has the following limitations:

- It does not support [message types defined using C# interfaces](/nservicebus/messaging/messages-as-interfaces.md)
- It is not able to infer the message type from the payload like some other serializers; therefore, the [`NServiceBus.EnclosedMessageTypes` header](/nservicebus/messaging/headers.md#serialization-headers-nservicebus-enclosedmessagetypes) must be present on all messages.

## Configuration

Configure the endpoint to use the Avro serializer as follows:

<!-- snippet: config -->

```cs
var endpointConfiguration = new EndpointConfiguration("Samples.Serialization.Avro");

endpointConfiguration.UseSerialization<AvroSerializer>();
```

<!-- endsnippet -->

## Schema registry

The sample expects the message schema to be present as an embedded resource in the same folder where the message type resides. The schemas are read on startup and cached into a SchemaRegistry.
This choice was made to simplify the use of this sample. In production, it's recommended to use a central registry like:

- [Event Hubs Schema Registry](https://learn.microsoft.com/en-us/azure/event-hubs/schema-registry-concepts)
- [Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/serdes-develop/index.html)
- [AWS Glue Schema Registry](https://docs.aws.amazon.com/glue/latest/dg/schema-registry.html)
- [Apicurio Registry](https://www.apicur.io/registry/)
- [Redpanda Schema Registry](https://docs.redpanda.com/current/manage/schema-reg)

> [!NOTE]
> When a schema is not found, a `MessageDeserializationException` will be thrown, which will cause the message to [be moved to the configured error queue](/nservicebus/recoverability/index.md#fault-handling) without retries.

## Code

The serializer is implemented as a [custom serializer](/nservicebus/serialization/custom-serializer.md) by creating a `SerializerDefinition`:

<!-- snippet: serializer-definition -->

```cs
public class AvroSerializer : SerializationDefinition
{
    public override Func<IMessageMapper, IMessageSerializer> Configure(IReadOnlySettings settings)
    {
        var registry = settings.Get<MessageMetadataRegistry>();
        var messageTypes = registry.GetAllMessages().Select(m => m.MessageType);
        var schemaCache = new SchemaRegistry();
        var assembly = Assembly.GetExecutingAssembly();

        foreach (var messageType in messageTypes)
        {
            var manifestNamespace = "Sample.";
            var schemaResourceName = manifestNamespace + messageType.Name + ".avsc";
            using var stream = assembly.GetManifestResourceStream(schemaResourceName);

            if (stream == null)
            {
                throw new InvalidOperationException(
                    $"Resource '{schemaResourceName}' not found in assembly '{assembly.FullName}'.");
            }

            // Load the schema from the embedded resource
            using var reader = new StreamReader(stream);
            var schemaJson = reader.ReadToEnd();

            // Parse and cache the schema
            schemaCache.Add(messageType, Schema.Parse(schemaJson));
        }

        return _ => new AvroMessageSerializer(schemaCache, new ClassCache());
    }
}
```

<!-- endsnippet -->

and also implementing the message serializer interface:

<!-- snippet: serializer-implementation -->

```cs
public class AvroMessageSerializer(SchemaRegistry schemaRegistry, ClassCache classCache) : IMessageSerializer
{
    public string ContentType => "avro/json";

    public void Serialize(object message, Stream stream)
    {
        var messageType = message.GetType();
        var schema = schemaRegistry.GetSchema(messageType);
        var writer = new ReflectDefaultWriter(messageType, schema, classCache);

        var encoder = new JsonEncoder(schema, stream);

        writer.Write(message, encoder);

        encoder.Flush();
    }

    public object[] Deserialize(ReadOnlyMemory<byte> body, IList<Type> messageTypes = null)
    {
        if (messageTypes == null)
        {
            throw new MessageDeserializationException(
                "Avro is not able to infer message types from the body content only," +
                "the NServiceBus.EnclosedMessageTypes header must be present");
        }

        var messages = new List<object>();
        foreach (var messageType in messageTypes)
        {
            try
            {
                var schema = schemaRegistry.GetSchema(messageType);
                var reader = new ReflectDefaultReader(messageType, schema, schema, classCache);
                using var stream = new ReadOnlyStream(body);
                var message = reader.Read(null, schema, schema, new JsonDecoder(schema, stream));
                messages.Add(message);
            }
            catch (KeyNotFoundException)
            {
                throw new MessageDeserializationException(
                    $"No schema found for message type {messageType.FullName}");
            }
        }

        return messages.ToArray();
    }
}
```

<!-- endsnippet -->

## Sending the message

Prepare and send an order message with sample data:

<!-- snippet: message -->

```cs
var message = new CreateOrder
{
    OrderId = 9,
    Date = DateTime.Now,
    CustomerId = 12,
    OrderItems =
    [
        new OrderItem
        {
            ItemId = 6,
            Quantity = 2
        },

        new OrderItem
        {
            ItemId = 5,
            Quantity = 4
        }
    ]
};

await messageSession.SendLocal(message);
```

<!-- endsnippet -->

## Output

The serialized message output appears as follows:

```json
{
  "OrderId": 9,
  "Date": "2015-09-15T10:23:44.9367871+10:00",
  "CustomerId": 12,
  "OrderItems": [
    {
      "ItemId": 6,
      "Quantity": 2
    },
    {
      "ItemId": 5,
      "Quantity": 4
    }
  ]
}
```
