🧠 Mastering 5 Design Patterns in C# - Parte 6

✨ Introduction

These patterns focus on business logic composition, fault tolerance, and efficient processing. None of the previously listed patterns are repeated. Let’s dive into:

  • Specification

  • Event Sourcing

  • Throttling

  • Retry

  • Pipeline


πŸ“‹ 1. Specification – Composable Business Rules

βœ… Intent

Encapsulate business rules into reusable, combinable objects.

🎯 Use When

You need complex, reusable validation logic, such as deciding whether a notification should be sent.

🧩 Code

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T item);
}

public class LengthSpecification : ISpecification<string>
{
    public bool IsSatisfiedBy(string message) => message.Length <= 140;
}

public class NoSpamSpecification : ISpecification<string>
{
    public bool IsSatisfiedBy(string message) => !message.Contains("spam");
}

public class AndSpecification<T> : ISpecification<T>
{
    private readonly ISpecification<T> _a, _b;

    public AndSpecification(ISpecification<T> a, ISpecification<T> b)
    {
        _a = a;
        _b = b;
    }

    public bool IsSatisfiedBy(T item) => _a.IsSatisfiedBy(item) && _b.IsSatisfiedBy(item);
}

βœ… Explanation

Each rule is encapsulated in its own class and can be combined using logical operators.

πŸ‘ Pros

  • Highly reusable and testable

  • Easy to compose complex rules

  • Promotes separation of concerns

πŸ‘Ž Cons

  • Can lead to many small classes

  • Slightly more verbose than inline logic


🧾 2. Event Sourcing – Storing State as Events

βœ… Intent

Store the state of a system as a sequence of events.

🎯 Use When

You want to track everything that happened, such as notifications sent, canceled, or retried.

🧩 Code

public abstract class NotificationEvent
{
    public DateTime Timestamp { get; } = DateTime.UtcNow;
}

public class NotificationSent : NotificationEvent
{
    public string Message { get; }

    public NotificationSent(string message)
    {
        Message = message;
    }
}

public class EventStore
{
    private readonly List<NotificationEvent> _events = new();

    public void Add(NotificationEvent e) => _events.Add(e);

    public IEnumerable<NotificationEvent> GetAll() => _events;
}

βœ… Explanation

Events are stored instead of final state, allowing full reconstruction and auditability.

πŸ‘ Pros

  • Full history of changes

  • Great for auditing and debugging

  • Enables time travel and replay

πŸ‘Ž Cons

  • Requires more storage and management

  • Complex to query current state


🚦 3. Throttling – Rate Limiting

βœ… Intent

Limit the number of actions within a time window.

🎯 Use When

You want to prevent overload, such as limiting the number of notifications per minute.

🧩 Code

public class Throttler
{
    private readonly int _limit;
    private readonly TimeSpan _interval;
    private readonly Queue<DateTime> _timestamps = new();

    public Throttler(int limit, TimeSpan interval)
    {
        _limit = limit;
        _interval = interval;
    }

    public bool CanSend()
    {
        var now = DateTime.UtcNow;

        while (_timestamps.Count > 0 && now - _timestamps.Peek() > _interval)
            _timestamps.Dequeue();

        if (_timestamps.Count < _limit)
        {
            _timestamps.Enqueue(now);
            return true;
        }

        return false;
    }
}

βœ… Explanation

Keeps track of timestamps and enforces a limit within a time window.

πŸ‘ Pros

  • Prevents abuse or overload

  • Easy to implement

  • Useful for APIs and messaging systems

πŸ‘Ž Cons

  • Requires time tracking logic

  • May block legitimate requests


πŸ” 4. Retry – Fault-Tolerant Execution

βœ… Intent

Retry a failed operation until it succeeds or reaches a limit.

🎯 Use When

You want to handle transient failures, such as network issues when sending notifications.

🧩 Code

public class RetryPolicy
{
    public static void Execute(Action action, int maxAttempts = 3)
    {
        int attempts = 0;
        while (attempts < maxAttempts)
        {
            try
            {
                action();
                return;
            }
            catch
            {
                attempts++;
                Thread.Sleep(500); // wait between attempts
            }
        }

        Console.WriteLine("Failed after multiple attempts.");
    }
}

βœ… Explanation

Wraps an action in retry logic with a delay between attempts.

πŸ‘ Pros

  • Improves resilience

  • Handles temporary failures gracefully

  • Simple to apply

πŸ‘Ž Cons

  • Can mask deeper issues

  • May cause delays or overload if abused


πŸ§ͺ 5. Pipeline – Step-by-Step Processing

βœ… Intent

Chain multiple processing steps in sequence.

🎯 Use When

You want to process notifications through multiple filters or transformations.

🧩 Code

public interface IPipelineStep
{
    string Process(string message);
}

public class TrimStep : IPipelineStep
{
    public string Process(string message) => message.Trim();
}

public class UppercaseStep : IPipelineStep
{
    public string Process(string message) => message.ToUpper();
}

public class NotificationPipeline
{
    private readonly List<IPipelineStep> _steps = new();

    public void AddStep(IPipelineStep step) => _steps.Add(step);

    public string Execute(string message)
    {
        foreach (var step in _steps)
            message = step.Process(message);

        return message;
    }
}

βœ… Explanation

Each step transforms the message, allowing modular and reusable processing.

πŸ‘ Pros

  • Clean separation of concerns

  • Easy to extend or reorder steps

  • Great for validation, transformation, and enrichment

πŸ‘Ž Cons

  • Can become hard to debug if steps fail silently

  • Requires consistent step interfaces


🧾 Conclusion

These 5 patterns are especially useful in modern, distributed, or event-driven systems:

  • Specification enables flexible rule composition

  • Event Sourcing captures system history

  • Throttling controls frequency

  • Retry adds resilience

  • Pipeline organizes processing flow

#CSharp #DesignPatterns #DotNet #SoftwareEngineering #CleanArchitecture #BackendDev #CodeQuality #ProgrammingTips #SpecificationPattern #EventSourcingPattern #ThrottlingPattern #RetryPattern #PipelinePattern

0
Subscribe to my newsletter

Read articles from Johnny Hideki Kinoshita de Faria directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Johnny Hideki Kinoshita de Faria
Johnny Hideki Kinoshita de Faria

Technology professional with over 15 years of experience delivering innovative, scalable, and secure solutions β€” especially within the financial sector. I bring deep expertise in Oracle PL/SQL (9+ years), designing robust data architectures that ensure performance and reliability. On the back-end side, I’ve spent 6 years building enterprise-grade applications using .NET, applying best practices like TDD and clean code to deliver high-quality solutions. In addition to my backend strengths, I have 6 years of experience with PHP and JavaScript, allowing me to develop full-stack web applications that combine strong performance with intuitive user interfaces. I've led and contributed to projects involving digital account management, integration of VISA credit and debit transactions, modernization of payment systems, financial analysis tools, and fraud prevention strategies. Academically, I hold a postgraduate certificate in .NET Architecture and an MBA in IT Project Management, blending technical skill with business acumen. Over the past 6 years, I’ve also taken on leadership roles β€” managing teams, mentoring developers, and driving strategic initiatives. I'm fluent in agile methodologies and make consistent use of tools like Azure Boards to coordinate tasks and align team performance with delivery goals.