Skip to content

Arquitectura en Capas del Backend

Pregunta central: El código se vuelve cada vez más desordenado, ¿cómo organizarlo para que sea claro y comprensible?

Cuando un proyecto pasa de docenas de líneas de código a decenas de miles, de un solo desarrollador a colaboración en equipo, de un simple CRUD a lógica de negocio compleja, la forma de organizar el código determina directamente la viabilidad del proyecto. La arquitectura en capas no es para presumir ni seguir dogmas, sino para resolver un conflicto fundamental en la ingeniería de software: el crecimiento natural de la complejidad del negocio frente a la capacidad cognitiva limitada del ser humano.


1. ¿Por qué necesitamos capas?

1.1 La raíz del problema

Versión inicial (100 líneas de código):

java
@PostMapping("/register")
public Result register(@RequestBody User user) {
    // 1. Verificar si el nombre de usuario está duplicado
    if (userRepository.findByUsername(user.getUsername()) != null) {
        return Result.error("El nombre de usuario ya existe");
    }
    // 2. Encriptar contraseña
    user.setPassword(encrypt(user.getPassword()));
    // 3. Guardar usuario
    userRepository.save(user);
    // 4. Enviar correo de bienvenida
    emailService.sendWelcome(user.getEmail());
    // 5. Registrar log
    log.info("Usuario registrado: {}", user.getUsername());
    return Result.success();
}

6 meses después (500 líneas de código):

  • Se añadió validación de número de teléfono
  • Se añadió verificación de identidad real
  • Se añadieron recompensas por invitación
  • Se añadió control de riesgos
  • ...

Ahora este método tiene 500 líneas, y cada modificación da miedo, porque:

  • La lógica está mezclada, cambiar una parte puede afectar otras funcionalidades
  • Es difícil de probar, cada prueba requiere simular una solicitud HTTP completa
  • Los nuevos no entienden nada, porque toda la lógica está amontonada

La esencia del problema: el código no tiene "fronteras", todas las responsabilidades están mezcladas.

El efecto acumulativo de la deuda técnica:

  • Alto acoplamiento: la lógica de negocio está acoplada al acceso a datos y al protocolo HTTP, un cambio arrastra todo lo demás
  • Baja cohesión: un solo método asume múltiples responsabilidades, violando el principio de responsabilidad única
  • Difícil de probar: no se puede probar la lógica de negocio de forma aislada, hay que levantar un contenedor HTTP completo
  • Difícil de reutilizar: la lógica de negocio está atada a las peticiones HTTP, no se puede reutilizar en tareas programadas o colas de mensajes
  • Carga cognitiva: el desarrollador necesita entender simultáneamente los detalles de todas las capas, sin poder enfocarse

1.2 La idea central de las capas

La arquitectura en capas consiste en trazar fronteras claras en el código:

┌─────────────────────────────────────┐
│  Recibir peticiones ← Controller    │  Solo "tomar el pedido"
├─────────────────────────────────────┤
│  Orquestación de negocio ← Service  │  Solo "cocinar"
├─────────────────────────────────────┤
│  Acceso a datos ← Repository        │  Solo "conseguir ingredientes"
├─────────────────────────────────────┤
│  Definición de negocio ← Domain     │  Solo "estándar de recetas"
└─────────────────────────────────────┘

Principios clave:

  • Cada capa solo hace lo suyo
  • Las capas se comunican mediante interfaces bien definidas
  • La lógica de negocio se concentra en Service y Domain
  • La lógica de acceso a datos se concentra en Repository

Valor de ingeniería de la arquitectura en capas:

  1. Reduce la carga cognitiva: el desarrollador puede enfocarse en la responsabilidad de la capa actual, sin necesidad de entender todos los detalles globales
  2. Mejora la testeabilidad: cada capa se puede probar unitariamente de forma independiente, solo hace falta mockear las dependencias
  3. Aumenta la mantenibilidad: cuando cambian los requisitos, el alcance de la modificación está claro, reduciendo el riesgo
  4. Fomenta la reutilización de código: la lógica de negocio no depende de HTTP, se puede reutilizar en tareas programadas y colas de mensajes
  5. Facilita la colaboración en equipo: diferentes desarrolladores pueden trabajar en paralelo en distintas capas, reduciendo conflictos
  6. Prolonga la vida del código: las fronteras claras facilitan la refactorización y evolución del código

2. Las cuatro capas en detalle

2.1 Estructura general

La esencia de la arquitectura en capas es la separación de responsabilidades (Separation of Concerns) y el control de la dirección de dependencias:

┌─────────────────────────────────────────────────────┐
│  Petición del frontend                               │
└────────────────────┬────────────────────────────────┘
                     │ HTTP Request

┌─────────────────────────────────────────────────────┐
│  Controller (capa de controlador)                    │
│  - Recibir peticiones, validar parámetros            │
│  - Conversión de DTO                                │
│  - Llamar a Service                                  │
│  - Devolver respuesta                                │
└────────────────────┬────────────────────────────────┘
                     │ Llamada de negocio

