Arquitetura
Referências de design de sistemas, padrões e decisões técnicas.
Clean Architecture
Clean Architecture organiza o código em camadas concêntricas onde as dependências sempre apontam para dentro, do framework para o domínio, nunca o contrário.
Camadas
- Entities: regras de negócio puras, sem dependências externas
- Use Cases: orquestra as entidades, define o que o sistema faz
- Interface Adapters: controllers, presenters, gateways
- Frameworks & Drivers: banco, UI, APIs externas
// Entidade pura, sem dependência de framework
export class User {
constructor(
public readonly id: string,
public readonly email: string,
private readonly passwordHash: string
) {}
isPasswordValid(hash: string): boolean {
return this.passwordHash === hash;
}
}
// Use Case, depende apenas da entidade e do repositório (interface)
export class GetUserByIdUseCase {
constructor(private readonly repo: UserRepository) {}
async execute(id: string): Promise<User | null> {
return this.repo.findById(id);
}
}// Interface, definida no domínio, implementada na camada externa
export interface UserRepository {
findById(id: string): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
save(user: User): Promise<void>;
}💡 Dica
SOLID
Os cinco princípios que tornam software orientado a objetos mais compreensível, flexível e manutenível.
- S : Single Responsibility: cada classe tem uma razão para mudar
- O : Open/Closed: aberto para extensão, fechado para modificação
- L : Liskov Substitution: subtipos devem ser substituíveis por seus tipos base
- I : Interface Segregation: interfaces específicas são melhores que uma geral
- D : Dependency Inversion: dependa de abstrações, não de concretizações
// ❌ Depende de implementação concreta
class OrderService {
private mailer = new SendgridMailer(); // acoplado
}
// ✅ Depende de abstração
interface Mailer {
send(to: string, subject: string, body: string): Promise<void>;
}
class OrderService {
constructor(private readonly mailer: Mailer) {} // injeção de dependência
async confirmOrder(order: Order) {
await this.mailer.send(order.email, "Confirmação", `Pedido #${order.id} confirmado`);
}
}Design Patterns
Padrões de projeto são soluções reutilizáveis para problemas recorrentes de design de software.
Repository
Abstrai o acesso a dados. O domínio usa uma interface; a implementação pode ser SQL, MongoDB, in-memory.
Factory
Encapsula a criação de objetos complexos. Útil quando a criação depende de condições ou configurações.
Observer
Objetos observam mudanças em outro objeto. Base de eventos, pub/sub, reactive programming.
Strategy
Define uma família de algoritmos e os torna intercambiáveis sem alterar o cliente.
Decorator
Adiciona responsabilidades a um objeto dinamicamente. Alternativa limpa à herança excessiva.
Adapter
Converte a interface de uma classe em outra esperada pelo cliente. Integração de sistemas legados.
// Repository Pattern, desacopla o domínio do banco
interface ProductRepository {
findAll(): Promise<Product[]>;
findById(id: string): Promise<Product | null>;
save(product: Product): Promise<void>;
delete(id: string): Promise<void>;
}
// Implementação PostgreSQL
class PostgresProductRepository implements ProductRepository {
constructor(private readonly db: Database) {}
async findAll() {
const rows = await this.db.query("SELECT * FROM products");
return rows.map(toProduct);
}
async findById(id: string) {
const row = await this.db.queryOne("SELECT * FROM products WHERE id = $1", [id]);
return row ? toProduct(row) : null;
}
// ...
}
// Implementação in-memory (para testes)
class InMemoryProductRepository implements ProductRepository {
private store = new Map<string, Product>();
async findAll() { return [...this.store.values()]; }
async findById(id: string) { return this.store.get(id) ?? null; }
async save(product: Product) { this.store.set(product.id, product); }
async delete(id: string) { this.store.delete(id); }
}Microservices vs Monolith
Monolito Modular
Um único deploy com fronteiras de módulo bem definidas internamente. Ponto de partida recomendado.
- Desenvolvimento e deploy simples
- Transações ACID nativas
- Fácil de refatorar e testar
- Escala vertical eficiente até certo ponto
Microserviços
Serviços independentes com responsabilidades bem definidas. Justificado quando há necessidades reais de escala ou times grandes.
- Deploy independente por serviço
- Escala granular por serviço
- Complexidade de rede e consistência eventual
- Overhead operacional elevado
⚠️ Atenção
Deploy & Infra
Estratégias de Deploy
- Blue/Green: dois ambientes idênticos, troca de tráfego atômica
- Canary: rollout gradual para % dos usuários
- Rolling: atualiza instâncias gradualmente, sem downtime
- Feature Flags: deploy desacoplado de release, código no ar, feature desabilitada
Este site
Exportação estática com next build → S3 + CloudFront. Zero servidor em runtime. Deploy via aws s3 sync + invalidação de CDN.
#!/bin/bash
# Build estático
npm run build
# Sync para S3
aws s3 sync ./out s3://${BUCKET_NAME} \
--delete \
--cache-control "public, max-age=31536000, immutable" \
--exclude "*.html"
aws s3 sync ./out s3://${BUCKET_NAME} \
--exclude "*" \
--include "*.html" \
--cache-control "no-cache, no-store"
# Invalida cache do CloudFront
aws cloudfront create-invalidation \
--distribution-id ${CF_DIST_ID} \
--paths "/*"