Dirora
Voltar ao blog
Engineering

Como Construímos a Nossa Arquitetura de Microsserviços

Dirora Team10 de abril de 20268 min read

A Dirora é API-first, construída sobre mais de 40 microsserviços em Go — um serviço por domínio, cada um responsável por uma fatia específica da plataforma de comércio: produtos, encomendas, pagamentos, lojas, subscrições, análises e muito mais. Este artigo é uma visita honesta e de alto nível às decisões por trás desse backend: porque escolhemos as ferramentas que escolhemos, como impedimos que dezenas de peças em movimento se transformem em caos, e o que diríamos a qualquer equipa que construa um produto SaaS multitenant de raiz. Está escrito para engenheiros curiosos sobre o que está debaixo do capô, mas deverá fazer sentido mesmo que nunca tenha escrito uma linha de Go.

Nada disto é arquitetura pela arquitetura. Todas as escolhas abaixo remontam a algo que um comerciante sente: páginas que carregam depressa, finalizações que não cobram a dobrar, carregamentos que nunca ficam sem espaço, e uma plataforma que continua a funcionar enquanto lançamos alterações por baixo dela. Se quiser ver como esses alicerces se traduzem no que pode realmente fazer com uma loja, a nossa visão geral das funcionalidades é o companheiro deste artigo, virado para o comerciante.

Porquê o Go?

No início avaliámos a sério três linguagens — Node.js, Rust e Go — porque a escolha do runtime molda tudo o que se segue. Cada uma tinha forças genuínas, e nenhuma era uma perdedora óbvia.

  • O Node.js tem um ecossistema enorme, uma bolsa de contratação fácil e uma sintaxe familiar a quem já escreveu código de frontend. Mas o trabalho intensivo em CPU — processamento de imagens, encriptação, geração de grandes relatórios — compete com o tratamento de pedidos num ciclo de eventos de thread único, e a falta de segurança de tipos em runtime tende a manifestar-se como bugs subtis em produção em vez de erros em tempo de compilação.

  • O Rust oferece um desempenho de topo e segurança de memória sem coletor de lixo. Os compromissos, na nossa fase, eram tempos de compilação mais lentos, uma curva de aprendizagem mais acentuada para novos colaboradores e um ecossistema assíncrono que ainda estava a assentar quando começámos. Para um núcleo de sistemas, o Rust é soberbo; para uma grande superfície de lógica de negócio corrente que muda semanalmente, o custo pareceu-nos alto.

  • O Go deu-nos um modelo de concorrência genuinamente agradável de usar — as goroutines são baratas o suficiente para se poderem lançar milhares sem pensar em pools de threads — mais tempos de compilação rápidos e uma linguagem deliberadamente pequena que os novos engenheiros conseguem ler e para a qual conseguem contribuir em poucos dias. A sua biblioteca padrão cobre a maior parte do que um backend web precisa sem puxar uma dependência para cada tarefa.

O Go venceu porque otimiza para aquilo que mais importa quando se constrói em largura e se avança depressa: velocidade de desenvolvimento sem abdicar do desempenho em runtime. Uma base de código pequena e legível que um novo engenheiro consegue navegar ao segundo dia vale mais, ao longo da vida de um produto, do que espremer os últimos pontos percentuais de débito. Esse backend em Go de alto desempenho é também a razão pela qual as nossas lojas renderizam depressa sob carga — algo que aprofundamos no nosso guia sobre otimização do desempenho da loja.

Um serviço por domínio

O princípio organizador central é simples: cada serviço possui exatamente um domínio de negócio, e possui o seu próprio esquema de base de dados. O serviço de produtos possui os produtos; o serviço de encomendas possui as encomendas; os pagamentos, as subscrições, a renderização da loja, as análises e os restantes vivem cada um por trás da sua própria fronteira. Nenhum serviço mete a mão nas tabelas de outro serviço. Se o serviço de encomendas precisa de dados de produto, pede-os ao serviço de produtos através de uma interface definida — nunca executa uma consulta diretamente às tabelas de produtos.

Este isolamento é a disciplina que mantém um sistema em crescimento sustentável. Como ninguém pode depender discretamente da forma interna das tabelas de outrem, podemos reestruturar o armazenamento de um serviço, alterar os seus índices ou reformular a sua lógica sem quebrar cinco outros serviços por acidente. A fronteira é o contrato. Custa um pouco mais à partida — escreve-se uma API onde um monólito escreveria um join — mas compensa sempre que um domínio precisa de evoluir de forma independente. Como a plataforma é API-first, esses mesmos serviços de domínio são expostos aos programadores através de uma API REST pública, webhooks neutros quanto ao fornecedor, uma CLI e o editor de temas — as aplicações de loja e de administração são apenas clientes da mesma API contra a qual pode desenvolver. Se está a construir sobre a Dirora, o nosso artigo sobre comércio API-first e headless aborda esse lado virado para o programador.

Como os serviços conversam entre si

