Devo criar meu framework de desenvolvimento? O que ele deve possuir?
- Marcos Jonatan Suriani
- 3 de fev. de 2021
- 5 min de leitura

O precípuo propósito da utilização de um framework é deixá-lo se preocupar com requisitos não-funcionais - como telemetria, log, auditoria, acesso a dados, dentre outros. Ou seja, os times passam a focar na codificação do negócio para entregar valor ao cliente, enquanto detalhes técnicos são indirecionados e abstraídos.
Já parou para pensar se você deve utilizar um framework de mercado ou o seu próprio?
Deixo um alerta para a síndrome NIH (Not Invented Here), muito comum em indústria de software onde se acredita que desenvolvimento de ferramental "dentro de casa" são mais seguros, mais baratos e eficientes. Portanto, são evitados softwares e ferramentas de terceiros.

Que tal elaborar um Golden Circle? Desta forma, você cria e desenvolve o valor da ideia sobre o framework.
Comece pelo por quê! Um motivo claro e honesto é o que aumentará a apoio e adesão do seu framework dentro da companhia. Afinal, também podemos ver entre times e setores a síndrome NIH, e até dentro da companhia fica complicado o reuso.
People don't buy what you do, they buy why you do it! [Simon Sinek]
Próximo passo são aspectos e características técnicas fundamentais que softwares necessitam1.
Já possui uma visão sobre seus requisitos?

Indireção
Com a indireção nosso objetivo é forçar baixo acoplamento e evitar breaking-changes para quem utiliza o framework em caso de alteração de uma biblioteca interna, por exemplo.
All problems in computer science can be solved by another level of indirection. [David John Wheeler]
A lei de Demeter vem como reforço e deixa o guia simples no sentido do como indirecionar:
Cada unidade deve ter conhecimento limitado sobre outras unidades: apenas unidades próximas se relacionam.
Cada unidade deve apenas conversar com seus amigos; Não fale com estranhos.
Apenas fale com seus amigos imediatos.
Demeter-style software development is about growing software as opposed to building software. [Demeter Project]
Multi-tenancy
Em resumo, temos o conceito tenant como um grupo de usuários. É importante que o framework consiga atender e abstrair seu modelo de multi-tenancy, principalmente quando o tópico é acesso a dados.
Por exemplo, estratégias comuns para segregação de tenants em banco de dados são:
Base de dados dedicada: Cada tenant possui sua base de dados. Afeta diretamente a estratégia para aquisição da connection string;
Schema Dedicado: Cada tenant possui um schema dedicado ainda dentro de um mesmo banco de dados. Estratégia para aquisição do schema é afetada;
Tabela Dedicada: Cada tenant possui uma tabela dedicada ainda dentro de um mesmo banco de dados. Afeta diretamente resolução de nome de tabelas;
Tabela Compartilhada: Tenants estão utilizando mesmo banco de dados e mesmas tabelas. Identificação do tenant é em coluna específica. Afeta diretamente comandos e queries executadas, pois é necessário forçar tenant corrente (afinal, o framework deve deixar esta estratégia transparente);
Observabilidade
Neste artigo do New Relic há uma envolvente definição do termo:
A observabilidade é a prática de instrumentar sistemas com ferramentas para coletar dados que fornecem não apenas "quando" um erro ou problema ocorreu, mas - mais importante - o porquê.

