Você já notou que desde a chegada do .NET Core, consistentemente reaproveitamos menos nossas classes, principalmente de infraestrutura. Escrevemos mais classes e cada vez reaproveitamos menos nosso código?
Bom se você não notou isso, então ao invés de 1 problema, temos 2!
Quando você desenha uma classe ela está ali para prover funcionalidade para quem a consome. Sempre que construo uma classe eu penso não só nas suas responsabilidades, mas também em como ela será consumida.
A fase de lapidação que detalho, por exemplo no RingBuffer (post) e projeto Oragon.Common.RingBuffer (GitHub), trata-se de endereçar essa preocupação, transformando em ocupação, para prover um design que de fato favoreça o consumo.
Essas preocupações consistem em:
- Avaliar todos os métodos
- Entender quais parâmetros são necessários para operar usar essa classe.
- Identificar parâmetros com potencial para serem promovidos a estado da instância.
- Avaliar as propriedades
- Identificar propriedades com potencial para serem despromovidos para parâmetros de método (identificando qual/quais métodos).
- SOLID – Estar alinhado com SOLID
Essa simples preocupação, com o que é parâmetro e o que é propriedade (ou estado) da instância é fundamental para uma arquitetura limpa.
Exemplo: Envio de Email
Vamos supor o clássico exemplo de envio de email. Se nosso cenário precisa levar em conta a utilização de um servidor SMTP então precisamos de:
- Texto da Mensagem
- Destinatários do email
- Credenciais de conectividade com o servidor SMTP
Note, a classe de infraestrutura que prove essa funcionalidade tem todas essas características, mas do nosso lado, há boas chances de que devamos construir uma abstração que promova as credenciais de envio para propriedades e estado de um serviço de envio de email.
Isso para que quem consuma nossa classe, não precise lidar com a obtenção desses dados.
É uma bobeira, mas que limpa substancialmente o código.
Outra coisa é talvez, uma infraestrutura de template engine, que faça o papel de ter os templates de email e faça o parser desses emails.
Exemplo: Log
Log é um bom exemplo disso, onde usamos um ILogger sem nos preocuparmos com os detalhes de sua implementação. Não nos preocupamos com o tipo de persistência usada.
Isso tudo por conta de como essa interface foi desenhada. Houve uma ocupação ali para desenhar algo que produzisse o mínimo de acoplamento.
Exemplo: DBContext do Entity Framework
E se você usa um DBContext do EF, está aqui um exemplo: Você não passa a connectionstring para cada método. Ela é um estado do DBContext. Isso tudo para que você tenha uma experiência de uso, que seja direta ao ponto.
Busque exemplos
Você vai achar esse tipo de preocupação em toda boa modelagem. Aliás, até nas ruins!
E porque será que não fazemos isso com os nossos projetos?
Se você concorda que isso é importante para a saúde e longevidade do código, será que de fato está sendo coerente e fazendo seu dever de casa?
Quando comecei o Oragon.Common.RingBuffer desde os primeiros dias já havia um pequeno roadmap. Mas falando a verdade, na real em geral eu conduzo esse tipo de roadmap na cabeça mesmo. É tão pequeno, tão simples, tão dia-a-dia, que escrever no papel toma mais tempo que fazer de fato.
Mas dessa vez eu resolvi documentar o processo, passo-a-passo. Como uma forma de mostrar como essas preocupações acontecem.
O Register/Resolver nesse contexto
A injeção de dependência produz implicações diretas na forma como modelamos. A forma como desenhamos nossas dependências injetadas leva em conta o estado vs parâmetros em que elas se encontram.
Em um cenário onde eu tenha 2 tipos de envio de email, com 2 conjuntos de credenciais, poderíamos ter apenas 1 classe, com 2 instâncias, onde cada uma teria consigo a credencial certa. Injetaríamos a instância correta de acordo com a classe a ser construída.
Mas é aí que o register/resolver dificulta a coisa. Como somos obrigados a pensar em 1 tipo, mapeado para outro tipo, não temos parâmetros adicionais para construir nossas dependências, então somos empurrados a criar managers, factories, ou mesmo Service Locators.
E note, o Register/Resolver em geral tem condições de oferecer a implementação que te permite ter esse controle, no entanto, não é intuitivo e você precisa claramente saber o que quer, para conseguir realizar a melhor modelagem, na minha opnião.
E a melhor modelagem na minha opinião é aquela que não ignora o que suas classes são, o que elas fazem, como fazem em como são consumidas.
Eu fico encantado com projetos que ao ser dissecado você enxerga os pedaços como um conjunto de peças de um lego. E você? O que te encanta nos projetos que curte?
0 comentários