Implementações
Implementações de Microsserviços
Pessoal até aqui tudo muito bom, tudo muito bonito, conseguimos discutir diversos pontos conceituais sobre a aplicação de microsserviços. Agora como implementamos?
Pessoal vamos avaliar algumas formas de realizar essas implementações. Vale lembrar que existem diversas formas de implementar microsserviços, e a escolha da melhor abordagem depende do contexto e dos requisitos do projeto.
Hello World Microsserviços
Vamos criar três serviços e acessar eles através de um gateway.
Pessoal vamos discultir diversos novos conceitos aqui. Fiquem tranquilos e vamos em conjunto avaliando eles.

Vamos criar um diretório chamado hello-world-microservices e dentro dele vamos criar três diretórios: service1, service2 e service3. Todo esse material estará no repositório do módulo.
mkdir hello-world-microservices
cd hello-world-microservices
mkdir service1
mkdir service2
mkdir service3
Para cada um dos serviços, vamos criar um arquivo Python para implementar um servidor HTTP simples que responde a uma solicitação GET com uma mensagem de "Olá, Mundo!" e o nome do serviço. Vamos utilizar o FastAPI para criar os servidores.
# service1/server.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/service1")
async def read_root():
return {"message": "Hello, World! from service1"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8001)
O código será o mesmo para os outros serviços, apenas mudando o nome do serviço. Vale destacar que vamos utilizar o uvicorn para rodar os servidores. Por hora, vamos rodar cada um dos serviços em uma porta diferente, utilizando a porta 8001 para o service1, a porta 8002 para o service2 e a porta 8003 para o service3.
Vamos ajustar o arquivo de requirements.txt para cada um dos serviços.
fastapi
uvicorn
Vamos tomar cuidado com as versões das bibliotecas que estamos utilizando. Sempre é interessante deixar essas versões especificadas. Vamos alterar o arquivo requirements.txt para cada um dos serviços.
Novo arquivo requirements.txt para o service1 (depois da bronca).
fastapi==0.110.3
uvicorn==0.29.0
Agora, antes de rodarmos nossos serviços, vamos criar um dockerfile para cada um dos serviços.
# service1/Dockerfile
FROM python:3.8-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "server.py"]
Agora vamos construir nossa imagem e rodar nosso container.
docker build -t service1 .
docker run -d -p 8001:8001 service1
Vamos ter o nosso serviço rodando e agora é possível acessar ele através do endereço http://localhost:8001/service1.
Vamos fazer o mesmo para os outros serviços.
Poxa Murilo vamos ter que fazer isso para todos os serviços? Não tem uma forma mais fácil?
Ahhhh tem sim, vamos utilizar o docker-compose para facilitar nossa vida.
Vamos construir um arquivo docker-compose.yml na raiz do nosso projeto. A funcionalidade dele vai ser construir e rodar todos os nossos serviços.
version: '3'
services:
service1:
build: ./service1
ports:
- "8001:8001"
service2:
build: ./service2
ports:
- "8002:8002"
service3:
build: ./service3
ports:
- "8003:8003"
O que está acontecendo neste docker-compose é que estamos construindo e rodando os nossos serviços. Vamos rodar o comando docker-compose up na raiz do nosso projeto.
docker-compose up
Legal agora todos os nossos serviços estão rodando e podemos acessar eles através dos endereços http://localhost:8001/service1, http://localhost:8002/service2 e http://localhost:8003/service3.
Gateway
Pessoal, vamos analisar uma coisa aqui. Nós temos três serviços rodando e cada um deles em uma porta diferente. Como podemos acessar todos esses serviços através de um único ponto de entrada?
Murilo como assim? É só utilizar o endereço e a porta de cada um dos serviços.
Sim, você está correto, mas imagine que você tenha um serviço que chama outros serviços e você quer que ele seja o único ponto de entrada para acessar todos os outros serviços. Ainda temos outro problema aqui, quando a nossa quantidade de serviços aumentar vamos ter que ficar atualizando o nosso cliente para acessar os novos serviços. Isso não parece ser uma boa prática. Para resolver este tipo de problema, podemos utilizar um gateway.
Pessoal sugiro fortemente a leitura do artigo: O padrão de gateway de API versus a comunicação direta cliente-microsserviço.
Além dele, vamos utilizar essa referência de API Gateway.
Vamos realizar a implementação do nosso gateway. Vamos criar um novo diretório chamado gateway e dentro dele vamos copiar os arquivos Dockerfile, server.py e requirements.txt dos nossos serviços.
Agora vamos configurar nosso gateway. Para isso vamos utilizar o Nginx para fazer o roteamento das requisições para os nossos serviços.
O Nginx é um servidor web de código aberto que também pode ser usado como um proxy reverso, balanceador de carga, cache HTTP e muito mais. Ele é conhecido por sua alta performance, estabilidade, recursos ricos e baixo uso de recursos.
Vamos construir o nosso Dockerfile para o gateway.
# gateway/Dockerfile
FROM nginx:1.19.0
COPY nginx.conf /etc/nginx/nginx.conf
Repare que estamos fazendo levando o arquivo nginx.conf para o diretório /etc/nginx/nginx.conf do container. Dentro do arquivo nginx.conf vamos configurar o nosso gateway.
# gateway/nginx.conf
worker_processes 1;
events { worker_connections 1024; }
http {
sendfile on;
upstream service1 {
server service1:8001;
}
upstream service2 {
server service2:8002;
}
upstream service3 {
server service3:8003;
}
server {
listen 80;
location /service1 {
proxy_pass http://service1;
}
location /service2 {
proxy_pass http://service2;
}
location /service3 {
proxy_pass http://service3;
}
}
}
Vamos compreender o que está acontecendo aqui:
- Estamos configurando o
Nginxpara escutar na porta80. - Estamos configurando o
Nginxpara fazer o roteamento das requisições para os serviçosservice1,service2eservice3.
Agora vamos construir a nossa imagem e rodar o nosso container.
docker build -t gateway .
docker run -d -p 8000:80 gateway
Agora podemos acessar todos os nossos serviços através do endereço http://localhost:8000/service1, http://localhost:8000/service2 e http://localhost:8000/service3.
Vamos adicionar nosso serviço no nosso arquivo docker-compose.yml.
version: '3'
services:
service1:
build: ./service1
ports:
- "8001:8001"
service2:
build: ./service2
ports:
- "8002:8002"
service3:
build: ./service3
ports:
- "8003:8003"
gateway:
build: ./gateway
ports:
- "8000:80"
Pessoal reparem no que aconteceu aqui. Nós temos um gateway que é o único ponto de entrada para acessar todos os nossos serviços. Isso é muito interessante, pois podemos adicionar novos servi ços sem precisar atualizar o nosso cliente.
Ponto de Discussão
Pessoal, vamos avaliar um ponto que vale a nossa atenção: considerando a abordagem e as definições que discultimos até aqui, se um microsserviço é uma entindade de software independente, como ficam os bancos de dados?
Murilo, cada microsserviço tem o seu próprio banco de dados?
Essa resposta é mais complexa do que parece. Vamos avaliar algumas abordagens:
-
Banco de Dados Compartilhado: Todos os microsserviços compartilham o mesmo banco de dados. Isso pode simplificar a implementação, mas pode levar a problemas de escalabilidade e acoplamento.
-
Banco de Dados por Microsserviço: Cada microsserviço tem seu próprio banco de dados. Isso pode aumentar a complexidade, mas também pode melhorar a escalabilidade e a autonomia dos serviços.
-
Banco de Dados por Domínio: Os microsserviços são agrupados em domínios e cada domínio tem seu próprio banco de dados. Isso pode ser uma abordagem intermediária que combina os benefícios das duas abordagens anteriores.
-
Banco de Dados por Tabela: Cada microsserviço tem seu próprio esquema de banco de dados, mas compartilha o mesmo banco de dados físico. Isso pode ser uma abordagem eficiente em termos de recursos, mas pode levar a problemas de acoplamento e desempenho.
Aqui pessoal não existe uma resposta certa ou errada, a escolha da melhor abordagem depende do contexto e dos requisitos do projeto.
Sugiro que vocês leiam esses dois artigos para entender melhor sobre o assunto:
Sugestão de atividade
Até aqui, desenvolvemos diversos monolitos com nossas aplicações. Vamos realizar agora uma mudança nessa arquitetura e vamos implementar ela utilizando microsserviços.