┌─────────────────────────────────────────────────────┐
│  Service (capa de lógica de negocio)                 │
│  - Orquestación de lógica de negocio                 │
│  - Gestión de transacciones                          │
│  - Coordinar múltiples Repository                    │
│  - Coordinación entre módulos                        │
└────────────────────┬────────────────────────────────┘
                     │ Acceso a datos

┌─────────────────────────────────────────────────────┐
│  Repository (capa de acceso a datos)                 │
│  - CRUD de base de datos                             │
│  - Encapsulación de consultas                        │
│  - Mapeo ORM                                         │
└────────────────────┬────────────────────────────────┘
                     │ Objetos de dominio

┌─────────────────────────────────────────────────────┐
│  Domain (capa de modelo de dominio)                  │
│  - Entidades (Entity)                                │
│  - Objetos de valor (Value Object)                   │
│  - Reglas de negocio                                 │
└─────────────────────────────────────────────────────┘

Dirección de dependencias: las dependencias del código deben apuntar hacia lo más estable y más abstracto

  • Controller depende de la interfaz de Service (abstracción)
  • Service depende de la interfaz de Repository (abstracción)
  • Todas las capas dependen de Domain (núcleo del negocio, lo más estable)
  • No se permiten dependencias inversas (por ejemplo, Repository dependiendo de Service)
后端四层架构总览
点击各层查看详细说明
客户端 (Web / App)
↓ HTTP
Controller入口
接收请求、参数校验、调用 Service
Service业务核心
业务逻辑编排、事务管理、跨模块协调
Repository数据访问
数据持久化、查询封装、ORM 映射
Domain领域模型
实体定义、业务规则、值对象
↓ SQL
数据库 (MySQL / PostgreSQL)

2.2 Capa Controller

Responsabilidad: el "recepcionista" de las peticiones

  • Recibir peticiones HTTP, parsear parámetros
  • Validar parámetros (formato, obligatoriedad, etc.)
  • Conversión de DTO (Request → Param)
  • Llamar a Service para ejecutar la lógica de negocio
  • Conversión de DTO (Result → Response)
  • Devolver respuesta HTTP

Lo que no debe hacer:

  • Escribir lógica de negocio directamente
  • Operar directamente sobre la base de datos
  • Gestionar transacciones

Filosofía de diseño: El Controller es la "fachada" del sistema, asumiendo el rol de adaptador: adapta el protocolo HTTP externo a llamadas internas de negocio. No debe contener ninguna decisión de negocio, porque las decisiones de negocio son manifestaciones del conocimiento del dominio y deben estar desacopladas del protocolo de transporte.

Ejemplo:

java
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    @PostMapping
    public UserResponse createUser(
            @RequestBody @Valid UserRequest request) {

        // 1. Request DTO → Param DTO
        UserParam param = UserParam.builder()
                .username(request.getUsername())
                .password(encrypt(request.getPassword()))
                .email(request.getEmail())
                .build();

        // 2. Llamar a Service
        User user = userService.createUser(param);

        // 3. Entity → Response DTO
        return UserResponse.from(user);
    }
}

Puntos clave:

  • Usar @Valid para validar parámetros automáticamente
  • Usar DTO para aislar las estructuras de datos entre frontend y backend
  • Solo hacer "traducción" y "despacho", sin contener lógica de negocio
Controller 层:请求的"接待员"
点击流程节点查看详情
客户端发起请求
POST /api/users/register
Content-Type: application/json
{ "username": "张三", "email": "zhangsan@example.com", "password": "123456" }
↓ 请求到达
Controller 接收并解析请求
@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping("/register")
    public ResponseEntity<UserDTO> register(
        @RequestBody @Valid UserRegisterRequest request) {
        UserDTO user = userService.register(request);
        return ResponseEntity.ok(user);
    }
}
↓ 参数校验 + 调用
参数校验(Controller 的职责之一)
public class UserRegisterRequest {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20) private String username;
    @Email(message = "邮箱格式不正确") private String email;
    @Size(min = 6, message = "密码至少6位") private String password;
}
↓ 返回结果
Controller 封装响应返回
HTTP/1.1 200 OK
{ "code": 200, "message": "注册成功",
  "data": { "id": 10001, "username": "张三", "email": "zhangsan@example.com" } }
Controller 的核心职责
接收请求
映射 HTTP 请求到方法
参数校验
基础格式和必填校验
调用 Service
将请求转发给业务层
封装响应
统一响应格式返回

2.3 Capa Service

Responsabilidad: el "cocinero" del negocio

  • Implementar la lógica de negocio central
  • Orquestar operaciones de múltiples Repository
  • Gestionar los límites de las transacciones
  • Manejar la coordinación entre módulos

Lo que no debe hacer:

  • Escribir SQL directamente (eso es para Repository)
  • Manejar asuntos relacionados con HTTP
  • Devolver entidades de base de datos al Controller

Filosofía de diseño: La capa Service es el portador de la lógica de negocio y debe mantenerse pura. No depende de ningún framework ni protocolo de transporte, lo que permite:

  • Realizar pruebas unitarias independientes de la capa web
  • Reutilizarse en tareas programadas y consumidores de colas de mensajes
  • Evitar que los cambios de stack tecnológico afecten la lógica de negocio

