No debate dos modelos de domínio Rich vs. Anêmico, a Internet está cheia de conselhos filosóficos, mas com poucos exemplos de autoridade. O objetivo desta pergunta é encontrar diretrizes definitivas e exemplos concretos de modelos adequados de Design Orientado a Domínios. (Idealmente em c #.)
Para um exemplo do mundo real, essa implementação do DDD parece estar errada:
Os modelos de domínio WorkItem abaixo são nada além de pacotes de propriedades, usados pelo Entity Framework para um banco de dados com código primeiro. Para Fowler, é anêmico .
A camada WorkItemService é aparentemente uma percepção errada comum dos Serviços de Domínio; Ele contém toda a lógica de comportamento / negócios do WorkItem. Por Yemelyanov e outros, é processual . (pág. 6)
Então, se o abaixo está errado, como posso fazer isso certo?
O comportamento, ou seja, AddStatusUpdate ou Checkout , deve pertencer à classe WorkItem correta?
Quais dependências o modelo WorkItem deve ter?
public class WorkItemService : IWorkItemService {
private IUnitOfWorkFactory _unitOfWorkFactory;
//using Unity for dependency injection
public WorkItemService(IUnitOfWorkFactory unitOfWorkFactory) {
_unitOfWorkFactory = unitOfWorkFactory;
}
public void AddStatusUpdate(int workItemId, int statusId) {
using (var unitOfWork = _unitOfWorkFactory.GetUnitOfWork<IWorkItemUnitOfWork>()) {
var workItemRepo = unitOfWork.WorkItemRepository;
var workItemStatusRepo = unitOfWork.WorkItemStatusRepository;
var workItem = workItemRepo.Read(wi => wi.Id == workItemId).FirstOrDefault();
if (workItem == null)
throw new ArgumentException(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 é complicado, porque é uma tentativa confusa, mas o comportamento do domínio era: atualizar status adicionando o novo status ao histórico do arquivo. Finalmente, concordo com as outras respostas, este pode ser tratado pelo CRUD.)
Atualizar
O @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 forneceu informações suficientes além do link. Eu tenho um rascunho das minhas anotações resumindo o vídeo na minha resposta abaixo. Por favor, sinta-se livre para comentar a resposta com quaisquer correções. O vídeo dura uma hora, mas vale 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, ainda não posso prometer que sei o "caminho certo" de fazê-lo. Linguagem onipresente, raízes agregadas e sua abordagem ao design orientado por comportamento são as valiosas contribuições da DDD para a indústria. A ignorância da persistência e a fonte de eventos causam confusão, e acho que uma filosofia assim impede a adoção mais ampla. Mas se eu tivesse que repetir esse código novamente, com o que aprendi, acho que seria algo assim:
Ainda agradeço qualquer resposta a esta postagem (muito ativa) que forneça qualquer código de práticas recomendadas para um modelo de domínio válido.
"I don't want to duplicate all my entities into DTOs simply because I don't need it and it violates DRY, and I also don't want my client application to take a dependency on EntityFramework.dll"
. "Entidades" no Entity Framework jargão não são os mesmos como "Entidades" como em "Modelo de Domínio"Respostas:
A resposta mais útil foi dada por Alexey Zimarev e obteve pelo menos 7 votos positivos antes que um moderador o movesse para um comentário abaixo da minha pergunta original ....
Sua resposta:
Fiz algumas anotações para resumir o vídeo, tanto para o benefício da minha equipe quanto para fornecer detalhes um pouco mais imediatos neste post. (O vídeo dura uma hora, mas vale a pena cada minuto, se você tiver tempo. Jimmy Bogard merece muito crédito por sua explicação.)
Por favor, sinta-se livre para comentar com outros pontos que você acha que devem ser incluídos, ou se você acha que alguma dessas notas está errada. Tentou citar diretamente ou parafrasear o máximo possível.
fonte
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
AddStatusUpdate
método 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 havia um requisito de que um item de trabalho específico possa ter apenas status específico ou que possa ter apenas status N, isso é lógica de domínio e deve fazer parte de um
WorkItem
ou deStatusHistory
um 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ão relevantes apenas 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 trata da manipulação de entidades a partir de dados externos, 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, isso é lógica de domínio.Um dos problemas da modelagem de domínio verdadeiro é o gerenciamento de requisitos complexos. E, como tal, seu verdadeiro poder e benefícios não podem ser exibidos em código simples. Você precisa de dezenas de entidades com muitos requisitos ao seu redor para realmente ver os benefícios. Novamente, seu exemplo é muito simples para o modelo de domínio brilhar de verdade.
Finalmente, uma coisa do AT que eu mencionaria é que seria realmente difícil persistir um modelo de domínio verdadeiro com design de OOP real usando o Entity Framework. Embora os ORMs tenham sido projetados com o mapeamento da verdadeira estrutura de POO para as relacionais, ainda existem muitos problemas, e o modelo relacional geralmente vaza para o modelo de POO. Mesmo com o nHibernate, que considero muito mais poderoso que o EF, isso pode ser um problema.
fonte
Sua suposição de que encapsular sua lógica de negócios associada ao WorkItem em um "serviço fat" é um antipadrão inerente que eu argumentaria que 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 Line of Business .NET incentivam uma abordagem em camadas transacional composta por 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 .NET, bem como componentes em diferentes pilhas de tecnologia ou em camadas físicas.
Um exemplo disso seria um serviço Web SOAP baseado em .NET que se comunica com um aplicativo cliente Silverlight que possui uma DLL contendo tipos de dados simples. Esse projeto de entidade do domínio pode ser incorporado a um assembly .NET ou Silverlight, onde os componentes Silverlight interessados que possuem essa DLL não serão expostos a comportamentos de objetos que podem depender de componentes disponíveis apenas para o serviço.
Independentemente da sua posição sobre esse debate, esse é o padrão adotado e aceito apresentado pela Microsoft e, na minha opinião profissional, não é uma abordagem errada, mas um modelo de objeto que define seu próprio comportamento também não é necessariamente um antipadrão. Se você avançar com esse design, é melhor perceber e entender algumas das limitações e pontos problemáticos que você pode enfrentar se precisar se integrar a outros componentes que precisam ver o seu modelo de domínio. Nesse caso específico, 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 certos métodos de comportamento.
fonte
Sei que essa pergunta é bastante antiga, então essa resposta é para a posteridade. Quero responder com um exemplo concreto em vez de um baseado na teoria.
Encapsule a "alteração do status do item de trabalho" na
WorkItem
classe da seguinte maneira:Agora sua
WorkItem
turma é responsável por se manter em um estado legal. A implementação é bem fraca, no entanto. O proprietário do produto deseja um histórico de todas as atualizações de status feitas noWorkItem
.Nós mudamos para algo assim:
A implementação mudou drasticamente, mas o responsável pela chamada do
ChangeStatus
método desconhece os detalhes subjacentes da implementação e não tem motivos para alterar a si próprio.Este é um exemplo de uma entidade de modelo de domínio avançado, IMHO.
fonte