Os serviços comunicam sobre HTTP usando JSON, autenticados para as chamadas internas de serviço para serviço com uma chave interna partilhada. Escolhemos deliberadamente o REST em vez de protocolos binários mais pesados, pelo menos enquanto o sistema estava a tomar forma: cada endpoint é inspecionável com ferramentas comuns, fácil de compreender e simples de depurar quando algo se comporta mal às 2 da manhã. Há um valor real em poder reproduzir manualmente uma chamada de produção em vez de precisar de ferramentas especiais para a descodificar.

Onde dois serviços precisam genuinamente de se manter sincronizados — uma encomenda a ser paga, uma subscrição a renovar — apoiamo-nos em eventos e webhooks em vez de cadeias de chamadas apertadas e síncronas. A regra à qual voltamos sempre é que um pedido não deve espalhar-se numa longa cadeia de dependências em que um serviço lento arrasta tudo o que está a montante dele. O acoplamento fraco não é apenas um chavão de arquitetura; é o que impede que um soluço num canto da plataforma se torne uma falha em todo o lado.

Acesso a dados com segurança de tipos e migrações versionadas

Para o acesso à base de dados, geramos código Go com segurança de tipos a partir de SQL escrito à mão, em vez de recorrer a um ORM pesado. Escrever o SQL nós próprios mantém as consultas transparentes e afináveis, enquanto a geração de código apanha classes inteiras de erros — uma coluna renomeada, um tipo errado, um argumento em falta — em tempo de compilação em vez de em produção. É o ponto ideal entre as consultas em string bruta e um ORM que esconde o que está realmente a acontecer.

As alterações de esquema passam por migrações versionadas e apenas para a frente. Cada alteração à base de dados é um passo numerado e revisto que pode ser aplicado de forma previsível em todos os ambientes, por isso o esquema à frente de um cliente nunca é um mistério. Combinado com a posse por serviço, isto significa que cada equipa pode evoluir o seu próprio modelo de dados ao seu próprio ritmo sem um lançamento coordenado gigante.

Multitenancy sem o problema do vizinho barulhento

A Dirora é multitenant por conceção: muitas lojas independentes partilham a mesma plataforma mantendo-se estritamente isoladas umas das outras. Acertar nesse isolamento — para que uma loja movimentada nunca possa ver, abrandar ou afetar outra — é uma das partes mais difíceis e importantes do sistema, e merece o seu próprio tratamento. Escrevemos sobre o modelo em detalhe em como lidamos com a multitenancy, que explica como a identidade do tenant flui através da pilha e como mantemos os dados de cada loja isolados.

A mesma conceção permite que uma única loja opere além-fronteiras — múltiplas moedas, múltiplos idiomas e lojas localizadas — sem bifurcar os serviços subjacentes. Se isso for relevante para o seu negócio, o nosso guia multimoeda e multilíngue e o artigo sobre tradução por IA num clique mostram como isso fica do lado do comerciante.

Arquitetura de armazenamento

Todos os ficheiros carregados — imagens de produtos, recursos de temas, transferências digitais — vivem em armazenamento de objetos privado compatível com S3, e não no disco de um único servidor. A conceção tem consciência da capacidade: os recursos de cada loja estão logicamente separados, e um passo de seleção encaminha os novos carregamentos para o armazenamento com mais margem disponível, para que possamos aumentar a capacidade de forma suave à medida que os comerciantes acrescentam mais produtos e multimédia. Os recursos ao nível do sistema são mantidos separados dos dados do tenant.

A consequência prática para os comerciantes é que o armazenamento não é uma parede contra a qual embate. Carregar imagens não compete com o tráfego ativo da sua loja, os ficheiros são servidos de forma eficiente, e a plataforma consegue escalar a sua pegada de armazenamento nos bastidores sem tempo de inatividade nem uma migração que alguma vez repare. A otimização automática de imagens assenta por cima disto para que as grandes fotografias que carrega sejam servidas em versões de tamanho adequado e carregamento rápido.

Renderizar lojas depressa

As lojas viradas para o cliente são renderizadas no servidor, o que significa que a primeira coisa que o navegador de um comprador recebe é HTML real e pronto a mostrar, e não uma página em branco à espera do JavaScript. Isso importa por duas razões: é mais rápido para o comprador, e é legível para os motores de busca, o que é grande parte da razão pela qual as lojas da Dirora vêm com fortes alicerces de SEO e dados estruturados integrados. A velocidade não é uma métrica de vaidade no comércio — está diretamente ligada a quantos visitantes se tornam compradores.

Observabilidade e fiabilidade

Não se consegue operar um sistema distribuído que não se consegue ver, por isso as métricas, o registo estruturado e o rastreio de erros fizeram parte da plataforma desde cedo, em vez de serem aparafusados mais tarde. Quando algo corre mal numa dúzia de serviços, a diferença entre uma correção de cinco minutos e uma investigação de cinco horas é quase inteiramente se investiu em observabilidade antes de precisar dela.

