EF Core - Criando aplicações mais Robustas


Hoje vamos abordar alguns procedimentos que nos ajudam a criar aplicações mais robustas usando o Entity Framework Core.

Quem não deseja criar aplicações mais robustas ?

O termo 'robustas' aqui refere-se a ter uma aplicação com baixo acoplamento e alta coesão.

Acoplamento

- Acoplamento é o nível de dependência/conhecimento que pode existir entre as classes;
- Uma classe com acoplamento fraco não é dependente de muitas classes para fazer o que ele tem que fazer;
- Uma classe com acoplamento forte depende de muitas outras classes para fazer o seu serviço;
- Uma classe com acoplamento forte é mais difícil de manter, de entender e de ser reusada;

Coesão

- Coesão é o nível de integralidade interna de uma classe; (Veja o principio da responsabilidade única - SRP)
- A coesão Mede o grau que um classe ou seus métodos fazem sentido, ou seja, quão claro é o entendimento do que a classe ou método possui
- Uma classe com alta coesão possui responsabilidades bem definidas e são difíceis de serem desmembradas em outras classes;
- Uma classe com baixa coesão possui muitas responsabilidades, geralmente que pertencem a outras classes, e podem ser facilmente desmembradas em outras classes;
- Uma classe com baixa coesão é difícil de entender, manter e reusar;

Portanto quanto mais forte o acoplamento e mais baixa a coesão de uma classe mais difícil ela será de entender, manter e ser reusada.

Assim, vou apresentar algumas abordagens (baseadas nas considerações de Jon P Smith) que nos ajudam a criar aplicações que usam o EF Core de forma mais robusta. Essas abordagens são baseadas em princípios e padrões de software que eu com frequência trato no site e serão apresentadas de forma resumida.

1- Separação de responsabilidades - Usando a arquitetura correta

Esse princípio de software conhecido como Separation of Concerns (SoC) dita a primeira abordagem e reza que :

Devemos agrupar o código com funcionalidades semelhantes ou fortemente relacionadas, separando-os em um lugar comun. Em termos de projeto na plataforma .NET isso implica colocar o código em um projeto diferente. Com isso estamos obtendo uma alta coesão.

Devemos tornar cada projeto o mais independente possível, onde cada parte do código deve ter uma interface clara e um escopo de trabalho que dificilmente deve ser alterado devido a outras alterações em partes do projeto. Com isso estamos obtendo um baixo acoplamento.

Em nosso projeto nunca devemos chamar os comandos do EF Core diretamente da aplicação, pois isso fere o princípio da separação das responsabilidades.

Na figura a seguir temos um exemplo de arquitetura que fere o princípio Soc e que apresenta uma baixa coesão e alto acoplamento:

Na figura a seguir temos uma abordagem que respeita o princípio SoC e apresenta alta coesão e baixo acoplamento:




Aqui podemos usar diferentes arquiteturas como: Domain Drive Design, Layered Architecture, N-Tier, SOA, etc.  Para detalhes veja este link:  MSDN - rchitectural Patterns and Styles

2- A camada de serviço - Separando ações de dados de ações de apresentação

A camada de serviço ou Service layer é definida da seguinte forma por Martin Fowler :

"Define os limites de um aplicativo com uma camada de serviços que estabelece um conjunto de operações disponíveis e coordena a resposta do aplicativo em cada operação".

Tudo isso parece ótimo, mas como isso ajuda meus aplicativos ?

Usando a camada de serviço como um adaptador

Em uma arquitetura em camadas, geralmente existe uma incompatibilidade de dados entre o banco de dados/ lógica de negócios e a camada de apresentação. A abordagem DDD(Domain-Driven Design), diz que o banco de dados e a lógica de negócios devem se concentrar nas regras de negócios, enquanto a camada de apresentação visa proporcionar ao usuário uma ótima experiência.

Por esse motivo, a Camada de serviço se torna uma camada crucial, pois pode ser a camada que compreende os dois lados e pode transformar os dados entre os dois mundos. Isso mantém a lógica de negócios e o banco de dados organizados pelas necessidades da apresentação.

Da mesma forma, ao fazer com que a Camada de Serviço forneça dados pré-formatados exatamente da forma que a camada de apresentação precisa, torna muito mais simples a camada de apresentação mostrar esses dados.

Ao lidar com acesso ao banco de dados via EF, a Camada de Serviço usa o padrão Adapter para transformar a partir da camada de acesso aos dados/lógica de negócios  para a camada de apresentação. Os bancos de dados tendem a minimizar a duplicação de dados e maximizar os links relacionais entre os dados, enquanto a camada de apresentação é usada para mostrar os dados de uma forma que o usuário achar útil.

Assim utilizar uma camada de serviço que permite fazer essa comunicação é muito importante.

3- Escolhendo o padrão de acesso a dados correto

