ENGENHARIA DE SOFTWARE · LEITURA DE ~12 MIN
Microsserviços na prática:
entendendo a arquitetura com Node.js e TypeScript
Pense em uma plataforma de IA. Em vez de colocar usuários, conversas, arquivos e requisições a modelos no mesmo servidor, cada parte vira um serviço separado.
SEÇÃO 01
O que é arquitetura de microsserviços
Arquitetura de microsserviços é uma forma de estruturar um sistema como um conjunto de serviços pequenos e independentes, em vez de uma única aplicação.
Cada serviço roda no seu próprio processo, tem sua própria responsabilidade e se comunica com os outros por API.
SEÇÃO 02
Exemplo de aplicação monolítica
Tudo está dentro do mesmo projeto:
import express from "express";
const app = express();
app.use(express.json());
const users = [
{ id: 1, name: "Marcelo" }
];
const conversations = [
{ id: 1, title: "Onboarding" }
];
const messages: any[] = [];
const files: any[] = [];
app.get("/users/:id", (req, res) => {
const user = users.find(
user => user.id === Number(req.params.id)
);
res.json(user);
});
app.get("/conversations/:id", (req, res) => {
const conversation = conversations.find(
conversation => conversation.id === Number(req.params.id)
);
res.json(conversation);
});
app.post("/conversations/:id/messages", (req, res) => {
const message = {
id: messages.length + 1,
conversationId: Number(req.params.id),
content: req.body.content
};
messages.push(message);
res.status(201).json(message);
});
app.post("/files", (req, res) => {
const file = {
id: files.length + 1,
name: req.body.name,
size: req.body.size
};
files.push(file);
res.status(201).json(file);
});
app.listen(3000);
Aqui, tudo roda junto:
Aplicação
├── Usuários
├── Conversas
├── Arquivos
└── Requisições a modelo
Se você alterar como as mensagens são geradas, normalmente precisa publicar novamente a aplicação inteira.
SEÇÃO 03
Separação em User, Conversation, File e Model Gateway
Agora dividimos o sistema em quatro aplicações independentes:
User Service porta 3001
Conversation Service porta 3002
File Service porta 3003
Model Gateway porta 3004
Cada serviço possui sua própria responsabilidade.
import express from "express";
const app = express();
const users = [
{ id: 1, name: "Marcelo" },
{ id: 2, name: "Ana" }
];
app.get("/users/:id", (req, res) => {
const user = users.find(
user => user.id === Number(req.params.id)
);
if (!user) {
return res.status(404).json({
message: "User not found"
});
}
res.json(user);
});
app.listen(3001, () => {
console.log("User Service running on port 3001");
});
Responsabilidade:
Criar, consultar e atualizar usuários
import express from "express";
const app = express();
app.use(express.json());
const files: any[] = [];
app.post("/files", (req, res) => {
const file = {
id: files.length + 1,
name: req.body.name,
size: req.body.size
};
files.push(file);
res.status(201).json(file);
});
app.listen(3003, () => {
console.log("File Service running on port 3003");
});
Responsabilidade:
Armazenar e servir arquivos enviados pelos usuários
import express from "express";
const app = express();
app.use(express.json());
app.post("/generate", async (req, res) => {
const { prompt } = req.body;
// Em produção, isso chama um provedor de IA
// (OpenAI, Anthropic etc.) por trás dos panos.
const reply = {
text: `Reply to: ${prompt}`
};
res.json(reply);
});
app.listen(3004, () => {
console.log("Model Gateway running on port 3004");
});
Responsabilidade:
Rotear requisições para provedores de modelos de IA
O serviço de conversas precisa conversar com os outros serviços por APIs.
import express from "express";
const app = express();
app.use(express.json());
const conversations = [
{ id: 1, title: "Onboarding" }
];
const messages: any[] = [];
app.get("/conversations/:id", (req, res) => {
const conversation = conversations.find(
conversation => conversation.id === Number(req.params.id)
);
if (!conversation) {
return res.status(404).json({
message: "Conversation not found"
});
}
res.json(conversation);
});
app.post("/conversations/:id/messages", async (req, res) => {
const { userId, content } = req.body;
const userResponse = await fetch(
`http://localhost:3001/users/${userId}`
);
if (!userResponse.ok) {
return res.status(400).json({
message: "Invalid user"
});
}
const modelResponse = await fetch(
"http://localhost:3004/generate",
{ method: "POST", body: JSON.stringify({ prompt: content }) }
);
if (!modelResponse.ok) {
return res.status(502).json({
message: "Model request failed"
});
}
const user = await userResponse.json();
const reply = await modelResponse.json();
const message = {
id: messages.length + 1,
conversationId: Number(req.params.id),
user,
content,
reply: reply.text
};
messages.push(message);
res.status(201).json(message);
});
app.listen(3002, () => {
console.log("Conversation Service running on port 3002");
});
SEÇÃO 04
Comunicação entre serviços por API
Em produção, os clientes não chamam cada serviço diretamente. Um API Gateway fica na frente de tudo e roteia cada requisição para o serviço certo:
Atrás do API Gateway, os serviços continuam se chamando diretamente quando precisam de dados uns dos outros — como o Conversation Service perguntando ao User Service e ao Model Gateway antes de responder a uma mensagem. Quando alguém envia uma mensagem:
POST http://localhost:8080/conversations/1/messages
Content-Type: application/json
{
"userId": 1,
"content": "Hello!"
}
O fluxo é:
Cliente
│
▼
API Gateway
│
▼
Conversation Service
│
├── GET User Service
│
└── POST Model Gateway
A resposta poderia ser:
{
"id": 1,
"conversationId": 1,
"user": {
"id": 1,
"name": "Marcelo"
},
"content": "Hello!",
"reply": "Reply to: Hello!"
}
SEÇÃO 05
Independência de deploy e escalabilidade
Cada serviço pode ser desenvolvido e publicado separadamente.
User Service
- Node.js
- PostgreSQL
- 2 servidores
File Service
- Go
- Armazenamento de objetos
- 3 servidores
Conversation Service
- Python
- PostgreSQL
- 10 servidores
Model Gateway
- Node.js
- Sem banco de dados
- 5 servidores
Não é obrigatório usar tecnologias diferentes, mas é possível.
Se o serviço de conversas recebe muito mais tráfego, você escala somente ele:
User Service
└── 1 instância
File Service
└── 1 instância
Conversation Service
├── instância 1
├── instância 2
├── instância 3
└── instância 4
SEÇÃO 06
Baixo acoplamento
O serviço de conversas não precisa conhecer o código interno do serviço de usuários.
Ele conhece apenas a API:
GET /users/1
O User Service pode mudar completamente internamente:
// Antes
const user = users.find(...);
// Depois
const user = await database.users.findById(id);
Enquanto a API continuar retornando o mesmo formato, o Conversation Service não precisa ser alterado.
SEÇÃO 07
Banco de dados por serviço
Normalmente, sim:
User Service
└── User Database
Conversation Service
└── Conversation Database
File Service
└── File Storage
Model Gateway
└── Model Providers (externo)
Nem todo serviço tem banco próprio. O Model Gateway não guarda nada — ele existe só para abstrair provedores externos de IA, para que o resto do sistema nunca precise saber qual provedor está por trás.
O Conversation Service não deveria acessar diretamente o banco do User Service:
// Evitar
const user = await userDatabase.findById(userId);
O correto seria chamar a API:
const response = await fetch(
`http://user-service/users/${userId}`
);
Assim, o banco de usuários continua sendo responsabilidade exclusiva do User Service.
SEÇÃO 08
Microsserviços versus monólito modular
Microservices não significa apenas separar arquivos:
src/
├── users/
├── conversations/
├── files/
└── model-gateway/
Isso ainda pode ser um monólito modular.
Microsserviços são aplicações realmente separadas:
projects/
├── user-service/
│ ├── package.json
│ ├── Dockerfile
│ └── src/
│
├── conversation-service/
│ ├── package.json
│ ├── Dockerfile
│ └── src/
│
├── file-service/
│ ├── package.json
│ ├── Dockerfile
│ └── src/
│
└── model-gateway/
├── package.json
├── Dockerfile
└── src/
Cada uma pode ter:
- servidor próprio;
- banco próprio;
- repositório próprio;
- Docker próprio;
- deploy próprio;
- equipe responsável.
SEÇÃO 09
Vantagens, desvantagens e quando usar
VANTAGENS
- deploy independente por serviço;
- escalabilidade independente — só escala o serviço que precisa;
- liberdade para usar stacks diferentes por serviço;
- falha isolada não derruba o sistema inteiro necessariamente.
DESVANTAGENS
- comunicação por rede adiciona latência e pontos de falha;
- consistência de dados fica mais difícil, sem transação única entre bancos;
- mais infraestrutura para operar: múltiplos deploys, monitoramento, logs distribuídos;
- exige mais maturidade de DevOps e observabilidade.
Monólito costuma ser suficiente para times pequenos ou produtos em estágio inicial. Microsserviços fazem mais sentido quando partes do sistema — como um motor de conversas que precisa escalar de repente, ou um gateway de modelo que precisa trocar de provedor — precisam escalar, evoluir ou ser publicadas de forma diferente umas das outras.
SEÇÃO 10
Conclusão
A ideia principal é:
Monólito:
um sistema grande com várias responsabilidades.
Microsserviços:
vários sistemas pequenos, cada um com uma responsabilidade,
comunicando-se por APIs ou eventos.
FIM · OBRIGADO POR LER
Quer entender a arquitetura completa?
O artigo de System Design junta cache, filas, resiliência e observabilidade num guia interativo só.