Ejemplo:

java
@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final EmailService emailService;

    @Transactional
    public User createUser(UserParam param) {
        // 1. Regla de negocio: verificar si el nombre de usuario está duplicado
        if (userRepository.existsByUsername(param.getUsername())) {
            throw new UserAlreadyExistsException();
        }

        // 2. Crear entidad de usuario
        User user = new User();
        user.setUsername(param.getUsername());
        user.setPassword(param.getPassword());
        user.setEmail(param.getEmail());

        // 3. Guardar en base de datos
        userRepository.save(user);

        // 4. Enviar correo de bienvenida (coordinación entre módulos)
        emailService.sendWelcomeEmail(user);

        return user;
    }
}

Puntos clave:

  • Usar @Transactional para garantizar la consistencia de la transacción
  • Lanzar excepciones de negocio para que el Controller las maneje de forma unificada
  • No depender de conceptos HTTP, se puede reutilizar
Service 层:业务逻辑的"指挥家"
选择业务场景,查看 Service 层如何编排逻辑
电商下单流程
用户下单涉及库存扣减、订单创建、支付记录,需保证事务一致性
1
参数校验与DTO转换
Controller
@PostMapping("/orders")
public ResponseEntity<OrderDTO> createOrder(
    @RequestBody @Valid CreateOrderRequest request) {
    OrderDTO order = orderService.createOrder(request);
    return ResponseEntity.ok(order);
}
2
业务逻辑编排(事务管理)
Service
@Transactional
public OrderDTO createOrder(CreateOrderRequest request) {
    inventoryService.checkAndDeduct(request.getSkuId(), request.getQuantity());
    Order order = new Order();
    order.setUserId(request.getUserId());
    order.setTotalAmount(calculateTotal(request));
    orderRepository.save(order);
    Payment payment = createPayment(order);
    paymentRepository.save(payment);
    return convertToDTO(order);
}
3
数据持久化
Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    // 基本 CRUD 已内置
}
Service 层设计原则
单一职责
一个 Service 只负责一块业务领域
UserService 只管用户,OrderService 只管订单
事务边界
在 Service 层声明式管理事务
@Transactional 放在 Service 方法上
避免循环依赖
Service 之间不要互相调用
A→B→A 会导致循环
DTO 转换
返回前转换为 DTO,不暴露实体
return new UserDTO(user)

2.4 Capa Repository

Responsabilidad: el "almacenista" de los datos

  • Encapsular toda la lógica de acceso a datos
  • Ejecutar operaciones CRUD
  • Manejar el mapeo ORM
  • Encapsular condiciones de consulta

Lo que no debe hacer:

  • Escribir lógica de negocio
  • Gestionar transacciones (lo maneja la capa Service)
  • Depender de módulos superiores

Filosofía de diseño: Repository es la capa de abstracción de acceso a datos, que oculta los detalles de la base de datos subyacente. El valor de esta abstracción radica en:

  • Al cambiar de base de datos solo se modifica la implementación del Repository, sin tocar la lógica de negocio
  • Facilita el mockeo para pruebas unitarias
  • La lógica de consulta se gestiona de forma centralizada, evitando código duplicado

Ejemplo:

java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    // Spring Data JPA implementa automáticamente
    Optional<User> findByUsername(String username);
    boolean existsByUsername(String username);

    // Consulta compleja personalizada
    @Query("SELECT u FROM User u WHERE u.email = :email AND u.deleted = false")
    Optional<User> findActiveByEmail(@Param("email") String email);
}

Puntos clave:

  • Repository es una interfaz, no contiene lógica de negocio
  • Usar nombres de método para expresar la intención de la consulta
  • Se puede usar @Query para consultas complejas personalizadas
Repository 层:数据的"仓库管理员"
Repository 封装数据访问逻辑,让上层无需关心数据库细节
使用 Repository 封装数据访问清晰解耦
// Repository 接口定义
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    // ✅ 自动生成查询
    List<Order> findByUserIdAndDeletedFalse(Long userId);

    // ✅ 自定义 JPQL
    @Query("SELECT o FROM Order o WHERE o.createdAt BETWEEN :start AND :end")
    List<Order> findByDateRange(@Param("start") LocalDateTime start,
                                @Param("end") LocalDateTime end);
}

// Service 层(纯业务逻辑)
@Service
public class OrderService {
    @Autowired private OrderRepository orderRepository; // ✅ 依赖接口

    public List<OrderDTO> getUserOrders(Long userId) {
        List<Order> orders = orderRepository.findByUserIdAndDeletedFalse(userId);
        return orders.stream().map(OrderDTO::from).collect(Collectors.toList());
    }
}
这样做的好处
  • 关注点分离:Service 专注业务,Repository 专注数据
  • 可测试性高:单元测试可用 Mock 替代真实数据库
  • 代码复用:通用查询方法定义一次,到处复用
  • 切换成本低:换数据库只需改 Repository 实现
