Featured image of post Quando preciso criar um SDK interno para minha organização?

Quando preciso criar um SDK interno para minha organização?

Como identificar quando a duplicação entre serviços já virou sinal para criar um SDK interno

Hoje estou em um projeto onde a gente tem trabalhado bastante na padronização e na criação de novos serviços. Em um refinamento técnico, no meio de uma discussão sobre repetição de código, alguém fez uma pergunta simples:

“Por que a gente ainda não tem uma biblioteca própria para isso?”

A pergunta ficou na cabeça porque ela tocava em um problema real. Em algum momento, a arquitetura cresce, os serviços se multiplicam e algumas coisas começam a aparecer em todo canto: logs, autenticação, autorização, clientes HTTP, integrações internas, tratamento de erro, observabilidade.

Quando isso acontece, a dúvida deixa de ser “será que dá para criar uma lib?” e passa a ser “quanto está custando não ter uma?”.

Antes de seguir, vale alinhar o que estou chamando de SDK aqui.

Um SDK, Software Development Kit, é um conjunto de bibliotecas, utilitários e padrões que ajudam os times a implementar funcionalidades recorrentes sem começar do zero em cada serviço. No nosso caso, a ideia era concentrar o que sempre se repetia e entregar isso de um jeito simples de instalar, versionar e evoluir.

Não era sobre criar uma biblioteca “bonita” por vaidade técnica. Era sobre reduzir atrito no dia a dia.

O cenário que motivou a criação

Nossa quantidade de microserviços começou a crescer rápido. Com isso, alguns problemas ficaram difíceis de ignorar.

Problemas identificados

  • Código repetido em vários serviços: muita coisa aparecia de novo em cada repositório: logs, autenticação, autorização, clientes para APIs internas e pequenos utilitários.

  • Copy and paste na criação de novos projetos: para iniciar um serviço, era comum buscar trechos em outro repositório e adaptar manualmente.

  • Manutenção espalhada: uma mudança simples em uma regra comum podia virar várias alterações em serviços diferentes.

  • Padrões parecidos, mas não iguais: cada serviço resolvia o mesmo problema com pequenas diferenças. No começo parece inofensivo. Depois vira ruído para manter, debugar e evoluir.

Impactos negativos

Na prática, isso gerava alguns efeitos bem concretos:

  • mais tempo para subir um serviço novo;
  • retrabalho em tarefas que já deveriam estar resolvidas;
  • inconsistência entre aplicações;
  • mais risco em mudanças transversais;
  • onboarding mais lento para quem entrava no time.

A decisão e os benefícios do SDK

A partir daí, fez sentido criar um SDK interno: um repositório versionado com módulos reutilizáveis para os problemas que apareciam com frequência.

O ponto principal foi definir bem o que entraria nele. Um SDK interno não pode virar uma pasta compartilhada para qualquer coisa. Se tudo entra, ele deixa de ser uma base útil e vira acoplamento disfarçado.

Começamos pelo que tinha repetição clara e valor imediato:

  • envio e formatação de logs;
  • padrões de observabilidade;
  • clientes para integrações internas;
  • utilitários usados por mais de um serviço;
  • contratos e tipos compartilhados quando fazia sentido.

Com isso, os ganhos começaram a aparecer de forma bem prática.

  • Manutenção em um lugar só: uma correção no SDK pode ser entregue para todos os serviços por meio de uma nova versão.
  • Novos serviços começam mais rápido: o time não precisa remontar a mesma base toda vez.
  • Padrões ficam mais fáceis de seguir: logs, métricas e integrações passam a ter uma forma recomendada de uso.
  • Mudanças transversais ficam menos dolorosas: em vez de abrir vários PRs com código quase igual, a evolução acontece primeiro no pacote.

Distribuição e versionamento

Para distribuir o SDK internamente, usamos o Azure Artifacts como repositório privado de pacotes. Isso deixou a instalação parecida com qualquer outra dependência do ecossistema Node.js.

