DDD: Diseño de fábrica de modelo de dominio

8

Estoy tratando de entender cómo y dónde implementar fábricas de modelos de dominio. He incluido mi Companyagregado como una demostración de cómo lo hice.

Incluí mis decisiones de diseño al final; agradecería cualquier comentario, sugerencia o crítica sobre esos puntos.

El Companymodelo de dominio:

public class Company : DomainEntity, IAggregateRoot
{
    private string name;
    public string Name
    {
        get
        {
            return name;
        }
        private set
        {
            if (String.IsNullOrWhiteSpace(value))
            {
                throw new ArgumentOutOfRangeException("Company name cannot be an empty value");
            }

            name = value;
        }
    }

    internal Company(int id, string name)
    {
        Name = name;
    }
}

La CompanyFactoryfábrica de dominios:

Esta clase se utiliza para garantizar que no se infrinjan las reglas comerciales y las invariantes al crear nuevas instancias de modelos de dominio. Residiría en la capa de dominio.

public class CompanyFactory
{
    protected IIdentityFactory<int> IdentityFactory { get; set; }

    public CompanyFactory(IIdentityFactory<int> identityFactory)
    {
        IdentityFactory = identityFactory;
    }

    public Company CreateNew(string name)
    {
        var id = IdentityFactory.GenerateIdentity();

        return new Company(id, name);
    }

    public Company CreateExisting(int id, string name)
    {
        return new Company(id, name);
    }
}

El CompanyMappermapeador de entidades:

Esta clase se utiliza para asignar entre modelos de dominio enriquecido y entidades de datos de Entity Framework. Residiría en capas de infraestructura.

public class CompanyMapper : IEntityMapper<Company, CompanyTable>
{
    private CompanyFactory factory;

    public CompanyMapper(CompanyFactory companyFactory)
    {
        factory = companyFactory;
    }

    public Company MapFrom(CompanyTable dataEntity)
    {
        return DomainEntityFactory.CreateExisting(dataEntity.Id, dataEntity.Name);
    }

    public CompanyTable MapFrom(Company domainEntity)
    {
        return new CompanyTable()
        {
            Id = domainEntity.Id,
            Name = domainEntity.Name
        };
    }
}
  1. El Companyconstructor se declara como internal.
    Motivo: solo la fábrica debe llamar a este constructor. internalasegura que ninguna otra capa pueda instanciarlo (las capas están separadas por proyectos VS).

  2. El CompanyFactory.CreateNew(string name)método se usaría al crear una nueva compañía en el sistema.
    Motivo: dado que aún no se habría persistido, será necesario generar una nueva identidad única para ello (utilizando el IIdentityFactory).

  3. El CompanyFactory.CreateExisting(int id, string name)método será utilizado por CompanyRepositoryal recuperar elementos de la base de datos.
    Motivo: el modelo ya tendría identidad, por lo que debería suministrarse a la fábrica.

  4. El CompanyMapper.MapFrom(CompanyTable dataEntity)será utilizado por CompanyRepositoryal recuperar datos de la persistencia.
    Motivo: Aquí, las entidades de datos de Entity Framework deben asignarse a modelos de dominio. Se CompanyFactoryutilizará para crear el modelo de dominio para garantizar que se cumplan las reglas de negocio.

  5. El CompanyMapper.MapFrom(Company domainEntity)será utilizado por CompanyRepositoryal agregar o actualizar modelos a la persistencia.
    Motivo: los modelos de dominio deben asignarse directamente a las propiedades de la entidad de datos para que Entity Framework pueda reconocer qué cambios realizar en la base de datos.

Gracias

Dave New
fuente

Respuestas:

2

Hay una cosa que no me gusta de su diseño. Y ese es el hecho de que tendrá 3 clases adicionales para cada raíz agregada (Factory, Mapper, Repository) con código semi-duplicado en forma de lectura y configuración de propiedades en todas partes. Esto será problemático a medida que agregue y elimine propiedades, porque olvidarse de cambiar un solo método puede causar un error. Y cuanto más compleja es la entidad de dominio, más de este código duplicado es. No quiero ver CompanyFactory.CreateExistingcuándo la compañía tiene 10 propiedades y 2 entidades en conjunto.

La segunda cosa de la que me puedo quejar es de IdentityFactory. En DDD, la identidad debe estar relacionada con el dominio, en cuyo caso la establece en algún valor calculado. O es transparente, en cuyo caso puede hacer que DB se encargue de ello. Agregar algún tipo de fábrica para la identidad es un exceso de la OMI.

Tampoco me gusta el mapeo. Si bien estoy de acuerdo en que podría no ser posible usar EntityFramework directamente, al menos podría intentarlo. EF está mejorando con POCO últimamente y te sorprenderá lo poderoso que es. Pero todavía no es NHibernate. Si realmente desea crear modelos separados, intente utilizar alguna biblioteca de mapeo automático.

Es bueno que esté demostrando el diseño en una clase simple, pero intente también demostrar o al menos imaginar cómo funcionará el diseño con docenas de agregados que tienen muchas propiedades y entidades. Además, escriba un código que use este diseño. Puede darse cuenta de que si bien el diseño puede verse bien desde el interior, es engorroso usarlo desde el exterior. Además, nadie aquí le dirá si el diseño es apropiado para su equipo y su dominio.

Eufórico
fuente
Gracias por tu respuesta. Hemos descartado EF POCO como modelos de dominio, ya que introduce restricciones sobre cómo diseñamos nuestros modelos. Podríamos usar una biblioteca de mapeo automático, pero eso solo sería aplicable al mapear a entidades de datos EF y no a modelos de dominio. La razón es: la asignación a un modelo de dominio debería usar una fábrica, ¿no? De lo contrario, corre el riesgo de que haya modelos de dominio no válidos flotando en su sistema. Estoy de acuerdo en que hay mucho "código de tubería". Agregar nuevos agregados, o incluso agregar propiedades a entidades existentes, requiere bastante código de andamiaje.
Dave New
1
+1. Estoy especialmente de acuerdo con el "Mapeador": a mi entender, un buen ORM debería hacer innecesaria la distinción entre "clases de dominio" y "clases de entidad". ¿Tiene alguna sugerencia para una alternativa a la CompanyFramework.CreateExistingconstrucción? Una cosa que no veo como debilidad es el IIdentityFactory. Si las ID se generan a partir de una base de datos, debe tener una mecánica para burlarse de esto, para permitir pruebas unitarias con una base de datos.
Doc Brown
DocBrown es acertado al respecto IIdentityFactory. En DDD, las entidades necesitan tener identidad desde el momento de la creación. Desafortunadamente no tenemos el lujo de poder usar Guidtipos. Tenemos que generar int Idsdesde la base de datos antes de que ocurra la persistencia. Otra razón para una fábrica.
Dave New
1
@DocBrown La alternativa a CompanyFramework.CreateExisting es un buen ORM. Lo que ocurre con los ID es que, a menos que el ID sea un problema de dominio, en cuyo caso lo genera de alguna manera, no debería haber ID. Las ID son para que DB guarde información sobre las relaciones, pero en DDD, eso debería ser manejado por referencias de objeto. En cuyo caso, no es necesario generarlo antes de guardarlo en DB.
Eufórico