Por que separar o acesso a dados?
No livro, acho que as duas primeiras páginas do capítulo Design Orientado a Modelo fornecem algumas justificativas para o motivo de você querer abstrair os detalhes técnicos da implementação a partir da implementação do modelo de domínio.
- Você deseja manter uma conexão estreita entre o modelo de domínio e o código
- Separar preocupações técnicas ajuda a provar que o modelo é prático para implementação
- Você deseja que a linguagem onipresente permeie o design do sistema
Isso parece ser tudo com o objetivo de evitar um "modelo de análise" separado que se divorcia da implementação real do sistema.
Pelo que entendi do livro, ele diz que esse "modelo de análise" pode acabar sendo projetado sem considerar a implementação de software. Uma vez que os desenvolvedores tentam implementar o modelo entendido pelo lado comercial, eles formam suas próprias abstrações devido à necessidade, causando uma barreira na comunicação e no entendimento.
Na outra direção, os desenvolvedores que apresentam muitas preocupações técnicas no modelo de domínio também podem causar essa divisão.
Portanto, você pode considerar que praticar a separação de preocupações, como persistência, pode ajudar a proteger contra esses projetos e modelos de análise divergentes. Se for necessário introduzir coisas como persistência no modelo, é uma bandeira vermelha. Talvez o modelo não seja prático para implementação.
Citação:
"O modelo único reduz as chances de erro, porque o design agora é uma conseqüência direta do modelo cuidadosamente considerado. O design e até o próprio código têm a comunicabilidade de um modelo".
Da maneira como estou interpretando isso, se você tiver mais linhas de código lidando com coisas como acesso ao banco de dados, você perde essa capacidade de comunicação.
Se a necessidade de acessar um banco de dados é para coisas como verificar a exclusividade, dê uma olhada em:
Udi Dahan: os maiores erros que as equipes cometem ao aplicar DDD
http://gojko.net/2010/06/11/udi-dahan-the-biggest-mistakes-teams-make-when-applying-ddd/
em "Todas as regras não são criadas iguais"
e
Empregando o padrão de modelo de domínio
http://msdn.microsoft.com/en-us/magazine/ee236415.aspx#id0400119
em "Cenários para não usar o modelo de domínio", que aborda o mesmo assunto.
Como separar o acesso a dados
Carregando dados através de uma interface
A "camada de acesso a dados" foi abstraída por meio de uma interface, que você chama para recuperar os dados necessários:
var orderLines = OrderRepository.GetOrderLines(orderId);
foreach (var line in orderLines)
{
total += line.Price;
}
Prós: A interface separa o código de canalização de "acesso a dados", permitindo que você ainda faça testes. O acesso a dados pode ser tratado caso a caso, permitindo melhor desempenho do que uma estratégia genérica.
Contras: O código de chamada deve assumir o que foi carregado e o que não foi.
Diga GetOrderLines retorna objetos OrderLine com uma propriedade ProductInfo nula por motivos de desempenho. O desenvolvedor deve ter um conhecimento profundo do código por trás da interface.
Eu tentei esse método em sistemas reais. Você acaba alterando o escopo do que é carregado o tempo todo, na tentativa de corrigir problemas de desempenho. Você acaba espiando por trás da interface para olhar o código de acesso a dados para ver o que está e não está sendo carregado.
Agora, a separação de preocupações deve permitir que o desenvolvedor se concentre em um aspecto do código ao mesmo tempo, o máximo possível. A técnica da interface remove o COMO esses dados são carregados, mas não QUANTO dados são carregados, QUANDO são carregados e ONDE são carregados.
Conclusão: Separação bastante baixa!
Carregamento lento
Os dados são carregados sob demanda. As chamadas para carregar dados estão ocultas no próprio gráfico do objeto, onde o acesso a uma propriedade pode fazer com que uma consulta sql seja executada antes de retornar o resultado.
foreach (var line in order.OrderLines)
{
total += line.Price;
}
Prós: O 'QUANDO, ONDE E COMO' do acesso a dados está oculto do desenvolvedor, focado na lógica do domínio. Não há código no agregado que lide com o carregamento de dados. A quantidade de dados carregados pode ser a quantidade exata exigida pelo código.
Contras: quando você é atingido por um problema de desempenho, é difícil corrigi-lo quando você tem uma solução genérica "tamanho único". O carregamento lento pode causar um desempenho pior no geral, e a implementação de um carregamento lento pode ser complicado.
Interface da função / busca ansiosa
Cada caso de uso é explicitado por meio de uma Interface de Função implementada pela classe agregada, permitindo que as estratégias de carregamento de dados sejam tratadas por caso de uso.
A estratégia de busca pode ficar assim:
public class BillOrderFetchingStrategy : ILoadDataFor<IBillOrder, Order>
{
Order Load(string aggregateId)
{
var order = new Order();
order.Data = GetOrderLinesWithPrice(aggregateId);
return order;
}
}
Em seguida, seu agregado pode se parecer com:
public class Order : IBillOrder
{
void BillOrder(BillOrderCommand command)
{
foreach (var line in this.Data.OrderLines)
{
total += line.Price;
}
etc...
}
}
O BillOrderFetchingStrategy é usado para criar o agregado e, em seguida, o agregado faz seu trabalho.
Prós: Permite código personalizado por caso de uso, permitindo o desempenho ideal. Está alinhado com o Princípio de Segregação de Interface . Não há requisitos de código complexos. Os testes de unidade agregados não precisam imitar a estratégia de carregamento. A estratégia de carregamento genérica pode ser usada na maioria dos casos (por exemplo, uma estratégia "carregar tudo") e estratégias especiais de carregamento podem ser implementadas quando necessário.
Contras: o desenvolvedor ainda precisa ajustar / revisar a estratégia de busca após alterar o código do domínio.
Com a abordagem da estratégia de busca, você ainda pode mudar o código de busca personalizado para alterar as regras de negócios. Não é uma separação perfeita de preocupações, mas acabará sendo mais sustentável e é melhor que a primeira opção. A estratégia de busca encapsula os dados HOW, WHEN e WHERE são carregados. Ele tem uma melhor separação de preocupações, sem perder a flexibilidade, pois o tamanho único se adapta a todas as abordagens de carregamento lento.