Como desenvolvedor de software regularmente tenho feito algumas provas de conceito sobre padrões arquitecturais e design: CQRS, DDD, Clean Architecture, Microsserviços, Modularização e outros. Mas só o cenário real nos mostra a importância desses padrões.
Há mais de um mês abracei o compromisso de implementar o backend de um aplicação grande, usando o Python com FastAPI e MongoDB, e tem sido um desafio interessante.
Desafios do NoSQL
O primeiro desafio está na mudança de pensamento em relação ao armazenamento de dados. Tive de aprender NoSQL e deixar de lado o pensamento relacional para esse sistema. Saí de um esquema rígido para outro mais permissivo e dinâmico.
No decorrer do desenvolvimento, notei que pensamento NoSQL pode influenciar no design dos componentes do software. A arquitectura também sofre influência do NoSQL.
As camadas/componentes mais comuns na implementação do software são: Models, DTOs, Repositories, Services e controllers.
Entretanto decidi usar essas camadas no projecto.
Primeira Fase
Inicialmente criei um repository genérico, que chamei de MongoRepository contendo os métodos básicos de manipulação da collection: create, update, delete, search, get all e outros.
Em seguida criei um repository para lidar com cada model, usando os métodos para alteração e busca sobre as collections.
Os services lidavam com a lógica de negócio e a persistência usando os repositories, acrescendo o lançamento de excepções de acordo as verificações.
Os controllers eram mais declarativos, sem lógica pesada, apenas responsáveis pela chamada dos services, roteamento e respostas http.
Segunda fase
Depois de comprovar que o repository funcionava corretamente, criei dtos responses para retornar os dados na api, as models ficaram para alteração (inserts e updates).
O passo seguinte foi criar uma camada de Mapeamento(mappers) que transformava uma model numa response, retornando os dados necessários para as consultas na API.
Terceira Fase
Nesta fase, explorei as views do mongo e criei as dtos responses baseadas nas view. Cada view estava optimizada para retornar dados embutidos e índices para busca.
Os repositories lidavam com as consultas que retornavam dados das views e com as operações sobre as models(collections).
Essa vantagens das views ajudou a eliminar os mappers naturalmente, já que as views forneciam os dados organizados e acesso mais rápido, sem transformações que poderiam comprometer a performance no futuro.
Na medida que as features aumentavam, os repositories e services cresciam muito dificultando a navegabilidade do código. O número de linhas crescia muito rápido.
Tinha que pensar numa estratégia de design que permitisse evoluir o código sem sacrificar a legibilidade.
Última Fase
Por último separei o repository genérico em duas partes com responsabilidade distintas:
- QueryRepository: para operações de consultas como , busca, verificação de registos existentes, busca por id e retorno de dados paginados.
- CommandRepository: para operações de inserção, actualização e exclusão de dados.
- As classes de consulta herdavam de Query e as de alterações geravam de Command.
Em seguida os services foram divididos em Command e Query respectivamente. Cada service era responsável pelas suas operações.
Os controllers apenas chamam os services de comando e consulta de acordo ao contexto. Um controller que trata de relatórios e estatísticas só precisa de services de consultas.
Resultados
O código ficou mais limpo e legível. Quando precisamos de um tipo de operação, procuramos o prefixo (..Command ou ..Query) e encontramos as funções relacionadas. Quem precisa de consultas não precisa ter acesso os comandos e vice-versa.
O outro ganho é a implementação de estratégias de escalabilidade, performance e algumas optimizações de forma separada.
Conclusão
Aplicar um padrão arquitectural ou de design prematuramente pode adicionar complexidade desnecessária no projecto e condicionar a sua evolução. O cenário real na maioria das vezes nos encaminha para a arquitectura adequada.
Top comments (0)