Event-driven Architecture using C# Observer Pattern

Summary

The Observer design pattern is a useful pattern when combined with Event-driven architectures to create reactive systems. A source system emits a state change; this change is observed by methods that immediately react based on the state change.

In Azure, the Observer pattern can be used to react to state changes which in turn trigger an HTTP event.

HTTP isn’t the only method that can be invoked, a method can trigger another function or a custom routine that sits in another tier (Durable Functions, Worker process in a Virtual Machine or a custom workflow built-in Elsa).

C# Implementation

The code below demonstrates two Observers registering, then firing when there is a state change.

namespace Eax360.Patterns.Observer
{
    public interface ISubject
    {
        string State { get; set; }

        void Attach(IObserver observer);

        void Detach(IObserver observer);

        void Notify();
    }

    public interface IObserver
    {
        void Update(ISubject subject);
    }

    public class Subject : ISubject
    {
        private readonly List<IObserver> _observers;

        private string _state = string.Empty;

        public string State
        {
            get { return _state; }
            set
            {
                _state = value;
                Notify();
            }
        }

        public void Attach(IObserver observer)
        {
            _observers.Add(observer);
        }

        public void Detach(IObserver observer)
        {
            _observers.Remove(observer);
        }

        public void Notify()
        {
            _observers.ForEach(x =>
            {
                x.Update(this);
            });
        }

        public Subject() => _observers = new List<IObserver>();
    }

    public class Observer : IObserver
    {
        public string ObserverName { get; set; }
        public string ObserverRoute { get; set; }

        public void Update(ISubject subject)
        {
            if (subject is Subject s)
            {
                // Use Delegates to Invoke a Function
                var actions = new Dictionary<string, Action>
                {
                    {"ConsumerA", () => EventMethods.MessageChannelOne(s.State)},
                    {"ConsumerB", () => EventMethods.MessageChannelTwo(s.State)}
                 };

                actions[ObserverName]();
            }
        }

        public Observer(string name)
        {
            ObserverName = name;
        }

        public Observer(string name, string route)
        {
            ObserverName = name;
            ObserverRoute = route;
        }
    }

    public class EventMethods
    {
        public static void MessageChannelOne(string state)
        {
            // Add Business Logic to Apache Kafka
            Console.WriteLine($"{System.Reflection.MethodBase.GetCurrentMethod()} with state: {state}");
        }

        public static void MessageChannelTwo(string state)
        {
            // Add Business Logic to Apache Kafka
            Console.WriteLine($"{System.Reflection.MethodBase.GetCurrentMethod()} with state: {state}");
        }
    }

    public class Program
    {
        private static void Main()
        {
            Subject subject = new();

            Observer observer1 = new("ConsumerA");
            Observer observer2 = new("ConsumerB");

            // Attach Observers
            subject.Attach(observer1);
            subject.Attach(observer2);

            // Observe State
            subject.State = "Active";
            subject.State = "Inactive";

            // Attach Observers
            subject.Detach(observer1);
            subject.Detach(observer2);
        }
    }
}