ASP .NET Core - Usando a Clean Architecture

 Hoje vamos apresentar os conceitos e princípios que nos ajudam implementar uma arquitetura aderente aos princípios da Clean Architecture.

O que é Arquitetura de Software ?

Parece ser uma definição simples e intuitiva mas existe certa confusão entre design e arquitetura. A arquitetura seria um aspecto do design que foca em recursos específicos.

A arquitetura define o que é o sistema em termos de componentes de software e, os relacionamentos entre estes componentes, os padrões que guiam a sua composição e restrições. (Shaw e Garlan)

Assim, a arquitetura significa o design geral do projeto. É a organização do código em classes, arquivos, componentes ou módulos. E é como todos esses grupos de código se relacionam entre si. A arquitetura define onde o aplicativo executa sua funcionalidade principal e como essa funcionalidade interage com coisas como o banco de dados e a interface do usuário.

E o que é Clean Architecture ?

Arquitetura limpa refere-se à organização do projeto de forma que seja fácil de entender e fácil de mudar conforme o projeto cresce. Isso não acontece por acaso. É preciso um planejamento intencional para que isso ocorra.

O segredo para construir um grande projeto que seja fácil de manter é este:

Vamos ilustrar isso com algumas imagens.

Vamos iniciar com uma imagem onde vamos representar o domínio e a infraestrutura da aplicação.

No círculo interno temos a camada de domínio da aplicação. É aqui que você coloca as regras de negócios que representa a alma do seu aplicativo, a funcionalidade central do seu projeto.

O círculo externo é onde esta a infraestrutura. Aqui você inclui a interface com o usuário (UI), o banco de dados, as APIs da web e frameworks. Essas coisas têm mais probabilidade de mudar do que o domínio. Por exemplo, é mais provável que você mude a aparência de um botão na interface do que mudar uma regra do seu negócio.

Uma fronteira entre o domínio e a infraestrutura é configurada de forma que o domínio não saiba nada sobre a infraestrutura. Isso significa que a IU e o banco de dados dependem das regras de negócios, mas as regras de negócios não dependem da IU ou do banco de dados.

Não importa se a IU é uma interface web, um aplicativo desktop ou um aplicativo mobile. Não importa se os dados são armazenados usando SQL Server, MySQL ou NoSQL ou na nuvem. O domínio não se importa. Isso torna mais fácil mudar a infraestrutura.

A seguir vamos usar outra imagem para definir alguns termos com mais detalhes:

Agora a camada de domínio foi subdividida em Entidades e Casos de uso, e uma camada adaptadora (Adapters) forma a fronteira entre o domínio e a camada de infraestrutura. Vejamos cada termo usado com mais detalhes :

Entidades (Entities)

As Entidades (Entities) representam um conjunto de regras de negócio relacionadas que são críticas para que a aplicação funcione corretamente para o seu propósito.

No contexto da ASP .NET Core e da linguagem C# as regras das entidades seriam agrupadas como métodos em classes, e,  mesmo que não houve nenhuma aplicação essas regras ainda existiriam.

O que é importante destacar aqui é que as entidades não conhecem nada sobre as demais camadas. Elas não possuem nenhuma dependência, ou seja, elas não usam os nomes de nenhuma outra classe ou componente que estejam nas camadas externas.

Os Casos de Uso (Use cases)

Os casos de uso são as regras de negócios para um aplicativo específico. Eles dizem como automatizar o sistema e isso determina o comportamento do aplicativo.

Os casos de uso interagem e dependem das entidades, mas não sabem nada sobre as camadas mais distantes. Eles não se importam se é uma página web ou um aplicativo para Celular. Eles não se importam se os dados estão armazenados na nuvem ou em um banco de dados SQL Server ou SQLite local.

Essa camada define interfaces ou possuem classes abstratas que as camadas externas podem usar.

Adaptadores (Adapters)

Os adaptadores, também chamados de adaptadores de interface, são os tradutores entre o domínio e a infraestrutura. Eles pegam dados de entrada da Interface com o usuário - UI, e os ajustam em um formato que seja conveniente para os casos de uso e entidades.

Em seguida, eles pegam a saída dos casos de uso e entidades e a ajustam em um formato que seja conveniente para exibir na interface com o usuário ou salvar em um banco de dados.

Infraestrutura (Infrastructure)

Essa camada é para onde vão todos os componentes de  entrada e saída ou I/O, a interface com o usuário - IU, o banco de dados, os frameworks, os dispositivos, etc.

É a camada mais volátil visto que as coisas nesta camada podem mudar, e, por isso, elas são mantidas o mais longe possível das camadas de domínio mais estáveis.

Como eles são mantidos separados, é relativamente fácil fazer alterações ou trocar um componente por outro.

Princípios para implementar uma arquitetura limpa

Para conseguir implementar uma arquitetura seguindo os requisitos expostos acima vamos precisar aplicar princípios fundamentais que iremos passar a descrever.

Vamos iniciar com os princípios SOLID que são princípios de nível de classe, mas têm contrapartes semelhantes que se aplicam a componentes (grupos de classes relacionadas).

  1. Princípio SRP - The Single Responsibility Principle - Principio da Responsabilidade Única - Uma classe deve ter um, e somente um, motivo para mudar;
     
  2. Princípio OCP - The Open Closed Principle - Princípio Aberto-Fechado - Você deve ser capaz de estender um comportamento de uma classe, sem ter que modificá-la;
     
  3. Princípio LSP - The Liskov Substitution Principle - Princípio da Substituição de Liskov - As classes derivadas devem poder substituir suas classes bases;
     
  4. Princípio ISP - The Interface Segregation Principle - Princípio da Segregação da Interface - Muitas interfaces específicas são melhores do que uma interface geral;
     
  5. Princípio DIP - The Dependency Inversion Principle - Princípio da inversão da dependência - Dependa de uma abstração e não de uma implementação.

