Guia Prático de Design de Sistemas para Iniciantes

Guia Prático de Design de Sistemas para Iniciantes
Objetivo: oferecer uma visão clara e didática sobre como conceber a estrutura de um software, apresentando conceitos, diagramas e trechos de código que podem ser copiados e executados imediatamente.
Introdução
Desenvolver um sistema sem um plano sólido é como montar um quebra‑cabeça às cegas: as peças podem até se encaixar, mas o resultado costuma ser instável e difícil de manter. O design de sistemas (ou design de software) fornece a base necessária para que a aplicação cresça de forma organizada, facilitando a leitura, a evolução e a correção de bugs.
Este guia foi pensado para quem ainda está dando os primeiros passos na construção de aplicações mais complexas. Ao final da leitura, você será capaz de:
Vamos começar?
1. Estrutura em Camadas: o alicerce do seu sistema
A maioria dos projetos bem‑sucedidos adota um modelo em camadas, que separa responsabilidades e reduz o acoplamento entre partes diferentes da aplicação. A seguir, descrevemos as três camadas mais comuns:
| Camada | Responsabilidade principal | Tecnologias típicas |
|---|---|---|
| Apresentação | Interage com o usuário (UI, API pública). | HTML/CSS, React, Vue, Flask (templates) |
| Domínio | Contém as regras de negócio, validações e orquestrações. | Classes de serviço, objetos de valor |
| Persistência | Gerencia o acesso a bancos de dados ou outros repositórios de dados. | ORMs (SQLAlchemy, TypeORM), drivers de banco |
Por que separar?
- Manutenibilidade: Alterações em uma camada raramente afetam as demais.
- Testabilidade: Cada camada pode ser testada isoladamente com unit tests.
- Escalabilidade: É mais fácil substituir, por exemplo, o banco de dados sem tocar na lógica de negócio.
Diagrama de componentes simplificado
graph TD
UI[Camada de Apresentação] -->|chama| Service[Camada de Domínio]
Service -->|acessa| Repo[Camada de Persistência]
Repo --> DB[(Banco de Dados)]
Observação: O diagrama acima usa a sintaxe Mermaid, suportada por diversas ferramentas de documentação (GitBook, MkDocs, etc.). Ele ilustra o fluxo de chamadas entre as camadas.
2. Padrões de Projeto Essenciais
Padrões de projeto são soluções reutilizáveis para problemas recorrentes. Para iniciantes, três padrões são particularmente úteis:
2.1. Factory (Fábrica)
Problema: Você precisa criar objetos de diferentes tipos sem expor a lógica de construção ao cliente.
Solução: Centralizar a criação em uma classe Factory.
# factory.py
from abc import ABC, abstractmethod
class Notificacao(ABC):
@abstractmethod
def enviar(self, mensagem: str) -> None:
pass
class Email(Notificacao):
def enviar(self, mensagem: str) -> None:
print(f"Enviando e‑mail: {mensagem}")
class SMS(Notificacao):
def enviar(self, mensagem: str) -> None:
print(f"Enviando SMS: {mensagem}")
class NotificacaoFactory:
@staticmethod
def criar(tipo: str) -> Notificacao:
if tipo == "email":
return Email()
elif tipo == "sms":
return SMS()
else:
raise ValueError(f"Tipo desconhecido: {tipo}")
Uso
if __name__ == "__main__":
canal = NotificacaoFactory.criar("email")
canal.enviar("Bem‑vindo ao nosso serviço!")
2.2. Repository (Repositório)
Problema: O código de acesso a dados se espalha por toda a aplicação, dificultando a manutenção.
Solução: Encapsular todas as operações de leitura/escrita em um repositório.
// repository.js (Node.js)
class UsuarioRepository {
constructor(db) {
this.db = db; // objeto de conexão (ex.: pg, mysql)
}
async buscarPorId(id) {
const result = await this.db.query('SELECT FROM usuarios WHERE id = $1', [id]);
return result.rows[0];
}
async criar(usuario) {
const { nome, email } = usuario;
const result = await this.db.query(
'INSERT INTO usuarios (nome, email) VALUES ($1, $2) RETURNING ',
[nome, email]
);
return result.rows[0];
}
// Outros métodos: atualizar, remover...
}
module.exports = UsuarioRepository;
2.3. Singleton (Único)
Problema: Você precisa de uma única instância de um recurso (ex.: logger, pool de conexão).
Solução: Garantir que a classe possua apenas uma instância global.
# logger.py
class Logger:
_instancia = None
def __new__(cls):
if cls._instancia is None:
cls._instancia = super(Logger, cls).__new__(cls)
cls._instancia._configurar()
return cls._instancia
def _configurar(self):
self.prefixo = "[MeuApp]"
def info(self, mensagem):
print(f"{self.prefixo} INFO: {mensagem}")
Uso
log = Logger()
log.info("Aplicação iniciada")
outro_log = Logger()
print(log is outro_log) # True
3. Diagramas UML Básicos
UML (Unified Modeling Language) oferece notações padronizadas para representar visualmente a estrutura e o comportamento do sistema. Para iniciantes, três diagramas são suficientes:
| Diagrama | O que representa |
|---|---|
| Diagrama de Classes | Estrutura estática: classes, atributos, métodos e relacionamentos. |
| Diagrama de Sequência | Interação temporal entre objetos (ex.: fluxo de um request). |
| Diagrama de Componentes | Organização de módulos, bibliotecas e dependências externas. |
3.1. Exemplo de Diagrama de Classes (texto)
+-------------------+ +-------------------+
| UsuarioService |<>------->| UsuarioRepository|
+-------------------+ +-------------------+
| +criar(usuario) | | +criar(usuario) |
| +obterPorId(id) | | +buscarPorId(id) |
+-------------------+ +-------------------+
+-------------------+ +-------------------+
| NotificacaoFactory|<>------>| Notificacao (interface) |
+-------------------+ +-------------------+
| +criar(tipo) | | +enviar(mensagem) |
+-------------------+ +-------------------+
Dica: Ferramentas gratuitas como draw.io ou PlantUML permitem gerar esses diagramas a partir de texto.
4. Exemplo Prático: Aplicação CRUD de Tarefas
Vamos colocar tudo em prática construindo uma aplicação simples de gerenciamento de tarefas. O projeto será dividido nas três camadas descritas na seção 1 e usará os padrões Repository e Factory.
4.1. Estrutura de pastas
task-app/
│
├─ src/
│ ├─ presentation/
│ │ └─ api.py # camada de apresentação (Flask)
│ ├─ domain/
│ │ ├─ services/
│ │ │ └─ task_service.py
│ │ └─ models/
│ │ └─ task.py
│ └─ persistence/
│ └─ task_repository.py
│
├─ requirements.txt
└─ run.py
4.2. Código da camada de domínio
# src/domain/models/task.py
from dataclasses import dataclass
from datetime import datetime
@dataclass
class Task:
id: int | None
titulo: str
concluida: bool = False
criado_em: datetime = datetime.utcnow()
```python
src/domain/services/task_service.py
from ..models.task import Task from ...persistence.task_repository import TaskRepository
class TaskService: def __init__(self, repo: TaskRepository): self.repo = repo
def criar(self, titulo: str) -> Task: nova = Task(id=None, titulo=t


