Key Architecture Concepts
Coupling, cohesion, separation of concerns, SOLID principles applied to architecture, and bounded contexts.
Building a shared language
Before diving into distributed architecture, itβs worth establishing a shared vocabulary. These terms will come up again and again throughout the entire platform.
Many technical decisions seem complex only because shared definitions are missing. In this section weβll build that common language along with the principles that guide architectural decisions.
Domain
This is the area of the business that the software aims to address. For example: users, products, orders, payments, inventory.
The domain is not a technical concept but a business one. Understanding the domain is the first step toward designing a good architecture.
Subdomain
A specific part within the overall business domain. It helps break the problem down into more manageable areas.
Bounded Context
An explicit boundary within which certain terms, models, and rules carry a concrete meaning.
Why do they matter?
In a large system, the same word can mean different things depending on the context:
- βUserβ in authentication: email, password, roles.
- βUserβ in billing: legal name, address, payment method.
- βUserβ in support: ticket history, satisfaction level.
Each bounded context has its own model with the attributes it needs.
Mapping to architecture
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β AutenticaciΓ³n β β FacturaciΓ³n β β Soporte β
β β β β β β
β Usuario: β β Cliente: β β ContactoSoporte:β
β - email β β - nombre_fiscal β β - historial β
β - password_hash β β - direcciΓ³n β β - satisfacciΓ³n β
β - roles β β - mΓ©todo_pago β β - prioridad β
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
Communication between contexts
- Well-defined APIs: Explicit contracts between contexts.
- Domain events: One context publishes events that others consume.
- Anti-Corruption Layer (ACL): A translation layer that protects one context from changes in another.
DTO (Data Transfer Object)
An object used to transfer data between layers or systems. It doesnβt necessarily represent the internal domain model.
Contract
An explicit definition of how two parties communicate. It can define:
- Request and response structure
- Required and optional fields
- Data types
- Possible errors
- Versioning rules
A contract reduces ambiguity and helps systems evolve without breaking consumers.
Event
A relevant fact that has already occurred in the system. For example: OrderPlaced, UserCreated, PaymentConfirmed.
Events are immutable and fundamental in event-driven architectures.
Integration
A connection between distinct systems or components, whether internal or external.
Consistency
The degree to which the data correctly reflects the state of the system.
Strong consistency
Everyone sees the same state immediately.
Eventual consistency
There may be temporary lags until the system converges.
Idempotency
The ability to execute an operation multiple times and get the same result without unwanted duplicate effects.
Coupling
Coupling measures how much one component depends on another.
Types of coupling (from highest to lowest)
- Content coupling: One module directly modifies anotherβs internal data. Itβs the worst type.
- Common coupling: Two modules share global variables or mutable state.
- Control coupling: One module tells another how to do its job (by passing control flags).
- Data coupling: Modules communicate only through well-defined parameters. Itβs the desirable type.
Practical example
// Alto acoplamiento: el servicio de pedidos conoce
// la estructura interna del servicio de usuarios
function crearPedido(usuarioId) {
const usuario = db.query('SELECT * FROM usuarios WHERE id = ?', usuarioId);
if (usuario.saldo_cuenta > 0) {
// Accede directamente a la tabla de otro dominio
}
}
// Bajo acoplamiento: comunicaciΓ³n a travΓ©s de una interfaz
function crearPedido(usuarioId) {
const puedeComprar = await servicioUsuarios.verificarCapacidadCompra(usuarioId);
if (puedeComprar) {
// No conoce los detalles internos del servicio de usuarios
}
}
Aim for low coupling between modules. Each component should be able to evolve independently.
Cohesion
Cohesion measures how closely related the responsibilities within a single component are. High cohesion means that everything a module does is tightly related.
Types of cohesion (from lowest to highest)
- Coincidental: The functions are grouped together by chance, with no logical relationship.
- Temporal: The functions are grouped because they run at the same time.
- Functional: All functions contribute to a single, well-defined responsibility. Itβs the ideal type.
Practical example
// Baja cohesiΓ³n: este mΓ³dulo hace demasiadas cosas no relacionadas
const utils = {
formatearFecha(fecha) { /* ... */ },
calcularImpuesto(monto) { /* ... */ },
enviarEmail(destinatario, asunto) { /* ... */ },
validarRUT(rut) { /* ... */ },
};
// Alta cohesiΓ³n: cada mΓ³dulo tiene una responsabilidad clara
const formatoFechas = {
formatear(fecha, formato) { /* ... */ },
parsear(texto, formato) { /* ... */ },
diferencia(fechaA, fechaB) { /* ... */ },
};
The coupling-cohesion relationship
These two concepts are inversely related: when you increase cohesion within a module, you naturally reduce coupling between modules.
Separation of Concerns
Each part of the system should handle a single concern. In architecture, this manifests as layers and modules with clear responsibilities.
Layered example
βββββββββββββββββββββββββββ
β PresentaciΓ³n (UI) β β CΓ³mo se muestra la informaciΓ³n
βββββββββββββββββββββββββββ€
β LΓ³gica de negocio β β QuΓ© reglas aplican
βββββββββββββββββββββββββββ€
β Acceso a datos β β DΓ³nde y cΓ³mo se almacenan los datos
βββββββββββββββββββββββββββ
SOLID principles in architecture
The SOLID principles, originally formulated for object-oriented design, also apply at the architectural level:
S β Single Responsibility
Each service or module should have one reason to change. If a change in billing rules requires modifying the users service, thereβs a design problem.
O β Open/Closed
The system should be extensible without modifying what already exists. Plugins, middleware, and event-driven architectures make this easier.
L β Liskov Substitution
If a service implements a contract (API), any alternative implementation must be interchangeable without breaking consumers.
I β Interface Segregation
Consumers shouldnβt depend on interfaces they donβt use. The BFF (Backend for Frontend) pattern is a direct example of this principle.
D β Dependency Inversion
High-level modules shouldnβt depend on low-level modules. Both should depend on abstractions.
// ViolaciΓ³n: la lΓ³gica de negocio depende de PostgreSQL
class ServicioPedidos {
constructor() {
this.db = new PostgresClient();
}
}
// Correcto: depende de una abstracciΓ³n
class ServicioPedidos {
constructor(repositorio) {
this.repositorio = repositorio; // Puede ser Postgres, Mongo, en memoria...
}
}
Practical summary
| Concept | Key question | Goal |
|---|---|---|
| Domain | What business problem are we solving? | Understand the context |
| Bounded Context | Where are the domain boundaries? | Clear, autonomous models |
| Contract | How do the parties communicate? | Explicit communication |
| Coupling | How much does A depend on B? | Minimize dependencies |
| Cohesion | How related is what A does? | Maximize internal relatedness |
| Consistency | Does the data reflect the real state? | Manage expectations |
| Idempotency | Can I repeat without duplicate effects? | Safe operations |
| SOLID | Is the design extensible and maintainable? | Long-term flexibility |
These concepts arenβt absolute rules but guides for making better decisions. The clearer this vocabulary is, the easier it will be to understand more advanced design decisions.