不同 Repository 实现方式对比
实现方式优点缺点适用场景
Spring Data JPA
主流方案
方法名自动推导、分页内置复杂查询性能一般快速开发、标准 CRUD
MyBatis / MyBatis-Plus
国内主流
SQL 完全可控、动态 SQL 强大需要手写 SQL复杂查询、性能敏感
Spring Data JDBC
轻量
简单轻量、启动快速无复杂映射微服务、简单聚合根

2.5 Capa Domain

Responsabilidad: el "estándar de recetas" del negocio

  • Definir entidades de negocio (Entity)
  • Definir objetos de valor (Value Object)
  • Encapsular reglas de negocio
  • Servir como dependencia común de todas las capas

Características importantes:

  • La capa Domain no depende de ninguna otra capa
  • Todas las capas dependen de la capa Domain
  • Es la base de la arquitectura en capas

Filosofía de diseño: La capa Domain es el núcleo de negocio de todo el sistema, expresa el conocimiento del dominio y las reglas de negocio. Su pureza es crucial:

  • No depender de frameworks significa que la lógica de negocio no está secuestrada por el stack tecnológico
  • Todas las capas dependen de ella, garantizando la uniformidad de las reglas de negocio
  • Facilita la evolución a largo plazo: el stack tecnológico puede reemplazarse, las reglas de negocio son relativamente estables

Ejemplo:

java
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String username;

    @Column(nullable = false)
    private String password;

    // ✅ Método de negocio: encapsula reglas de negocio
    public boolean isPasswordCorrect(String rawPassword) {
        return BCrypt.checkpw(rawPassword, this.password);
    }

    public void changePassword(String oldPassword, String newPassword) {
        if (!isPasswordCorrect(oldPassword)) {
            throw new IncorrectPasswordException();
        }
        this.password = BCrypt.hashpw(newPassword);
    }
}

Puntos clave:

  • Entity tiene un identificador único
  • Las reglas de negocio se encapsulan en los objetos Domain
  • La capa Domain es lógica de negocio pura, no depende del framework
Domain 层:领域模型设计
Domain 是业务概念的载体,所有层的依赖基础
贫血模型 (Anemic)传统做法
@Entity
public class Order {
    @Id private Long id;
    private BigDecimal totalAmount;
    private OrderStatus status;
    // 只有 getter/setter,没有业务逻辑
    public Long getId() { return id; }
    public void setStatus(OrderStatus s) { this.status = s; }
}
@Service
public class OrderService {
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        // 贫血模型:业务逻辑散落在 Service 里
        if (order.getStatus() == OrderStatus.SHIPPED)
            throw new IllegalStateException("已发货不能取消");
        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);
    }
}
贫血模型的问题
  • 违背面向对象:对象只有数据没有行为
  • 逻辑分散:同样的规则可能在多个 Service 重复
  • 难以维护:改一个规则要找所有用到的地方
充血模型 (Rich Domain)推荐做法
@Entity
public class Order {
    @Id private Long id;
    private BigDecimal totalAmount;
    private OrderStatus status;

    // 业务行为封装在实体里
    public void cancel() {
        if (this.status == OrderStatus.SHIPPED)
            throw new IllegalStateException("已发货不能取消");
        this.status = OrderStatus.CANCELLED;
        registerEvent(new OrderCancelledEvent(this.id));
    }

    public void pay(Payment payment) {
        if (this.status != OrderStatus.PENDING_PAYMENT)
            throw new IllegalStateException("状态不正确");
        this.status = OrderStatus.PAID;
    }
}
@Service
public class OrderService {
    @Transactional
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.cancel(); // 调用领域对象的业务方法
        orderRepository.save(order);
    }
}
充血模型的优势
  • 符合面向对象:数据和行为封装在一起
  • 业务内聚:规则跟着对象走,改一处处处生效
  • 可测试:领域对象是纯内存对象,不需要数据库
  • 表达力强:order.cancel() 比 orderService.cancel(order) 更自然

3. DTO: el "traductor" entre capas

3.1 ¿Por qué necesitamos DTO?

Problema: si devolvemos directamente la entidad de base de datos al frontend:

java
// ❌ Incorrecto: devolver Entity directamente
@Entity
public class User {
    private Long id;
    private String username;
    private String password;        // ¡Información sensible!
    private Boolean isDeleted;      // ¡Campo interno!
}

El frontend recibiría campos que no deberían exponerse, con riesgos de seguridad.

Solución: usar DTO como "traductor"

Entity de BD → Service Param/Result → Controller Request/Response → Frontend

3.2 Tipos de DTO

TipoUsoEjemplo
Request DTOController recibe parámetrosUserCreateRequest
Response DTOController devuelve datosUserResponse
Param DTOParámetros del método ServiceUserParam
Result DTOService devuelve resultadosUserResult
EntityMapeo de base de datosUser

Principio clave: Cada capa usa su propio DTO, no se debe pasar Entity directamente. Los DTO solo contienen los campos necesarios, lo que evita exponer detalles de implementación interna y garantiza la independencia de cada capa.

