Exercícios Práticos
Como usar
Seção intitulada “Como usar”- Resolva no editor, com código compilando — ler a solução não treina o músculo.
- Só depois abra o gabarito comentado (callout recolhido).
- Compare decisões, não sintaxe: o valor está no porquê de cada corte.
Bloco 1 — SOLID
Seção intitulada “Bloco 1 — SOLID”Exercício 1.1 — Três atores e uma cadeia de ifs
Seção intitulada “Exercício 1.1 — Três atores e uma cadeia de ifs”O serviço abaixo fecha faturas. Identifique todas as violações de SOLID e refatore.
@Servicepublic class FechamentoFaturaService {
private final JdbcTemplate jdbcTemplate; private final JavaMailSender mailSender;
public FechamentoFaturaService(JdbcTemplate jdbcTemplate, JavaMailSender mailSender) { this.jdbcTemplate = jdbcTemplate; this.mailSender = mailSender; }
public void fechar(String faturaId, String tipoCliente) { BigDecimal total = jdbcTemplate.queryForObject( "SELECT SUM(valor) FROM item_fatura WHERE fatura_id = ?", BigDecimal.class, faturaId);
BigDecimal desconto; if (tipoCliente.equals("VAREJO")) { desconto = total.multiply(new BigDecimal("0.05")); } else if (tipoCliente.equals("ATACADO")) { desconto = total.multiply(new BigDecimal("0.12")); } else if (tipoCliente.equals("PARCEIRO")) { desconto = total.multiply(new BigDecimal("0.20")); } else { desconto = BigDecimal.ZERO; }
jdbcTemplate.update("UPDATE fatura SET total = ?, desconto = ? WHERE id = ?", total, desconto, faturaId);
SimpleMailMessage msg = new SimpleMailMessage(); msg.setTo(buscarEmail(faturaId)); msg.setText("Fatura fechada: " + total.subtract(desconto)); mailSender.send(msg); }
private String buscarEmail(String faturaId) { return jdbcTemplate.queryForObject( "SELECT email FROM cliente c JOIN fatura f ON f.cliente_id = c.id WHERE f.id = ?", String.class, faturaId); }}Perguntas-guia: quantos atores mudam esta classe? O que acontece quando surgir o tipo de cliente “GOVERNO”? Como você testaria a regra de desconto sem banco e sem SMTP?
Gabarito comentado
- SRP: três atores — financeiro (desconto), persistência (SQLs), comunicação (e-mail). No mínimo três peças: uma política de desconto, um repositório, um notificador.
- OCP: a cadeia
if/elsesobretipoClientecresce a cada tipo novo. Extraia umaPoliticaDescontocom implementações por tipo, injetadas comoListpelo Spring (o mesmo desenho do exemplo de OCP - Open-Closed Principle). - DIP: a regra depende de
JdbcTemplateeJavaMailSenderconcretos. Inverta: interfacesFaturaRepositoryeNotificadorFaturadefinidas junto à regra, implementações na infraestrutura. - Resultado esperado: um
FecharFaturaServicefino orquestrandoPoliticaDesconto,FaturaRepositoryeNotificadorFatura— e a regra de desconto testável com um construtor e um assert.
Exercício 1.2 — A subclasse que não sustenta o contrato
Seção intitulada “Exercício 1.2 — A subclasse que não sustenta o contrato”Um agendador percorre List<Relatorio> chamando os dois métodos de exportação e agora explode em produção:
public abstract class Relatorio {
public abstract byte[] exportarPdf();
public abstract byte[] exportarExcel();}
public class RelatorioGerencial extends Relatorio {
@Override public byte[] exportarPdf() { return new byte[] {1}; }
@Override public byte[] exportarExcel() { return new byte[] {2}; }}
public class RelatorioAuditoria extends Relatorio {
@Override public byte[] exportarPdf() { return new byte[] {3}; }
@Override public byte[] exportarExcel() { throw new UnsupportedOperationException("Auditoria so sai em PDF"); }}Diagnostique pelo vocabulário de LSP e ISP e proponha a estrutura correta.
Gabarito comentado
A exceção em override é o cheiro duplo: RelatorioAuditoria não é substituível (LSP) porque foi forçado a implementar um contrato gordo (ISP). Segregue por papel: interfaces ExportavelPdf e ExportavelExcel separadas. O agendador de Excel passa a depender de List<ExportavelExcel> e o problema desaparece em compilação — quem não exporta Excel simplesmente não entra na lista.
Bloco 2 — DDD: modele a biblioteca
Seção intitulada “Bloco 2 — DDD: modele a biblioteca”Sem código no primeiro momento: papel, caneta ou Excalidraw.
Regras do negócio:
- Um título tem vários exemplares; empresta-se o exemplar, não o título.
- Empréstimo dura 14 dias; devolução atrasada gera multa de R$ 1,00 por dia.
- Um leitor pode ter no máximo 3 empréstimos ativos.
- Leitor com multa pendente não pode fazer novo empréstimo.
- Se todos os exemplares de um título estão emprestados, o leitor pode reservar; quando um exemplar volta, a reserva mais antiga é notificada.
Tarefas:
- Liste Entidades e Value Objects e escolha as raízes de Agregados com suas fronteiras.
- Diga onde vive cada invariante (regras 1 a 4) e por quê.
- Identifique os Domain Events e quem reage a eles.
- Marque o que viraria outro Bounded Context se o sistema crescesse.
Gabarito comentado (uma modelagem defensável — não a única)
- VOs:
Prazo,Multa(valor + dias de atraso),Isbn. Entidades:Leitor,Exemplar,Emprestimo,Reserva,Titulo. - Agregados:
Emprestimocomo raiz própria, referenciandoLeitorIdeExemplarIdpor identidade — a invariante 2 (prazo/multa) vive nele.Leitorcomo raiz que guarda contadores e multas pendentes — invariantes 3 e 4 vivem nele. - A tensão didática: as invariantes 3 e 4 cruzam
Leitor×Emprestimo. Se exigirem consistência imediata, criar empréstimo deve passar pelo agregadoLeitor(ex.:leitor.emprestar(...)fabrica oEmprestimo); se tolerarem uma corrida rara, valida-se por consulta e compensa-se depois — exatamente a discussão de Agregados e Domain Events. - Eventos:
EmprestimoRealizado,ExemplarDevolvido(dispara cálculo de multa e atendimento deReserva),ReservaDisponivel(a notificação em si é um adapter). - Contextos futuros: Catálogo (título, ISBN, dados bibliográficos) separado de Circulação (empréstimos e reservas); Cobrança de multas seria candidato a um terceiro.
Bloco 3 — Arquitetura: extraia o hexágono
Seção intitulada “Bloco 3 — Arquitetura: extraia o hexágono”O controller abaixo funciona — e concentra tudo:
@RestController@RequestMapping("/emprestimos")public class EmprestimoController {
private final JdbcTemplate jdbcTemplate;
public EmprestimoController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; }
@PostMapping public ResponseEntity<String> emprestar(@RequestBody Map<String, String> body) { Integer ativos = jdbcTemplate.queryForObject( "SELECT COUNT(*) FROM emprestimo WHERE leitor_id = ? AND devolvido = false", Integer.class, body.get("leitorId"));
if (ativos >= 3) { return ResponseEntity.unprocessableEntity().body("Limite de emprestimos atingido"); }
jdbcTemplate.update( "INSERT INTO emprestimo (leitor_id, exemplar_id, prazo) VALUES (?, ?, current_date + 14)", body.get("leitorId"), body.get("exemplarId"));
return ResponseEntity.ok("Emprestimo registrado"); }}Tarefas:
- Desenhe as Ports deste caso de uso (driving e driven).
- Reestruture em
domain/application/infrastructure, movendo a regra “máximo 3” para o domínio. - Escreva o teste do caso de uso com um fake em memória, sem Spring (molde em Testando o Núcleo).
- Aponte onde a tradução do erro para
422deve acontecer.
Gabarito comentado
- Driving port
EmprestarExemplarUseCase(comando comleitorId/exemplarId); driven portLeitorRepositoryouEmprestimoRepository, conforme sua modelagem no Bloco 2 — interfaces no núcleo, na linguagem do domínio. - A regra “máximo 3” vira invariante de agregado (ex.:
leitor.emprestar(...)lançaLimiteDeEmprestimosExcedido), não umifde controller com SQL de contagem. - O teste instancia o application service com o fake e cobre dois cenários em testes separados: limite atingido e limite livre.
- O
422nasce num@RestControllerAdvice(adapter primário) traduzindoLimiteDeEmprestimosExcedido— o núcleo nunca menciona HTTP (ver a seção de tradução de erros em Exemplo Hexagonal com Spring).
Bloco 4 — Patterns & Refactoring: do smell ao padrão
Seção intitulada “Bloco 4 — Patterns & Refactoring: do smell ao padrão”O cálculo de frete abaixo cresceu por acúmulo:
public BigDecimal calcularFrete(Pedido pedido, String transportadora, boolean seguro, boolean embalagemPresente) { BigDecimal base; if (transportadora.equals("CORREIOS")) { base = pedido.pesoTotal().multiply(new BigDecimal("1.20")); } else if (transportadora.equals("TRANSPORTADORA_X")) { base = pedido.pesoTotal().multiply(new BigDecimal("0.95")).add(new BigDecimal("12")); } else if (transportadora.equals("RETIRADA")) { base = BigDecimal.ZERO; } else { throw new IllegalArgumentException(transportadora); } if (seguro) { base = base.add(pedido.valorTotal().multiply(new BigDecimal("0.01"))); } if (embalagemPresente) { base = base.add(new BigDecimal("5")); } return base;}Tarefas:
- Nomeie os smells e os princípios violados (Code Smells e SOLID).
- Separe os dois eixos de variação e diga qual padrão cabe em cada um.
- Refatore com passos pequenos (Técnicas de Refactoring), testes verdes a cada passo.
Gabarito comentado
- O
if/elsesobretransportadoraé Repeated Switches → OCP → Replace Conditional with Polymorphism, chegando em Strategy (umaPoliticaFretepor transportadora, injetadas comoList— o mesmo desenho do exemplo de OCP). - Os flags
seguro/embalagemPresentesão responsabilidades cumulativas e combináveis — caso de Decorator sobre a interface de frete (new ComSeguro(new ComEmbalagem(politica))), não de mais estratégias: estratégias são alternativas, decorators se empilham. - Flags booleanas na assinatura eram o cheiro de que dois conceitos (escolha do transporte × extras) estavam fundidos numa função só.
Bloco 5 — Sistemas Distribuídos: pedido em três serviços
Seção intitulada “Bloco 5 — Sistemas Distribuídos: pedido em três serviços”Pedido, Pagamento e Estoque são serviços com bancos próprios. O fluxo de negócio: criar pedido → reservar estoque → cobrar → confirmar.
Tarefas:
- Desenhe a SAGA orquestrada: passos, replies e as compensações de cada falha possível (inclusive falha depois da cobrança).
- Aplique Message Outbox: em quais dos três serviços a tabela de outbox é necessária e por quê?
- Especifique a idempotência do consumidor de Pagamento: o que é a chave da mensagem, o que é o id de deduplicação (Event-Driven Architecture)?
- Em qual borda entra o API Gateway e o que ele não deve fazer nesse fluxo?
Gabarito comentado
- Compensações em ordem inversa: falha na cobrança → liberar estoque → cancelar pedido; falha após a cobrança (confirmação rejeitada) → estornar pagamento → liberar estoque — os estados
COMPENSATING_*do exemplo de SAGA. - Outbox nos três: todo serviço que muda estado local e publica reply/evento tem o problema do dual write.
- Chave = id do pedido (ordem por agregado na partição); deduplicação =
eventIddo payload registrado em tabela de processados na mesma transação do efeito. - O gateway roteia, autentica e aplica rate limit na entrada; ele não orquestra a saga nem contém regra de negócio — isso é o orquestrador, dentro do contexto dono do fluxo.
Bloco 6 — Frontend: extraia a porta do componente
Seção intitulada “Bloco 6 — Frontend: extraia a porta do componente”Um componente React faz fetch, calcula elegibilidade de desconto e renderiza — tudo no corpo. Tarefas:
- Separe domínio (regra pura), porta (
ClienteRepository), adapter HTTP e hook de caso de uso, no desenho de Arquitetura no Frontend. - Escreva dois testes: o hook com fake da porta (sem rede) e o componente integrado com MSW (Testes e TDD no Frontend).
- Justifique quando cada um dos dois testes é o certo.
Gabarito comentado
- A regra de elegibilidade vira função pura em
domain/(testável com assert direto); ofetchvira adapter da porta; o componente só renderiza o que o hook entrega. - Fake via porta: rápido, sem rede — ideal para variações da regra. MSW: exercita o adapter HTTP real e a integração — ideal para o caminho feliz e erros de rede. São seams complementares, não concorrentes: regra no fake, fronteira no MSW.
Bloco 7 — DevOps: o pipeline mínimo honesto
Seção intitulada “Bloco 7 — DevOps: o pipeline mínimo honesto”Para um serviço Spring Boot com testes de integração via Testcontainers:
- Escreva o workflow de CI (CI - GitHub Actions): etapas, cache, o que roda em cada job.
- Onde os testes de Testcontainers rodam e qual pré-requisito o runner precisa ter?
- Que permissão o
GITHUB_TOKENprecisa para push no ghcr.io? - O pipeline termina no push da imagem. Liste o que falta para virar CD e onde mora o risco de cada item.
Gabarito comentado
- Dois jobs:
build-and-test(checkout, JDK com cache,./mvnw verify) ebuild-imagecomneeds:— imagem só nasce de suíte verde. - Testcontainers roda no job de testes e exige Docker disponível no runner (os runners
ubuntu-latestjá o têm). permissions: packages: write— o token é read-only por padrão em repositórios novos.- Para CD: promoção da imagem por ambiente, migrações de banco versionadas, estratégia de deploy (rolling/blue-green), rollback automatizável e observabilidade para saber se o deploy deu certo — cada um é um modo de falha que o pipeline atual não cobre.
Capstone — Clube de assinatura de livros
Seção intitulada “Capstone — Clube de assinatura de livros”Especificação mínima:
- Assinante escolhe um plano (Básico: 1 livro/mês; Premium: 3 livros/mês) com cobrança mensal.
- Upgrade e downgrade valem a partir do próximo ciclo; upgrade no meio do ciclo libera os livros extras imediatamente.
- Assinatura pode ser pausada por até 2 meses por ano; pausada, não cobra nem envia.
- Três falhas de cobrança consecutivas suspendem a assinatura e publicam um fato para o time de retenção.
- A seleção do mês não pode repetir livro já enviado àquele assinante.
Entregáveis:
- Context map — quais contextos (Assinaturas, Cobrança, Envio, Catálogo?) e os padrões de integração entre eles (Design Estratégico e Tático).
- Modelo tático do contexto de Assinaturas: agregados, VOs, invariantes e eventos.
- Um caso de uso ponta a ponta no estilo de Exemplo Hexagonal com Spring ou Exemplo Clean com Spring.
- Testes: domínio puro + caso de uso com fakes + uma regra ArchUnit (Testando o Núcleo).
Checklist de autoavaliação:
- Alguma classe do domínio importa Spring ou JPA? (deveria ser zero)
- Toda invariante tem um teste que a exercita por cenário de negócio?
- Os eventos têm nome no passado e payload mínimo?
- Dá para trocar o banco escrevendo apenas um adapter novo?
- O caso de uso se lê como a frase que o especialista falaria?
Relações
Seção intitulada “Relações”- Recall antes de produzir: perguntas de revisão de cada nota, a partir de Home
- Moldes de teste: Testando o Núcleo
- Referências de implementação: Exemplo Hexagonal com Spring, Exemplo Clean com Spring
- Decisões de alocação de código: Como Tudo se Conecta