A estratégia ficou assim:

  • cada release do SDK é publicada no Azure Artifacts;
  • os serviços instalam a dependência via npm install;
  • o versionamento permite controlar quando cada serviço atualiza;
  • o pipeline de CI/CD publica novas versões sem depender de processo manual.

No package.json de um serviço, o uso fica simples:

{
  "dependencies": {
    "@org/opentrips-sdk": "^1.3.0"
  }
}

Desafios encontrados

Criar um SDK interno ajuda bastante. Ao mesmo tempo, ele traz responsabilidades novas. Algumas delas apareceram logo no começo.

Principais obstáculos

  • Generalizar sem engessar: o SDK precisa atender casos reais, mas não pode tentar prever todos os cenários possíveis. Quando a abstração tenta ser esperta demais, ela atrapalha.

  • Cuidar das dependências: uma dependência problemática no SDK pode impactar vários serviços. Atualização, compatibilidade e segurança passam a exigir mais atenção.

  • Lidar com mais de uma stack: hoje usamos principalmente Node.js e Java, mas o SDK nasceu para Node.js. Isso levanta uma discussão importante: criar SDKs equivalentes para outras linguagens ou reforçar a padronização da stack?

  • Evitar dependência excessiva: o SDK deve facilitar a vida dos serviços, não impedir que eles evoluam. Se uma alteração simples exige mexer no SDK toda hora, talvez a fronteira esteja errada.

Exemplo prático de uso

Um dos primeiros módulos que criamos foi o cliente de logs. A ideia era esconder detalhes de envio, formato e tratamento de resposta, deixando o uso simples dentro dos serviços.

import { createLogsClient } from 'opentrips-sdk/logs';

// Criar cliente de logs
const logs = createLogsClient({
  host: 'http://localhost:8080',
  // apiKey: process.env.LOGS_API_KEY  // Opcional
});

// Enviar log
const result = await logs.createLog({
  provider: 'clickbus',
  type: 'response',
  payload: '<?xml version="1.0" encoding="UTF-8"?><empresa>...</empresa>',
  correlationId: '038473a4-db4a-405a-b759-e52d035b3a0d',
  operation: 'reserva',
  resource: 'rodoviario',
  extra: { teste: 'teste' }
});

if (result.ok) {
  console.log('Log enviado com sucesso');
} else {
  console.error('Erro ao enviar log:', result.error);
}

Arquitetura e estrutura do projeto

Organizamos o SDK em módulos independentes. Cada módulo tem sua própria documentação, exemplos e testes. Isso ajuda a manter uma fronteira clara entre as responsabilidades.

src/
├── index.ts                    # Ponto de entrada principal
├── logs/                       # Módulo de Logs
│   ├── README.md
│   ├── example.ts
│   ├── index.ts
│   ├── client.ts
│   ├── http.ts
│   ├── types.ts
│   ├── utils.ts
│   └── __tests__/
├── observability/              # Módulo de Observabilidade
│   ├── README.md
│   ├── index.ts
│   ├── client.ts
│   ├── middleware.ts
│   ├── metrics-endpoint.ts
│   └── types.ts
└── [novos-módulos]/            # Futuros módulos...

Conclusão

Um SDK interno começa a fazer sentido quando a repetição deixou de ser só incômodo e virou custo real.

Para mim, alguns sinais são bons indicadores:

  • vários serviços repetem a mesma implementação;
  • mudanças comuns precisam ser aplicadas em muitos repositórios;
  • novos projetos começam com muito código copiado;
  • padrões importantes, como logs e observabilidade, variam demais entre serviços;
  • o time perde tempo resolvendo problemas que já deveriam estar encapsulados.

Também é importante lembrar que SDK interno não é solução mágica. Ele precisa de dono, versionamento, documentação e critério para aceitar novos módulos. Sem isso, o problema só muda de lugar.

No nosso caso, a decisão fez sentido porque a duplicação já estava cobrando caro. Centralizar as partes certas ajudou a reduzir retrabalho, melhorar a consistência entre serviços e deixar o caminho mais curto para novos projetos.

Criado com Hugo
Tema Stack desenvolvido por Jimmy