DTO 流转:数据在不同层之间的转换
DTO(Data Transfer Object)是层与层之间传递数据的载体
Controller 层
// 接收 Request DTO
public ResponseEntity<UserDTO> createUser(
    @RequestBody @Valid UserCreateRequest request) { ... }
↓ 转换为 Service 需要的参数
Service 层
public UserDTO createUser(UserCreateParam param) {
    User user = param.toEntity();   // 转换为 Entity
    userRepository.save(user);
    return UserDTO.from(user);      // Entity → DTO
}
↓ 转换为 Repository 需要的 Entity
Repository 层
public interface UserRepository
    extends JpaRepository<User, Long> { }
↑ 返回 Entity,转换为 DTO
返回给客户端
{ "id": 10001, "username": "张三",
  "email": "zhangsan@example.com", "createdAt": "2024-01-15T10:30:00Z" }
不同层的 DTO 职责
层级DTO 类型职责示例
ControllerRequest / Response DTO定义 API 契约、参数校验UserCreateRequest
ServiceParam / Result DTO封装业务方法参数,解耦层间依赖UserCreateParam
RepositoryEntity / DO映射数据库表结构UserEntity

4. Dirección de dependencias: la regla de oro de la arquitectura en capas

4.1 Principio de inversión de dependencias

Enfoque incorrecto:

Controller → UserServiceImpl → UserDaoImpl → UserEntity

Enfoque correcto:

Controller → UserService(interfaz) → UserRepository(interfaz) → UserEntity

Dirección de dependencias:

La dirección correcta de dependencias es que todas las capas dependan de capas más abstractas y estables. Concretamente, Controller depende de la interfaz de Service, Service depende de la interfaz de Repository, todas las capas dependen de la capa Domain, y la capa Domain no depende de ninguna otra capa. Esta dirección de dependencias garantiza la independencia y la testeabilidad de la lógica de negocio.

Los enfoques incorrectos incluyen que Service dependa directamente de la implementación concreta de Repository, que Controller opere directamente sobre la base de datos, o que la capa Domain dependa de otras capas. Todo esto aumenta el acoplamiento y reduce la mantenibilidad del sistema.

4.2 Ejemplo de código

java
// ✅ Correcto: depender de interfaces
@Service
public class OrderService {
    private final OrderRepository orderRepository;  // interfaz
    private final PaymentService paymentService;    // interfaz
}

// ✅ La implementación se inyecta automáticamente con Spring
@Repository
public class OrderRepositoryImpl implements OrderRepository {
    // Detalles de implementación
}
依赖方向:分层架构的核心规则
理解依赖方向,才能真正掌握分层架构
外层(UI / 外部系统)
Controller
↓ 依赖
中层(应用层)
Service
↓ 依赖
内层(领域层)
Domain / Repository
核心原则:依赖倒置(DIP)

上层模块不应该依赖下层模块的具体实现,而应该依赖于抽象。

Controller → Service 接口
Controller 只依赖 Service 的接口,不依赖实现类
Service → Repository 接口
Service 只依赖 Repository 接口,不关心数据怎么存
所有层依赖 Domain
Domain 是核心,被所有上层依赖,但 Domain 不依赖任何层

5. Caso práctico: sistema de pedidos de e-commerce

5.1 Requisitos

Crear un pedido:

  1. El usuario selecciona productos
  2. Verificar inventario
  3. Calcular importe
  4. Crear pedido
  5. Descontar inventario

5.2 Implementación del código

Capa Domain:

java
@Entity
public class Order {
    @Id
    private Long id;
    private Long userId;
    private List<OrderItem> items;
    private Money totalAmount;
    private OrderStatus status;

    public void calculateTotal() {
        Money total = Money.zero();
        for (OrderItem item : items) {
            total = total.add(item.getSubTotal());
        }
        this.totalAmount = total;
    }

    public void cancel() {
        if (this.status != OrderStatus.PENDING_PAYMENT) {
            throw new IllegalStateException("Solo los pedidos pendientes de pago pueden cancelarse");
        }
        this.status = OrderStatus.CANCELLED;
    }
}

Capa Repository:

java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByUserIdOrderByCreatedAtDesc(Long userId);
}

Capa Service:

java
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;

    @Transactional
    public OrderDTO createOrder(OrderParam param) {
        // 1. Validar productos y descontar inventario
        for (OrderItemParam item : param.getItems()) {
            inventoryService.reserveStock(item.getProductId(), item.getQuantity());
        }

        // 2. Crear pedido
        Order order = new Order();
        order.setUserId(param.getUserId());
        order.calculateTotal();

        // 3. Guardar pedido
        orderRepository.save(order);

        return OrderDTO.from(order);
    }
}

Capa Controller:

java
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService orderService;

    @PostMapping
    public OrderResponse createOrder(@RequestBody @Valid OrderRequest request) {
        OrderParam param = OrderParam.builder()
                .userId(request.getUserId())
                .items(request.getItems())
                .build();

        OrderDTO order = orderService.createOrder(param);

        return OrderResponse.from(order);
    }
}

6. Preguntas frecuentes

