Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Sugerencia
Este contenido es un extracto del libro electrónico, ".NET Microservices Architecture for Containerized .NET Applications" (Arquitectura de microservicios de .NET para aplicaciones de .NET contenedorizadas), disponible en Documentación de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.
En la sección anterior, se explicaron los principios y patrones de diseño fundamentales para diseñar un modelo de dominio. Ahora es el momento de explorar posibles formas de implementar el modelo de dominio mediante .NET (código de C# sin formato) y EF Core. El modelo de dominio se compondrá simplemente de tu código. Tendrá solo los requisitos del modelo de EF Core, pero no las dependencias reales de EF. No debe tener dependencias estrictas ni referencias a EF Core ni a ningún otro ORM en su modelo de dominio.
Estructura del modelo de dominio en una biblioteca personalizada de .NET Standard
La organización de carpetas usada para la aplicación de referencia eShopOnContainers muestra el modelo DDD para la aplicación. Es posible que encuentre que una organización de carpetas diferente comunica con más claridad las opciones de diseño realizadas para la aplicación. Como puede ver en la figura 7-10, en el modelo de dominio de ordenación hay dos agregados, el agregado de pedido y el agregado del comprador. Cada agregado es un grupo de entidades de dominio y objetos de valor, aunque podría tener un agregado compuesto por una sola entidad de dominio (la raíz agregada o la entidad raíz).
La vista Explorador de soluciones del proyecto Ordering.Domain, que muestra la carpeta AggregatesModel que contiene las carpetas BuyerAggregate y OrderAggregate, cada una de las cuales contiene sus clases de entidad, archivos de objetos de valor, etc.
Figura 7-10. Estructura del modelo de dominio para el microservicio de ordenación en eShopOnContainers
Además, la capa de modelo de dominio incluye los contratos de repositorio (interfaces) que son los requisitos de infraestructura del modelo de dominio. En otras palabras, estas interfaces expresan qué repositorios y los métodos que debe implementar la capa de infraestructura. Es fundamental que la implementación de los repositorios se coloque fuera de la capa de modelo de dominio, en la biblioteca de capas de infraestructura, por lo que la capa de modelo de dominio no está "contaminada" por api o clases de tecnologías de infraestructura, como Entity Framework.
También puede ver una carpeta SeedWork que contiene clases base personalizadas que puede usar como base para las entidades de dominio y los objetos de valor, por lo que no tiene código redundante en la clase de objeto de cada dominio.
Estructuración de los agregados en una biblioteca personalizada de .NET Standard
Un agregado hace referencia a un clúster de objetos de dominio agrupados para que coincidan con la coherencia transaccional. Esos objetos podrían ser instancias de entidades (una de las cuales es la entidad raíz o raíz agregada) además de cualquier objeto de valor adicional.
La coherencia transaccional significa que se garantiza que un agregado sea coherente y actualizado al final de una acción empresarial. Por ejemplo, el agregado de pedidos del modelo de dominio de microservicios de eShopOnContainers está compuesto como se muestra en la Figura 7-11.
Una vista detallada de la carpeta OrderAggregate: Address.cs es un objeto de valor, IOrderRepository es una interfaz de repositorio, Order.cs es una raíz de agregado, OrderItem.cs es una entidad secundaria y OrderStatus.cs es una clase de enumeración.
Figura 7-11. Agregado Order en la solución de Visual Studio
Si abre cualquiera de los archivos en una carpeta de agregado, puede ver cómo se marca como una clase base personalizada o una interfaz, como entidad o objeto de valor, tal como se implementa en la carpeta SeedWork .
Implementación de entidades de dominio como clases POCO
Para implementar un modelo de dominio en .NET, cree clases POCO que implementen las entidades de dominio. En el ejemplo siguiente, la clase Order se define como una entidad y también como una raíz de agregado. Dado que la clase Order deriva de la clase base Entity, puede reutilizar el código común relacionado con las entidades. Tenga en cuenta que estas interfaces y clases base se definen en el proyecto de modelo de dominio, por lo que es el código, no el código de infraestructura de un ORM como EF.
// COMPATIBLE WITH ENTITY FRAMEWORK CORE 5.0
// Entity is a custom base class with the ID
public class Order : Entity, IAggregateRoot
{
private DateTime _orderDate;
public Address Address { get; private set; }
private int? _buyerId;
public OrderStatus OrderStatus { get; private set; }
private int _orderStatusId;
private string _description;
private int? _paymentMethodId;
private readonly List<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
public Order(string userId, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null)
{
_orderItems = new List<OrderItem>();
_buyerId = buyerId;
_paymentMethodId = paymentMethodId;
_orderStatusId = OrderStatus.Submitted.Id;
_orderDate = DateTime.UtcNow;
Address = address;
// ...Additional code ...
}
public void AddOrderItem(int productId, string productName,
decimal unitPrice, decimal discount,
string pictureUrl, int units = 1)
{
//...
// Domain rules/logic for adding the OrderItem to the order
// ...
var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
_orderItems.Add(orderItem);
}
// ...
// Additional methods with domain rules/logic related to the Order aggregate
// ...
}
Es importante tener en cuenta que se trata de una entidad de dominio implementada como una clase POCO. No tiene ninguna dependencia directa de Entity Framework Core ni de ningún otro marco de infraestructura. Esta implementación es como debería estar en DDD, solo el código de C# que implementa un modelo de dominio.
Además, la clase está decorada con una interfaz denominada IAggregateRoot. Esa interfaz es una interfaz vacía, a veces denominada interfaz de marcador, que se usa solo para indicar que esta clase de entidad también es una raíz de agregado.
A veces, una interfaz de marcador se considera un antipatrón; sin embargo, también es una manera limpia de marcar una clase, especialmente cuando esa interfaz podría estar evolucionando. Un atributo podría ser la otra opción para el marcador, pero es más rápido ver la clase base (Entity) junto a la interfaz IAggregate en lugar de colocar un marcador de atributo Aggregate encima de la clase. Es una cuestión de preferencias, en cualquier caso.
Tener una raíz de agregado significa que la mayoría del código relacionado con las reglas de coherencia y negocios de las entidades del agregado se deben implementar como métodos en la clase raíz de agregado Order (por ejemplo, AddOrderItem al agregar un objeto OrderItem al agregado). No debe crear ni actualizar objetos OrderItems de forma independiente o directamente; La clase AggregateRoot debe mantener el control y la coherencia de cualquier operación de actualización en sus entidades secundarias.
Encapsular datos en las entidades de dominio
Un problema común en los modelos de entidad es que exponen las propiedades de navegación de colecciones como tipos de lista accesibles públicamente. Esto permite que cualquier desarrollador de colaboradores manipule el contenido de estos tipos de colección, lo que puede omitir reglas de negocios importantes relacionadas con la colección, posiblemente dejando el objeto en un estado no válido. La solución a esto consiste en exponer el acceso de solo lectura a colecciones relacionadas y proporcionar explícitamente métodos que definen formas en las que los clientes pueden manipularlas.
En el código anterior, tenga en cuenta que muchos atributos son de solo lectura o privados y solo son actualizables por los métodos de clase, por lo que cualquier actualización consideravariantes de dominio empresarial y lógica especificada dentro de los métodos de clase.
Por ejemplo, siguiendo los patrones DDD, no debe hacer lo siguiente desde ningún método de controlador de comandos o clase de capa de aplicación (en realidad, debería ser imposible hacerlo):
// WRONG ACCORDING TO DDD PATTERNS – CODE AT THE APPLICATION LAYER OR
// COMMAND HANDLERS
// Code in command handler methods or Web API controllers
//... (WRONG) Some code with business logic out of the domain classes ...
OrderItem myNewOrderItem = new OrderItem(orderId, productId, productName,
pictureUrl, unitPrice, discount, units);
//... (WRONG) Accessing the OrderItems collection directly from the application layer // or command handlers
myOrder.OrderItems.Add(myNewOrderItem);
//...
En este caso, el método Add es puramente una operación para agregar datos, con acceso directo a la colección OrderItems. Por lo tanto, la mayoría de la lógica de dominio, las reglas o las validaciones relacionadas con esa operación con las entidades secundarias se distribuirán en la capa de aplicación (controladores de comandos y controladores de API web).
Si omite la raíz de agregado, esta no puede garantizar sus invariables, su validez ni su coherencia. Al final se acaba con código espagueti o un código de script transaccional.
Para seguir los patrones DDD, las entidades no deben tener establecedores públicos en ninguna propiedad de entidad. Los cambios en una entidad deben estar controlados por métodos explícitos con lenguaje ubicuo explícito sobre el cambio que realizan en la entidad.
Además, las colecciones dentro de la entidad (como los elementos de pedido) deben ser propiedades de solo lectura (el método AsReadOnly explicado más adelante). Debe ser capaz de actualizarla solo desde los métodos de la clase raíz de agregado o los métodos de entidad secundaria.
Como puede ver en el código de la raíz de agregado Order, todos los establecedores deben ser privados o al menos de solo lectura externamente para que cualquier operación en los datos de la entidad o sus entidades secundarias tenga que realizarse mediante métodos en la clase de entidad. Esto mantiene la coherencia de una manera controlada y orientada a objetos en lugar de implementar código de script transaccional.
El siguiente fragmento de código muestra la manera adecuada de codificar la tarea de agregar un objeto OrderItem al agregado Order.
// RIGHT ACCORDING TO DDD--CODE AT THE APPLICATION LAYER OR COMMAND HANDLERS
// The code in command handlers or WebAPI controllers, related only to application stuff
// There is NO code here related to OrderItem object's business logic
myOrder.AddOrderItem(productId, productName, pictureUrl, unitPrice, discount, units);
// The code related to OrderItem params validations or domain rules should
// be WITHIN the AddOrderItem method.
//...
En este fragmento de código, la mayoría de las validaciones o lógica relacionadas con la creación de un objeto OrderItem estará bajo el control de la raíz de agregado Order, en el método AddOrderItem, especialmente validaciones y lógica relacionadas con otros elementos del agregado. Por ejemplo, puede obtener el mismo elemento de producto como resultado de varias llamadas a AddOrderItem. En ese método, podría examinar los elementos de producto y consolidar los mismos elementos de producto en un único objeto OrderItem con varias unidades. Además, si hay diferentes cantidades de descuento, pero el identificador del producto es el mismo, es probable que aplique el mayor descuento. Este principio se aplica a cualquier otra lógica de dominio para el objeto OrderItem.
Además, la nueva operación OrderItem(params) también es controlada y realizada por el método AddOrderItem de la raíz de agregado Order. Por lo tanto, la mayoría de la lógica o las validaciones relacionadas con esa operación (especialmente todo lo que afecta a la coherencia entre otras entidades secundarias) estará en una única ubicación dentro de la raíz de agregado. Ese es el propósito final del patrón raíz agregado.
Cuando se usa Entity Framework Core 1.1 o posterior, se puede expresar mejor una entidad DDD porque permite la asignación a campos además de las propiedades. Esto resulta útil al proteger colecciones de entidades secundarias u objetos de valor. Con esta mejora, puede usar campos privados simples en lugar de propiedades y puede implementar cualquier actualización en la colección de campos en métodos públicos y proporcionar acceso de solo lectura a través del método AsReadOnly.
En DDD, se desea actualizar la entidad solo a través de los métodos dentro de la entidad (o el constructor) para controlar cualquier invariante y la consistencia de los datos, por lo que las propiedades solo se definen con un accesor get. Las propiedades están respaldadas por campos privados. A los miembros privados solo se puede acceder desde la clase. Sin embargo, hay una excepción: EF Core también debe establecer estos campos (por lo que puede devolver el objeto con los valores adecuados).
Asignación de propiedades con solo los descriptores de acceso get a los campos de la tabla de base de datos
La asignación de propiedades a columnas de tabla de base de datos no es responsabilidad de dominio, sino parte de la infraestructura y la capa de persistencia. Esto se menciona aquí para que conozca las nuevas funcionalidades de EF Core 1.1 o posterior relacionada con cómo puede modelar entidades. En la sección infraestructura y persistencia se explican detalles adicionales sobre este tema.
Cuando se usa EF Core 1.0 o posterior, en DbContext es necesario asignar las propiedades definidas únicamente con captadores a los campos reales de la tabla de base de datos. Esto se hace con el método HasField de la clase PropertyBuilder.
Mapear campos sin propiedades
Con la característica de EF Core 1.1 o posterior para asignar columnas a campos, también es posible no usar propiedades. En su lugar, solo puede asignar columnas de una tabla a campos. Un caso de uso común para esto es campos privados para un estado interno al que no es necesario tener acceso desde fuera de la entidad.
Por ejemplo, en el ejemplo de código OrderAggregate anterior, hay varios campos privados, como el _paymentMethodId
campo , que no tienen ninguna propiedad relacionada para un establecedor o captador. Ese campo también se puede calcular dentro de la lógica de negocios del pedido y usarse a partir de los métodos del pedido, pero también debe conservarse en la base de datos. Por lo tanto, en EF Core (desde la versión 1.1), hay una manera de asignar un campo sin una propiedad relacionada a una columna de la base de datos. Esto también se explica en la sección Capa de infraestructura de esta guía.
Recursos adicionales
Vaughn Vernon. Modelado de agregados con DDD y Entity Framework. Tenga en cuenta que esto no es Entity Framework Core.
https://kalele.io/blog-posts/modeling-aggregates-with-ddd-and-entity-framework/Julie Lerman. Puntos de datos: programación para un diseño guiado por el dominio: sugerencias para los desarrolladores enfocados en datos
https://learn.microsoft.com/archive/msdn-magazine/2013/august/data-points-coding-for-domain-driven-design-tips-for-data-focused-devsUdi Dahan. Cómo crear modelos de dominio totalmente encapsulados
https://udidahan.com/2008/02/29/how-to-create-fully-encapsulated-domain-models/Steve Smith. ¿Cuál es la diferencia entre un DTO y un POCO? \ https://ardalis.com/dto-or-poco/