Na abordagem de monitoramento o Google SRE possui a seguinte definição em seu capítulo sobre monitoramento de sistemas distribuídos:
Seu sistema de monitoramento deve abordar duas questões: o que está quebrado e por quê? "O que" está quebrado indica o sintoma; o "porquê" indica uma causa.
Sendo simplistas podemos resumir observabilidade em Tracing, Logging e Métricas.
Tracing
Tracing é uma forma de log especializado sobre detalhes da execução de um software. Quando falamos de sistemas distribuídos o tracing distribuído envolve também rastreio da chamada e execução de serviços dependentes.
Há excelentes bibliotecas para agregar ao seu framework: Elastic APM (open source), Application Insights, New Relic.
Logging
Há excelentes opções para log, principalmente os que possuem capacidade de hidratar e transformar como o Log4j ou Serilog. Por exemplo, o Serilog ainda consegue enviar os logs direto para o Elastic Search utilizando o Sink.
Auditoria
Auditoria de criação, alteração e exclusão de registros pode ser realizado tanto utilizando bibliotecas auxiliares como Audit.NET, Envers do Hibernate.
Autenticação
Possua um Identity Provider, seja extensível e permita protocolos de autenticação como Open ID Connect e OAuth, que são bem difundidos e vastamente utilizados.
Provedores Cloud (como AWS Cognito e Microsoft Identity) já disponibilizam Authentication as a Service (AaaS), o que facilita todo o trabalho relacionado a autenticação multi-fator, single-sign-on e gerenciamento.
Cache
Em resumo, objetivo do cache é evitar acesso direto ao armazenamento de dados - geralmente lento, como uma base de dados relacional que armazena os dados em disco - e utilizar uma abordagem que possui hardware de acesso rápido (memória RAM, por exemplo).
Uma abordagem rápida (e tentadora) de se aplicar, porém limitada e com baixa escalabilidade, é a utilização do cache local (on-box) que possui característica in-process e, por consequência, utiliza da RAM local, assim possuindo limitações inerentes ao hardware e, principalmente, não compartilhando seu valor com outro servidor e/ou container.
Para maior parte dos cenários o modelo ideal ainda é o cache distribuído, como Redis ou Memcached.
Este artigo da AWS debate sobre os desafios e estratégias para cache.
Acesso a Dados
Para acesso a bancos de dados relacionais já temos ORMs muito bem difundidos e maduros, o que facilita drasticamente para o desenvolvedor. Exemplos categóricos são Hibernate e Entity Framework.
Criar uma camada de repositório genérica para abstrair todo acesso a dados é um trabalho delicado, pois o ORM já realiza este trabalho, inclusive abstraindo e entregando funcionalidades como Unit Of Work. Este ponto exige um cautela ciclópica, pois é uma linha tênue até o anti-pattern da camada de repositório genérico.
Há diversos materiais (aqui e aqui), e apenas sugiro que leia e pondere. Afinal, é um grande ponto de discussão já na comunidade.
Códigos de Erro
Além de possuir uma estrutura de resposta padrão para suas APIs é imprescindível ter uma identificar unicamente um erro negocial. Tenha uma estrutura que possua além das mensagens de erro também códigos.
Desta forma, caso necessário, terceiros, integrações e front-end conseguem fazer tratamentos específicos sem a necessidade de fazer condicionais sobre uma mensagem, por exemplo. Afinal, se seu software aplica internacionalização sobre mensagens, estas estarão traduzidas de acordo com idioma do seu usuário.
{
"errors":[{
"code": "lorem_ipsum",
"message": "lorem ipsum"
}]
}
Como sugestão utilize códigos de erro como string e, caso facilite, utilize algo que seja inteligível, facilitando identificação da origem pelo time, como no formato a seguir:
[namespace].[classe].[erro]
Exemplo:
suriani.domain.pricing.price_table_disabled
E que tal aplicar um ROT-13 para o retorno para o mundo externo (a ideia não é criptografar, é apenas evitar apego aos detalhes do código de erro)?

[Fonte: Wikipedia]
Exemplo com ROT-13:
fhevnav.qbznva.cevpvat.cevpr_gnoyr_qvfnoyrq
Retorno final (accept-language = pt-BR):
{
"errors":[{
"code": "fhevnav.qbznva.cevpvat.cevpr_gnoyr_qvfnoyrq",
"message": Tabela de preço não habilitada."
}]
}
Avalie a utilização conjunta com o padrão notification.
An object that collects together information about errors and other information in the domain layer and communicates it to the presentation.
Jobs & Schedulers
É muito comum a presença de rotinas que precisam ser executadas em background e possuir uma característica de agendamento com Cron.

Caso você utilize Kubernetes uma boa saída é simplesmente utilizar CronJobs. Porém para situações mais específicas, há ótimos produtos para integrar com sua aplicação como Quartz ou Hangfire.
Mensageria
Uma técnica comum para desacoplamento é utilização de message brokers (como RabbitMQ ou ActiveMQ) como meio de comunicação entre aplicações.
Indirecionar uma camada de comunicação com o message broker pode possuir benefícios como padronização das mensagens, tratamento de erros, estratégias de retry e adição de detalhes para trace distribuído.

Quando atuando em um software orientado a domínio, disponibilização de abstração para envio e consumo de eventos e comandos são imprescindíveis.
Por fim
O que citei até aqui são dicas e alguns pontos para apoio e direcionamento e, decerto, necessitam de aprofundamento - sem contar pontos que nem foram abordados por razões de brevidade.
Continuo acreditando que o caminho mais saudável e sensato ainda é criar um framework pelas razões certas.
Se você está em time que recebe demandas/requisitos para incrementar um framework de desenvolvimento, certifique-se que está entregando o valor necessário e, principalmente, que o que foi abstraído/indirecionado reduziu complexidade, pois as vezes podemos nos preocupar demais em entregar design patterns e acabamos entregando over-engineering.
Avalie e garanta que está aplicando dog-fooding (utilização de seu próprio produto), afinal se nem você utiliza seu próprio framework, como deseja que times utilizem?
Acredito muito na visão de que a gente deve investir tempo e esforço em coisas mais fundamentais.
O framework não deveria se propor a resolver tudo e nem a “ensinar” como um desenvolvedor deve ou não escrever seu código, no máximo direcionar pra que se mantenha um padrão dentro de cada projeto.
Tive algumas experiências com soluções que se propunham a ser um pouco mágicas e a resolver todos os problemas, o que na prática não se tornava uma verdade.
Em um mundo de micro-serviços ou ate do velho e bom monólito, o que mais vale é a independência de deploy e a inexistência de dependências externas ao meu ver.
Parabéns pelos artigos! Abraço!
Suriani, muito bom seu artigo. Como sempre mandando bem! Algumas ferramentas eu sinceramente não conhecia! Obrigado por compartilhar seu conhecimento! Abraços!