# How Common Design Patterns Are Applied in Backend Architecture: A Deep Dive into architect-awesome

> Explore architect-awesome to see how common design patterns like Singleton and Strategy solve backend challenges. Learn about request pipelining and service decoupling.

- Repository: [xingshaocheng/architect-awesome](https://github.com/xingshaocheng/architect-awesome)
- Tags: deep-dive
- Published: 2026-03-05

---

**The *architect-awesome* repository catalogs 23 classic design patterns alongside SOLID principles, demonstrating how Singleton, Chain-of-Responsibility, Strategy, and other patterns solve specific backend challenges like request pipelining, service decoupling, and cross-cutting concerns.**

The open-source knowledge base maintained by `xingshaocheng/architect-awesome` serves as a comprehensive reference for backend architects, mapping each pattern to concrete implementation scenarios found in enterprise Java and microservices codebases. By grounding abstract pattern definitions in real-world backend contexts—such as servlet filters for Chain-of-Responsibility or runtime metrics collectors for Singleton—the repository illustrates how **common design patterns applied in backend architecture** yield modular, testable, and scalable systems.

## SOLID Foundations: The Six Principles Powering Backend Patterns

Before diving into the 23 concrete patterns listed in the repository’s [Design Patterns section](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#设计模式), *architect-awesome* establishes the six SOLID principles as non-negotiable foundations. These principles ensure that pattern implementations do not introduce rigidity or fragility into backend services.

| Principle | Backend Implementation Impact |
|-----------|------------------------------|
| **Single Responsibility Principle (SRP)** | Each service or module handles one business concern. For example, a `UserService` manages only user data, while `OrderService` handles order logic, preventing bloated "god classes" in `service` layers. |
| **Open/Closed Principle (OCP)** | New features are added by extending existing abstractions rather than modifying tested code. Payment processors, for instance, extend a base `PaymentHandler` to support new gateways without altering core transaction logic. |
| **Liskov Substitution Principle (LSP)** | Substitutable implementations keep APIs stable. A `Cache` interface accepts both `RedisCache` and `MemoryCache` implementations without requiring changes to calling services. |
| **Interface Segregation Principle (ISP)** | Small, focused interfaces prevent "fat" contracts. Separating `ReadRepository` from `WriteRepository` ensures that read-only consumers are not burdened with write-method dependencies. |
| **Dependency Inversion Principle (DI)** | High-level modules depend on abstractions, not concrete implementations. Inversion of Control (IoC) containers wire concrete classes at runtime, enabling testability through mock injection and configuration flexibility. |

These fundamentals are referenced throughout the pattern descriptions in [`README.md`](https://github.com/xingshaocheng/architect-awesome/blob/main/README.md), ensuring that every pattern—from Singleton to Observer—is applied in a manner that respects SOLID boundaries.

## Core Design Patterns for Backend Systems

The repository enumerates 23 classic patterns, but several appear repeatedly in backend architectures due to their specific utility in handling HTTP requests, managing shared resources, and decoupling business logic.

### Singleton: Global Service Coordination

The **Singleton** pattern ensures that a class has only one instance and provides a global point of access to it. In backend systems, this is critical for resources such as configuration managers, thread pools, and metrics collectors where duplicate instances would cause state inconsistency or resource exhaustion.

According to the [Singleton section](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#单例模式) in [`README.md`](https://github.com/xingshaocheng/architect-awesome/blob/main/README.md), a classic Java example is `java.lang.Runtime#getRuntime()`. In microservices, a `MetricsCollector` singleton aggregates telemetry without race conditions:

```java
public final class MetricsCollector {
    private static volatile MetricsCollector INSTANCE;

    private MetricsCollector() { /* init resources */ }

    public static MetricsCollector getInstance() {
        if (INSTANCE == null) {
            synchronized (MetricsCollector.class) {
                if (INSTANCE == null) {
                    INSTANCE = new MetricsCollector();
                }
            }
        }
        return INSTANCE;
    }

    public void record(String name, long value) { /* … */ }
}

```

### Chain-of-Responsibility: Request Processing Pipelines

The **Chain-of-Responsibility** pattern decouples senders from receivers by giving multiple objects a chance to handle a request. In backend architecture, this manifests as HTTP filter chains, middleware stacks, and validation pipelines.

The repository’s [Chain-of-Responsibility section](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#责任链模式) cites `javax.servlet.Filter#doFilter()` as the canonical implementation. Each filter (authentication, logging, compression) processes the request and optionally passes it to the next handler:

```java
public interface Filter {
    void doFilter(HttpRequest req, HttpResponse res, FilterChain chain);
}

public class FilterChain {
    private final List<Filter> filters = new ArrayList<>();
    private int index = 0;

    public void add(Filter filter) { filters.add(filter); }

    public void doFilter(HttpRequest req, HttpResponse res) {
        if (index < filters.size()) {
            filters.get(index++).doFilter(req, res, this);
        }
    }
}

// Example filter implementation
public class AuthFilter implements Filter {
    public void doFilter(HttpRequest req, HttpResponse res, FilterChain chain) {
        if (!req.isAuthenticated()) {
            res.setStatus(401);
            return;
        }
        chain.doFilter(req, res);
    }
}

```

### Strategy: Pluggable Algorithms

The **Strategy** pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Backend services use this to support varying business rules, such as pricing calculations, cache eviction policies, or payment processing methods.

In the repository context, Strategy enables runtime selection of algorithms without conditional spaghetti code. A cache system might switch between LRU and FIFO strategies via dependency injection:

```java
public interface EvictionStrategy {
    void evict(Cache cache);
}

public class LruStrategy implements EvictionStrategy {
    public void evict(Cache cache) { /* remove least-recently-used */ }
}

public class Cache {
    private final EvictionStrategy strategy;

    public Cache(EvictionStrategy strategy) {
        this.strategy = strategy;
    }

    public void put(String key, Object value) { /* … */ }

    public void clean() { strategy.evict(this); }
}

```

### Factory: Object Creation Encapsulation

The **Factory** pattern (and Abstract Factory) delegates object creation to specialized classes, decoupling client code from concrete implementations. This is essential in data access layers where the underlying database might vary between environments (MySQL in production, H2 in testing).

The repository references factories under discussions of "对象创建的封装". A typical backend implementation abstracts DAO creation:

```java
public interface UserDao { User findById(Long id); }

public class MySqlUserDao implements UserDao { /* … */ }
public class PgUserDao implements UserDao { /* … */ }

public class DaoFactory {
    public static UserDao createUserDao(String db) {
        return switch (db) {
            case "mysql" -> new MySqlUserDao();
            case "postgres" -> new PgUserDao();
            default -> throw new IllegalArgumentException("Unsupported DB");
        };
    }
}

```

### Observer: Event-Driven Decoupling

The **Observer** pattern establishes a subscription mechanism to notify multiple objects about events. In backend architecture, this enables event-driven microservices, domain event publishing, and loose coupling between business logic and side effects (email sending, audit logging).

The repository lists Observer as critical for decoupling producers from consumers. A lightweight event bus implementation demonstrates the pattern:

```java
public interface EventListener<E> { void onEvent(E event); }

public class EventBus {
    private final Map<Class<?>, List<EventListener<?>>> listeners = new ConcurrentHashMap<>();

    public <E> void register(Class<E> type, EventListener<E> listener) {
        listeners.computeIfAbsent(type, k -> new CopyOnWriteArrayList<>()).add(listener);
    }

    @SuppressWarnings("unchecked")
    public <E> void publish(E event) {
        List<EventListener<?>> list = listeners.getOrDefault(event.getClass(), List.of());
        for (EventListener<?> l : list) {
            ((EventListener<E>) l).onEvent(event);
        }
    }
}

```

### Additional Patterns in Backend Contexts

While the repository enumerates 23 classic patterns, several others merit mention for their specific backend utility:

- **Inversion of Control (IoC) / Dependency Injection (DI)**: Referenced in the [IoC section](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#ioc), this pattern enables containers like Spring to wire dependencies at runtime, decoupling configuration from usage.
- **Aspect-Oriented Programming (AOP)**: Listed under [AOP](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#aop), this pattern extracts cross-cutting concerns (logging, transactions, security) from business logic.
- **Proxy**: Used for remote service invocation (gRPC, RMI) or lazy loading, adding caching or access control without modifying target classes.
- **Decorator**: Dynamically adds responsibilities to objects, such as wrapping a `DataSource` with a `CachingDataSource`.
- **Builder**: Constructs complex objects step-by-step, essential for immutable DTOs and HTTP request builders.
- **Adapter**: Integrates third-party libraries (payment SDKs, external APIs) with internal domain models by translating interfaces.

## Layered Application: Mapping Patterns to Backend Tiers

The *architect-awesome* repository implicitly organizes patterns by architectural layer, demonstrating how **common design patterns applied in backend architecture** solve specific tier-level concerns.

### Presentation Layer

At the HTTP boundary, patterns manage request routing and response composition:

- **MVC** (Model-View-Controller) separates routing logic from business rules and view rendering, forming the backbone of Spring MVC and similar frameworks.
- **Facade** provides simplified interfaces over complex subsystems, hiding intricate order-processing steps behind a clean `OrderFacade`.
- **Decorator** wraps request/response objects to add compression, encryption, or logging transparently.

### Service and Domain Layer

Business logic remains flexible and testable through behavioral patterns:

- **Strategy** encapsulates varying algorithms (pricing models, discount rules) that can be swapped at runtime via dependency injection.
- **Template Method** defines algorithm skeletons in abstract classes, allowing subclasses to refine specific steps without altering the overall workflow—common in batch processing frameworks.
- **Command** encapsulates requests as objects, enabling transactional rollbacks, task queues, and undo functionality.
- **State** manages complex object lifecycle transitions (order workflows, payment states) without proliferating conditional statements.

### Data Access Layer

Persistence abstraction relies on creational and structural patterns:

- **Factory** and **Abstract Factory** create DAO families for different database vendors (MySQL, PostgreSQL) without exposing concrete classes to business logic.
- **Repository** and **Iterator** provide uniform collection-like access to domain objects while hiding query complexity.
- **Flyweight** minimizes memory footprint for large datasets by sharing immutable value objects across multiple contexts.

### Integration and Infrastructure Layer

External communication and cross-cutting concerns utilize:

- **Proxy** for remote service invocation (gRPC stubs, RMI) or lazy loading with added caching layers.
- **Adapter** to bridge third-party SDKs (payment gateways, external APIs) with internal domain models.
- **Observer** for event-driven architectures, decoupling message producers from consumers via event buses or message queues.
- **Mediator** to centralize complex component communication, often realized through message brokers.

### Cross-Cutting Concerns

System-wide functionality integrates via:

- **AOP** (Aspect-Oriented Programming) to modularize logging, transaction management, and security without scattering code through business classes.
- **Singleton** for managed access to shared resources like configuration managers and thread pools.
- **Builder** for constructing complex configuration objects or immutable DTOs in a readable, step-by-step manner.

## Summary

- **SOLID principles** form the foundation for all pattern applications in *architect-awesome*, ensuring that Singleton, Strategy, and other patterns do not violate encapsulation or create tight coupling.
- **Creational patterns** (Singleton, Factory, Builder) manage object lifecycle and configuration, critical for database connections and service initialization.
- **Behavioral patterns** (Chain-of-Responsibility, Strategy, Observer, Command) decouple request handling, algorithm selection, and event propagation, enabling microservices to scale without cascading changes.
- **Structural patterns** (Proxy, Adapter, Decorator, Facade) simplify integration with external systems and add cross-cutting functionality like caching and logging without modifying core business logic.
- The repository’s [`README.md`](https://github.com/xingshaocheng/architect-awesome/blob/main/README.md) serves as the canonical reference, mapping each pattern to concrete backend scenarios from servlet filters to DAO factories.

## Frequently Asked Questions

### What are the most critical design patterns for microservices backend architecture?

**Singleton**, **Factory**, and **Observer** rank among the most critical for microservices. Singleton ensures single instances of configuration managers and connection pools, preventing resource exhaustion. Factory abstracts the creation of service clients and DAOs, allowing services to switch between database vendors or message brokers without code changes. Observer enables event-driven communication between services via domain events, decoupling producers from consumers and supporting eventual consistency across distributed systems.

### How does the Chain-of-Responsibility pattern improve HTTP request handling?

The **Chain-of-Responsibility** pattern improves HTTP handling by decoupling authentication, logging, compression, and business logic into discrete, reusable filters. As documented in the repository’s reference to `javax.servlet.Filter#doFilter()`, each filter processes the request and optionally passes it to the next handler in the chain. This structure allows developers to add, remove, or reorder processing steps—such as inserting a rate-limiting filter between authentication and routing—without modifying existing filter code or the core controller logic.

### Why are SOLID principles prerequisites for applying design patterns in backend systems?

**SOLID principles** act as guardrails that prevent pattern misuse from creating architectural debt. For example, without the **Single Responsibility Principle**, a Singleton could evolve into a "god object" managing configuration, caching, and business logic simultaneously. The **Dependency Inversion Principle** ensures that Factory and Strategy patterns depend on abstractions rather than concrete classes, enabling testability with mocks. By adhering to SOLID fundamentals first—as emphasized in the repository’s principles section—backend developers ensure that patterns like Proxy, Observer, and Command enhance rather than complicate the architecture.

### Where does Aspect-Oriented Programming (AOP) fit into backend design patterns?

**AOP** addresses **cross-cutting concerns**—functionality that spans multiple layers such as logging, transaction management, and security—without scattering code through business classes. According to the repository’s [AOP section](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#aop), this pattern complements core design patterns by modularizing orthogonal concerns. For example, while a `PaymentService` uses **Strategy** to select payment methods, AOP can wrap the service to manage database transactions and audit logging, keeping the business logic pure and focused solely on payment processing.