Compartir a través de


Seedwork (clases base reutilizables e interfaces para el modelo de dominio)

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.

Miniatura de la portada del libro electrónico 'Arquitectura de microservicios de .NET para aplicaciones .NET contenedorizadas'.

La carpeta de solución contiene una carpeta SeedWork . Esta carpeta contiene clases base personalizadas que puede usar como base para las entidades de dominio y los objetos de valor. Use estas clases base para que no tenga código redundante en la clase de objeto de cada dominio. La carpeta de estos tipos de clases se denomina SeedWork y no es algo parecido a Framework. Se denomina SeedWork porque la carpeta contiene solo un pequeño subconjunto de clases reutilizables que realmente no se pueden considerar un marco. Seedwork es un término introducido por Michael Feathers y popularizado por Martin Fowler, pero también podría nombrar esa carpeta Common, SharedKernel o similar.

La figura 7-12 muestra las clases que forman la infraestructura básica del modelo de dominio en el microservicio de pedidos. Tiene algunas clases base personalizadas, como Entity, ValueObjecty Enumeration, además de algunas interfaces. Estas interfaces (IRepository y IUnitOfWork) informan al nivel de infraestructura sobre lo que debe implementarse. Estas interfaces también se usan mediante la inserción de dependencias del nivel de aplicación.

Captura de pantalla de las clases contenidas en la carpeta SeedWork.

Contenido detallado de la carpeta SeedWork, que contiene clases e interfaces base: Entity.cs, Enumeration.cs, IAggregateRoot.cs, IRepository.cs, IUnitOfWork.cs y ValueObject.cs.

Figura 7-12. Un conjunto de ejemplos de clases e interfaces base del modelo de dominio "seedwork"

Este es el tipo de reutilización 'copy-paste' que muchos desarrolladores comparten entre proyectos, no un marco formal. Puede tener seedworks en cualquier nivel o biblioteca. Sin embargo, si el conjunto de clases e interfaces es lo suficientemente grande, es posible que quiera crear una sola biblioteca de clases.

La clase base de entidad personalizada

El código siguiente es un ejemplo de una clase base de entidad en la que puede colocar código que se puede usar de la misma manera por cualquier entidad de dominio, como el identificador de entidad, los operadores de igualdad, una lista de eventos de dominio por entidad, etc.

// COMPATIBLE WITH ENTITY FRAMEWORK CORE (1.1 and later)
public abstract class Entity
{
    int? _requestedHashCode;
    int _Id;
    private List<INotification> _domainEvents;
    public virtual int Id
    {
        get
        {
            return _Id;
        }
        protected set
        {
            _Id = value;
        }
    }

    public List<INotification> DomainEvents => _domainEvents;
    public void AddDomainEvent(INotification eventItem)
    {
        _domainEvents = _domainEvents ?? new List<INotification>();
        _domainEvents.Add(eventItem);
    }
    public void RemoveDomainEvent(INotification eventItem)
    {
        if (_domainEvents is null) return;
        _domainEvents.Remove(eventItem);
    }

    public bool IsTransient()
    {
        return this.Id == default(Int32);
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity))
            return false;
        if (Object.ReferenceEquals(this, obj))
            return true;
        if (this.GetType() != obj.GetType())
            return false;
        Entity item = (Entity)obj;
        if (item.IsTransient() || this.IsTransient())
            return false;
        else
            return item.Id == this.Id;
    }

    public override int GetHashCode()
    {
        if (!IsTransient())
        {
            if (!_requestedHashCode.HasValue)
                _requestedHashCode = this.Id.GetHashCode() ^ 31;
            // XOR for random distribution. See:
            // https://dori-uw-1.kuma-moon.com/archive/blogs/ericlippert/guidelines-and-rules-for-gethashcode
            return _requestedHashCode.Value;
        }
        else
            return base.GetHashCode();
    }
    public static bool operator ==(Entity left, Entity right)
    {
        if (Object.Equals(left, null))
            return (Object.Equals(right, null));
        else
            return left.Equals(right);
    }
    public static bool operator !=(Entity left, Entity right)
    {
        return !(left == right);
    }
}

El código anterior que usa una lista de eventos de dominio por entidad se explicará en las secciones siguientes al centrarse en eventos de dominio.

Contratos de repositorio (interfaces) en la capa de modelo de dominio

Los contratos de repositorio son simplemente interfaces de .NET que expresan los requisitos de contrato de los repositorios que se usarán para cada agregado.

Los propios repositorios, con código de EF Core o cualquier otra dependencia y código de infraestructura (Linq, SQL, etc.), no deben implementarse dentro del modelo de dominio; los repositorios solo deben implementar las interfaces que defina en el modelo de dominio.

Un patrón relacionado con esta práctica (colocar las interfaces del repositorio en la capa de modelo de dominio) es el patrón de interfaz separada. Como explica Martin Fowler, "Usar interfaz separada para definir una interfaz en un paquete, pero implementarla en otra. De este modo, un cliente que necesita la dependencia a la interfaz puede no ser consciente de la implementación".

Si sigue el patrón de interfaz separada, la capa de aplicación (en este caso, el proyecto de API web para el microservicio) tiene una dependencia de los requisitos definidos en el modelo de dominio, pero no una dependencia directa a la capa de infraestructura o persistencia. Además, puede usar la inserción de dependencias para aislar la implementación, que se implementa en la capa de infraestructura o persistencia mediante repositorios.

Por ejemplo, en el ejemplo siguiente con la interfaz IOrderRepository se define qué operaciones tendrá que implementar la clase OrderRepository en el nivel de infraestructura. En la implementación actual de la aplicación, el código solo necesita agregar o actualizar pedidos a la base de datos, ya que las consultas se dividen siguiendo el enfoque simplificado de CQRS.

// Defined at IOrderRepository.cs
public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);

    void Update(Order order);

    Task<Order> GetAsync(int orderId);
}

// Defined at IRepository.cs (Part of the Domain Seedwork)
public interface IRepository<T> where T : IAggregateRoot
{
    IUnitOfWork UnitOfWork { get; }
}

Recursos adicionales