A seguir veremos outros princípios que nos ajudam a conseguir o objetivo que é ter uma arquitetura limpa.

Princípio de Equivalência de Reutilização/Liberação (REP)

O REP é um princípio de nível de componente. Reutilizar refere-se a um grupo de classes ou módulos reutilizáveis. Liberar se refere a publicá-lo com um número de versão. Este princípio diz que tudo o que você liberar deve ser reutilizável como uma unidade coesa. Não deve ser uma coleção aleatória de classes não relacionadas.

Princípio Comum de Fechamento (CCP)

Esse princípio diz que os componentes devem ser uma coleção de classes que mudam pelo mesmo motivo ao mesmo tempo. Se houver motivos diferentes para mudar ou se as classes mudarem em taxas diferentes, o componente deve ser dividido.

Princípio de Reutilização Comum (CRP)

O CRP afirma que você não deve depender de um componente que possui classes que você não precisa. Esses componentes devem ser divididos para que os usuários não precisem depender de classes que não usam. Isso é basicamente a mesma coisa que o Princípio de Segregação de Interface descrito acima.

Princípio de Dependência Acíclica (ADP)

O princípio ADP significa que você não deve ter nenhum ciclo de dependência em seu projeto. Por exemplo, se o componente A depende do componente B e o componente B depende do componente C e o componente C depende do componente A, então você tem um ciclo de dependência.

Princípio de Dependência Estável (SDP)

Este princípio diz que as dependências devem ser na direção da estabilidade. Ou seja, componentes menos estáveis devem depender de componentes mais estáveis. Isso minimiza o efeito da mudança. Alguns componentes devem ser voláteis. Tudo bem, mas você não deve fazer com que os componentes estáveis dependam deles.

Princípio de abstração estável (SAP)

O princípio SAP declara que quanto mais estável um componente é, mais abstrato ele deve ser, ou seja, mais classes abstratas ele deve conter. As classes abstratas são mais fáceis de estender, portanto, isso evita que os componentes estáveis se tornem muito rígidos.

Esses princípios foram definidos no livro Clean Architecture e aqui foram apresentados de forma resumida.

Para concluir vejamos algumas abordagens que podem ser usadas em conjunto com os princípios para implementar a arquitetura limpa.

Dividindo componentes por casos de uso

Lembra das camadas de domínio e infraestrutura apresentadas. Se você pensar nelas como camadas horizontais, elas podem ser divididas verticalmente em grupos de componentes de acordo com os diferentes casos de uso que um aplicativo pode ter. É como um bolo em camadas em que cada fatia é um caso de uso e cada camada da fatia forma um componente.

Forçando uma divisão de camadas

Você pode ter a melhor arquitetura do mundo, mas se um novo desenvolvedor aparecer e adicionar uma dependência que contorne seus limites, isso anulará completamente o propósito. A melhor maneira de evitar isso é usar o compilador para ajudá-lo a proteger sua arquitetura.

Outra opção é usar um software de terceiros que o ajudará a verificar se algo está usando algo que não deveria.

Adicione complexidade conforme necessário

No início do seu projeto você deve usar apenas a arquitetura necessária. Mas, dentro de sua arquitetura, mantenha limites que facilitem a separação de componentes no futuro. Por exemplo, para começar, você pode implantar o que é externamente um aplicativo monolítico, mas por dentro as classes mantêm os limites adequados. Mais tarde, você pode dividi-los em módulos separados.

Ainda mais tarde, você pode implantá-los como serviços. Contanto que você mantenha as camadas e limites ao longo do caminho, você tem a liberdade de ajustar como eles são implantados. Desta forma, você não está criando complexidade desnecessária que pode nunca ser usada.

Detalhes

Ao iniciar um projeto, você deve primeiro trabalhar nas regras de negócios. Todas as outras coisas são detalhes. O banco de dados é um detalhe. A UI é um detalhe. O SO é um detalhe. Uma web API é um detalhe. Uma framework é um detalhe.

Adie as decisões sobre eles pelo maior tempo possível, assim, quando precisar deles, você estará em uma posição melhor para fazer uma escolha inteligente. Esses detalhes não importam para o seu desenvolvimento inicial, porque as camadas de domínio não sabem nada sobre a infraestrutura.

Quando estiver pronto para escolher um banco de dados, preencha o código do adaptador de banco de dados e conecte-o. Quando estiver pronto para criar a IU, preencha o código do adaptador de IU e conecte-o. Entendeu ?

A essência da Clean Architecture é que você precisa criar uma arquitetura de plugins. As classes que podem mudar ao mesmo tempo e pelo mesmo motivo devem ser agrupadas em componentes.

Os componentes da regra de negócios são mais estáveis e não devem saber nada sobre os componentes de infraestrutura que são mais voláteis, os que lidam com a interface do usuário, o banco de dados, web, frameworks e outros detalhes.

O limite entre as camadas de componentes é mantido usando adaptadores de interface que traduzem os dados entre as camadas e mantêm as dependências apontando na direção dos componentes internos mais estáveis.

Agora que foram apresentados os principais conceitos relacionados com a Arquitetura Limpa, e, de como podemos usar essa abordagem para ter uma arquitetura limpa, veremos a seguir uma abordagem prática da implementação da arquitetura limpa em uma aplicação ASP .NET Core.

"Se o mundo vos odeia, sabei que, primeiro do que a vós, me odiou a mim.
Se vós fôsseis do mundo, o mundo amaria o que era seu, mas porque não sois do mundo, antes eu vos escolhi do mundo, por isso é que o mundo vos odeia."

João 15:18,19


Referências:


José Carlos Macoratti