6.1 ¿Puede el Controller contener lógica de negocio?

El Controller no debe contener lógica de negocio, solo debe encargarse de recibir peticiones y devolver respuestas. La lógica de negocio debe estar encapsulada en la capa Service, lo que permite que el código sea reutilizable: por ejemplo, las tareas programadas o los consumidores de colas de mensajes pueden llamar directamente al Service sin necesidad de pasar por HTTP. Además, tener la lógica de negocio concentrada en un solo lugar facilita las pruebas y el mantenimiento, evitando inconsistencias por lógica dispersa.

6.2 ¿Qué son el modelo anémico y el modelo rico?

El modelo anémico (Anemic Domain Model) es aquel donde las clases de entidad solo contienen atributos y sus correspondientes getters/setters, sin ninguna lógica de negocio; todas las reglas de negocio se implementan en la capa Service. Este modelo tiene una estructura simple, es fácil de entender y es el enfoque adoptado por la mayoría de los proyectos.

El modelo rico (Rich Domain Model) es aquel donde las clases de entidad no solo contienen atributos, sino también métodos de negocio relacionados con la entidad, encapsulando las reglas de negocio dentro de la propia entidad. Este enfoque se ajusta más al diseño orientado a objetos, manteniendo datos y comportamiento juntos y aumentando la cohesión del código.

Se recomienda elegir el modelo adecuado según el nivel técnico del equipo y la complejidad del proyecto, pero independientemente de la elección, se debe mantener la consistencia. La capa Domain debería al menos contener métodos básicos de comportamiento de negocio, en lugar de ser un cascarón completamente vacío.

6.3 ¿Cómo manejar transacciones que abarcan múltiples Service?

Cuando una operación de negocio necesita abarcar múltiples Service, se debe usar la anotación de transacción en el Service de nivel superior, y dentro de ese método llamar secuencialmente a los Service de nivel inferior. Esto garantiza que todas las operaciones se ejecuten en el mismo contexto transaccional: o todas tienen éxito o todas fallan, asegurando la consistencia de los datos. Hay que tener en cuenta que los límites de la transacción deben ser lo más pequeños posible, incluyendo solo las operaciones necesarias, para evitar mantener bloqueos de base de datos durante mucho tiempo y afectar el rendimiento de concurrencia.


7. Resumen

CapaResponsabilidadPalabra clave
ControllerRecibir peticiones, validar parámetros, llamar a Service, devolver respuestaRecepcionista
ServiceOrquestación de lógica de negocio, gestión de transacciones, coordinar RepositoryCocinero
RepositoryAcceso a datos, mapeo ORM, encapsulación de consultasAlmacenista
DomainDefinición de entidades, reglas de negocio, objetos de valorEstándar de recetas

Principios fundamentales:

  1. Cada capa solo hace lo suyo
  2. Las capas se comunican mediante interfaces
  3. La lógica de negocio se concentra en Service y Domain
  4. La lógica de acceso a datos se concentra en Repository
  5. Usar DTO para aislar las estructuras de datos entre capas

8. Más patrones arquitectónicos

Este artículo presenta la arquitectura en capas (Layered Architecture), el patrón de arquitectura backend más común y fácil de adoptar. Pero la arquitectura backend no se limita a este patrón; según el escenario de negocio, existen otros patrones que vale la pena conocer:

8.1 Otros patrones arquitectónicos comunes

Patrón arquitectónicoEscenario aplicableCaracterísticas
Arquitectura monolíticaProyectos pequeños, MVPTodas las funcionalidades en una sola aplicación, despliegue simple
Arquitectura de microserviciosSistemas grandes y complejosDividido en múltiples servicios independientes, cada uno desplegable por separado
Arquitectura dirigida por eventosAlta concurrencia, procesamiento asíncronoEl flujo de procesamiento se activa por eventos, alto desacoplamiento
Arquitectura limpiaSistemas de negocio complejosLa lógica de negocio en el centro, las dependencias solo hacia adentro, el framework en la capa más externa
Arquitectura hexagonalNecesidad de múltiples adaptadores externosAísla el núcleo de los sistemas externos mediante puertos y adaptadores
Arquitectura de cebollaDiseño guiado por el dominioCapas concéntricas, el modelo de dominio en el centro, la infraestructura en el exterior

A continuación se detalla cada uno:

Arquitectura monolítica (Monolithic)

Todas las funcionalidades se empaquetan en una sola aplicación, compartiendo la misma base de datos y proceso.

┌──────────────────────────────┐
│         Aplicación monolítica │
│  ┌────┐ ┌────┐ ┌────┐       │
│  │Usuario│ │Pedido│ │Pago│ ... │
│  └──┬─┘ └──┬─┘ └──┬─┘       │
│     └──────┼──────┘          │
│         BD compartida         │
└──────────────────────────────┘
  • Ventajas: desarrollo simple, despliegue sencillo, depuración local fácil
  • Desventajas: alto acoplamiento del código, difícil de escalar, un fallo en un módulo puede tumbar todo el sistema
  • Aplicable: proyectos startup tempranos, desarrollo con un solo equipo, validación rápida de prototipos

