Devo usar o repositório no Objeto de Domínio ou enviar o Objeto de Domínio de volta à Camada de Serviço?

10

Eu vim de um mundo de scripts de transações e estou apenas começando a dar uma olhada no DDD. Não tenho certeza da maneira correta de integrar um design DDD à persistência do banco de dados. Isto é o que eu tenho:

Uma classe de serviço chamada OrganisationService cuja interface contém métodos para recuperar e salvar instâncias de objetos de domínio da Organização. A organização é uma raiz agregada e possui outros dados relacionados a ela: Membros e licenças. O primeiro DBContext do banco de dados EF6 é usado no OrganisationService para recuperar entidades do OrganisationDB e as entidades MemberDB e LicenseDB relacionadas. Todos eles são transformados em seus equivalentes de classe de objeto de domínio quando recuperados pelo OrganisationService e carregados no objeto de domínio da Organização. Este objeto tem a seguinte aparência:

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }
}

Não estou usando o padrão Repository no OrganisationService ... Estou usando o próprio EF como repositório, pois parece que o EF6 tornou o repositório redundante agora.

Nesse ponto do design, o objeto de domínio da organização é anêmico: parece com a classe de organização EF POCO. A classe OrganisationService se parece muito com um Repositório!

Agora eu preciso começar a adicionar lógica. Essa lógica inclui o gerenciamento de licenças e membros de organizações. Agora, nos dias de script de transação, eu adicionava métodos ao OrganisationService para manipular essas operações e chamava um Repositório para interagir com o DB, mas com o DDD acredito que essa lógica deve ser encapsulada no próprio objeto de domínio da Organização ...

É aqui que não tenho certeza do que devo fazer: precisarei persistir esses dados de volta ao banco de dados como parte da lógica. Isso significa que devo usar o DbContext no objeto de domínio da organização para fazer isso? O uso do repositório / EF dentro do objeto de domínio é uma prática ruim? Em caso afirmativo, a que lugar pertence essa persistência?

public class Organisation
{
    public IList<Member> Members { get; set; }
    public IList<License> Licenses { get; set; }

