Modelos Anémicos vs. Modelos Enriquecidos

Modelos y DTOs

By Alejandro Urrestarazu

Modelos Anémicos vs. Modelos Enriquecidos

Plano arquitectura laberinto de dédalo

Dentro del ámbito del desarrollo de software, se debate constantemente la distinción entre modelos anémicos y modelos enriquecidos (también conocidos como modelos de dominio ricos), especialmente en el contexto de la programación orientada a objetos y el diseño de arquitecturas de software. Cada uno de estos enfoques tiene un impacto significativo en la estructura y gestión de la lógica de negocio en las aplicaciones.

Modelos Anémicos

Un modelo anémico se refiere a una aproximación donde los objetos de dominio son esencialmente contenedores de datos sin comportamientos o lógicas de negocio relevantes. Estos modelos suelen estar constituidos principalmente por campos de datos y sus respectivos métodos getters y setters. La lógica de negocio se gestiona generalmente fuera de los objetos de dominio, a menudo en clases de servicio separadas.

La exposición de datos mediante getter y setter puede quebrantar el principio de encapsulación, al permitir a fuentes externas acceder y modificar los datos dentro de un objeto en lugar de mantenerlos protegidos dentro del objeto mismo. Esta vulnerabilidad puede hacer que el objeto sea susceptible a daños o alteraciones no deseadas. La forma anémica de diseño también puede conducir a un enfoque más procedimental de la programación, donde el principal énfasis radica en la manipulación de datos en lugar de encapsularlos en objetos con un comportamiento significativo.

Características de los modelos anémicos:

  • Separación de datos y comportamiento.
  • Facilidad inicial en la separación y organización del código, aunque puede resultar en problemas de mantenimiento.
  • Frecuencia en arquitecturas basadas en transacciones o en aplicaciones donde la lógica de negocio no está estrechamente ligada a los estados de los objetos.

Modelos Enriquecidos o de Dominio Rico

Un modelo de dominio rico se caracteriza por objetos de dominio que encapsulan tanto datos como comportamientos. Esto implica que, además de contener campos de datos, también incluyen la lógica necesaria para manipular esos datos y representar adecuadamente las operaciones del dominio.

Características de los modelos enriquecidos:

  • Integración de datos y comportamientos en las mismas entidades o clases.
  • Mayor adhesión a los principios de la programación orientada a objetos, como encapsulación e herencia.
  • Fomenta un diseño más intuitivo y cohesivo, lo que puede mejorar la mantenibilidad y robustez del código.
  • Puede complicar el diseño inicial y aumentar la curva de aprendizaje para nuevos desarrolladores en el proyecto.

Relación con los DTOs (Data Transfer Objects)

Los DTOs (Objetos de Transferencia de Datos) son una técnica utilizada en el desarrollo de software para transferir datos entre sub-sistemas de una aplicación, especialmente en aplicaciones con una arquitectura de capas o en entornos distribuidos. En muchos casos, un DTO se asemeja a una instancia de un modelo anémico, ya que actúa principalmente como un contenedor de datos sin funcionalidades significativas, diseñado específicamente para la transferencia de datos entre capas o servicios.

Conexión entre DTOs y modelos de diseño:

  • Los DTOs suelen utilizarse en conjunto con modelos anémicos para transferir datos entre las capas de servicio donde se implementa la lógica de negocio.
  • En el contexto de modelos de dominio ricos, los DTOs pueden seguir siendo útiles para simplificar y desacoplar la transferencia de datos entre distintas partes de un sistema o entre sistemas diferentes; sin embargo, es fundamental gestionar cuidadosamente el diseño de los DTOs para evitar replicar de forma indebida la lógica o el estado interno del dominio.

En resumen, los modelos anémicos y ricos representan filosofías distintas en el diseño de software y la elección entre uno u otro puede depender de diversos factores, como los requisitos específicos del proyecto, las preferencias del equipo y el contexto técnico y comercial.

Los DTOs desempeñan un papel importante en ambos contextos, sirviendo principalmente como facilitadores en la transferencia de datos. Los modelos enriquecidos ofrecen una mayor cohesión y robustez al encapsular tanto datos como lógica de negocio en las entidades, mientras que los DTOs son cruciales para la transferencia eficiente de datos entre diferentes componentes del sistema.

¿Son los DTOs antipatrones?

No necesariamente. La consideración de los DTOs como antipatrones depende en gran medida del contexto específico y de cómo se implementan.

Casos en que los DTOs son útiles y no representan un antipatrón:

  1. Reducir el tráfico de red: En aplicaciones distribuidas, especialmente aquellas que operan sobre redes más lentas o sobre Internet, minimizar el número de llamadas remotas es crucial. Los DTOs pueden encapsular múltiples datos necesarios para una sola llamada, reduciendo la necesidad de múltiples interacciones entre las capas cliente y servidor.

  2. Desacoplamiento: Los DTOs ayudan a desacoplar la capa de presentación de la capa de negocio al servir como un intermediario que solo transfiere datos necesarios para la presentación, sin exponer la lógica de negocio interna o las entidades de persistencia.

  3. Integración entre sistemas: En los escenarios de integración de sistemas, donde diferentes aplicaciones necesitan intercambiar datos, los DTOs proporcionan una forma estandarizada y simplificada de compartir datos sin necesidad de exponer las estructuras internas de los sistemas involucrados.