Existem várias maneiras diferentes de criar o acesso ao seu banco de dados via EF Core, com diferentes níveis de ocultação de código de acesso EF Core do restante da sua aplicação. Os mais usados são:

  1. Repository mais Unity Of Work - Oculta todo o código EF Core por trás do código que fornece uma interface diferente do EF. A idéia é que você poderia substituir o EF por outra estrutura de acesso ao banco de dados sem alterar os métodos que chamam o repositório e o Unit of work;
     
  2. Repository EF Core - Usa um padrão repositório que não tenta ocultar o código EF, como o anterior. Os repositórios do EF assumem que você como desenvolvedor conhece as regras do EF, como usar entidades rastreadas e chamar SaveChanges para atualizações, e você tem que respeitar essas regras;
     
  3. Query Ojects - Os objetos de consulta encapsulam o código para uma consulta ao banco de dados. Eles mantêm o código inteiro para uma consulta ou para consultas complexas que possam conter parte de uma consulta. Os objetos de consulta são normalmente criados como métodos de extensão com entradas e saídas IQueryable<T>, para que possam ser encadeados para criar consultas mais complexas;
     
  4. Chamadas diretas ao EF Core - Aqui você simplesmente coloca o código EF necessário no método necessário. Por exemplo, todo o código EF para criar uma lista de objetos estaria no método Action do controlador que ASP.NET que mostra essa lista. (Fere a primeira abordagem apresentada...)

Aqui não existe uma regra que permita indicar qual é o melhor padrão a ser usado. Podemos descartar o último, que deve ser usado apenas em protótipos.

Para projetos muito complexos o primeiro padrão (Repo+UoW) pode se tornar muito dispendioso em termos de código e manutenção.

Assim o ideal é procurar sempre isolar o código EF Core usado.

4- Transformando o seu código de acesso a dados em serviço

Utilizar a injeção de dependência para injetar o código de acesso a dados na sua aplicação ASP .NET Core pode lhe trazer muitos benefícios.

Em primeiro lugar, a injeção de dependência  vai vincular dinamicamente o acesso ao seu banco de dados nas partes do código da apresentação que precisam dele. Em segundo lugar, com a utilização de interfaces, é muito fácil substituir as chamadas para o código de acesso ao banco de dados por simulações para teste de unidade.

A seguir temos as etapas principais para realizar essa tarefa em aplicações ASP .NET Core:

1- Transforme o seu código de acesso ao banco de dados em repositórios. Você faz isso criando uma classe que contém métodos, que o código front-end precisa chamar.

2- Adicione uma interface para cada classe de repositório EF definindo a interface e a implementação;

3- Registre sua classe de repositório EF em sua interface no provedor de DI nativo da ASP .NET Core;

4- Injete a dependência no método do front-end se necessário. Na ASP.NET Core, você pode injetar um método Action usando o [FromServices];

Como resultado final teremos o código do banco de dados separado em sua classe própria e agora seu código de apresentação precisa apenas chamar o método sem saber o que ele contém. Além disso os testes de unidades ficam fáceis de implementar pois você pode verificar o código de acesso a dados substituindo o código na sua chamada do front-end por uma classe que simula os dados.

5- Crie uma camada de negócios (Domain Drive Design)

Os aplicativos do mundo real são criados para fornecer algum tipo de serviço, desde a manutenção de uma lista simples de itens no seu computador até o gerenciamento de um reator nuclear. Todo problema do mundo real tem um conjunto de regras, geralmente chamadas de regras de negócios ou pelo nome mais genérico, regras de domínio.

A abordagem DDD (Domain-Driven Design) diz que o problema de negócios que você está tentando resolver deve impulsionar todo o desenvolvimento. Eric Evans trata disso em seu livro Domain Drive Design, e continua explicando como a lógica de negócios deve ser isolada de todo o resto, exceto as classes de banco de dados, para que você possa dar toda a sua atenção ao que Eric Evans chama de "tarefa difícil" de escrever a lógica de negócios.

Há muitos debates sobre se o EF Core é adequado para uma abordagem DDD, porque o código da lógica de negócios é normalmente separado das classes de entidade EF que ele mapeia para o banco de dados.  A recomendação é aplicar os conceitos do DDD usando o framework até quando ele não for hostil à aplicação destes conceitos.

Como o assunto é muito extenso listo a seguir algumas diretrizes que devem nortear a aplicação do DDD com EF Core:

Concluindo

A abordagem recomendada para o desenvolvimento de software é fazê-lo funcionar e depois se preocupar em torná-lo mais rápido.

Outra abordagem mais sutil, atribuída a Kent Beck, é Make it Work é : Faça certo. Faça rápido.

De qualquer forma, esse princípio diz que devemos deixar o ajuste de desempenho para o fim. Assim:

Respondendo a essas perguntas você poderá traçar ações para otimizar o seu código e ajustar o desempenho do seu projeto.

"Portanto, agora nenhuma condenação há para os que estão em Cristo Jesus, que não andam segundo a carne, mas segundo o Espírito."
Romanos 8:1

Referências:


José Carlos Macoratti