Arquitetura de Software para Iniciantes: Guia passo a passo

Guia de Arquitetura de Software para Iniciantes
Um caminho didático, com diagramas e exemplos reais, para quem quer projetar sistemas sólidos desde o primeiro código.
Introdução
A arquitetura de software é o “esqueleto” que sustenta todo o desenvolvimento de um produto digital. Ela define como os componentes se relacionam, quais responsabilidades cada parte tem e como o sistema evolui ao longo do tempo. Para quem está começando, o desafio costuma ser escolher entre diversos estilos (camadas, hexagonal, microserviços, etc.) e entender quais padrões de projeto são mais adequados a cada cenário.
Este guia tem como objetivo:
Ao final, você será capaz de desenhar a primeira versão de uma arquitetura limpa, documentá‑la e implementá‑la em código.
1. Conceitos Fundamentais
| Conceito | O que é | Por que importa |
|---|---|---|
| Responsabilidade Única | Cada módulo deve ter uma única razão para mudar. | Facilita manutenção e testes. |
| Acoplamento e Coesão | Acoplamento baixo + coesão alta = módulos independentes e focados. | Reduz risco de efeitos colaterais. |
| Separação de Camadas | Divisão lógica (ex.: apresentação, domínio, infraestrutura). | Permite evoluir partes sem impactar as demais. |
| Contratos (Interfaces) | Definem como os componentes se comunicam. | Permite substituir implementações sem quebrar o sistema. |
| Escalabilidade Horizontal | Capacidade de adicionar nós para atender carga. | Fundamental para aplicações que crescem rapidamente. |
1.1. Camada de Domínio (Domain Layer)
A camada de domínio contém as regras de negócio. Ela não conhece detalhes de bancos de dados, APIs externas ou frameworks de UI. Seu código deve ser puro, facilitando testes unitários e reutilização.
# domain/entities/user.py
class User:
def __init__(self, user_id: int, name: str, email: str):
self.id = user_id
self.name = name
self.email = email
def change_email(self, new_email: str):
if "@" not in new_email:
raise ValueError("E‑mail inválido")
self.email = new_email
1.2. Camada de Aplicação (Application Layer)
Orquestra casos de uso, chamando serviços da camada de domínio e coordenando fluxos de trabalho.
# application/use_cases/update_user_email.py
from domain.entities.user import User
from domain.repositories.user_repo import UserRepository
class UpdateUserEmail:
def __init__(self, repo: UserRepository):
self.repo = repo
def execute(self, user_id: int, new_email: str):
user = self.repo.get_by_id(user_id)
user.change_email(new_email)
self.repo.save(user)
1.3. Camada de Infraestrutura (Infrastructure Layer)
Implementa detalhes técnicos: acesso a banco, APIs externas, filas, etc. Ela depende das abstrações definidas nas camadas internas, nunca o contrário.
# infrastructure/repositories/sql_user_repo.py
import sqlite3
from domain.repositories.user_repo import UserRepository
from domain.entities.user import User
class SqlUserRepository(UserRepository):
def __init__(self, db_path: str):
self.conn = sqlite3.connect(db_path)
def get_by_id(self, user_id: int) -> User:
cursor = self.conn.execute(
"SELECT id, name, email FROM users WHERE id = ?", (user_id,)
)
row = cursor.fetchone()
return User(row)
def save(self, user: User):
self.conn.execute(
"UPDATE users SET name = ?, email = ? WHERE id = ?",
(user.name, user.email, user.id)
)
self.conn.commit()
2. Estilos de Arquitetura Mais Utilizados
2.1. Arquitetura em Camadas (Layered)
- Divisão clássica: UI → Aplicação → Domínio → Infraestrutura.
- Prós: Simplicidade, fácil de entender.
- Contras: Pode gerar dependências circulares se não houver rigor nas interfaces.
2.2. Arquitetura Hexagonal (Ports & Adapters)
- Ideia central: O núcleo (domínio) expõe ports (interfaces) e o mundo externo se conecta por adapters.
- Benefício: Trocar banco, framework ou UI sem tocar no domínio.
@startuml
!define RECTANGLE class
RECTANGLE "Domínio
(User, Use Cases)" as Domain
RECTANGLE "Portas
(UserRepository)" as Port
RECTANGLE "Adaptadores
(SQL, REST)" as Adapter
Domain --> Port : usa
Port --> Adapter : implementa
@enduml
Nota: O diagrama acima pode ser renderizado em ferramentas que suportam PlantUML.
2.3. Microserviços
- Divisão por negócios: Cada serviço tem seu próprio banco e API.
- Quando usar: Sistemas de grande escala, equipes distribuídas, necessidade de evolução independente.
- Desafios: Complexidade de orquestração, consistência eventual, monitoramento.
2.4. Serverless (Funções como Serviço)
- Modelo: Código executado sob demanda, sem gerenciamento de servidores.
- Ideal para: APIs leves, processamento de eventos, protótipos rápidos.
3. Ferramentas de Apoio e IA na Escolha de Padrões
A decisão entre estilos e padrões pode ser difícil. Ferramentas baseadas em IA, como ChatGPT ou Claude, ajudam a:
| Função | Como a IA pode ajudar |
|---|---|
| Análise de Requisitos | Gera um resumo das necessidades e sugere o estilo mais adequado. |
| Sugestão de Padrões | A partir de um caso de uso, recomenda padrões de projeto (Factory, Strategy, Repository). |
| Esboço de Diagramas | Produz diagramas de componentes em PlantUML ou Mermaid a partir de texto. |
| Validação de Código | Detecta violações de princípios SOLID e recomenda refatorações. |
3.1. Exemplo de Prompt para IA
“Preciso de uma arquitetura para um sistema de gerenciamento de reservas de hotéis, que deve suportar alta concorrência e permitir a adição de novos tipos de acomodação sem mudar o código existente. Qual estilo de arquitetura e quais padrões de projeto você recomenda?”*
A IA pode responder:
- Estilo: Arquitetura Hexagonal com microserviços para cada domínio (Reserva, Pagamento, Inventário).
- Padrões: Repository (acesso a dados), Strategy (cálculo de preço por tipo de acomodação), Event Sourcing (registro de alterações).
4. Exemplos Práticos
4.1. Construindo um Microserviço Minimal com Flask (Python)
Objetivo: Criar um endpoint que permite cadastrar e consultar usuários, seguindo a separação de camadas apresentada anteriormente.
# app.py
from flask import Flask, request, jsonify
from application.use_cases.update_user_email import UpdateUserEmail
from infrastructure.repositories.sql_user_repo import SqlUserRepository
app = Flask(__name__)
Injeção de dependência simples
repo = SqlUserRepository(db_path="users.db")
update_email_uc = UpdateUserEmail(repo)
@app.route("/users/<int:user_id>/email", methods=["PUT"])
def change_email(user_id):
data = request.get_json()
try:
update_email_uc.execute(user_id, data["email"])
return jsonify({"status": "ok"}), 200
except Exception as e:
return jsonify({"error": str(e)}), 400
@app.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id):
user = repo.get_by_id(user_id)
return jsonify({"id": user.id, "name": user.name, "email": user.email})
if __name__ == "__main__":
app.run(debug=True)
Passos para executar
sqlite3 users.db "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);"
sqlite3 users.db "INSERT INTO users (name, email) VALUES ('Ana Silva', 'ana@example.com');"
pip install flask
python app.py
Agora você