    public void AddLicensesToOrganisation(IList<License> licensesToAdd)
    {
        // Do validation/other stuff/

        // And now Save to the DB???
        using(var context = new EFContext())
        {
            context.Licenses.Add(...
        }

        // Add to the Licenses collection in Memory
        Licenses.AddRange(licensesToAdd);
    }
}

Em vez disso, devo apenas alterar o objeto de domínio da organização na memória e enviá-lo de volta ao OrganisationService para persistência? Então, eu tenho que rastrear o que realmente mudou no objeto (que é o que a EF faz em seus próprios POCOs! Estou começando a achar que a EF não é apenas uma substituição de repositório, mas também pode ser a camada de domínio!)

Qualquer orientação aqui é apreciada.

MrLane
fonte

Respostas:

2

Você pode adotar qualquer uma das abordagens e fazer com que funcione bem - existem, é claro, prós e contras.

Definitivamente, o Entity Framework pretende difundir suas entidades de domínio. Funciona bem quando suas entidades de domínio e entidades de dados são as mesmas classes. É muito melhor se você puder confiar na EF para acompanhar as alterações e apenas ligar context.SaveChanges()quando terminar o seu trabalho transacional. Isso também significa que seus atributos de validação não precisam ser definidos duas vezes, uma vez em seus modelos de domínio e outras em entidades persistentes - coisas como [Required]ou [StringLength(x)]podem ser verificadas em sua lógica de negócios , permitindo capturar estados de dados inválidos antes de tentar faça uma transação de banco de dados e obtenha umaEntityValidationException. Por fim, é rápido o código - você não precisa escrever uma camada ou repositório de mapeamento, mas pode trabalhar diretamente com o contexto EF. Já é um repositório e uma unidade de trabalho, para que camadas extras de abstração não realizem nada.

Uma desvantagem de combinar seu domínio e entidades persistentes é que você acaba com vários [NotMapped]atributos espalhados por suas propriedades. Freqüentemente, você deseja propriedades específicas do domínio, que são filtros somente de obtenção em dados persistentes ou são definidas posteriormente na lógica de negócios e não permanecem novamente no banco de dados. Algumas vezes, você deseja expressar seus dados nos modelos de domínio de uma maneira que não funcione muito bem com um banco de dados - por exemplo, ao usar enumerações, o Entity os mapeará para uma intcoluna - mas talvez você queira mapear para legíveis por humanos string, para que você não precise consultar uma pesquisa ao examinar o banco de dados. Então você acaba com uma stringpropriedade que é mapeada, umenumpropriedade que não é (mas obtém a sequência e os mapas para o enum) e uma API que expõe os dois! Da mesma forma, se você deseja combinar tipos complexos (tabelas) entre contextos, pode acabar com uma mappedOtherTableId e uma ComplexTypepropriedade não mapeada , ambas expostas como públicas em sua classe. Isso pode ser confuso para alguém que não está familiarizado com o projeto e inchar desnecessariamente seus modelos de domínio.

Quanto mais complexo meu domínio / lógica de negócios, mais restritivo ou complicado eu acho que combina meu domínio e entidades persistentes. Para projetos com prazos curtos, ou que não expressem uma camada de negócios complexa, acho que o uso de entidades EF para ambos os fins é apropriado e não há necessidade de abstrair seu domínio da sua persistência. Para projetos que precisam da máxima facilidade de extensão ou que precisam expressar uma lógica muito complicada, acho melhor você separar os dois e lidar com a complexidade extra de persistência.

Um truque para evitar o problema de rastrear manualmente as alterações de sua entidade é armazenar o ID da entidade persistente correspondente no seu modelo de domínio. Isso pode ser preenchido automaticamente pela sua camada de mapeamento. Em seguida, quando precisar persistir uma alteração no EF, recupere a entidade persistente relevante antes de fazer qualquer mapeamento. Então, quando você mapeia as alterações, a EF as detecta automaticamente e você pode ligar context.SaveChanges()sem precisar rastreá-las manualmente.

public class OrganisationService
{
    public void PersistLicenses(IList<DomainLicenses> licenses) 
    {
        using (var context = new EFContext()) 
        {
            foreach (DomainLicense license in licenses) 
            {
                var persistedLicense = context.Licenses.Find(pl => pl.Id == license.PersistedId);
                MappingService.Map(persistedLicense, license); //Right-left mapping
                context.Update(persistedLicense);
            }
            context.SaveChanges();
        }
    }
}
NWard
fonte
Existe algum problema ao combinar abordagens diferentes com diferentes complexidades de dados? se você tiver algo que mapeie bem o DB, use EF diretamente. Se você tem algo que não tem, introduza um mapeamento / abstração antes de usar o EF.
Euphoric
@Euphoric as diferenças de domínio e db podem ser tratadas no mapeamento. Não consigo pensar em um caso de uso em que adicionar o domínio duas vezes (não persistente e persistente) e manipular o rastreamento de alterações manualmente seja menos trabalhoso do que adicionar mapeamentos para casos especiais. Você pode me indicar uma? (Presumo usando NHibernate, talvez EF é mais limitada?)
Firo
Resposta muito útil, prática e informativa. Marcando como a resposta. Obrigado!
MrLane
3

Eu não conheço EF6, mas outro ORM lida com isso de forma transparente, para que você não precise adicionar explicitamente as licenças ao contexto, ele as detectará ao salvar as alterações. Portanto, o método será reduzido para

public void AddLicenses(IEnumerable<License> licensesToAdd)
{
    // Do validation/other stuff/
    // Add to the Licenses collection in Memory
    Licenses.AddRange(licensesToAdd);
}

e o código ao seu redor

var someOrg = Load(orgId);

someOrg.AddLicenses(someLicences);

SaveChanges();
Firo
fonte
Meus objetos de domínio não são as Entidades, portanto, adicioná-los à coleção de Licenças, que é apenas uma Lista, não será cortada. A questão não é tanto sobre a EF, mas é onde a persistência real ocorre. Toda persistência no EF deve ocorrer dentro de um escopo de contexto. A questão é onde isso pertence?
MrLane
2
entidades de domínio e entidades persistentes podem / devem ser as mesmas, caso contrário, você introduz outra camada de abstração, que 99% do tempo não agrega valor e o rastreamento de alterações é perdido.
Firo 23/07