Digamos que tenhamos um sistema de registro de tarefas, quando uma tarefa é registrada, o usuário especifica uma categoria e a tarefa assume o status 'Excelente'. Suponha, neste caso, que Categoria e Status tenham que ser implementados como entidades. Normalmente eu faria isso:
Camada de aplicação:
public class TaskService
{
//...
public void Add(Guid categoryId, string description)
{
var category = _categoryRepository.GetById(categoryId);
var status = _statusRepository.GetById(Constants.Status.OutstandingId);
var task = Task.Create(category, status, description);
_taskRepository.Save(task);
}
}
Entidade:
public class Task
{
//...
public static void Create(Category category, Status status, string description)
{
return new Task
{
Category = category,
Status = status,
Description = descrtiption
};
}
}
Faço-o assim porque sempre me dizem que as entidades não devem acessar os repositórios, mas faria muito mais sentido para mim se eu fizesse isso:
Entidade:
public class Task
{
//...
public static void Create(Category category, string description)
{
return new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
}
}
O repositório de status é injetado com segurança de qualquer maneira, portanto, não há dependência real, e isso me parece mais o fato de que é o domínio que está fazendo a decisão que uma tarefa tem como padrão pendente. A versão anterior parece que é o leigo do aplicativo que toma essa decisão. Qualquer um dos motivos pelos quais os contratos de repositório estão frequentemente no domínio, se isso não deveria ser uma possibilidade?
Aqui está um exemplo mais extremo, aqui o domínio decide a urgência:
Entidade:
public class Task
{
//...
public static void Create(Category category, string description)
{
var task = new Task
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
};
if(someCondition)
{
if(someValue > anotherValue)
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
task.Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
Não há como você desejar passar em todas as versões possíveis do Urgency e não deseja calcular essa lógica de negócios na camada de aplicativos; portanto, certamente essa seria a maneira mais apropriada.
Portanto, esse é um motivo válido para acessar repositórios do domínio?
EDIT: também pode ser o caso de métodos não estáticos:
public class Task
{
//...
public void Update(Category category, string description)
{
Category = category,
Status = _statusRepository.GetById(Constants.Status.OutstandingId),
Description = descrtiption
if(someCondition)
{
if(someValue > anotherValue)
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.UrgentId);
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.SemiUrgentId);
}
}
else
{
Urgency = _urgencyRepository.GetById
(Constants.Urgency.NotId);
}
return task;
}
}
fonte
Status = _statusRepository.GetById(Constants.Status.OutstandingId)
é uma regra de negócios , uma que você pode ler como "A empresa determina que o status inicial de todas as tarefas será excelente" e é por isso que essa linha de código não pertence a um repositório, cujas únicas preocupações são o gerenciamento de dados por meio de operações CRUD.Não sei se o seu exemplo de status é um código real ou aqui apenas para fins de demonstração, mas me parece estranho que você implemente o Status como uma entidade (para não mencionar uma raiz agregada) quando seu ID é uma constante definida no código -
Constants.Status.OutstandingId
. Isso não anula o objetivo dos status "dinâmicos" que você pode adicionar quantos quiser no banco de dados?Eu acrescentaria que, no seu caso, a construção de um
Task
(incluindo o trabalho de obter o status correto do StatusRepository, se necessário) pode merecer umTaskFactory
pouco, em vez de permanecer porTask
si só, já que é um conjunto não trivial de objetos.Mas :
Essa afirmação é imprecisa e simplista, na melhor das hipóteses, enganosa e perigosa, na pior das hipóteses.
É comumente aceito nas arquiteturas controladas por domínio que uma entidade não deve saber como se armazenar - esse é o princípio da ignorância da persistência. Portanto, nenhuma chamada para seu repositório se adiciona ao repositório. Deve saber como (e quando) armazenar outras entidades ? Novamente, essa responsabilidade parece pertencer a outro objeto - talvez um objeto que esteja ciente do contexto de execução e do progresso geral do caso de uso atual, como um serviço da camada de Aplicativo.
Uma entidade poderia usar um repositório para recuperar outra entidade ? 90% do tempo não deveria, uma vez que as entidades necessárias geralmente estão no escopo de seu agregado ou podem ser obtidas pela passagem de outros objetos. Mas há momentos em que não são. Se você adota uma estrutura hierárquica, por exemplo, as entidades geralmente precisam acessar todos os seus ancestrais, um neto em particular etc. como parte de seu comportamento intrínseco. Eles não têm uma referência direta a esses parentes remotos. Seria inconveniente passar esses parentes para eles como parâmetros da operação. Então, por que não usar um Repositório para obtê-los - desde que sejam raízes agregadas?
Existem alguns outros exemplos. O problema é que, às vezes, há um comportamento que você não pode colocar em um serviço de Domínio, pois ele parece se encaixar perfeitamente em uma entidade existente. E, no entanto, essa entidade precisa acessar um Repositório para hidratar uma raiz ou uma coleção de raízes que não podem ser passadas para ele.
Portanto, o acesso a um Repositório a partir de uma Entidade não é ruim por si só , ele pode assumir diferentes formas que resultam de uma variedade de decisões de design, que variam de catastróficas a aceitáveis.
fonte
Esse é um dos motivos pelos quais não uso Enums ou tabelas de pesquisa pura no meu domínio. Urgência e Status são estados e existe uma lógica associada a um estado que pertence diretamente ao estado (por exemplo, em quais estados posso fazer a transição para o estado atual). Além disso, ao registrar um estado como um valor puro, você perde informações como por quanto tempo a tarefa estava em um determinado estado. Eu represento status como uma hierarquia de classes assim. (Em c #)
A implementação do CompletedTaskStatus seria praticamente a mesma.
Há várias coisas a serem observadas aqui:
Eu protejo os construtores padrão. É assim que a estrutura pode chamá-lo ao extrair um objeto da persistência (o EntityFramework Code-first e o NHibernate usam proxies derivados dos objetos do seu domínio para fazer a mágica deles).
Muitos dos configuradores de propriedades são protegidos pelo mesmo motivo. Se eu quiser alterar a data final de um Intervalo, preciso chamar a função Interval.End () (isso faz parte do Design Orientado a Domínio, fornecendo operações significativas em vez de Objetos de Domínio Anêmicos).
Eu não o mostro aqui, mas a Tarefa também oculta os detalhes de como armazena seu status atual. Normalmente, tenho uma lista protegida de HistoricalStates que eu permito ao público consultar se estiver interessado. Caso contrário, eu exponho o estado atual como um getter que consulta HistoricalStates.Single (state.Duration.End == null).
A função TransitionTo é significativa porque pode conter lógica sobre quais estados são válidos para transição. Se você apenas tem um enum, essa lógica deve estar em outro lugar.
Felizmente, isso ajuda a entender um pouco melhor a abordagem DDD.
fonte
Eu tenho tentado resolver o mesmo problema há algum tempo, decidi que gostaria de poder chamar Task.UpdateTask () assim, embora eu prefira que seja específico do domínio, no seu caso, talvez eu o chame de Task.ChangeCategory (...) para indicar uma ação e não apenas CRUD.
Enfim, eu tentei o seu problema e vim com isso ... pegue meu bolo e coma também. A idéia é que ações ocorram na entidade, mas sem injeção de todas as dependências. Em vez disso, o trabalho é feito em métodos estáticos para que eles possam acessar o estado da entidade. A fábrica reúne tudo e normalmente terá tudo o que precisa para fazer o trabalho que a entidade precisa fazer. O código do cliente agora parece limpo e claro e sua entidade não depende de nenhuma injeção de repositório.
fonte