Rich Domain Models - como, exatamente, o comportamento se encaixa?

80

No debate dos modelos de domínio Rich versus Anemic, a internet está cheia de conselhos filosóficos, mas poucos exemplos de autoridade. O objetivo desta questão é encontrar diretrizes definitivas e exemplos concretos de modelos apropriados de Design Dirigido por Domínio. (Idealmente em c #.)

Para um exemplo do mundo real, essa implementação do DDD parece estar errada:

Os modelos de domínio do WorkItem abaixo são apenas pacotes de propriedades, usados pelo Entity Framework para um banco de dados de código-primeiro. Por Fowler, é anêmica .

A camada WorkItemService é aparentemente uma percepção equivocada comum dos Serviços de Domínio; contém toda a lógica de comportamento / negócios do WorkItem. Por Yemelyanov e outros, é procedural . (pg. 6)

Então, se o abaixo está errado, como posso fazer certo?
O comportamento, ou seja, AddStatusUpdate ou Checkout , deve pertencer à classe WorkItem correta?
Quais dependências o modelo WorkItem deve ter?

publicclassWorkItemService:IWorkItemService{privateIUnitOfWorkFactory_unitOfWorkFactory;//usingUnityfordependencyinjectionpublicWorkItemService(IUnitOfWorkFactoryunitOfWorkFactory){_unitOfWorkFactory=unitOfWorkFactory;}publicvoidAddStatusUpdate(intworkItemId,intstatusId){using(varunitOfWork=_unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()){varworkItemRepo=unitOfWork.WorkItemRepository;varworkItemStatusRepo=unitOfWork.WorkItemStatusRepository;varworkItem=workItemRepo.Read(wi=>wi.Id==workItemId).FirstOrDefault();if(workItem==null)thrownewArgumentException(string.Format(@"The provided WorkItem Id '{0}' is not recognized", workItemId), "workItemId");

            var status = workItemStatusRepo.Read(s => s.Id == statusId).FirstOrDefault();
            if (status == null)
                throw new ArgumentException(string.Format(@"The provided Status Id '{0}' is not recognized", statusId), "statusId");

            workItem.StatusHistory.Add(status);

            workItemRepo.Update(workItem);
            unitOfWork.Save();
        }
    }
}