Arquitectura de microservicios (Microservices)

Divide el sistema en múltiples servicios independientes, cada uno con sus propios datos y lógica de negocio, desplegables y escalables de forma independiente.

┌────────┐  ┌────────┐  ┌────────┐
│Servicio │  │Servicio │  │Servicio │
│Usuario  │  │Pedido   │  │Pago     │
│  DB-1  │  │  DB-2  │  │  DB-3  │
└───┬────┘  └───┬────┘  └───┬────┘
    └───────────┼───────────┘
          API Gateway
  • Ventajas: despliegue y escalado independientes, flexibilidad de stack tecnológico, aislamiento de fallos
  • Desventajas: comunicación entre servicios compleja, consistencia de datos distribuidos difícil, requiere capacidad DevOps madura
  • Aplicable: sistemas grandes y complejos, colaboración entre múltiples equipos, escenarios que requieren escalado independiente

Arquitectura dirigida por eventos (Event-Driven)

Comunicación mediante eventos asíncronos: los productores emiten eventos y los consumidores responden a ellos, con alto desacoplamiento entre componentes.

Productor ──→ [Bus de eventos/Cola de mensajes] ──→ Consumidor A
                                                   ──→ Consumidor B
                                                   ──→ Consumidor C
  • Ventajas: alto desacoplamiento, escalabilidad natural, adecuado para procesamiento en tiempo real
  • Desventajas: depuración difícil, el orden de eventos y la idempotencia requieren tratamiento adicional
  • Aplicable: análisis de datos en tiempo real, sistemas IoT, comunicación asíncrona entre microservicios

Arquitectura limpia (Clean Architecture)

Propuesta por Robert C. Martin, divide el sistema en cuatro capas concéntricas, donde las dependencias solo pueden apuntar de afuera hacia adentro:

┌─────────────────────────────────────┐
│  Frameworks & Drivers               │
│  ┌─────────────────────────────┐    │
│  │  Interface Adapters          │    │
│  │  ┌─────────────────────┐    │    │
│  │  │  Use Cases           │    │    │
│  │  │  ┌─────────────┐    │    │    │
│  │  │  │  Entities    │    │    │    │
│  │  │  │  (dominio)   │    │    │    │
│  │  │  └─────────────┘    │    │    │
│  │  └─────────────────────┘    │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘
         Dirección de dependencias: exterior → interior
  • Regla fundamental: las capas internas no conocen la existencia de las externas, la lógica de negocio es completamente independiente del framework y la base de datos
  • Ventajas: alta testeabilidad, stack tecnológico reemplazable, lógica de negocio clara
  • Desventajas: alto coste inicial de desarrollo, mucho código de mapeo entre capas, riesgo de sobre-ingeniería en proyectos pequeños
  • Aplicable: sistemas de negocio complejos, proyectos que requieren mantenimiento a largo plazo
整洁架构与分层架构对比
分层架构是整洁架构的基础,理解两者关系有助于构建更灵活的系统
Controller 层 接收请求、参数校验
Service 层 业务逻辑、事务管理
Repository 层 数据访问、ORM 映射
Domain 层 实体定义、业务规则
传统分层架构特点
  • 垂直依赖:上层直接依赖下层
  • 简单直观:结构清晰,易于理解
  • 适合中小型项目:快速开发,上手简单
  • 潜在问题:底层变更可能影响上层

Arquitectura hexagonal (Hexagonal / Ports & Adapters)

Define las interfaces de entrada/salida del núcleo de negocio mediante "puertos", y conecta los sistemas externos mediante "adaptadores":

        ┌─────────────┐
  HTTP ──→ Puerto     │
  CLI  ──→ (entrada)  │  Lógica de negocio  │  (salida) ──→ Base de datos
  MQ   ──→            │      central        │  Puerto   ──→ API externa
        └─────────────┘
  • Idea central: la lógica de negocio no depende de ninguna tecnología externa, los sistemas externos se conectan mediante adaptadores
  • Ventajas: los sistemas externos se pueden reemplazar libremente, para pruebas basta con usar adaptadores Mock
  • Aplicable: escenarios que necesitan integrarse con múltiples sistemas externos

Arquitectura de cebolla (Onion Architecture)

Similar a la arquitectura limpia, enfatiza que el modelo de dominio está en la capa más interna y la infraestructura en la más externa, con dependencias solo hacia adentro:

┌──────────────────────────────┐
│  Infraestructura              │
│  ┌────────────────────────┐  │
│  │  Servicios de aplicación│  │
│  │  ┌──────────────────┐  │  │
│  │  │ Servicios de dominio│  │  │
│  │  │  ┌────────────┐   │  │  │
│  │  │  │Modelo de   │   │  │  │
│  │  │  │dominio     │   │  │  │
│  │  │  └────────────┘   │  │  │
│  │  └──────────────────┘  │  │
│  └────────────────────────┘  │
└──────────────────────────────┘
  • Idea central: el modelo de dominio es el núcleo del sistema, todas las dependencias apuntan hacia él
  • Diferencia con la arquitectura limpia: la arquitectura de cebolla enfatiza más la capa de servicios de dominio, la arquitectura limpia enfatiza más la capa de casos de uso
  • Aplicable: proyectos que adoptan Diseño Guiado por el Dominio (DDD)

