Complete Implementation

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json; // For serialization and deserialization

public abstract class MessageEnvelope<T>
{
    // Immutable properties
    public string EventType { get; private init; }
    public string SourceService { get; private init; }
    public DateTime Timestamp { get; private init; }
    public Guid TraceId { get; private init; }
    public T Payload { get; private init; }

    // Private constructor to enforce the use of the builder
    private MessageEnvelope() { }

    // Static method to start building the envelope
    public static Builder CreateBuilder() => new Builder();

    // Nested Builder class
    public class Builder
    {
        private readonly MessageEnvelope<T> _envelope = new ConcreteMessageEnvelope();

        public Builder WithEventType(string eventType)
        {
            if (string.IsNullOrWhiteSpace(eventType))
                throw new ArgumentException("EventType cannot be null or empty");
            _envelope.EventType = eventType;
            return this;
        }

        public Builder WithSourceService(string sourceService)
        {
            if (string.IsNullOrWhiteSpace(sourceService))
                throw new ArgumentException("SourceService cannot be null or empty");
            _envelope.SourceService = sourceService;
            return this;
        }

        public Builder WithPayload(T payload)
        {
            _envelope.Payload = payload;
            return this;
        }

        public Builder WithTimestamp(DateTime timestamp)
        {
            _envelope.Timestamp = timestamp;
            return this;
        }

        public Builder WithTraceId(Guid traceId)
        {
            _envelope.TraceId = traceId;
            return this;
        }

        public Builder WithoutPayload()
        {
            _envelope.Payload = default;
            return this;
        }

        public MessageEnvelope<T> Build()
        {
            // Set defaults if not already set
            _envelope.EventType ??= "Unknown";
            _envelope.Timestamp = _envelope.Timestamp == default ? DateTime.UtcNow : _envelope.Timestamp;
            _envelope.TraceId = _envelope.TraceId == default ? Guid.NewGuid() : _envelope.TraceId;
            return _envelope;
        }
    }

    // Clone method to replicate an envelope with modifications
    public MessageEnvelope<T> Clone()
    {
        return CreateBuilder()
            .WithEventType(this.EventType)
            .WithSourceService(this.SourceService)
            .WithPayload(this.Payload)
            .WithTimestamp(this.Timestamp)
            .WithTraceId(this.TraceId)
            .Build();
    }

    // Serialization to JSON
    public string ToJson()
    {
        return JsonConvert.SerializeObject(this);
    }

    // Deserialization from JSON
    public static MessageEnvelope<T> FromJson(string json)
    {
        return JsonConvert.DeserializeObject<ConcreteMessageEnvelope>(json);
    }

    // Batch creation for multiple payloads
    public static IEnumerable<MessageEnvelope<T>> CreateBatch(IEnumerable<T> payloads, string eventType, string sourceService)
    {
        return payloads.Select(payload =>
            CreateBuilder()
                .WithEventType(eventType)
                .WithSourceService(sourceService)
                .WithPayload(payload)
                .Build());
    }

    // Example of a concrete implementation
    private class ConcreteMessageEnvelope : MessageEnvelope<T>
    {
    }
}

Examples

1. Basic Envelope Creation

var envelope = MessageEnvelope<Reservation>
    .CreateBuilder()
    .WithEventType("ReservationExpiry")
    .WithSourceService("ReservationService")
    .WithPayload(new Reservation
    {
        ReservationId = "res-001",
        SlotId = "slot-123",
        ExpiryTime = DateTime.UtcNow
    })
    .Build();

Console.WriteLine(envelope.ToJson());

2. Creating an Envelope Without Payload

var metadataOnlyEnvelope = MessageEnvelope<object>
    .CreateBuilder()
    .WithEventType("SystemEvent")
    .WithSourceService("MonitoringService")
    .WithoutPayload()
    .Build();

3. Cloning an Envelope

var clonedEnvelope = envelope.Clone();
Console.WriteLine(clonedEnvelope.ToJson());

4. Batch Creation

var reservations = new List<Reservation>
{
    new Reservation { ReservationId = "res-001", SlotId = "slot-123", ExpiryTime = DateTime.UtcNow },
    new Reservation { ReservationId = "res-002", SlotId = "slot-456", ExpiryTime = DateTime.UtcNow.AddHours(1) }
};

var envelopes = MessageEnvelope<Reservation>.CreateBatch(reservations, "ReservationExpiry", "ReservationService");

foreach (var env in envelopes)
{
    Console.WriteLine(env.ToJson());
}

5. Serialization and Deserialization

string serialized = envelope.ToJson();
var deserialized = MessageEnvelope<Reservation>.FromJson(serialized);

Console.WriteLine($"Deserialized TraceId: {deserialized.TraceId}");

Conclusion

This implementation leverages Newtonsoft.Json to serialize and deserialize objects efficiently. The inclusion of batch creation, cloning, and flexibility makes this envelope a robust solution for designing reliable messaging systems in distributed architectures.

Related Posts