Software Architecture

Common Architecture Patterns

Clean Architecture

Clean Architecture is a software architecture pattern proposed by Robert C. Martin (Uncle Bob), focused on separation of concerns by dividing the application into concentric layers. The inner layers (e.g. Entities, Use Cases) do not depend on the outer layers (e.g. Frameworks, UI, DB). This makes the system easy to test, easy to maintain, and independent of implementation details. The pattern is one of several covered in this note. The dependency rule is the core constraint. Dependencies only point inward, so the outer layers can change without touching the domain. The later sections turn to design principles and to the goals to keep in mind when shaping an architecture.

Hexagonal Architecture (Ports and Adapters Architecture)

Hexagonal Architecture, also called Ports and Adapters, focuses on separating the core business logic (the application core) from external elements (UI, database, third-party services). Communication between the application core and the outside world goes through "ports" (interfaces that define how to interact) and "adapters" (concrete implementations of ports for each technology or service).

Microservices

Microservices architecture structures an application as a collection of small, independent services that can be deployed individually and communicate with each other through well-defined APIs.

Microservice Design Patterns

Anti-corruption Layer Pattern

A design pattern used to separate and isolate the components of the current system from external systems or services (usually legacy or third-party systems) that may be untrustworthy, unstable, or have a different data model. This layer acts as a translator, ensuring the current system's domain model is not "polluted" by external systems.

Compensating Transaction Pattern

A design pattern for managing and restoring system state after a distributed transaction or a sequence of operations fails. When an operation in the sequence fails, "compensating transactions" are executed to undo the operations that previously succeeded, in order to ensure data consistency (eventual consistency) or bring the system back to a safe state.

Sidecar Pattern

The Sidecar pattern is a design pattern in which a secondary application (the sidecar) is deployed alongside the main application to extend or enhance functionality without changing the main application's source code. It is commonly used in container environments such as Kubernetes to provide features like logging, monitoring, service discovery, and proxying.

Event-Driven Architecture (EDA)

Event-Driven Architecture (EDA) is a software architecture pattern that promotes the production, detection, consumption of, and reaction to events. An event is a significant change of state. EDA lets system components be highly decoupled, easy to scale, and able to react flexibly to change.

Event Sourcing

Event Sourcing is a pattern that stores application state as a chronologically ordered sequence of events. Instead of storing the current state of an entity, the system stores all the changes (events) that led to that state. The current state can be rebuilt by replaying all the events.

Domain-Driven Design (DDD)

Domain-Driven Design (DDD) is a software development approach focused on modeling a complex business domain. Key concepts include Ubiquitous Language, Bounded Context, Entities, Value Objects, Aggregates, Repositories, and Domain Services. DDD helps build systems that faithfully reflect business logic and are easy to evolve and maintain.

Command Query Responsibility Segregation (CQRS)

Command Query Responsibility Segregation (CQRS) is an architecture pattern that separates data write operations (Commands) from read operations (Queries). This allows each type of operation to be optimized separately, using different data models, databases, and scaling strategies if needed.

LMAX Architecture

The LMAX architecture is designed for high-frequency financial trading systems, which require extremely low latency and the ability to handle a large volume of transactions.

  • Key characteristics:
    • Real-time: processes transactions immediately.
    • Event-driven: events are placed in a queue and processed sequentially.
    • Single-thread: uses a single thread to process events, avoiding conflicts and ensuring consistency. Takes advantage of optimizations to reach high performance.
    • Entity-driven: focuses on the entity objects in the system.
  • References:

VIPER

VIPER is an application architecture pattern for iOS that clearly separates the responsibilities of its components: View, Interactor, Presenter, Entity, and Router.

  • View: displays the UI and receives user interaction.
  • Interactor: contains the business logic related to the data entities (Entities).
  • Presenter: receives data from the Interactor, formats and prepares data for the View. Receives events from the View and routes them to the Interactor or Router.
  • Entity: represents the application's data objects.
  • Router: handles navigation between screens.
  • Comparison: similar to MVC, but the Controller is split into Presenter and Interactor. Routing is a separate layer.
  • References:

MVP (Model-View-Presenter)