8.2 Ruta de evolución arquitectónica

Estos patrones no se sustituyen entre sí, sino que evolucionan gradualmente:

text
Arquitectura en capas tradicional (N-Layered)
  │  Problema: acoplamiento entre capas, difícil reemplazar dependencias externas

Arquitectura hexagonal (Ports & Adapters)
  │  Mejora: aísla sistemas externos con puertos y adaptadores

Arquitectura de cebolla (Onion)
  │  Mejora: capas concéntricas explícitas, modelo de dominio en el centro

Arquitectura limpia (Clean Architecture)
  │  Mejora: reglas de dependencia unificadas, cuatro capas con responsabilidades claras

Elegir la arquitectura adecuada según las necesidades del negocio

8.3 Guía de selección de patrones arquitectónicos

text
Usuarios < 1k, líneas de código < 5000

Arquitectura monolítica + capas simples

Usuarios 1k-100k, necesidad de colaboración entre equipos

Arquitectura en capas (la presentada en este artículo)

Usuarios > 100k, alta complejidad de negocio

Arquitectura de microservicios / Arquitectura dirigida por eventos

Dimensiones de selección más detalladas:

Factor a considerarCapas simplesArquitectura limpia/hexagonalMicroservicios
Tamaño del equipo1-5 personas5-20 personas20+ personas
Complejidad del negocioBajaMedia-altaAlta
Frecuencia de despliegueBajaMediaAlta (despliegue independiente)
Diversidad de stack tecnológicoÚnicoÚnicoPuede ser diverso
Coste de operacionesBajoMedioAlto

8.4 Lecturas recomendadas

  • Arquitectura monolítica: consulta el artículo complementario backend-project-architecture.md, para entender la evolución de script a monolito
  • Arquitectura de microservicios: consulta De monolito a microservicios
  • Arquitectura limpia: "Clean Architecture" de Robert C. Martin — obra clásica que propone las reglas de dependencia y el modelo de cuatro capas concéntricas
  • Patrones de arquitectura empresarial: "Patterns of Enterprise Application Architecture" de Martin Fowler — referencia autorizada sobre arquitectura en capas y organización de lógica de dominio

8.5 ¿Cómo elegir?

Recuerda este principio: la arquitectura sirve al negocio, no se hace arquitectura por la arquitectura misma.

  • Proyectos pequeños: arquitectura simple, lanzamiento rápido para validar
  • Proyectos grandes: considerar arquitecturas más complejas, evitar el sobre-diseño
  • La familiaridad del equipo también es importante: elegir soluciones que todos puedan entender

9. Resumen

CapaResponsabilidadPalabra clave
ControllerRecibir peticiones, validar parámetros, llamar a Service, devolver respuestaRecepcionista
ServiceOrquestación de lógica de negocio, gestión de transacciones, coordinar RepositoryCocinero
RepositoryAcceso a datos, mapeo ORM, encapsulación de consultasAlmacenista
DomainDefinición de entidades, reglas de negocio, objetos de valorEstándar de recetas

Principios fundamentales:

El núcleo de la arquitectura en capas radica en una división clara de responsabilidades y el control de la dirección de dependencias. Cada capa se enfoca solo en su propia responsabilidad, se comunica con las capas adyacentes mediante interfaces, la lógica de negocio se concentra en las capas Service y Domain, la lógica de acceso a datos se concentra en la capa Repository, y las estructuras de datos entre capas se aíslan mediante DTO, evitando exponer directamente los detalles de implementación interna. Este diseño hace que el sistema sea más fácil de entender, probar y mantener, capaz de afrontar la evolución continua del negocio.


Referencias

  1. Catalog of Patterns of Enterprise Application Architecture - Martin Fowler — Catálogo de patrones de arquitectura de aplicaciones empresariales de Martin Fowler, referencia clásica de arquitectura en capas
  2. Backend Side Architecture Evolution (N-layered, DDD, Hexagon, Onion, Clean Architecture) — De la arquitectura en N capas a la arquitectura limpia, entendiendo el origen de cada patrón
  3. Complete Guide to Clean Architecture - GeeksforGeeks — Guía completa de arquitectura limpia, explicando capas, reglas de dependencia y separación de responsabilidades
  4. Understanding Hexagonal, Clean, Onion, and Traditional Layered Architectures: A Deep Dive — Comparativa profunda entre arquitecturas hexagonal, limpia, de cebolla y en capas tradicional
  5. Building Clean Architectures in Modern Backend Frameworks — Guía práctica para implementar arquitectura limpia en frameworks backend modernos
  6. Backend Architecture Patterns: From Monoliths to Microservices — Panorama completo de patrones de arquitectura backend, del monolito a los microservicios
  7. Explicación detallada de un caso de arquitectura MVC de tres capas — Relación entre MVC y arquitectura de tres capas con casos prácticos, adecuado para lectores de habla china