domain-model - resueltos - modelo de dominio online
Modelo de dominio rico versus anémico (8)
Estoy decidiendo si debería usar un modelo de dominio enriquecido sobre un modelo de dominio anémico y buscar buenos ejemplos de los dos.
He estado construyendo aplicaciones web usando un Modelo de dominio Anemic, respaldado por un servicio -> Repositorio -> Sistema de capa de almacenamiento, usando FluentValidation para la validación de BL, y poniendo todo mi BL en la capa de Servicio.
He leído el libro DDD de Eric Evan, y él (junto con Fowler y otros) parecen pensar que los Modelos de Dominio Anemico son un antipatrón.
Así que solo quería obtener una idea de este problema.
También estoy buscando algunos ejemplos buenos (básicos) de un modelo de dominio enriquecido y los beneficios sobre el modelo de dominio anémico que proporciona.
Antes que nada, copio y pego la respuesta de este artículo http://msdn.microsoft.com/en-gb/magazine/dn385704.aspx
La figura 1 muestra un modelo de dominio anémico, que básicamente es un esquema con getters y setters.
Figure 1 Typical Anemic Domain Model Classes Look Like Database Tables
public class Customer : Person
{
public Customer()
{
Orders = new List<Order>();
}
public ICollection<Order> Orders { get; set; }
public string SalesPersonId { get; set; }
public ShippingAddress ShippingAddress { get; set; }
}
public abstract class Person
{
public int Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string CompanyName { get; set; }
public string EmailAddress { get; set; }
public string Phone { get; set; }
}
En este modelo más rico, en lugar de simplemente exponer propiedades para leer y escribir, la superficie pública del Cliente se compone de métodos explícitos.
Figure 2 A Customer Type That’s a Rich Domain Model, Not Simply Properties
public class Customer : Contact
{
public Customer(string firstName, string lastName, string email)
{
FullName = new FullName(firstName, lastName);
EmailAddress = email;
Status = CustomerStatus.Silver;
}
internal Customer()
{
}
public void UseBillingAddressForShippingAddress()
{
ShippingAddress = new Address(
BillingAddress.Street1, BillingAddress.Street2,
BillingAddress.City, BillingAddress.Region,
BillingAddress.Country, BillingAddress.PostalCode);
}
public void CreateNewShippingAddress(string street1, string street2,
string city, string region, string country, string postalCode)
{
ShippingAddress = new Address(
street1,street2,
city,region,
country,postalCode)
}
public void CreateBillingInformation(string street1,string street2,
string city,string region,string country, string postalCode,
string creditcardNumber, string bankName)
{
BillingAddress = new Address (street1,street2, city,region,country,postalCode );
CreditCard = new CustomerCreditCard (bankName, creditcardNumber );
}
public void SetCustomerContactDetails
(string email, string phone, string companyName)
{
EmailAddress = email;
Phone = phone;
CompanyName = companyName;
}
public string SalesPersonId { get; private set; }
public CustomerStatus Status { get; private set; }
public Address ShippingAddress { get; private set; }
public Address BillingAddress { get; private set; }
public CustomerCreditCard CreditCard { get; private set; }
}
Aquí hay un ejemplo que podría ayudar:
Anémico
class Box
{
public int Height { get; set; }
public int Width { get; set; }
}
No anémico
class Box
{
public int Height { get; private set; }
public int Width { get; private set; }
public Box(int height, int width)
{
if (height <= 0) {
throw new ArgumentOutOfRangeException(nameof(height));
}
if (width <= 0) {
throw new ArgumentOutOfRangeException(nameof(width));
}
Height = height;
Width = width;
}
public int area()
{
return Height * Width;
}
}
Bozhidar Bozhanov parece argumentar a favor del modelo anémico en this publicación de blog.
Aquí está el resumen que presenta:
los objetos de dominio no deben administrarse en primavera (IoC), no deben tener DAO ni nada relacionado con la infraestructura inyectada en ellos.
los objetos de dominio tienen los objetos de dominio de los que dependen configurados por hibernación (o el mecanismo de persistencia)
los objetos de dominio realizan la lógica de negocios, como es la idea central de DDD, pero esto no incluye consultas de bases de datos o operaciones de CRUD solamente en el estado interno del objeto
rara vez se necesitan DTO: los objetos de dominio son los propios DTO en la mayoría de los casos (lo que ahorra un código repetitivo)
los servicios realizan operaciones CRUD, envían correos electrónicos, coordinan los objetos de dominio, generan informes basados en múltiples objetos de dominio, ejecutan consultas, etc.
la capa de servicio (aplicación) no es tan delgada, pero no incluye reglas de negocio que son intrínsecas a los objetos de dominio
la generación de código debe ser evitada. La abstracción, los patrones de diseño y DI se deben utilizar para superar la necesidad de generación de código y, en última instancia, para eliminar la duplicación de código.
ACTUALIZAR
Recientemente leí ADM+DDD artículo donde el autor aboga por seguir una especie de enfoque híbrido: los objetos de dominio pueden responder varias preguntas basándose únicamente en su estado (lo que en el caso de modelos totalmente anémicos probablemente se haría en la capa de servicio)
Cuando solía escribir aplicaciones de escritorio monolíticas construí modelos de dominio enriquecido, que solía disfrutar construyéndolos.
Ahora escribo microservicios HTTP pequeños, hay el menor código posible, incluidos los DTO anémicos.
Creo que DDD y este argumento anémico datan de la época monolítica de la aplicación de escritorio o servidor. Recuerdo esa época y estoy de acuerdo en que los modelos anémicos son extraños. Construí una gran aplicación de comercio de divisas monolítica y no había modelo, realmente, era horrible.
Con microservicios, los pequeños servicios con su rico comportamiento, son sin duda los modelos compostables y agregados dentro de un dominio. Por lo tanto, las implementaciones de microservicio pueden no requerir más DDD. La aplicación de microservicio puede ser el dominio.
Un microservicio de órdenes puede tener muy pocas funciones, expresadas como recursos RESTful o vía SOAP o lo que sea. El código de microservicio de pedidos puede ser extremadamente simple.
Un servicio único (micro) más grande y monolítico, especialmente uno que lo mantenga como modelo en RAM, puede beneficiarse con DDD.
Los modelos de dominio anémico son importantes para ORM y fáciles de transferir a través de redes (el alma de todas las aplicaciones comerciales), pero OO es muy importante para encapsular y simplificar las partes ''transaccionales / de manejo'' de su código.
Por lo tanto, lo importante es ser capaz de identificar y convertir de un mundo a otro.
El nombre Anemic modela algo así como AnemicUser, o UserDAO, etc. por lo que los desarrolladores saben que hay una mejor clase para usar, luego tienen un constructor apropiado para la clase none Anemic
User(AnemicUser au)
y método de adaptador para crear la clase anémica para el transporte / persistencia
User::ToAnemicUser()
Intente utilizar el usuario anémico none en todas partes fuera del transporte / persistencia
Mi punto de vista es este:
Modelo de dominio anémico = tablas de base de datos mapeadas a objetos (solo valores de campo, sin comportamiento real)
Modelo de dominio enriquecido = una colección de objetos que exponen el comportamiento
Si desea crear una aplicación CRUD simple, tal vez un modelo anémico con un marco MVC clásico sea suficiente. Pero si desea implementar algún tipo de lógica, el modelo anémico significa que no hará programación orientada a objetos.
* Tenga en cuenta que el comportamiento del objeto no tiene nada que ver con la persistencia. Una capa diferente (Data Mappers, Repositorios, etc.) es responsable de persistir en los objetos de dominio.
Una de las ventajas de las clases de dominio enriquecidas es que puede invocar su comportamiento (métodos) cada vez que tenga referencia al objeto en cualquier capa. Además, tiendes a escribir métodos pequeños y distribuidos que colaboran juntos. En las clases de dominio anémico, tiende a escribir métodos de procedimiento gordo (en la capa de servicio) que generalmente se manejan por caso de uso. Por lo general, son menos fáciles de mantener en comparación con las clases de dominio enriquecido.
Un ejemplo de clases de dominio con comportamientos:
class Order {
String number
List<OrderItem> items
ItemList bonus
Delivery delivery
void addItem(Item item) { // add bonus if necessary }
ItemList needToDeliver() { // items + bonus }
void deliver() {
delivery = new Delivery()
delivery.items = needToDeliver()
}
}
El método needToDeliver()
devolverá la lista de elementos que deben entregarse, incluida la bonificación. Se puede llamar dentro de la clase, desde otra clase relacionada, o desde otra capa. Por ejemplo, si pasa Order
para ver, puede usar needToDeliver()
del Order
seleccionado para mostrar la lista de elementos que debe confirmar el usuario antes de hacer clic en el botón Guardar para conservar el Order
.
Respondiendo al comentario
Así es como uso la clase de dominio del controlador:
def save = {
Order order = new Order()
order.addItem(new Item())
order.addItem(new Item())
repository.create(order)
}
La creación de Order
y su LineItem
está en una transacción. Si no se puede crear uno de LineItem
, no se creará ningún Order
.
Tiendo a tener un método que represente una sola transacción, como por ejemplo:
def deliver = {
Order order = repository.findOrderByNumber(''ORDER-1'')
order.deliver()
// save order if necessary
}
Cualquier cosa dentro de deliver()
se ejecutará como una sola transacción. Si necesito ejecutar muchos métodos no relacionados en una sola transacción, crearía una clase de servicio.
Para evitar la excepción de carga diferida, uso JPA 2.1 named entity graph. Por ejemplo, en el controlador para la pantalla de entrega, puedo crear un método para cargar el atributo de delivery
e ignorar la bonus
, como repository.findOrderByNumberFetchDelivery()
. En la pantalla de bonificación, invoco otro método que cargue el atributo de bonus
e ignore la delivery
, como repository.findOrderByNumberFetchBonus()
. Esto requiere una diciplina ya que todavía no puedo llamar a deliver()
dentro de la pantalla de bonificación.
La diferencia es que un modelo anémico separa la lógica de los datos. La lógica a menudo se ubica en las clases denominadas **Service
, **Util
, **Manager
, **Helper
etc. Estas clases implementan la lógica de interpretación de datos y, por lo tanto, toman el modelo de datos como argumento. P.ej
public BigDecimal calculateTotal(Order order){
...
}
mientras que el enfoque de dominio enriquecido invierte esto al colocar la lógica de interpretación de datos en el modelo de dominio enriquecido. Por lo tanto, junta la lógica y los datos, y un modelo de dominio enriquecido se vería así:
order.getTotal();
Esto tiene un gran impacto en la consistencia del objeto. Dado que la lógica de interpretación de datos envuelve los datos (solo se puede acceder a los datos a través de métodos de objetos), los métodos pueden reaccionar a los cambios de estado de otros datos -> Esto es lo que llamamos comportamiento.
En un modelo anémico, los modelos de datos no pueden garantizar que estén en un estado legal mientras que en un modelo de dominio enriquecido puedan. Un modelo de dominio enriquecido aplica principios de OO como la encapsulación, la ocultación de información y la unión de datos y lógica, por lo que un modelo anémico es un patrón anti desde la perspectiva de OO.
Para una visión más profunda, eche un vistazo a mi blog https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/