(Este exemplo foi simplificado para ser mais legível. O código definitivamente ainda é desajeitado, porque é uma tentativa confusa, mas o comportamento do domínio era: atualizar o status adicionando o novo status ao histórico de arquivamento. Finalmente eu concordo com o outro respostas, isso poderia ser tratado apenas pelo CRUD.

Atualizar

@AlexeyZimarev deu a melhor resposta, um vídeo perfeito sobre o assunto em C # por Jimmy Bogard, mas aparentemente foi movido para um comentário abaixo porque não deu informações suficientes além do link. Eu tenho um rascunho de minhas anotações resumindo o vídeo na minha resposta abaixo. Por favor, sinta-se à vontade para comentar a resposta com quaisquer correções. O vídeo tem uma hora de duração, mas vale muito a pena assistir.

Atualização - 2 anos depois

Eu acho que é um sinal da maturidade nascente do DDD que, mesmo depois de estudá-lo por 2 anos, eu ainda não posso prometer que conheço o "jeito certo" de fazê-lo. Linguagem ubíqua, raízes agregadas e sua abordagem ao design orientado a comportamento são valiosas contribuições da DDD para a indústria. A ignorância da persistência e a terceirização de eventos causam confusão, e acho que uma filosofia como essa impede a adoção mais ampla. Mas se eu tivesse que fazer esse código novamente, com o que aprendi, acho que seria algo assim:

Ainda saúdo qualquer resposta a esta publicação (muito ativa) que forneça qualquer código de práticas recomendadas para um modelo de domínio válido.

    
por RJB 06.10.2013 / 20:49
fonte

4 respostas

52

A resposta mais útil foi dada por Alexey Zimarev e conseguiu pelo menos 7 votos positivos antes que um moderador a movesse para um comentário abaixo da minha pergunta original ....

Sua resposta:

I would recommend you to watch Jimmy Bogard's NDC 2012 session "Crafting Wicked Domain Models" on Vimeo. He explains what rich domain should be and how to implement them in real life by having behaviour in your entities. Examples are very practical and all in C#.

http://vimeo.com/43598193

Eu tomei algumas notas para resumir o vídeo para o benefício da minha equipe e fornecer detalhes um pouco mais imediatos neste post. (O vídeo tem uma hora de duração, mas vale muito a pena se você tiver tempo. Jimmy Bogard merece muito crédito por sua explicação.)

  • "Para a maioria das aplicações ... não sabemos que elas serão complexas quando começarmos. Elas acabam se tornando assim."
    • A complexidade cresce naturalmente à medida que o código e os requisitos são adicionados. As aplicações podem começar de forma muito simples, como o CRUD, mas o comportamento / regras podem ser incorporados.
    • "O bom é que não precisamos começar de maneira complexa. Podemos começar com o modelo de domínio anêmico, que é apenas um pacote de propriedades, e com apenas técnicas de refatoração padrão podemos avançar para um verdadeiro modelo de domínio." li>
  • Modelos de domínio = objetos de negócios. Comportamento do domínio = regras de negócios.
  • O comportamento é geralmente oculto em um aplicativo - pode estar em PageLoad, Button1_Click ou frequentemente em classes auxiliares como 'FooManager' ou 'FooService'.
  • As regras de negócios que são separadas dos objetos de domínio "exigem que nos lembremos" dessas regras.
    • No meu exemplo pessoal acima, uma regra de negócios é WorkItem.StatusHistory.Add (). Não estamos apenas alterando o status, estamos arquivando para auditoria.
  • Comportamentos de domínio "eliminam erros em um aplicativo com muito mais facilidade do que apenas escrever vários testes". Os testes exigem que você saiba escrever esses testes. Os comportamentos do domínio oferecem-lhe os caminhos corretos para testar .
  • Os serviços de domínio são "classes auxiliares para coordenar atividades entre diferentes entidades de modelo de domínio".
    • Serviços de domínio! = comportamento do domínio. Entidades têm comportamento, serviços de domínio são apenas intermediários entre as entidades.
  • Objetos de domínio não devem ter a infraestrutura necessária (por exemplo, IOfferCalculatorService). O serviço de infraestrutura deve ser passado para o modelo de domínio que o utiliza.
  • Modelos de domínio devem se oferecer para lhe dizer o que podem fazer, e eles só devem ser capazes de fazer essas coisas.
  • As propriedades dos modelos de domínio devem ser protegidas com setters privados, para que apenas o modelo possa definir suas próprias propriedades, por meio de seus próprios comportamentos . Caso contrário, é "promíscuo".
  • Objetos de modelo de domínio anêmico, que são apenas pacotes de propriedades para um ORM, são apenas "um verniz fino - uma versão strongmente tipada sobre o banco de dados".
    • "Por mais fácil que seja obter uma linha de banco de dados em um objeto, é isso que temos."
    • 'A maioria dos modelos de objetos persistentes é apenas isso. O que diferencia um modelo de domínio anêmico de um aplicativo que realmente não tem comportamento é se um objeto tiver regras de negócios, mas essas regras não são encontradas em um modelo de domínio. '
  • "Para muitas aplicações, não há necessidade real de construir qualquer tipo de camada lógica de aplicativos de negócios reais, é apenas algo que pode falar com o banco de dados e talvez alguma maneira fácil de representar os dados que estão lá."
    • Em outras palavras, se tudo o que você está fazendo é o CRUD sem objetos de negócios especiais ou regras de comportamento, você não precisa de DDD.

Por favor, sinta-se livre para comentar com qualquer outro ponto que você achar que deva ser incluído, ou se você acha que alguma dessas notas está fora do alvo. Tentei citar diretamente ou parafrasear o máximo possível.

    
por 13.12.2013 / 04:22
fonte
6

Sua pergunta não pode ser respondida, porque seu exemplo está errado. Especificamente, porque não há comportamento. Pelo menos não na área do seu domínio. O exemplo do método AddStatusUpdate não é uma lógica de domínio, mas uma lógica que usa esse domínio. Esse tipo de lógica faz sentido estar dentro de algum tipo de serviço, que lida com solicitações externas.

Por exemplo, se houver um requisito de que um item de trabalho específico possa ter apenas status específicos ou que possa ter apenas N status, então isso é lógica de domínio e deve ser parte de WorkItem ou StatusHistory como método.

O motivo da sua confusão é porque você está tentando aplicar uma diretriz ao código que não precisa dela. Os modelos de domínio só são relevantes se você tiver muita lógica de domínio complexa. Por exemplo. lógica que funciona nas próprias entidades e decorre dos requisitos. Se o código é sobre a manipulação de entidades a partir de dados externos, então isso não é, muito provavelmente, uma lógica de domínio. Mas no momento em que você obtém muitos if s com base em quais dados e entidades você está trabalhando, então isso é lógica de domínio.

Um dos problemas da verdadeira modelagem de domínio é o gerenciamento de requisitos complexos. E como tal, seu verdadeiro poder e benefícios não podem ser exibidos em um código simples. Você precisa de dezenas de entidades com muitos requisitos em torno deles para realmente ver os benefícios. Novamente, seu exemplo é simples demais para que o modelo de domínio realmente brilhe.

Finalmente, alguma coisa do OT que eu mencionaria é que um verdadeiro modelo de domínio com design OOP real seria realmente difícil de persistir usando o Entity Framework. Embora os ORMs tenham sido projetados com o mapeamento da verdadeira estrutura OOP para os relacionais, ainda há muitos problemas, e o modelo relacional geralmente vaza para o modelo OOP. Mesmo com o nHibernate, que considero muito mais poderoso que o EF, isso pode ser um problema.

    
por 07.10.2013 / 08:13
fonte
5

Sua suposição de que encapsular sua lógica de negócios associada a WorkItem em um "serviço fat" é um anti-padrão inerente que eu diria não é necessariamente.

Independentemente de suas opiniões sobre o modelo de domínio anêmico, os padrões e práticas padrão típicos de um aplicativo .NET de Linha de Negócios incentivam uma abordagem em camadas transacional composta de vários componentes. Eles incentivam a separação da lógica de negócios do modelo de domínio especificamente para facilitar a comunicação de um modelo de domínio comum entre outros componentes do .NET, bem como componentes em diferentes pilhas de tecnologia ou em camadas físicas.

Um exemplo disso seria um serviço da Web SOAP baseado em .NET que se comunica com um aplicativo cliente do Silverlight que, por acaso, possui uma DLL contendo tipos de dados simples. Este projeto de entidade de domínio pode ser construído em um assembly .NET ou em um assembly do Silverlight, onde os componentes interessados do Silverlight que possuem essa DLL não serão expostos a comportamentos de objeto que podem ser dependentes de componentes disponíveis apenas para o serviço.

Independentemente da sua posição sobre este debate, este é o padrão adotado e aceito pela Microsoft e, na minha opinião profissional, não é uma abordagem errada, mas um modelo de objeto que define seu próprio comportamento não é necessariamente um antipadrão. ou. Se você seguir em frente com esse design, é melhor perceber e entender algumas das limitações e pontos problemáticos que você pode encontrar se precisar integrar-se a outros componentes que precisam ver seu modelo de domínio. Nesse caso em particular, talvez você queira que um tradutor converta seu modelo de domínio de estilo orientado a objetos em objetos de dados simples que não exponham determinados métodos de comportamento.

    
por 07.10.2013 / 02:27
fonte
4

Eu percebo que essa pergunta é bem antiga, então essa resposta é para a posteridade. Eu quero responder com um exemplo concreto em vez de um baseado na teoria.

Encapsule a "alteração do status do item de trabalho" na classe WorkItem da seguinte forma:

public SomeStatusUpdateType Status { get; private set; }

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Maybe we designed this badly at first ;-)
    Status = status;       
}

Agora, sua classe WorkItem é responsável por manter-se em um estado legal. A implementação é bem fraca, no entanto. O proprietário do produto quer um histórico de todas as atualizações de status feitas no WorkItem .

Nós mudamos para algo assim:

private ICollection<SomeStatusUpdateType> StatusUpdates { get; private set; }
public SomeStatusUpdateType Status => StatusUpdates.OrderByDescending(s => s.CreatedOn).FirstOrDefault();

public void ChangeStatus(SomeStatusUpdateType status)
{
    // Better...
    StatusUpdates.Add(status);       
}

A implementação mudou drasticamente, mas o chamador do método ChangeStatus não tem conhecimento dos detalhes subjacentes da implementação e não tem motivos para mudar a si mesmo.

Este é um exemplo de uma rica entidade de modelo de domínio, IMHO.

    
por 07.05.2018 / 18:45
fonte