# Rails sostenible (III): la lógica de negocio no va en los Active Records

*Tercera entrega de la serie **[Rails sostenible](/search?tag=rails-sostenible)**, sobre el libro de David Bryant Copeland. Tiempo de lectura estimado: 14 minutos.*

Si esta serie tuviera que reducirse a un solo post, sería este. Toda la propuesta de Copeland gira alrededor de una frase que da título a su capítulo cinco y que reaparece, ampliada, en el capítulo quince: **la lógica de negocio no va en los Active Records**. Vimos en [la primera entrega](/post/rails-sostenible-i-sostenibilidad-y-arquitectura) que Rails no le da casa a la lógica de negocio. Hoy se la construimos.

## Qué es la lógica de negocio (y por qué es peligrosa)

Copeland define la lógica de negocio como **lo que hace especial a tu aplicación**. No es el CRUD —eso lo hace cualquier framework—, sino las reglas concretas de tu dominio: cómo se calcula el precio de un pedido con descuentos y cupones, qué pasa cuando un usuario cancela una suscripción, cuándo se considera que un envío está "retrasado". Es el código que no podrías copiar de otro proyecto porque es tuyo.

Y precisamente por eso es peligroso, por tres razones que el libro desgrana:

1. **Es un imán de complejidad.** Las reglas de negocio se acumulan, se contradicen, tienen excepciones y casos límite. Es, de lejos, la parte más enrevesada del sistema.
2. **Sufre mucho *churn*.** Cambia constantemente, porque el negocio cambia constantemente. Lo que hoy es "10% de descuento a partir de 50 €" mañana es otra cosa.
3. **Un bug aquí tiene efectos amplios.** Si la lógica vive en clases muy referenciadas, un error se propaga por todo el sistema.

![Logo de Ruby](fig-01.webp)

## El problema de meterla en los Active Records

Junta esas tres características con la realidad de un Active Record y verás el desastre. El modelo `Order` lo usa medio sistema: los controladores, las vistas, los jobs, los mailers, los tests. Es una clase **crítica y omnipresente**. Si además le metes dentro toda la lógica de cálculo de precios, validaciones contextuales y disparo de side effects, conviertes una clase ya frágil por su uso en una clase que *además* cambia cada semana.

```ruby
# El anti-patrón: el "fat model" que lo sabe todo
class Order < ApplicationRecord
  belongs_to :customer
  has_many :line_items

  after_create :send_confirmation_email
  after_create :notify_warehouse
  after_update :recalculate_loyalty_points

  def total
    subtotal = line_items.sum(&:price)
    subtotal -= discount_for(customer)
    subtotal += shipping_cost
    subtotal * (1 + tax_rate)
  end

  def discount_for(customer)
    # 40 líneas de reglas de cupones, fidelidad, promociones...
  end
  # ...y otras 600 líneas
end
```

Esta clase mezcla **tres responsabilidades** que cambian por motivos distintos: el acceso a datos (las asociaciones), las reglas de negocio (cálculo de precios) y los efectos colaterales (emails, almacén). Cada una tiene su propio ritmo de cambio. Tenerlas pegadas significa que tocar una te obliga a entender y arriesgar las otras. Eso es lo contrario de sostenible.

## El *seam*: una costura entre Rails y tu dominio

Aquí Copeland introduce el concepto que vertebra su solución: el **seam** (costura). Un *seam* es un punto del sistema donde puedes separar dos mundos: el mundo de Rails (HTTP, base de datos, vistas) y el mundo de tu lógica de negocio. La idea es que los controladores, jobs y demás clases frontera **deleguen** la lógica de negocio a clases que no saben nada de Rails.

> Tu lógica de negocio debería poder leerse y entenderse sin necesidad de saber que existe un controlador, un request HTTP o una tabla en Postgres.

Esa frontera —el seam— es lo que te permite que cada lado evolucione a su ritmo. Los controladores cambian cuando cambia la interfaz; la lógica de negocio cambia cuando cambian las reglas; y la una no arrastra a la otra.

## Servicios stateless con nombres explícitos

¿Cómo se materializa el seam? Con una **capa de servicios**. Pero ojo, Copeland es muy específico sobre cómo deben ser estos servicios, porque "service object" en la comunidad Rails significa muchas cosas y no todas buenas:

- **Stateless:** el servicio no guarda estado entre llamadas. No tiene atributos mutables que representen "el progreso" de algo. Recibe lo que necesita, hace su trabajo, devuelve un resultado.
- **Nombres explícitos de clase:** nada de un cajón de sastre `OrderService` que acumula 30 métodos inconexos. Mejor clases con nombre propio que describan una operación del dominio.
- **Nombres explícitos de método:** el método dice qué hace en lenguaje del negocio.

Copeland propone agrupar estos servicios en una clase de dominio que actúa como punto de entrada. Por ejemplo, un objeto `Customers` con métodos como `create_customer` o `cancel_subscription`:

```ruby
# app/services/customers.rb
class Customers
  def create_customer(email:, name:)
    customer = Customer.create!(email: email, name: name)
    Welcome.deliver_later(customer)   # side effect explícito
    Result.new(created: true, customer: customer)
  end
end
```

Y el controlador, que vive del lado de Rails del seam, se limita a traducir el request y delegar:

```ruby
class CustomersController < ApplicationController
  def create
    result = Customers.new.create_customer(
      email: params[:email],
      name: params[:name],
    )
    if result.created?
      redirect_to customer_path(result.customer)
    else
      render :new, status: :unprocessable_entity
    end
  end
end
```

Fíjate en lo que ha pasado: el controlador no sabe *cómo* se crea un cliente, solo que existe una operación de dominio llamada "crear cliente". El servicio no sabe que existe un controlador. La costura está limpia. Y el `Customer` (el Active Record) ha vuelto a su sitio: ser una pasarela hacia la base de datos, no el cerebro del negocio.

## Patrones que conviene evitar

El capítulo quince dedica buena parte a desaconsejar implementaciones populares de "service object" que, según Copeland, suelen empeorar las cosas:

- **El servicio con estado y un único `call`.** Ese patrón de `MyService.new(args).call` donde el objeto guarda los argumentos como atributos introduce estado mutable innecesario y oscurece qué hace cada método. Copeland prefiere métodos explícitos sobre objetos stateless.
- **Abusar de `ApplicationService` con magia compartida.** Las clases base que inyectan comportamiento mágico (`success`, `failure`, callbacks) tienen su propio carrying cost: hay que entender la base para entender cualquier servicio.
- **Confundir "un servicio por acción de controlador" con diseño.** Tener un `CreateOrderService`, `UpdateOrderService`, `DeleteOrderService` que reflejan el CRUD no aporta nada: estás renombrando el controlador. Los servicios deben modelar **operaciones del dominio**, no acciones HTTP.

La regla de fondo: el código de negocio debe **revelar comportamiento**. Al leer la clase, debes entender qué hace el negocio, no perderte en andamiaje genérico.

## Mi versión

Esta es la idea del libro que más he peleado en proyectos reales, y la que más resistencia genera, porque va contra el "Rails way" que mucha gente interioriza como dogma. El argumento que mejor me funciona no es teórico, es práctico: enseño el modelo `User` de 900 líneas y pregunto quién se atreve a tocarlo sin sudar. Nadie. Ese miedo *es* el coste de la insostenibilidad, hecho carne.

Dicho esto, también he visto el extremo opuesto: equipos que, mal interpretando el consejo, montan una capa de servicios con 200 clases `XxxService` de un solo método que son indistinguibles de funciones sueltas y que, encima, todas heredan de una base mágica imposible de seguir. Eso *también* es insostenible. La clave que Copeland repite y que yo suscribo: servicios **stateless, con nombres del dominio, sin magia**. Si tu capa de servicios no se lee como un glosario de tu negocio, algo va mal.

Mi heurística personal: el Active Record puede tener métodos de *consulta* y de *presentación de datos propios* (un `full_name`, un scope sencillo), pero en cuanto aparece un side effect, una regla con varias ramas o una operación que coordina varios objetos, eso se va al seam. El modelo guarda y lee; el servicio decide.

## Lo que viene

Hemos construido la pieza central: un seam que separa Rails de tu dominio, con servicios stateless de nombres explícitos. A partir de aquí, el libro recorre cada capa de Rails aplicando esta filosofía. En el **próximo post** empezamos por la puerta de entrada de toda petición: las **rutas y las plantillas HTML** —rutas canónicas, recursos en vez de acciones custom, HTML semántico, partials como componentes reutilizables y por qué Copeland defiende seguir usando ERB sin complejos.
