Bobby Encoded
PostsAbout
PostsAbout

© 2026 Bobby Jose

← Back to Blog

Design Patterns I Keep Coming Back To

October 10, 2025 · 6 min read

Design Patterns, .NET, Architecture, Software Engineering

After years of building enterprise systems, I've realized that not all design patterns are created equal. Some I use daily without thinking about them. Others I learned once for an interview and never touched again. Here's my honest breakdown of what actually matters.

The Patterns I Actually Use Daily

Creational Patterns

Singleton - I rarely implement this myself anymore. DI containers handle it with AddSingleton(). But understanding thread-safety issues here has saved me from some nasty production bugs.

Factory Method - This one's everywhere. IHttpClientFactory, ILoggerFactory - I use these without even thinking about it being a "pattern." The key insight: create objects without specifying the exact class.

Builder - Fluent APIs have made this pattern second nature:

var app = WebApplication.CreateBuilder(args)
    .ConfigureServices()
    .ConfigureLogging()
    .Build();

Every time I chain methods like this, that's the Builder pattern at work.

Structural Patterns

Decorator - ASP.NET Core middleware is essentially a decorator chain. Understanding this helped me write better custom middleware:

app.UseAuthentication();  // Decorator 1
app.UseAuthorization();   // Decorator 2
app.UseCustomMiddleware(); // My decorator

Adapter - I reach for this constantly when wrapping third-party APIs or integrating legacy systems. The goal: make incompatible interfaces work together.

Facade - My service layers are often facades. They hide the complexity of multiple repositories, external APIs, and business logic behind a clean interface.

Behavioral Patterns

Strategy - Swapping algorithms at runtime. Payment processors, validation strategies, different export formats. Once you see it, you can't unsee how useful this is:

public interface IPaymentStrategy
{
    Task<PaymentResult> ProcessAsync(Order order);
}

// Swap implementations without changing calling code

Chain of Responsibility - The entire ASP.NET middleware pipeline. Also useful for validation pipelines and approval workflows.

Command - MediatR made me appreciate this. Encapsulating requests as objects enables so much: logging, validation, undo/redo, queuing.

Architecture Patterns That Changed How I Think

CQRS

This was a game-changer for me. Separating read and write models sounds simple, but the implications are huge:

Commands (writes) → Domain logic → SQL Server (source of truth)
                                        ↓ Events
Queries (reads)  → Optimized read model → Cosmos/Redis (fast reads)

I don't use full CQRS everywhere, but even partial separation (different DTOs for reads vs writes) has cleaned up so much code.

Event Sourcing

I only use this where audit trails matter - financial systems, compliance-heavy domains. The idea of storing events instead of current state felt weird at first, but it's powerful when you need to answer "what happened and when?"

Repository Pattern - My Honest Take

Controversial opinion: with EF Core, I often skip the repository abstraction. DbContext already implements Unit of Work. Adding another layer sometimes just adds complexity without benefit.

That said, for complex queries or when I need to mock data access in tests, repositories still make sense.

Cloud Patterns I Learned the Hard Way

Working with Azure taught me these through painful production incidents.

Retry with Exponential Backoff

Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(3, attempt =>
        TimeSpan.FromSeconds(Math.Pow(2, attempt)));

The first time a downstream service had a brief hiccup and my entire application fell over, I learned why this matters.

Circuit Breaker

Stop hammering a failing service. Give it time to recover:

Policy
    .Handle<HttpRequestException>()
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 5,
        durationOfBreak: TimeSpan.FromMinutes(1));

Saga Pattern

Distributed transactions across microservices. When the payment service succeeds but inventory fails, you need compensating actions:

Order → Payment → Inventory
  ↑        ↑          ↓
  └── Compensate ←────┘ (on failure)

Outbox Pattern

This one took me a while to appreciate. The problem: you save to the database and publish a message, but what if the message publish fails after the DB commit?

Solution: save the message to an outbox table in the same transaction, then publish from there.

Strangler Fig

For migrating legacy systems. Route traffic gradually to the new implementation. I used this to migrate a monolith to microservices over 18 months without a "big bang" release.

DDD Patterns - When Complexity Demands It

I only reach for full DDD on complex domains. For CRUD apps, it's overkill.

PatternWhen I Use It
AggregateWhen multiple entities must change together consistently
Value ObjectImmutable concepts like Money, Address, DateRange
Domain EventWhen something happens that other parts of the system care about
Bounded ContextLarge systems with distinct subdomains
Anti-Corruption LayerIntegrating with messy external systems

Modern .NET Patterns I've Adopted

Vertical Slice Architecture

Organizing by feature instead of layer has been refreshing:

Features/
├── CreateOrder/
│   ├── CreateOrderCommand.cs
│   ├── CreateOrderHandler.cs
│   └── CreateOrderEndpoint.cs
├── GetOrder/
│   ├── GetOrderQuery.cs
│   └── GetOrderHandler.cs

Everything for a feature lives together. No more jumping between Controllers/, Services/, Repositories/ folders.

Result Pattern

Railway-oriented programming for cleaner error handling:

return await GetCustomer(id)
    .Bind(customer => ValidateOrder(customer, order))
    .Bind(order => ProcessPayment(order))
    .Map(payment => new OrderConfirmation(payment));

No more scattered try-catch blocks. Errors flow through the pipeline.

Minimal APIs

For simple endpoints, the ceremony of controllers feels heavy:

app.MapPost("/orders", async (CreateOrderCommand cmd, ISender sender) =>
    await sender.Send(cmd));

My Quick Reference

When I need to pick a pattern:

ScenarioMy Go-To Patterns
Flexible object creationFactory, Builder
Adding behavior without changing classDecorator
Multiple interchangeable algorithmsStrategy
Processing through multiple handlersChain of Responsibility
Decoupling componentsObserver, Mediator
Distributed transactionsSaga, Outbox
Resilient HTTP callsRetry, Circuit Breaker
Complex domain logicDDD patterns
Separating read/write concernsCQRS
Migrating legacy systemsStrangler Fig

What Interviewers Actually Ask

In my experience, these come up most frequently:

  1. Singleton - Thread safety, lazy initialization, why DI is better
  2. Factory - When and why, real examples like IHttpClientFactory
  3. Strategy - Swapping algorithms, payment processor example
  4. Repository - And whether it's needed with EF Core (trick question!)
  5. CQRS - Especially with MediatR
  6. Circuit Breaker / Retry - Polly, resilience patterns
  7. Dependency Injection - Not GoF but absolutely essential

The patterns I've outlined here aren't just theoretical knowledge - they're tools I've used to solve real problems. The key is knowing when to apply them and, equally important, when not to over-engineer with patterns you don't need.

← Previous

Mastering Kotlin Flow and Reactive Patterns

Next →

Android Data Persistence with Room