Este artigo foi traduzido por máquina.
Modelos de domínio
Empregando o padrão de modelo de domínio
Udi Dahan
Este artigo discute:
|
Este artigo utiliza as seguintes tecnologias: Padrão do modelo de domínio |
Neste artigo, passarmos pelo empregar motivos para (e não) o padrão de modelo de domínio, os benefícios que ela traz, bem como fornecer algumas dicas práticas em manter a solução geral tão simples quanto possível.
Conteúdo
O que É?
Motivos para não usar o modelo de domínio
Tecnologia
Motivos para usar o modelo de domínio
Cenários para não usar o modelo de domínio
Cenários para usar o modelo de domínio
Interações mais complexas
Regras de negócio Cutting entre
Eventos de domínio e seus chamadores
Eventos de domínio explícita
Capacidade de teste
Consultas e comandos
Mantendo a empresa no domínio
Concorrência
Localizando uma solução abrangente
Works citado
Se você tinha se deparar para mim há alguns anos e me perguntou se eu nunca usado o padrão de modelo de domínio, que poderia ter respondeu com um absoluto "Sim". Foi se minha compreensão do padrão. Eu tinha suporte a tecnologias que tornaram-lo a trabalhar.
Mas, eu teria sido completamente errado.
Minha compreensão evoluiu nos anos e tenha obtido um reconhecimento para como um aplicativo pode se beneficiar com alinhando próprio com os mesmos princípios controlado por domínio.
Neste artigo, vamos através de por que seria mesmo queremos considerar empregando o padrão de modelo de domínio (bem como por que não), os benefícios deveria fazer, como ele interage com outras partes de um aplicativo e os recursos que seria deseja ser fornecido pelo suporte a tecnologias e discuta algumas dicas práticas em manter a solução geral tão simples quanto possível.
O que É?
O autor do padrão de modelo de domínio, Martin Fowler, fornece essa definição (Fowler, 2003):
Um modelo de objeto do domínio que incorpora o comportamento e dados.
Para dizer a verdade, essa definição pode ser interpretada para caber praticamente qualquer parte do código — um motivo muito bom por que eu pensei que Estava usando o padrão quando na verdade, não estava sendo true para sua intenção original.
Vamos aprofundar mais profundo.
Motivos para não usar o modelo de domínio
O texto seguinte descrição original, originalmente, eu tinha queimado após esta passagem inofensiva, mas acontece que muitas decisões importantes hinge em Noções básicas sobre ele.
Como o comportamento da empresa é sujeitos muita alteração, é importante ser capaz de modificar, criar e testar essa camada facilmente. Como resultado, você desejará o mínimo de acoplamento do modelo de domínio a outras camadas no sistema.
Para um motivo para não fazer uso do padrão de modelo de domínio é se a empresa que é automatizar o seu software não alterará muito. É por isso não quer para dizer não é alterado em todos os — mas em vez disso, que as regras subjacentes ditando como comercial é feito não são muito dinâmicas. Embora outros fatores tecnológicos e ambientais podem mudar, que não é o contexto desse padrão.
Alguns exemplos disso incluem suporte a vários bancos de dados (como SQL Server e Oracle) ou várias tecnologias de interface do usuário (Windows, Web, Mobile e assim por diante). Se o comportamento da empresa não tenha alterado, eles não justificar o uso do padrão de modelo de domínio. Que é não dizer um pode não obter uma ótima lidar de valor do uso de tecnologias que suporte o padrão, mas é necessário para ser honesto sobre quais regras, quebrar e por quê.
Motivos para usar o modelo de domínio
Nesses casos onde o comportamento da empresa é, sujeito muita alteração, ter um modelo de domínio irá diminuir o custo total dessas alterações. Ter todos os o comportamento da empresa provavelmente alterar encapsulado em uma única parte do nosso diminui de software do tempo que é necessário realizar uma alteração porque ele será todas ser realizado em um local. Ao isolar esse código tanto quanto possível, diminui a probabilidade das alterações em outros locais fazendo com que quebra, diminuindo, portanto, o tempo para estabilizar o sistema.
Cenários para não usar o modelo de domínio
Isso nos leva ao campo no. 1 fallacy mais comuns sobre como usar modelos de domínio. Eu me foi o culpado de fazer essa pressuposição false para um número de anos e consulte agora onde ele levou me astray.
Fallacy: Qualquer modelo de objeto persistente é um modelo de domínio
Em primeiro lugar, um modelo de objeto persistente não encapsular inerentemente todos os comportamentos da empresa que provavelmente mudarão. Segundo, um modelo de objeto persistente pode incluem funcionalidade que não é provável que alterar.
A natureza deste fallacy é semelhante a informando que qualquer chave de fenda é um martelo. Enquanto você pode martelo (tentar) em pregos com uma chave de fenda, não será muito eficaz fazê-lo dessa forma. Um dificilmente poderia dizer que estavam sendo true para o padrão de martelo.
Colocar essa configuração novamente concretas amor e cenários que todos nós sabemos, vamos considerar o requisito sempre presente endereço de email do usuário deve ser exclusivo.
Por um tempo, pensei que era o objetivo de ter um modelo de domínio que requisitos desta poderiam ser implementados existe. No entanto, quando consideramos a orientação sobre como capturar os comportamentos de negócios que estão sujeitos a alterações que o modelo de domínio, podemos ver que esse requisito não couber que mold. É provável que esse requisito nunca será alterado.
Portanto, optar por implementar esse requisito na parte do sistema que é sobre encapsular as partes da empresa volátil faz pouco sentido, pode ser difícil implementar e não pode executar que bem. Colocar todos os endereços de email na memória provavelmente teria você travado pela polícia de desempenho. Mesmo tendo o modelo de domínio chamada algum serviço, que chama o banco de dados, para ver se o endereço de email é desnecessário. Uma restrição exclusiva no banco de dados seria suficiente.
Este pensar pragmático é muito no núcleo do padrão de modelo de domínio e design controlado por domínio e é o que irá manter a simplicidade, mesmo que nós lidar com requisitos mais atrativo que exclusividade de email simples.
Cenários para usar o modelo de domínio
Regras comerciais que indicam quando certas ações permitidas são boas candidatas para sendo implementado em um modelo de domínio.
Por exemplo, em um sistema de comércio uma regra declarando que um cliente pode ter mais de r$ 1.000 em pedidos seria provavelmente pertencer no modelo de domínio. Observe que essa regra envolve várias entidades e precisa ser avaliada em uma variedade de casos de uso.
Claro que, em um modelo de determinado domínio seria esperamos ver várias desses tipos de regras, inclusive casos onde algumas regras substituem outras pessoas. Em nosso exemplo acima, se o usuário efetuando uma alteração em uma ordem é o gerente da conta gerente para a conta do cliente pertence a, a regra anterior não se aplica.
Pode parecer desnecessária para gastar tempo sai interface por meio de quais regras precisam aplicar no quais casos de uso e rapidamente surgir com uma lista de entidades e relações entre eles — eschewing o "grande design antecipadamente" que práticas agile rail contra. No entanto, as regras de negócios e casos de uso são os motivos muito que nós estiver aplicando o padrão de modelo de domínio em primeiro lugar.
Ao solucionar esses tipos de problemas no passado, eu não pensaram duas vezes e poderia ter criado rapidamente uma classe de cliente com uma coleção de objetos Order. Mas nossas regras até indicam apenas uma única propriedade no cliente em vez disso — UnpaidOrdersAmount. Poderíamos passar por várias regras e nunca realmente executar em algo que claramente apontava para uma coleção de pedidos. Nesse caso, maxim ágil "você não tiver gonna necessário" (YAGNI) deve impedir nos criação dessa coleção.
Ao observar como manter este gráfico de objetos, pode achamos prático adicionar suporte objetos e coleções abaixo. Precisamos claramente diferenciar entre os detalhes da implementação e comportamentos de negócios principal que são de responsabilidade do modelo de domínio.
Interações mais complexas
Considere o requisito de que quando um cliente fez mais de $ 10.000 que vale a pena de compras com nossa empresa, eles se tornam um cliente "preferencial". Quando um cliente torna-se um cliente preferencial, o sistema deve enviá-los um email notificando-os dos benefícios de nosso programa cliente preferencial.
O que torna diferente do requisito de endereço de email exclusivo descrito anteriormente neste cenário é que essa interação necessariamente envolvem o modelo de domínio. Uma opção é implementar essa lógica no código que chama o modelo de domínio da seguinte maneira:
public void SubmitOrder(OrderData data)
{
bool wasPreferredBefore = customer.IsPreferred;
// call the domain model for regular order submit logic
if (customer.IsPreferred && !wasPreferredBefore)
// send email
}
Uma armadilha que evita o código de exemplo é a de verificar o valor que constitui quando um cliente torna-se preferencial. Que lógica adequadamente é confiado ao modelo de domínio.
Infelizmente, podemos ver que o código de exemplo é responsável se tornam inflada medida mais regras são adicionadas ao sistema que precisa ser avaliada quando ordens são enviados. Mesmo se fôssemos Mova este código para o modelo de domínio, nós ainda poderia ser deixados com os seguintes problemas.
Regras de negócio Cutting entre
Pode haver outros casos de uso que resultam em cliente preferencial se tornando cada vez. Nós não precise duplicar a lógica em vários locais (se ele está no modelo de domínio ou não), especialmente porque refatoração para um método extraído ainda exigiria captura de estado preferencial original do cliente.
Pode ainda precisamos ir até o momento como para incluir algum tipo de método de (AOP) programação orientada a interceptação/aspecto para evitar a duplicação.
Parece que é melhor seria repensar nossa abordagem antes de cortar sozinhos em Navalha de razor. Examinando nossos requisitos novamente pode fornecer alguns direção.
Quando um cliente tornou uma [algo] do sistema [faça algo].
Parece estar faltando uma boa maneira de representar padrão este requisito, embora isso parecer algo que pudesse lidar com um modelo com base em eventos bem. Dessa forma, se é necessário que mais o "deve fazer algo" parte, pode implementar que facilmente como um manipulador de eventos adicionais.
Eventos de domínio e seus chamadores
Eventos de domínio são a maneira que representam a primeira parte do requisito descrito explicitamente:
Quando um [algo] tornou uma [algo]...
Enquanto podemos implementar esses eventos em entidades próprios, poderá ser vantajoso para que estar acessível no nível do domínio inteiro. Vamos comparar como a camada de serviço se comporta em ambos os casos:
public void SubmitOrder(OrderData data)
{
var customer = GetCustomer(data.CustomerId);
var sendEmail = delegate { /* send email */ };
customer.BecamePreferred += sendEmail;
// call the domain model for the rest of the regular order submit logic
customer.BecamePreferred -= sendEmail; // to avoid leaking memory
}
Embora seja bom não precisar verificar o estado antes e após a chamada, nós já negociado essa complexidade com o de inscrição e removendo inscrições do evento domínio. Além disso, código que chama o modelo de domínio em qualquer caso de uso não deve ter que saber se um cliente pode se tornar preferencial não existe. Quando o código está interagindo diretamente com o cliente, isso não é como um grande problema. Mas considere que ao submeter um pedido, pode trazer o estoque de um dos produtos ordem abaixo de seu limite de reabastecimento — não desejamos manipular esse evento no código, muito.
Seria melhor se poderíamos ter cada evento ser manipulado por uma classe dedicada que não lida com qualquer caso de uso específico, mas pode ser ativada genericamente conforme necessário em todos os casos de uso. Eis como uma classe como seria:
public class CustomerBecamePreferredHandler : Handles<CustomerBecamePreferred>
{
public void Handle(CustomerBecamePreferred args)
{
// send email to args.Customer
}
}
Falaremos sobre que tipo de infra-estrutura fará com que essa classe magicamente obter chamado quando necessário, mas vamos ver o que resta do código de pedido de envio original:
public void SubmitOrder(OrderData data)
{
// call the domain model for regular order submit logic
}
É como limpo e simples como um pôde espero — nosso código não precisa saber nada sobre eventos.
Eventos de domínio explícita
A classe CustomerBecamePreferredHandler vemos a referência a um tipo chamado CustomerBecamePreferred — uma representação explícita no código de ocorrência mencionada na necessidade. Essa classe pode ser tão simples como este:
public class CustomerBecamePreferred : IDomainEvent
{
public Customer Customer { get; set; }
}
A próxima etapa é ter a capacidade para qualquer classe dentro do nosso modelo de domínio para elevar um evento, que é facilmente conseguido com a seguinte classe estática que faz uso de um recipiente como Unity, castelo ou Spring.NET:
public static class DomainEvents
{
public IContainer Container { get; set; }
public static void Raise<T>(T args) where T : IDomainEvent
{
foreach(var handler in Container.ResolveAll<Handles<T>>())
handler.Handle(args);
}
}
Agora, qualquer classe em nosso modelo de domínio pode elevar um evento de domínio, com classes de entidade geralmente disparar os eventos assim:
public class Customer
{
public void DoSomething()
{
// regular logic (that also makes IsPreferred = true)
DomainEvents.Raise(new CustomerBecamePreferred() { Customer = this });
}
}
Capacidade de teste
Embora a classe DomainEvents mostrada seja funcional, ele pode tornar unidade teste um modelo de domínio um pouco complicado, como seria precisamos fazer usar de um recipiente para verificar esse domínio eventos foram gerados. Algumas adições à classe DomainEvents podem contornar o problema, conforme mostrado na Figura 1 .
A Figura 1 adições à classe DomainEvents
public static class DomainEvents
{
[ThreadStatic] //so that each thread has its own callbacks
private static List<Delegate> actions;
public IContainer Container { get; set; } //as before
//Registers a callback for the given domain event
public static void Register<T>(Action<T> callback) where T : IDomainEvent
{
if (actions == null)
actions = new List<Delegate>();
actions.Add(callback);
}
//Clears callbacks passed to Register on the current thread
public static void ClearCallbacks ()
{
actions = null;
}
//Raises the given domain event
public static void Raise<T>(T args) where T : IDomainEvent
{
foreach(var handler in Container.ResolveAll<Handles<T>>())
handler.Handle(args);
if (actions != null)
foreach (var action in actions)
if (action is Action<T>)
((Action<T>)action)(args);
}
}
Agora um teste de unidade pode ser totalmente independente sem precisar de um recipiente, como Figura 2 mostra.
Teste de unidade a Figura 2 sem contêiner
public class UnitTest
{
public void DoSomethingShouldMakeCustomerPreferred()
{
var c = new Customer();
Customer preferred = null;
DomainEvents.Register<CustomerBecamePreferred>(
p => preferred = p.Customer
);
c.DoSomething();
Assert(preferred == c && c.IsPreferred);
}
}
Consultas e comandos
Os casos de uso que tenha sido examinando até todos os trataram com alteração de dados e as regras em torno delas. Ainda em muitos sistemas, os usuários também precisará ser capaz de exibir esses dados, bem como executar todos os tipos de pesquisas, classificações e filtros.
Tinha originalmente pensei que as mesmas classes de entidade que estavam no modelo de domínio devem ser usadas para mostrar dados para o usuário. Nos anos, foi Obtendo usei para noções básicas sobre que meu original pensando geralmente acontece estar errado. O modelo de domínio é tudo sobre encapsulamento de dados com comportamentos de negócios.
Mostrar as informações do usuário envolve um comportamento de negócios e é tudo sobre abrindo os dados. Mesmo quando temos certos requisitos de segurança ao redor dos quais usuários podem ver quais informações, que geralmente pode ser representado como uma filtragem obrigatório de dados.
Enquanto eu estava "êxito" no passado na criação de um modelo de objeto persistente único que tratado comandos e consultas, geralmente era muito difícil para dimensioná-la, como cada parte do sistema tugged o modelo em uma direção diferente.
Acontece que os desenvolvedores geralmente assumir exíguas mais requisitos de negócios realmente precisa. A decisão de usar as entidades de modelo de domínio para mostrar informações para o usuário é apenas como um exemplo.
Você ver, em um sistema multi-usuário, as alterações feitas por um usuário não precisam necessariamente ser imediatamente visíveis para todos os outros usuários. Todos nós implicitamente entender isso quando apresentamos cache para melhorar o desempenho — mas as perguntas mais profundas permanecem: se você não precisa os dados mais atualizados, por que vá através do modelo de domínio que necessariamente funciona em que os dados? Se você não precisa o comportamento encontrado nessas classes de modelo de domínio, por que plough por eles para obter seus dados?
Para esses antigo suficiente para lembrar-se, as práticas recomendadas ao redor usando + interativa nos para criar componentes separados para leitura - somente e para lógica de leitura / gravação. Aqui estamos, uma década posteriormente, com novas tecnologias como o Entity Framework, ainda que esses mesmos princípios continuam armazenar.
Obter dados de um banco de dados e mostrá-lo a um usuário são um problema bastante trivial para resolver estes dias. Isso pode ser tão simples quanto usar um leitor de dados ADO.NET ou conjunto de dados.
Figura 3 mostra o que talvez a aparência de nossa arquitetura "novo".
Figura 3 modelo para Obtendo dados de um banco de dados
Uma coisa que seja diferente nesse modelo de abordagens comuns com base em vinculação de dados bidirecional, é que a estrutura que é usada para mostrar os dados não é usada para alterações. Isso torna as coisas como controle de alterações não totalmente necessário.
Nesta arquitetura, fluxos de dados até o lado direito do banco de dados para o usuário no formulário de consultas e na parte inferior esquerda do usuário volta ao banco de dados na forma de comandos. Escolher ir para um banco de dados totalmente separado usado para essas consultas é uma opção atraente em termos de desempenho e escalabilidade, como leituras não interferem com gravações no banco de dados (incluindo as páginas de dados são mantidas na memória no banco de dados), mas é necessário um mecanismo de sincronização explícita entre os dois. As opções para isso incluem serviços de sincronização do ADO.NET, o SSIS (SQL Server Integration Services) e publicação/assinatura de mensagens. Escolher uma destas opções está além do escopo deste artigo.
Mantendo a empresa no domínio
Um dos desafios enfrentados pelos desenvolvedores ao criar um modelo de domínio é como garantir que a lógica de negócios não sangramento check-out do modelo de domínio. Não há uma marcador prata solução para isso, mas um estilo de trabalho Gerenciar encontrar um equilíbrio delicate entre a simultaneidade, correção e encapsulamento de domínio que ainda pode ser testado para com ferramentas de análise estática como FxCop.
Aqui está um exemplo do tipo de código que não queremos ver interagindo com um modelo de domínio:
public void SubmitOrder(OrderData data)
{
var customer = GetCustomer(data.CustomerId);
var shoppingCart = GetShoppingCart(data.CartId);
if (customer.UnpaidOrdersAmount + shoppingCart.Total > Max)
// fail (no discussion of exceptions vs returns codes here)
else
customer.Purchase(shoppingCart);
}
Embora esse código seja bastante orientada a objeto, podemos ver que está sendo executada uma certa quantidade de lógica de negócios aqui em vez de no modelo de domínio. Uma abordagem melhor seria este:
public void SubmitOrder(OrderData data)
{
var customer = GetCustomer(data.CustomerId);
var shoppingCart = GetShoppingCart(data.CartId);
customer.Purchase(shoppingCart);
}
No caso da nova ordem exceder o limite de pedidos pago, que poderia ser representada por um evento de domínio, manipulado por uma classe separada, como demonstrado anteriormente. O método de compra não causaria alterações de dados nesse caso, resultando em uma transação tecnicamente bem-sucedida sem qualquer efeito de negócios.
Ao inspecionar a diferença entre os dois exemplos de código, podemos ver que chamar somente um método único no modelo de domínio necessariamente significa que toda a lógica comercial tem a ser encapsulada existe. A API mais concentrada do modelo de domínio geralmente mais melhora a capacidade de teste.
Embora essa seja uma boa etapa na direção certa, ele é aberto até algumas perguntas sobre simultaneidade.
Concorrência
Você verá, entre o momento em que obtemos o cliente e o tempo que pedimos que executar a compra, outra transação pode vêm em e alterar o cliente de tal forma que o valor do pedido é atualizado. Que podem causar nossa transação para executar a compra (com base nos dados recuperados anteriormente), embora ele não cumprir o estado atualizado.
A maneira mais simples para resolver este problema é nos fazer com que o registro de cliente a ser bloqueada quando nós originalmente lê-lo — realizado por indicando um nível de isolamento de transação de menos repetitiva leitura (ou serializável — que é o padrão) da seguinte maneira:
public void SubmitOrder(OrderData data)
{
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.RepeatableRead }
))
{
// regular code
}
}
Embora isso que demora um bloqueio um pouco mais caro do que o nível de isolamento confirmada leitura alguns ambientes de alto desempenho tem liquidada, desempenho pode ser mantido em níveis semelhantes quando as entidades envolvidas em um caso de uso determinado são avidamente carregadas e estão conectadas por colunas indexadas. Isso geralmente amplamente é deslocado por modelo codificação applicative muito mais simples, pois nenhum código é necessário para identificar ou resolver problemas de simultaneidade. Quando empregando um banco de dados separado para a consulta partes do sistema e todas as leituras são descarregadas do banco de dados OLTP que atende o modelo de domínio, desempenho e escalabilidade podem ser praticamente idêntico ao leitura confirmada-soluções baseadas em.
Localizando uma solução abrangente
O padrão de modelo de domínio, na verdade, é uma ferramenta poderosa nas mãos de um profissional de fabricação capacitado. Como muitos outros desenvolvedores, na primeira vez que pegou essa ferramenta, eu over-used-lo e pode até mesmo ter abusos-lo com resultados muito menor do que. Durante a criação de um modelo de domínio, gastar mais tempo observando as especificações encontrado em vários casos de uso em vez de jumping diretamente em relações de entidades de modelagem — especialmente cuidado de configurar essas relações para fins de mostrando os dados do usuário. Melhor que é servido com consultas de banco de dados simples e direto, com possivelmente uma fina camada de fachada em cima dele para alguns independência de provedor de banco de dados.
Ao observar como código fora o modelo de domínio interage com ele, procure a ágil "mais simples coisa que possivelmente funcionaria" — uma chamada de método único em um único objeto de domínio, mesmo o caso quando você estiver trabalhando em vários objetos. Eventos de domínio podem ajudar redonda sua solução para tratamento de interações mais complexas e tecnológicas integrações, sem introduzir qualquer complicações.
Ao iniciar esse caminho, demorei algum tempo para ajustar meu pensamento, mas os benefícios de cada padrão foram sentiram rapidamente. Quando comecei a empregando todos esses padrões juntos, descobri que eles fornecido uma solução abrangente para domínios de negócios até mesmo mais exigentes, mantendo todo o código em cada parte do sistema pequenas, foco e testável — tudo o que um desenvolvedor pode desejar.
Works citado
Fowler, M. Patterns of Enterprise Application Architecture, (Addison Wesley, 2003).
Udi Dahan Reconhecido como MVP, um arquiteto do IASA mestre e Dr.. Especialista em SOA do Dobb, Udi Dahan é o Software Simplist, um consultor independente, alto-falante, autor e instrutor fornecendo serviços de high-end no design e arquitetura empresarial e orientados a serviços, escalonável e seguro. Entre em contato com Udi pelo seu blog em UdiDahan.com.