A fiabilidade também vem de uma disciplina simples e pouco glamorosa: tornar tudo idempotente. Os manipuladores de webhooks, os trabalhos em segundo plano, os retornos de chamada de pagamento e as migrações são todos escritos de forma a que executá-los duas vezes seja seguro. As redes repetem, as mensagens são reentregues e os utilizadores fazem duplo clique. Assumir que cada operação pode acontecer mais do que uma vez — e garantir que não faz mal quando acontece — é o que evita encomendas duplicadas, cobranças a dobrar e estados corrompidos.

O que isto significa para os comerciantes

A arquitetura é invisível quando funciona, o que é precisamente o objetivo. A recompensa de tudo o que foi dito acima é uma plataforma que se mantém rápida sob carga, mantém os dados de cada loja isolados e nos permite lançar melhorias continuamente sem lhe pedir para migrar nada. É também o que nos permite manter o nosso modelo comercial limpo: a Dirora não cobra comissões de transação em nenhum plano. A única fatia que retiramos é uma pequena taxa de plataforma que diminui à medida que cresce — 1,5 % no plano Starter gratuito, 0,75 % no Pro, 0,25 % no Business e 0 % no Enterprise — por isso, quanto mais vende, menos retiramos. A boa engenharia é o que torna um modelo de preços justo e simples sustentável, em vez de uma promessa de marketing que não conseguimos cumprir.

Lições aprendidas

  1. Invista em alicerces partilhados. Uma biblioteca interna comum para middleware, tratamento de erros, auxiliares de base de dados, armazenamento e verificações de saúde significa que cada serviço se comporta de forma consistente. À escala, a consistência entre serviços vale mais do que a independência de cada equipa.

  2. Prefira o acesso a dados gerado e com segurança de tipos. Escrever SQL à mão e gerar código tipado a partir dele é mais rápido de trabalhar e mais seguro do que as consultas em string bruta ou um ORM pesado.

  3. Acrescente observabilidade antes de precisar dela. Métricas, registos estruturados e rastreio de erros desde o primeiro dia poupam muito mais tempo do que custam. Não se consegue depurar o que não se consegue ver.

  4. Torne tudo idempotente. Se uma operação pode ser executada duas vezes — e num sistema distribuído acabará por ser — faça com que executá-la duas vezes seja inofensivo.

  5. Trace fronteiras e defenda-as. Um domínio por serviço, um esquema por serviço, sem meter a mão por cima da parede. A disciplina é irritante ao primeiro dia e valiosíssima ao ducentésimo.

Se quiser ver a plataforma que estas escolhas produziram — do editor visual de temas aos domínios personalizados com SSL automático — a página de funcionalidades é o lugar por onde começar, e se está a ponderar-nos face a alternativas, a nossa honesta comparação de plataformas apresenta os compromissos.

Perguntas frequentes

Em que linguagem está escrito o backend da Dirora?

O backend da Dirora está construído em Go, organizado como uma plataforma API-first de mais de 40 microsserviços — um por domínio — contra a qual os programadores podem desenvolver através de uma API REST pública. Escolhemos o Go pela sua concorrência leve, tempos de compilação rápidos e base de código legível, que em conjunto nos permitem construir uma plataforma ampla rapidamente sem sacrificar o desempenho em runtime.

Porquê usar microsserviços em vez de uma única aplicação?

Dividir a plataforma em mais de 40 serviços por domínio — produtos, encomendas, pagamentos, lojas e por aí fora — significa que cada parte pode evoluir, escalar e ser corrigida de forma independente sem pôr o resto em risco. Cada serviço possui o seu próprio esquema de base de dados e nunca consulta diretamente as tabelas de outro serviço, o que mantém o sistema sustentável à medida que cresce e nos permite expor toda a plataforma através de uma única API REST pública consistente.

Onde são armazenadas as imagens e os ficheiros da minha loja?

Os ficheiros carregados vivem em armazenamento de objetos privado compatível com S3, com os recursos de cada loja logicamente separados e a capacidade gerida automaticamente. O armazenamento escala nos bastidores, por isso carregar mais produtos e multimédia nunca compete com o tráfego ativo da sua loja.

Como é que a Dirora mantém as diferentes lojas isoladas umas das outras?

A Dirora é multitenant por conceção, com isolamento estrito para que uma loja nunca possa ver nem afetar os dados ou o desempenho de outra. Abordamos os pormenores no nosso artigo dedicado sobre como lidamos com a multitenancy.

Esta arquitetura afeta o que pago?

Sim, de uma boa forma. A Dirora não cobra comissões de transação em nenhum plano. A única fatia é uma pequena taxa de plataforma que diminui à medida que cresce — 1,5 % no Starter, 0,75 % no Pro, 0,25 % no Business e 0 % no Enterprise. A engenharia eficiente é o que torna esse preço sustentável.

architecturegomicroservices

Pronto para criar a sua loja?

Comece gratuitamente — não é necessário cartão de crédito.

Começar