Model-View-Presenter (MVP) is a user interface architecture pattern.

  • Model: holds data and business logic.
  • View: displays data (from the Model) and forwards the user's commands (events) to the Presenter for handling.
  • Presenter: acts as the intermediary, receiving events from the View, interacting with the Model to fetch or update data, and then updating the View. The View and Presenter usually have a 1-1 relationship.
  • Comparison: similar to MVC, but the View receives the request first, and the Controller is replaced by the Presenter.
  • References:

Comparing MVC, MVP, and MVVM

Common user interface architecture models include:

  • MVC (Model-View-Controller): the Controller handles input, interacts with the Model, and chooses the View to display.
  • MVP (Model-View-Presenter): the Presenter handles input from the View, interacts with the Model, and updates the View. The View is usually more passive.
  • MVVM (Model-View-ViewModel): the ViewModel exposes data and commands to the View, and the View binds to the ViewModel's properties. Usually uses data binding.
  • References:

Design Principles

SOLID

SOLID is a set of 5 fundamental design principles in object-oriented programming that help produce software that is easy to understand, flexible, and maintainable.

  • S - Single Responsibility Principle: each class should be responsible for only one specific task.
  • O - Open/Closed Principle: software entities (classes, modules, functions) should be open for extension (new behavior) but closed for modification (of existing source code).
  • L - Liskov Substitution Principle: objects of a subclass can replace objects of the parent class without changing the correctness of the program.
  • I - Interface Segregation Principle: clients should not be forced to depend on interfaces they do not use. Create small, specific interfaces instead of one large, general one.
  • D - Dependency Inversion Principle:
    • High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).
    • Abstractions should not depend on details. Details should depend on abstractions.
  • References:

DRY (Don't Repeat Yourself) / DIE (Duplication Is Evil)

  • DRY (Don't Repeat Yourself): this principle emphasizes avoiding repeated code or logic in a program. Instead, use functions, classes, or modules for reuse. It helps reduce bugs and improve maintainability.
  • DIE (Duplication Is Evil): similar to DRY, focused on avoiding copying code between different components. Copied code can lead to inconsistencies and makes maintenance difficult.

Aspect-Oriented Programming (AOP)

Aspect-Oriented Programming (AOP) is a programming paradigm that lets you separate cross-cutting concerns such as logging, security, transaction management, and caching from the application's core business logic. These "aspects" are defined separately and "woven" into the source code at specified points (join points).

Command Bus

The Command Bus is a design pattern that separates sending a request (command) from handling that request. It acts as an intermediary channel that receives command objects and dispatches them to the corresponding handlers for execution.

  • Benefits:
    • Makes use cases explicit: the command bus is usually used in the Service Layer, which documents the project's actual use cases.
    • High reusability: business logic is encapsulated in commands and handlers, and can be reused from multiple entry points (HTTP, console, message queue).
    • High extensibility: it is easy to add middleware to the command bus to handle common concerns (logging, validation, transactions).
    • Easy to test: each command and handler represents a single use case, making it easy to isolate dependencies for testing.

Event Loop Pattern

The Event Loop is a concurrency model design pattern used in many environments such as Node.js, web browsers (JavaScript), and GUI frameworks. It lets you handle asynchronous tasks and non-blocking I/O efficiently by using a single main thread to manage an event queue and execute callbacks. When an I/O task (e.g. reading a file, calling an API) is started, the main thread does not wait for it to complete but continues processing other events. When the I/O task finishes, an event is added to the queue and the corresponding callback is executed by the event loop.

Goals and Notes When Building an Architecture

  • The goal of architecture patterns is to help separate the work in a project into distinct layers, increasing modularity and reducing mutual dependencies.
  • A good architecture needs to be:
    • Easy to change: able to adapt to new or changing requirements without too many modifications.
    • Easy to extend: new features can be added easily.
  • Rules to follow:
    • When updating use cases (methods), create a new method instead of updating an old method that already works (Open/Closed Principle). Only delete the old method when no module uses it anymore.
    • Inner layers should not know about the work of outer layers (the Dependency Rule in Clean Architecture).
    • Outer layers are affected in input/output by inner layers but should not know the details of what the inner layers do.
    • Layers should not know about each other's work except through defined interfaces.

References and Examples

Clean Code

Writing clean code is the foundation of a good architecture.

Design Patterns

Design patterns are proven solutions to common problems in software design.

Architecture Examples

Tech Stack Examples

Tips

See also: Modular Monolith Vs Microservices · Clean Code Notes · Technical Solutions · Development Documentation