Situaciones en que los DTOs podrían considerarse un antipatrón:

  1. Sobreutilización/Abuso: Si los DTOs se utilizan en exceso donde no son necesarios, pueden llevar a un diseño sobrecomplicado y a una gran cantidad de mapeo redundante y tedioso entre las entidades del dominio y los DTOs, lo cual podría introducir errores y complicaciones de mantenimiento.

  2. Desincronización: Si no se gestionan adecuadamente, los DTOs pueden llevar a una desincronización entre las múltiples representaciones de los objetos (por ejemplo, entidades de dominio versus DTOs), haciendo que el sistema sea más difícil de entender y mantener.

  3. Rendimiento: Aunque los DTOs pueden mejorar el rendimiento reduciendo las llamadas de red, el coste de mapear datos entre entidades del dominio y DTOs puede ser significativo, especialmente si el mapeo es complejo o si se manejan grandes volúmenes de datos.

Veamos con un ejemplo

Imaginemos que tenemos que modelar una cuenta bancaria simple que tiene dos operaciones una para retirar dinero y otra para depositar.

Modelo enriquecido de una Cuenta Bancaria

El siguiente código pertenece a una clase de modelo enriquecido que permite acceder al monto y el identificador de la cuenta bancaria. Además, incluye dos operaciones para depositar y retirar dinero.

public class CuentaBancaria {
  private double monto;
  private final int idCuenta;

  public CuentaBancaria(double monto, int idCuenta) {
    this.monto = monto;
    this.idCuenta = idCuenta;
  }

  public double getMonto() {
    return monto;
  }

  public int getIdCuenta() {
    return idCuenta;
  }

  public void depositar(double cantidad) {
    this.monto += cantidad;
  }

  public void retirar(double cantidad) {
    if (cantidad <= this.monto) {
      this.monto -= cantidad;
    } else {
      System.out.println("No tienes suficiente saldo en la cuenta.");
    }
  }
}
  • ¿Qué pasaría si necesitáramos cambiar los ID de las cuentas debido a la fusión de dos bancos?
  • ¿Qué ocurriría si se retirara un saldo negativo?
  • ¿Debería la lógica para prestar dinero estar incluida en esta clase?
  • ¿Qué pasaría si algunos de los atributos fueran de tipo mutable?

Los invito a reflexionar sobre las distintas variantes de este problema, que son las que nos proporcionarán el famoso “depende” al decidir cuándo y cómo usar cada modelo.

Modelo anémico de una Cuenta Bancaria

A continuación tenemos la clase cuenta Bancaria como un modelo anémico que se puede usar como contenedor de los datos (DTOs) o se puede pensar como el estado en un momento determinado de la ejecución.

public class CuentaBancaria {
    private double monto;
    private int idCuenta;

    public CuentaBancaria() {
    }

    public double getMonto() {
        return this.monto;
    }

    public void setMonto(double monto) {
        this.monto = monto;
    }

    public int getIdCuenta() {
        return this.idCuenta;
    }

    public void setIdCuenta(int idCuenta) {
        this.idCuenta = idCuenta;
    }
}

Para poder operar o cambiar su estado es necesario tener un caso de uso para poder replicar el comportamiento como el del modelo enriquecido:

public class OperacionesCuenta {
    public void depositar(CuentaBancaria cuenta, double cantidad) {
        double nuevoSaldo = cuenta.getMonto() + cantidad;
        cuenta.setMonto(nuevoSaldo);
    }

    public void retirar(CuentaBancaria cuenta, double cantidad) {
        double nuevoSaldo = cuenta.getMonto() - cantidad;
        if (nuevoSaldo >= 0) {
            cuenta.setMonto(nuevoSaldo);
        } else {
            System.out.println("Fondos insuficientes.");
        }
    }
}

En este caso, aunque ganamos la flexibilidad de modificar los atributos, el precio a pagar es la ruptura del principio de encapsulamiento. Por otro lado, estas clases por sí solas no garantizan los principios SOLID, y es necesario que el módulo esté bien diseñado para evitar errores o violaciones de límites que expongan entidades o comportamientos no deseados.

Como reflexión final, los DTOs no son necesariamente antipatrones. Son herramientas que, utilizadas correctamente, pueden mejorar la estructura y el rendimiento de una aplicación. Sin embargo, como ocurre con cualquier herramienta en la ciencia del software, el uso inapropiado puede acarrear problemas de diseño y mantenimiento. Es crucial evaluar la necesidad de su uso en el contexto específico del proyecto y aplicarlos de manera que maximicen su utilidad sin introducir complejidad innecesaria.

No te olvides de socializar y comptarte!

Share: Facebook LinkedIn