DDD: design de fábrica de modelo de domínio

8

Estou tentando entender como e onde implementar fábricas de modelos de domínio. Incluí meu Companyagregado como uma demonstração de como o fiz.

Eu incluí minhas decisões de design no final - eu gostaria de receber comentários, sugestões e críticas sobre esses pontos.

O Companymodelo de domínio:

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;
    }
}

A CompanyFactoryfábrica de domínio:

Essa classe é usada para garantir que regras de negócios e invariantes não sejam violados ao criar novas instâncias de modelos de domínio. Ele residiria na camada de domínio.

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);
    }
}

O CompanyMappermapeador de entidades:

Essa classe é usada para mapear entre modelos de domínio avançado e entidades de dados do Entity Framework. Ele residiria em camadas de infraestrutura.

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. O Companyconstrutor é declarado como internal.
    Razão: Somente a fábrica deve chamar esse construtor. internalgarante que nenhuma outra camada possa instanciar (as camadas são separadas por projetos do VS).

  2. O CompanyFactory.CreateNew(string name)método seria usado ao criar uma nova empresa no sistema.
    Razão: Como ainda não teria sido persistida, uma nova identidade exclusiva precisará ser gerada para ela (usando o IIdentityFactory).

  3. O CompanyFactory.CreateExisting(int id, string name)método será usado pelo CompanyRepositoryao recuperar itens do banco de dados.
    Motivo: o modelo já teria identidade, portanto, isso precisaria ser fornecido à fábrica.

  4. O CompanyMapper.MapFrom(CompanyTable dataEntity)será usado pelo CompanyRepositoryao recuperar dados da persistência.
    Razão: Aqui, as entidades de dados do Entity Framework precisam ser mapeadas nos modelos de domínio. O CompanyFactoryserá usado para criar o modelo de domínio para garantir que as regras de negócios sejam atendidas.

  5. O CompanyMapper.MapFrom(Company domainEntity)será usado CompanyRepositoryao adicionar ou atualizar modelos para persistência.
    Motivo: Os modelos de domínio precisam ser mapeados diretamente nas propriedades da entidade de dados, para que o Entity Framework possa reconhecer quais alterações devem ser feitas no banco de dados.

obrigado

Dave New
fonte

Respostas:

2

Há uma coisa que eu não gosto no seu design. E isso é fato: você terá 3 classes adicionais para cada raiz agregada (Factory, Mapper, Repository) com código semi-duplicado na forma de leitura e configuração de propriedades em todos os lugares. Isso será problemático quando você adicionar e remover propriedades, pois esquecer de alterar um único método pode causar um erro. E quanto mais complexa é a entidade do domínio, mais esse código duplicado é. Não quero ver CompanyFactory.CreateExistingquando a empresa possui 10 propriedades e 2 entidades no total.

Segunda coisa que eu poderia reclamar é o IdentityFactory. No DDD, a identidade deve estar relacionada ao domínio; nesse caso, você a define com algum valor calculado. Ou é transparente; nesse caso, você pode fazer com que o DB cuide dele. Adicionar algum tipo de fábrica para identidade é uma superengenharia da OMI.

Eu também não gosto do mapeamento. Embora eu concorde que talvez não seja possível usar o EntityFramework diretamente, você pode pelo menos tentar. Ultimamente, a EF está melhorando com o POCO e você pode se surpreender com a sua força. Mas ainda não é NHibernate. Se você realmente deseja criar modelos separados, tente pensar em usar alguma biblioteca de mapeamento automático.

É bom que você esteja demonstrando o design em uma classe simples, mas tente também demonstrar ou pelo menos imaginar como o design funcionará com dezenas de agregados com muitas propriedades e entidades. Além disso, escreva um código que usará esse design. Você pode perceber que, embora o design pareça bom por dentro, é complicado usá-lo por fora. Além disso, ninguém aqui lhe dirá se o design é apropriado para sua equipe e seu domínio.

Eufórico
fonte
Obrigado pela sua resposta. Nós descartamos o EF POCO como modelos de domínio, pois introduz restrições sobre como projetamos nossos modelos. Poderíamos usar uma biblioteca de automação, mas isso só seria aplicável ao mapear para entidades de dados EF e não para modelos de domínio. Razão: o mapeamento para um modelo de domínio deve usar uma fábrica, não deveria? Caso contrário, você corre o risco de haver modelos de domínio inválidos flutuando em seu sistema. Eu concordo que existe muito "código de canalização". Adicionar novos agregados, ou mesmo adicionar propriedades a entidades existentes, exige bastante código de andaime.
31514 Dave
1
+1. Concordo especialmente sobre o "Mapper" - no meu entender, um bom ORM deve fazer a distinção entre "classes de domínio" e "classes de entidade" desnecessárias. Você tem uma sugestão para uma alternativa ao CompanyFramework.CreateExistingconstruto? Uma coisa que não vejo como fraqueza é a IIdentityFactory. Se os IDs são gerados a partir de um banco de dados, você deve ter uma mecânica para zombar disso, para permitir testes de unidade com um banco de dados.
Doc Brown
DocBrown é um local a respeito IIdentityFactory. No DDD, as entidades precisam ter identidade desde o momento da criação. Infelizmente, não temos o luxo de poder usar Guidtipos. Temos que gerar a int Idspartir do banco de dados antes que a persistência ocorra. Outro motivo para uma fábrica.
Dave New
1
@DocBrown A alternativa ao CompanyFramework.CreateExisting é um bom ORM. O que acontece com os IDs é que, a menos que o ID seja uma questão de domínio, nesse caso, você o gera de alguma maneira, não deve haver IDs. Os IDs são para o DB manter informações sobre relações, mas no DDD, isso deve ser tratado por referências a objetos. Nesse caso, não há necessidade de gerá-lo antes de ser salvo no DB.
eufórico