{"source":{"chapters":130},"concepts":[{"id":"source-bytecode-runtime","name":"Código-fonte, bytecode e execução","aliases":["ciclo Java"],"introducedIn":"intro","reinforcedIn":["primeiro-programa"],"prerequisiteConceptIds":[],"commonConfusions":["achar que javac executa o programa","confundir bytecode com código de máquina"],"outcomes":["descrever o caminho do arquivo .java até a JVM"]},{"id":"jdk-jvm","name":"JDK e JVM","aliases":["kit de desenvolvimento","máquina virtual"],"introducedIn":"intro","reinforcedIn":["primeiro-programa"],"prerequisiteConceptIds":[],"commonConfusions":["tratar JDK e JVM como sinônimos"],"outcomes":["escolher a ferramenta de compilação e a de execução"]},{"id":"terminal-diretorio-comando","name":"Terminal, diretório e comando","aliases":["CLI básica"],"introducedIn":"intro","reinforcedIn":["primeiro-programa","entrada-console"],"prerequisiteConceptIds":[],"commonConfusions":["executar no diretório errado","digitar o prompt como parte do comando"],"outcomes":["verificar a instalação e localizar o arquivo a compilar"]},{"id":"classe-main","name":"Classe e ponto de entrada main","aliases":["entry point"],"introducedIn":"primeiro-programa","reinforcedIn":["metodos-escopo","entrada-console"],"prerequisiteConceptIds":["source-bytecode-runtime"],"commonConfusions":["achar que toda classe é automaticamente executável"],"outcomes":["criar e executar um programa mínimo"]},{"id":"stdout-println","name":"Saída padrão e println","aliases":["stdout"],"introducedIn":"primeiro-programa","reinforcedIn":["entrada-console","mini-caixa-eletronico"],"prerequisiteConceptIds":["terminal-diretorio-comando"],"commonConfusions":["confundir valor produzido com texto exibido"],"outcomes":["observar resultados pela saída padrão"]},{"id":"categorias-de-falha","name":"Falha de compilação, execução e lógica","aliases":["tipos de erro"],"introducedIn":"primeiro-programa","reinforcedIn":["entrada-console","logica-programacao"],"prerequisiteConceptIds":["source-bytecode-runtime"],"commonConfusions":["chamar todo comportamento incorreto de erro de compilação"],"outcomes":["classificar uma falha antes de corrigi-la"]},{"id":"variavel-tipo-estatico","name":"Variável e tipo estático","aliases":["declaração"],"introducedIn":"variaveis-tipos","reinforcedIn":["operadores-expressoes","metodos-escopo"],"prerequisiteConceptIds":["classe-main"],"commonConfusions":["confundir variável com o valor atual"],"outcomes":["declarar, inicializar e atualizar variáveis compatíveis"]},{"id":"primitivo-referencia","name":"Valor primitivo e referência","aliases":["tipos primitivos e de referência"],"introducedIn":"variaveis-tipos","reinforcedIn":["arrays-matrizes","strings-wrapper","metodos-escopo"],"prerequisiteConceptIds":["variavel-tipo-estatico"],"commonConfusions":["achar que a referência contém o objeto inteiro","tratar null como zero"],"outcomes":["prever o efeito de copiar valores e referências"]},{"id":"conversao-numerica","name":"Promoção, casting e perda numérica","aliases":["widening","narrowing"],"introducedIn":"variaveis-tipos","reinforcedIn":["operadores-expressoes","entrada-console"],"prerequisiteConceptIds":["variavel-tipo-estatico"],"commonConfusions":["achar que casting arredonda"],"outcomes":["identificar conversões seguras e perdas de informação"]},{"id":"expressao-operador","name":"Expressões e operadores","aliases":["operandos"],"introducedIn":"operadores-expressoes","reinforcedIn":["controle-fluxo","lacos-repeticao"],"prerequisiteConceptIds":["variavel-tipo-estatico"],"commonConfusions":["confundir atribuição com comparação"],"outcomes":["calcular e explicar o valor e o tipo de uma expressão"]},{"id":"curto-circuito","name":"Avaliação lógica em curto-circuito","aliases":["short-circuit"],"introducedIn":"operadores-expressoes","reinforcedIn":["controle-fluxo","entrada-console"],"prerequisiteConceptIds":["expressao-operador"],"commonConfusions":["supor que os dois operandos sempre executam"],"outcomes":["prever quais expressões são avaliadas"]},{"id":"identidade-conteudo","name":"Identidade e igualdade de conteúdo","aliases":["== versus equals"],"introducedIn":"operadores-expressoes","reinforcedIn":["strings-wrapper"],"prerequisiteConceptIds":["primitivo-referencia"],"commonConfusions":["usar == para conteúdo de String"],"outcomes":["escolher igualdade adequada ao tipo"]},{"id":"ramificacao-condicional","name":"Ramificação condicional","aliases":["if else"],"introducedIn":"controle-fluxo","reinforcedIn":["lacos-repeticao","mini-caixa-eletronico"],"prerequisiteConceptIds":["expressao-operador"],"commonConfusions":["esquecer que apenas um ramo de uma cadeia é escolhido"],"outcomes":["modelar regras mutuamente compreensíveis"]},{"id":"ordem-de-condicoes","name":"Ordem, abrangência e casos-limite","aliases":["decision order"],"introducedIn":"controle-fluxo","reinforcedIn":["entrada-console","logica-programacao"],"prerequisiteConceptIds":["ramificacao-condicional"],"commonConfusions":["colocar uma condição ampla antes da específica"],"outcomes":["demonstrar a cobertura dos ramos"]},{"id":"switch-expression","name":"Switch como expressão","aliases":["switch"],"introducedIn":"controle-fluxo","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["ramificacao-condicional"],"commonConfusions":["misturar a sintaxe clássica com a expressão switch"],"outcomes":["mapear valores discretos para um resultado"]},{"id":"contrato-do-laco","name":"Estado, condição e progresso do laço","aliases":["loop contract"],"introducedIn":"lacos-repeticao","reinforcedIn":["arrays-matrizes","entrada-console"],"prerequisiteConceptIds":["ramificacao-condicional"],"commonConfusions":["alterar estado sem aproximar o término"],"outcomes":["justificar por que um laço termina"]},{"id":"while-for-foreach","name":"while, for e for-each","aliases":["estruturas de repetição"],"introducedIn":"lacos-repeticao","reinforcedIn":["arrays-matrizes","logica-programacao"],"prerequisiteConceptIds":["contrato-do-laco"],"commonConfusions":["usar for-each quando precisa do índice"],"outcomes":["escolher a estrutura pela forma do problema"]},{"id":"fronteira-off-by-one","name":"Fronteiras e off-by-one","aliases":["erro de uma posição"],"introducedIn":"lacos-repeticao","reinforcedIn":["arrays-matrizes","logica-programacao"],"prerequisiteConceptIds":["contrato-do-laco"],"commonConfusions":["trocar limite exclusivo por inclusivo"],"outcomes":["simular primeira e última iteração"]},{"id":"array-indice-length","name":"Array, índice e length","aliases":["vetor"],"introducedIn":"arrays-matrizes","reinforcedIn":["strings-wrapper","metodos-escopo"],"prerequisiteConceptIds":["while-for-foreach","fronteira-off-by-one"],"commonConfusions":["usar length como último índice"],"outcomes":["criar e percorrer arrays sem ultrapassar limites"]},{"id":"aliasing-array","name":"Referência, aliasing e cópia de array","aliases":["cópia rasa"],"introducedIn":"arrays-matrizes","reinforcedIn":["metodos-escopo"],"prerequisiteConceptIds":["primitivo-referencia","array-indice-length"],"commonConfusions":["achar que atribuição duplica o array"],"outcomes":["prever mutações visíveis por duas referências"]},{"id":"array-multidimensional","name":"Array de arrays","aliases":["matriz"],"introducedIn":"arrays-matrizes","reinforcedIn":["logica-programacao"],"prerequisiteConceptIds":["array-indice-length"],"commonConfusions":["supor que todas as linhas têm o mesmo tamanho"],"outcomes":["percorrer estruturas retangulares e irregulares"]},{"id":"string-imutavel","name":"String imutável","aliases":["imutabilidade de texto"],"introducedIn":"strings-wrapper","reinforcedIn":["entrada-console"],"prerequisiteConceptIds":["primitivo-referencia"],"commonConfusions":["achar que métodos alteram a String original"],"outcomes":["capturar o resultado de transformações de texto"]},{"id":"stringbuilder","name":"Construção mutável de texto","aliases":["StringBuilder"],"introducedIn":"strings-wrapper","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["string-imutavel","contrato-do-laco"],"commonConfusions":["usar concatenação repetida sem avaliar o custo"],"outcomes":["escolher entre String e StringBuilder"]},{"id":"wrapper-boxing-parsing","name":"Wrappers, boxing e parsing","aliases":["Integer","autoboxing"],"introducedIn":"strings-wrapper","reinforcedIn":["entrada-console"],"prerequisiteConceptIds":["conversao-numerica","string-imutavel"],"commonConfusions":["confundir converter texto com casting","desempacotar null"],"outcomes":["converter texto explicitamente e reconhecer risco de unboxing"]},{"id":"metodo-contrato","name":"Método como contrato","aliases":["parâmetros e retorno"],"introducedIn":"metodos-escopo","reinforcedIn":["entrada-console","mini-caixa-eletronico"],"prerequisiteConceptIds":["variavel-tipo-estatico"],"commonConfusions":["confundir imprimir com retornar"],"outcomes":["definir entrada, resultado e efeito de um método"]},{"id":"passagem-por-valor","name":"Passagem por valor em Java","aliases":["cópia do argumento"],"introducedIn":"metodos-escopo","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["primitivo-referencia","metodo-contrato"],"commonConfusions":["dizer que objetos são passados por referência"],"outcomes":["prever reatribuição e mutação dentro de métodos"]},{"id":"escopo-sobrecarga-varargs","name":"Escopo, sobrecarga e varargs","aliases":["method scope","overload"],"introducedIn":"metodos-escopo","reinforcedIn":["entrada-console"],"prerequisiteConceptIds":["metodo-contrato","array-indice-length"],"commonConfusions":["distinguir sobrecarga apenas pelo retorno","achar que varargs não é array"],"outcomes":["delimitar nomes e selecionar assinaturas sem ambiguidade"]},{"id":"cli-stdin-stdout","name":"CLI, entrada e saída padrão","aliases":["console","stdin","stdout"],"introducedIn":"entrada-console","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["terminal-diretorio-comando","stdout-println"],"commonConfusions":["confundir terminal, shell e programa Java"],"outcomes":["descrever o caminho entre teclado, processo e tela"]},{"id":"scanner-token-linha","name":"Scanner, tokens e linhas","aliases":["buffer de entrada"],"introducedIn":"entrada-console","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["cli-stdin-stdout","string-imutavel"],"commonConfusions":["misturar nextInt e nextLine sem consumir a quebra de linha"],"outcomes":["adotar uma estratégia previsível de leitura"]},{"id":"parse-validacao-eof","name":"Parsing, validação e fim da entrada","aliases":["EOF","contrato de entrada"],"introducedIn":"entrada-console","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["scanner-token-linha","wrapper-boxing-parsing","ramificacao-condicional"],"commonConfusions":["tratar formato válido como regra de negócio válida","tratar EOF sempre como erro"],"outcomes":["separar coleta, conversão, validação e encerramento"]},{"id":"decomposicao-algoritmica","name":"Decomposição algorítmica","aliases":["pseudocódigo"],"introducedIn":"logica-programacao","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["metodo-contrato","while-for-foreach"],"commonConfusions":["começar pela sintaxe antes de definir passos"],"outcomes":["transformar um problema em passos verificáveis"]},{"id":"dry-run","name":"Rastreamento manual de estado","aliases":["dry run"],"introducedIn":"logica-programacao","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["contrato-do-laco","variavel-tipo-estatico"],"commonConfusions":["olhar apenas a saída final"],"outcomes":["registrar o estado a cada passo e localizar divergências"]},{"id":"casos-limite-invariantes","name":"Casos-limite e invariantes","aliases":["edge cases"],"introducedIn":"logica-programacao","reinforcedIn":["mini-caixa-eletronico"],"prerequisiteConceptIds":["ordem-de-condicoes","fronteira-off-by-one"],"commonConfusions":["testar apenas o caminho feliz"],"outcomes":["definir o que deve permanecer verdadeiro e testar limites"]},{"id":"incremento-executavel","name":"Construção incremental executável","aliases":["small steps"],"introducedIn":"mini-caixa-eletronico","reinforcedIn":[],"prerequisiteConceptIds":["decomposicao-algoritmica"],"commonConfusions":["implementar todos os requisitos antes de compilar"],"outcomes":["entregar uma etapa funcional de cada vez"]},{"id":"separacao-io-regra","name":"Separação entre entrada, regra e saída","aliases":["separação de responsabilidades"],"introducedIn":"mini-caixa-eletronico","reinforcedIn":[],"prerequisiteConceptIds":["metodo-contrato","cli-stdin-stdout"],"commonConfusions":["ler Scanner dentro de toda regra"],"outcomes":["isolar cálculo de interação com usuário"]},{"id":"evidencia-manual","name":"Evidência de teste manual","aliases":["roteiro de verificação"],"introducedIn":"mini-caixa-eletronico","reinforcedIn":[],"prerequisiteConceptIds":["casos-limite-invariantes","categorias-de-falha"],"commonConfusions":["considerar captura de tela uma especificação reproduzível"],"outcomes":["registrar entrada, resultado esperado e observado"]},{"id":"terminal-vs-shell","name":"Terminal, terminal emulator e shell","aliases":["janela de comando","linha de comando"],"introducedIn":"terminal-shell-fundamentos","reinforcedIn":["intro"],"prerequisiteConceptIds":[],"commonConfusions":["achar que terminal e shell são sinônimos","achar que só existe um shell"],"outcomes":["diferenciar terminal emulator, shell, CLI, comando e processo"]},{"id":"working-directory-path","name":"Diretório atual, home e caminhos absoluto/relativo","aliases":["pwd","cwd"],"introducedIn":"terminal-shell-fundamentos","reinforcedIn":["intro"],"prerequisiteConceptIds":["terminal-vs-shell"],"commonConfusions":["confundir caminho relativo com absoluto","esquecer que cd muda o diretório atual do shell, não do sistema"],"outcomes":["navegar e criar estrutura de pastas usando pwd/ls/cd/mkdir"]},{"id":"path-env-var-resolution","name":"PATH e resolução de comandos","aliases":["variável de ambiente PATH"],"introducedIn":"terminal-shell-fundamentos","reinforcedIn":["intro"],"prerequisiteConceptIds":["terminal-vs-shell"],"commonConfusions":["achar que o shell sabe onde todo programa está sem procurar","confundir command not found com bug do programa"],"outcomes":["usar command -v para diagnosticar se um programa está no PATH"]},{"id":"pipe-redirection-exit-status","name":"Redirecionamento, pipes e exit status","aliases":["stdout/stderr","|","código de saída"],"introducedIn":"terminal-shell-fundamentos","reinforcedIn":[],"prerequisiteConceptIds":["terminal-vs-shell"],"commonConfusions":["achar que pipe executa os dois comandos ao mesmo tempo sem relação","achar que exit status diferente de zero é sempre um crash"],"outcomes":["compor comandos com |, ;, && e || entendendo exit status"]},{"id":"classe-instancia-objeto","name":"Classe, instância e objeto","aliases":["class","instance"],"introducedIn":"classes","reinforcedIn":["atributos","construtores"],"prerequisiteConceptIds":["variavel-tipo-estatico"],"commonConfusions":["chamar classe e objeto de mesma coisa","achar que declarar uma referência cria um objeto"],"outcomes":["distinguir declaração de classe, referência e instância criada com new"]},{"id":"identidade-referencia-objeto","name":"Identidade e referências de objetos","aliases":["object identity","aliasing"],"introducedIn":"classes","reinforcedIn":["atributos","associacoes-cardinalidade"],"prerequisiteConceptIds":["primitivo-referencia","identidade-conteudo"],"commonConfusions":["achar que atribuição copia o objeto","confundir mesmo estado com mesma identidade"],"outcomes":["prever aliasing, null e comparação de identidade"]},{"id":"ciclo-alcancabilidade","name":"Criação, alcance e coleta de objetos","aliases":["garbage collection"],"introducedIn":"classes","reinforcedIn":["associacoes-cardinalidade"],"prerequisiteConceptIds":["identidade-referencia-objeto"],"commonConfusions":["tratar stack e heap como regra exata para toda variável","achar que GC fecha recursos"],"outcomes":["explicar quando um objeto deixa de ser alcançável sem prometer momento de coleta"]},{"id":"estado-comportamento","name":"Estado e comportamento","aliases":["fields and methods"],"introducedIn":"atributos","reinforcedIn":["encapsulamento","pilares-profundo"],"prerequisiteConceptIds":["classe-instancia-objeto","metodo-contrato"],"commonConfusions":["modelar classe como saco de dados","confundir variável local com campo"],"outcomes":["atribuir responsabilidades e manter regras próximas do estado"]},{"id":"campo-instancia","name":"Campos e valores padrão","aliases":["instance field"],"introducedIn":"atributos","reinforcedIn":["construtores"],"prerequisiteConceptIds":["variavel-tipo-estatico","classe-instancia-objeto"],"commonConfusions":["presumir que campo e variável local inicializam igual"],"outcomes":["distinguir campo, parâmetro e variável local"]},{"id":"comando-consulta","name":"Comandos, consultas e efeitos","aliases":["command-query distinction"],"introducedIn":"atributos","reinforcedIn":["encapsulamento","mini-biblioteca-cli"],"prerequisiteConceptIds":["metodo-contrato","estado-comportamento"],"commonConfusions":["getter que altera estado","método que imprime no lugar de retornar"],"outcomes":["tornar efeitos e resultados de métodos observáveis"]},{"id":"construtor-invariante","name":"Construtor e estado inicial válido","aliases":["constructor"],"introducedIn":"construtores","reinforcedIn":["encapsulamento","associacoes-cardinalidade"],"prerequisiteConceptIds":["estado-comportamento","ordem-de-condicoes"],"commonConfusions":["achar que construtor tem tipo de retorno","deixar objeto parcialmente inicializado"],"outcomes":["criar objetos que já nascem respeitando suas regras"]},{"id":"construtor-padrao","name":"Construtor padrão implícito","aliases":["default constructor"],"introducedIn":"construtores","reinforcedIn":["heranca"],"prerequisiteConceptIds":["classe-instancia-objeto"],"commonConfusions":["achar que Java sempre mantém construtor sem argumentos"],"outcomes":["prever quando o compilador declara ou deixa de declarar construtor padrão"]},{"id":"this-encadeamento","name":"this e encadeamento de construtores","aliases":["this()"],"introducedIn":"construtores","reinforcedIn":["static"],"prerequisiteConceptIds":["construtor-invariante"],"commonConfusions":["confundir this com a classe","chamar this() fora da primeira instrução"],"outcomes":["delegar inicialização sem duplicar regras"]},{"id":"encapsulamento-invariante","name":"Encapsulamento e invariantes","aliases":["information hiding"],"introducedIn":"encapsulamento","reinforcedIn":["pilares-profundo","mini-biblioteca-cli"],"prerequisiteConceptIds":["construtor-invariante","estado-comportamento"],"commonConfusions":["equacionar encapsulamento a getters e setters"],"outcomes":["proteger estado por operações de domínio"]},{"id":"controle-acesso","name":"Controle de acesso","aliases":["public","private","protected","package access"],"introducedIn":"encapsulamento","reinforcedIn":["heranca","interfaces"],"prerequisiteConceptIds":["classe-instancia-objeto"],"commonConfusions":["chamar ausência de modificador de public","usar protected como public para subclasses"],"outcomes":["escolher a menor visibilidade necessária"]},{"id":"imutabilidade-objeto","name":"Imutabilidade de objeto","aliases":["immutable object"],"introducedIn":"encapsulamento","reinforcedIn":["static","associacoes-cardinalidade"],"prerequisiteConceptIds":["encapsulamento-invariante","string-imutavel"],"commonConfusions":["achar que referência final torna o objeto imutável"],"outcomes":["projetar estado que não muda após construção"]},{"id":"membro-estatico","name":"Membros de classe com static","aliases":["class member"],"introducedIn":"static","reinforcedIn":["pilares-profundo"],"prerequisiteConceptIds":["classe-instancia-objeto","campo-instancia"],"commonConfusions":["achar que existe uma cópia static por objeto","acessar estado de instância sem objeto"],"outcomes":["distinguir estado da classe e de cada instância"]},{"id":"final-referencia-tipo","name":"final em variável, método e classe","aliases":["final"],"introducedIn":"static","reinforcedIn":["heranca","abstracao"],"prerequisiteConceptIds":["imutabilidade-objeto","identidade-referencia-objeto"],"commonConfusions":["confundir referência final com objeto imutável"],"outcomes":["explicar o que final restringe em cada contexto"]},{"id":"this-instancia","name":"this como objeto atual","aliases":["current object"],"introducedIn":"static","reinforcedIn":["heranca","associacoes-cardinalidade"],"prerequisiteConceptIds":["this-encadeamento","membro-estatico"],"commonConfusions":["usar this em contexto static"],"outcomes":["identificar o receptor de uma chamada de instância"]},{"id":"heranca-subtipo","name":"Herança e relação de subtipo","aliases":["extends","is-a"],"introducedIn":"heranca","reinforcedIn":["polimorfismo","pilares-profundo"],"prerequisiteConceptIds":["controle-acesso","final-referencia-tipo"],"commonConfusions":["usar herança apenas para reaproveitar código","achar que construtores são herdados"],"outcomes":["validar se toda subclasse pode substituir a superclasse"]},{"id":"super-sobrescrita","name":"super, herança e sobrescrita","aliases":["override"],"introducedIn":"heranca","reinforcedIn":["polimorfismo","abstracao"],"prerequisiteConceptIds":["heranca-subtipo","construtor-padrao"],"commonConfusions":["confundir sobrescrita com sobrecarga","reduzir visibilidade ao sobrescrever"],"outcomes":["estender ou substituir comportamento herdado conscientemente"]},{"id":"heranca-composicao","name":"Herança versus composição","aliases":["is-a versus has-a"],"introducedIn":"heranca","reinforcedIn":["pilares-profundo","associacoes-cardinalidade"],"prerequisiteConceptIds":["heranca-subtipo","estado-comportamento"],"commonConfusions":["usar extends para uma relação tem-um"],"outcomes":["escolher colaboração quando não existe subtipo verdadeiro"]},{"id":"polimorfismo-substituicao","name":"Polimorfismo por substituição","aliases":["upcasting"],"introducedIn":"polimorfismo","reinforcedIn":["interfaces","pilares-profundo"],"prerequisiteConceptIds":["heranca-subtipo","super-sobrescrita"],"commonConfusions":["achar que o objeto perde seu tipo real"],"outcomes":["tratar subtipos uniformemente sem perder comportamento específico"]},{"id":"despacho-dinamico","name":"Despacho dinâmico de método","aliases":["dynamic dispatch"],"introducedIn":"polimorfismo","reinforcedIn":["interfaces","pilares-profundo"],"prerequisiteConceptIds":["polimorfismo-substituicao"],"commonConfusions":["afirmar que campos são polimórficos","ensinar vtable como exigência da linguagem"],"outcomes":["prever qual método de instância sobrescrito executa"]},{"id":"downcast-instanceof","name":"Downcast e teste de tipo","aliases":["instanceof"],"introducedIn":"polimorfismo","reinforcedIn":["pilares-profundo"],"prerequisiteConceptIds":["polimorfismo-substituicao"],"commonConfusions":["usar cast para mudar o objeto","downcastar sem verificar o tipo real"],"outcomes":["reconhecer quando downcast indica abstração insuficiente"]},{"id":"classe-metodo-abstrato","name":"Classe e método abstratos","aliases":["abstract class"],"introducedIn":"abstracao","reinforcedIn":["interfaces","pilares-profundo"],"prerequisiteConceptIds":["heranca-subtipo","despacho-dinamico"],"commonConfusions":["achar que abstrato significa sem construtor","confundir abstração com palavra-chave abstract"],"outcomes":["combinar estado compartilhado e operações obrigatórias"]},{"id":"contrato-parcial","name":"Contrato parcial e comportamento compartilhado","aliases":["partial implementation"],"introducedIn":"abstracao","reinforcedIn":["interfaces"],"prerequisiteConceptIds":["classe-metodo-abstrato","super-sobrescrita"],"commonConfusions":["criar método fictício que retorna zero"],"outcomes":["impedir instanciação de um conceito incompleto"]},{"id":"abstracao-modelagem","name":"Abstração como escolha de relevância","aliases":["domain abstraction"],"introducedIn":"abstracao","reinforcedIn":["pilares-profundo","associacoes-cardinalidade"],"prerequisiteConceptIds":["estado-comportamento"],"commonConfusions":["reduzir abstração a classe abstrata"],"outcomes":["selecionar dados e comportamentos relevantes ao problema"]},{"id":"interface-contrato","name":"Interface como contrato de tipo","aliases":["interface"],"introducedIn":"interfaces","reinforcedIn":["pilares-profundo","mini-biblioteca-cli"],"prerequisiteConceptIds":["polimorfismo-substituicao","classe-metodo-abstrato"],"commonConfusions":["dizer que interface nunca possui implementação","usar Object e cast quando o contrato pode ser tipado"],"outcomes":["programar contra capacidade sem exigir uma hierarquia de classes"]},{"id":"implementacao-multipla-interfaces","name":"Múltiplos contratos por interfaces","aliases":["implements"],"introducedIn":"interfaces","reinforcedIn":["mini-biblioteca-cli"],"prerequisiteConceptIds":["interface-contrato"],"commonConfusions":["confundir múltiplas interfaces com múltiplas superclasses"],"outcomes":["combinar capacidades independentes em um tipo"]},{"id":"default-conflito-interface","name":"Métodos default e resolução de conflito","aliases":["default method"],"introducedIn":"interfaces","reinforcedIn":["pilares-profundo"],"prerequisiteConceptIds":["interface-contrato","super-sobrescrita"],"commonConfusions":["achar que Java escolhe um default arbitrariamente"],"outcomes":["resolver explicitamente contratos default incompatíveis"]},{"id":"quatro-pilares-cooperacao","name":"Cooperação entre os pilares de POO","aliases":["POO integrada"],"introducedIn":"pilares-profundo","reinforcedIn":["associacoes-cardinalidade","mini-biblioteca-cli"],"prerequisiteConceptIds":["encapsulamento-invariante","heranca-composicao","despacho-dinamico","abstracao-modelagem","interface-contrato"],"commonConfusions":["decorar quatro definições isoladas"],"outcomes":["explicar qual problema cada mecanismo resolve no mesmo modelo"]},{"id":"decisao-modelagem-poo","name":"Decisões e custos da modelagem orientada a objetos","aliases":["OO trade-offs"],"introducedIn":"pilares-profundo","reinforcedIn":["associacoes-cardinalidade","mini-biblioteca-cli"],"prerequisiteConceptIds":["quatro-pilares-cooperacao"],"commonConfusions":["tratar herança ou interface como dogma"],"outcomes":["justificar encapsulamento, subtipo e colaboração pelos efeitos no modelo"]},{"id":"associacao-direcao","name":"Associação e direção","aliases":["unidirectional","bidirectional"],"introducedIn":"associacoes-cardinalidade","reinforcedIn":["mini-biblioteca-cli"],"prerequisiteConceptIds":["identidade-referencia-objeto","decisao-modelagem-poo"],"commonConfusions":["achar que toda relação precisa de dois lados","confundir navegação com tabela de banco"],"outcomes":["modelar somente direções exigidas pelos casos de uso"]},{"id":"cardinalidade-objeto","name":"Cardinalidade entre objetos","aliases":["0..1","1","0..N"],"introducedIn":"associacoes-cardinalidade","reinforcedIn":["mini-biblioteca-cli"],"prerequisiteConceptIds":["associacao-direcao","array-indice-length"],"commonConfusions":["deixar mínimo e máximo implícitos"],"outcomes":["representar e testar limites de participação"]},{"id":"composicao-ciclo-vida","name":"Associação, agregação e composição","aliases":["ownership","whole-part"],"introducedIn":"associacoes-cardinalidade","reinforcedIn":["mini-biblioteca-cli"],"prerequisiteConceptIds":["heranca-composicao","ciclo-alcancabilidade"],"commonConfusions":["chamar todo campo de composição","confundir composição de objetos com herança"],"outcomes":["justificar ownership pelo ciclo de vida da parte"]},{"id":"consistencia-relacao","name":"Consistência de relações","aliases":["relationship invariant"],"introducedIn":"associacoes-cardinalidade","reinforcedIn":["mini-biblioteca-cli"],"prerequisiteConceptIds":["encapsulamento-invariante","cardinalidade-objeto"],"commonConfusions":["atualizar apenas um lado de relação bidirecional"],"outcomes":["centralizar validação e transição de todos os lados"]},{"id":"projeto-poo-incremental","name":"Projeto POO incremental","aliases":["incremental OO design"],"introducedIn":"mini-biblioteca-cli","reinforcedIn":[],"prerequisiteConceptIds":["incremento-executavel","decisao-modelagem-poo"],"commonConfusions":["começar pelo menu antes das invariantes"],"outcomes":["construir e verificar o domínio antes da CLI"]},{"id":"evidencia-invariante-objeto","name":"Evidência de invariantes entre objetos","aliases":["object invariant evidence"],"introducedIn":"mini-biblioteca-cli","reinforcedIn":[],"prerequisiteConceptIds":["evidencia-manual","encapsulamento-invariante"],"commonConfusions":["testar apenas mensagens do menu"],"outcomes":["demonstrar transições válidas e recusas sem depender do terminal"]},{"id":"pacote-namespace","name":"Pacote como namespace","aliases":["package"],"introducedIn":"pacotes","reinforcedIn":["excecoes","colecoes"],"prerequisiteConceptIds":["controle-acesso"],"commonConfusions":["reduzir pacote a pasta","achar que subpacote pertence ao pacote pai"],"outcomes":["distinguir nome simples e qualificado"]},{"id":"import-resolucao","name":"Import e resolução de nomes","aliases":["import"],"introducedIn":"pacotes","reinforcedIn":["colecoes","generics"],"prerequisiteConceptIds":["pacote-namespace"],"commonConfusions":["achar que import carrega ou copia código","achar que wildcard inclui subpacotes"],"outcomes":["resolver colisões e imports explicitamente"]},{"id":"classpath-compilacao","name":"Classpath, source root e saída compilada","aliases":["class path","source root"],"introducedIn":"pacotes","reinforcedIn":["jvm-profundo"],"prerequisiteConceptIds":["source-bytecode-runtime","pacote-namespace"],"commonConfusions":["passar caminho .class ao launcher","confundir package com diretório por regra da linguagem"],"outcomes":["compilar e executar múltiplos pacotes sem build tool"]},{"id":"controle-abrupto-excecao","name":"Conclusão abrupta e propagação","aliases":["throw","stack unwinding"],"introducedIn":"excecoes","reinforcedIn":["java-21-profundo"],"prerequisiteConceptIds":["metodo-contrato","heranca-subtipo"],"commonConfusions":["achar que execução continua após throw","confundir falha com retorno comum"],"outcomes":["rastrear transferência até o handler compatível"]},{"id":"checked-unchecked-contrato","name":"Checked e unchecked como contrato","aliases":["throws clause"],"introducedIn":"excecoes","reinforcedIn":["streams","java-21-profundo"],"prerequisiteConceptIds":["controle-abrupto-excecao"],"commonConfusions":["chamar checked de recuperável por definição","capturar tudo sem política"],"outcomes":["escolher declarar, capturar ou propagar justificadamente"]},{"id":"try-resource-lifecycle","name":"try, finally e ciclo de recursos","aliases":["try-with-resources","AutoCloseable"],"introducedIn":"excecoes","reinforcedIn":["java-io"],"prerequisiteConceptIds":["controle-abrupto-excecao","interface-contrato"],"commonConfusions":["afirmar que finally sempre executa","achar que GC fecha recurso"],"outcomes":["encerrar AutoCloseable mesmo sob falha"]},{"id":"causa-excecao","name":"Causa e contexto de exceção","aliases":["exception chaining"],"introducedIn":"excecoes","reinforcedIn":["logging"],"prerequisiteConceptIds":["checked-unchecked-contrato"],"commonConfusions":["substituir exceção e perder causa","usar mensagem sem contexto"],"outcomes":["traduzir uma falha preservando causa"]},{"id":"contrato-collection-map","name":"Contratos Collection e Map","aliases":["Collections Framework"],"introducedIn":"colecoes","reinforcedIn":["generics","streams"],"prerequisiteConceptIds":["interface-contrato","array-indice-length"],"commonConfusions":["achar que Map estende Collection","escolher implementação pelo nome"],"outcomes":["escolher abstração pela operação necessária"]},{"id":"list-set-map-semantica","name":"Semântica de List, Set e Map","aliases":["sequence","uniqueness","key-value"],"introducedIn":"colecoes","reinforcedIn":["functional-semantics-lab"],"prerequisiteConceptIds":["contrato-collection-map"],"commonConfusions":["esperar ordem de HashSet ou HashMap","confundir duplicata de valor e chave"],"outcomes":["prever ordem, duplicidade, índice e substituição"]},{"id":"equals-hashcode-contrato","name":"Contrato equals e hashCode","aliases":["logical equality","hashing"],"introducedIn":"colecoes","reinforcedIn":["functional-semantics-lab","java-21-profundo"],"prerequisiteConceptIds":["identidade-conteudo","imutabilidade-objeto"],"commonConfusions":["sobrescrever só equals","exigir hashes diferentes para objetos diferentes"],"outcomes":["implementar igualdade reflexiva, simétrica, transitiva e hash coerente"]},{"id":"chave-hash-estavel","name":"Estabilidade de elementos e chaves hash","aliases":["mutable key"],"introducedIn":"colecoes","reinforcedIn":["functional-semantics-lab","jvm-profundo"],"prerequisiteConceptIds":["equals-hashcode-contrato"],"commonConfusions":["mudar campo da chave depois da inserção"],"outcomes":["explicar por que busca falha após mutação da identidade lógica"]},{"id":"ordem-comparable-comparator","name":"Ordem natural e contextual","aliases":["Comparable","Comparator"],"introducedIn":"colecoes","reinforcedIn":["streams"],"prerequisiteConceptIds":["interface-contrato","equals-hashcode-contrato"],"commonConfusions":["subtrair inteiros no compare","achar que comparação sempre coincide com equals"],"outcomes":["definir e combinar ordens consistentes"]},{"id":"copia-visao-imutabilidade","name":"Cópia, visão não modificável e imutabilidade","aliases":["List.copyOf","unmodifiable view"],"introducedIn":"colecoes","reinforcedIn":["java-21-profundo"],"prerequisiteConceptIds":["imutabilidade-objeto","list-set-map-semantica"],"commonConfusions":["achar que unmodifiableList copia","achar que contêiner não modificável congela elementos"],"outcomes":["distinguir alias, cópia rasa e visão"]},{"id":"tipo-parametrizado","name":"Tipo parametrizado e segurança estática","aliases":["parameterized type","raw type"],"introducedIn":"generics","reinforcedIn":["streams","javamoderno"],"prerequisiteConceptIds":["contrato-collection-map","variavel-tipo-estatico"],"commonConfusions":["usar raw type e confiar em cast","achar que List<Integer> é subtipo de List<Number>"],"outcomes":["eliminar casts inseguros por contrato genérico"]},{"id":"declaracao-generica-bound","name":"Classes, métodos genéricos e bounds","aliases":["type parameter","bounded type"],"introducedIn":"generics","reinforcedIn":["streams"],"prerequisiteConceptIds":["tipo-parametrizado","metodo-contrato"],"commonConfusions":["confundir parâmetro de tipo com Object","posicionar T antes do retorno incorretamente"],"outcomes":["declarar T no menor escopo e limitar capacidades"]},{"id":"wildcard-variancia","name":"Wildcards, captura e variância de uso","aliases":["PECS","extends wildcard","super wildcard"],"introducedIn":"generics","reinforcedIn":["streams"],"prerequisiteConceptIds":["declaracao-generica-bound","heranca-subtipo"],"commonConfusions":["chamar extends de lista somente leitura","usar wildcard em retorno sem necessidade"],"outcomes":["escolher producer extends e consumer super pelos acessos"]},{"id":"erasure-reificacao","name":"Erasure e tipos reificáveis","aliases":["type erasure","reifiable type"],"introducedIn":"generics","reinforcedIn":["jvm-profundo"],"prerequisiteConceptIds":["tipo-parametrizado"],"commonConfusions":["achar que toda informação genérica desaparece dos metadados","testar instanceof List<String>"],"outcomes":["prever restrições de arrays, instanceof e new T"]},{"id":"lambda-target-typing","name":"Lambda e tipo-alvo funcional","aliases":["functional interface","target typing"],"introducedIn":"streams","reinforcedIn":["functional-semantics-lab","java-21-profundo"],"prerequisiteConceptIds":["interface-contrato","tipo-parametrizado"],"commonConfusions":["tratar lambda como função sem tipo","achar que FunctionalInterface exige anotação"],"outcomes":["inferir parâmetros e retorno pelo método funcional"]},{"id":"captura-efetivamente-final","name":"Captura de variável efetivamente final","aliases":["lambda capture"],"introducedIn":"streams","reinforcedIn":["functional-semantics-lab"],"prerequisiteConceptIds":["lambda-target-typing","escopo-sobrecarga-varargs"],"commonConfusions":["tentar reatribuir local capturada","confundir final da referência com imutabilidade"],"outcomes":["prever o que uma lambda pode capturar"]},{"id":"pipeline-lazy-terminal","name":"Pipeline lazy e operação terminal","aliases":["intermediate operation","terminal operation"],"introducedIn":"streams","reinforcedIn":["functional-semantics-lab"],"prerequisiteConceptIds":["lambda-target-typing","list-set-map-semantica"],"commonConfusions":["esperar execução sem terminal","reutilizar stream consumido"],"outcomes":["rastrear fonte, operações e curto-circuito"]},{"id":"map-flatmap-cardinalidade","name":"map, flatMap e cardinalidade","aliases":["flattening"],"introducedIn":"streams","reinforcedIn":["functional-semantics-lab"],"prerequisiteConceptIds":["pipeline-lazy-terminal"],"commonConfusions":["achatar sem entender camada","produzir Stream<Stream<T>> sem intenção"],"outcomes":["escolher operação pela cardinalidade da função"]},{"id":"reduce-collect-contrato","name":"Redução, coleta e associatividade","aliases":["reduce","Collector"],"introducedIn":"streams","reinforcedIn":["functional-semantics-lab"],"prerequisiteConceptIds":["pipeline-lazy-terminal","expressao-operador"],"commonConfusions":["usar identidade não neutra","mutar acumulador externo"],"outcomes":["separar redução imutável de coleta mutável"]},{"id":"stream-nao-interferencia","name":"Não interferência e ausência de efeitos escondidos","aliases":["non-interference","stateless behavior"],"introducedIn":"streams","reinforcedIn":["functional-semantics-lab"],"prerequisiteConceptIds":["pipeline-lazy-terminal","comando-consulta"],"commonConfusions":["alterar fonte dentro do pipeline","usar parallel para corrigir lentidão sem medir"],"outcomes":["construir pipeline determinístico e consumido uma vez"]},{"id":"optional-zero-ou-um","name":"Optional como zero ou um retorno","aliases":["absence"],"introducedIn":"javamoderno","reinforcedIn":["functional-semantics-lab"],"prerequisiteConceptIds":["tipo-parametrizado","lambda-target-typing"],"commonConfusions":["usar Optional para toda referência","usar get sem provar presença"],"outcomes":["escolher map, orElse, orElseGet ou orElseThrow pelo contrato"]},{"id":"inferencia-var-local","name":"Inferência de tipo local com var","aliases":["local variable type inference"],"introducedIn":"javamoderno","reinforcedIn":["java-21-profundo"],"prerequisiteConceptIds":["variavel-tipo-estatico"],"commonConfusions":["achar que var cria tipagem dinâmica","usar quando o inicializador esconde intenção"],"outcomes":["reconhecer o tipo estático inferido e limites sintáticos"]},{"id":"java-time-semantica","name":"Data civil, instante, offset e fuso","aliases":["java.time","Instant","ZonedDateTime"],"introducedIn":"javamoderno","reinforcedIn":["java-21-profundo"],"prerequisiteConceptIds":["imutabilidade-objeto"],"commonConfusions":["tratar LocalDateTime como instante global","confundir duração e período"],"outcomes":["escolher tipo temporal pela informação conhecida"]},{"id":"record-text-block-base","name":"Record e text block: primeiro contato","aliases":["record","text block"],"introducedIn":"javamoderno","reinforcedIn":["java-21-profundo"],"prerequisiteConceptIds":["equals-hashcode-contrato","construtor-invariante"],"commonConfusions":["chamar acessor de getter gerado","achar record profundamente imutável"],"outcomes":["usar portador de dados compacto e texto multilinha"]},{"id":"escolha-api-semantica","name":"Escolha de API por semântica","aliases":["semantic API choice"],"introducedIn":"functional-semantics-lab","reinforcedIn":["java-21-profundo"],"prerequisiteConceptIds":["list-set-map-semantica","optional-zero-ou-um","map-flatmap-cardinalidade"],"commonConfusions":["escolher API por moda","começar pela sintaxe antes da cardinalidade"],"outcomes":["derivar List, Set, Optional, loop ou Stream do contrato"]},{"id":"evidencia-pipeline","name":"Evidência de pipeline e igualdade","aliases":["pipeline evidence"],"introducedIn":"functional-semantics-lab","reinforcedIn":["jvm-profundo"],"prerequisiteConceptIds":["stream-nao-interferencia","chave-hash-estavel"],"commonConfusions":["testar apenas caminho cheio","não verificar fonte preservada"],"outcomes":["provar vazio, duplicidade, ordem e ausência de mutação"]},{"id":"carregamento-execucao-classe","name":"Carregamento, ligação, inicialização e execução","aliases":["class loading","linking"],"introducedIn":"jvm-profundo","reinforcedIn":["java-21-profundo"],"prerequisiteConceptIds":["classpath-compilacao","source-bytecode-runtime"],"commonConfusions":["achar que import carrega classe","confundir class file e código nativo"],"outcomes":["narrar o caminho do classpath ao método executado"]},{"id":"bytecode-jit-perfil","name":"Bytecode, interpretação, JIT e perfil","aliases":["warmup","deoptimization"],"introducedIn":"jvm-profundo","reinforcedIn":[],"prerequisiteConceptIds":["carregamento-execucao-classe"],"commonConfusions":["afirmar que todo primeiro código é só interpretado","medir sem aquecimento ou distribuição"],"outcomes":["separar semântica do programa de estratégia do runtime"]},{"id":"areas-runtime-modelo","name":"Frames, heap e metadados como modelo de runtime","aliases":["JVM runtime data areas"],"introducedIn":"jvm-profundo","reinforcedIn":["java-21-profundo"],"prerequisiteConceptIds":["ciclo-alcancabilidade","erasure-reificacao"],"commonConfusions":["afirmar que primitivo sempre mora na stack","achar que local torna objeto thread-confined"],"outcomes":["usar áreas conceituais sem prometer layout físico"]},{"id":"alcancabilidade-gc","name":"Raízes, alcançabilidade e elegibilidade para GC","aliases":["GC roots","reachability"],"introducedIn":"jvm-profundo","reinforcedIn":[],"prerequisiteConceptIds":["areas-runtime-modelo","chave-hash-estavel"],"commonConfusions":["confundir sem uso com inalcançável","achar elegível igual a coletado imediatamente"],"outcomes":["rastrear caminhos vivos e retenção lógica"]},{"id":"coletores-tradeoff","name":"Coletores e trade-offs mensuráveis","aliases":["G1","ZGC","throughput","pause"],"introducedIn":"jvm-profundo","reinforcedIn":[],"prerequisiteConceptIds":["alcancabilidade-gc"],"commonConfusions":["prometer pausa fixa","escolher coletor sem carga representativa"],"outcomes":["comparar pausa, throughput, memória e versão"]},{"id":"escape-analysis-otimizacao","name":"Escape analysis e eliminação de alocação","aliases":["scalar replacement"],"introducedIn":"jvm-profundo","reinforcedIn":[],"prerequisiteConceptIds":["bytecode-jit-perfil","areas-runtime-modelo"],"commonConfusions":["ensinar alocação na stack como garantia","otimizar manualmente sem medir"],"outcomes":["explicar otimização sem alterar o modelo semântico"]},{"id":"record-invariante-copia","name":"Record, invariante e cópia defensiva","aliases":["canonical constructor"],"introducedIn":"java-21-profundo","reinforcedIn":[],"prerequisiteConceptIds":["record-text-block-base","copia-visao-imutabilidade"],"commonConfusions":["achar record profundamente imutável","não normalizar componente"],"outcomes":["proteger componentes no construtor compacto"]},{"id":"sealed-exaustividade","name":"Hierarquia selada e exaustividade","aliases":["sealed","permits"],"introducedIn":"java-21-profundo","reinforcedIn":[],"prerequisiteConceptIds":["heranca-subtipo","interface-contrato"],"commonConfusions":["selar extensão que deveria ser aberta","usar default para esconder subtipo faltante"],"outcomes":["modelar universo fechado de estados"]},{"id":"pattern-matching-record-switch","name":"Record patterns e pattern switch","aliases":["record pattern","pattern matching switch"],"introducedIn":"java-21-profundo","reinforcedIn":[],"prerequisiteConceptIds":["sealed-exaustividade","ramificacao-condicional"],"commonConfusions":["achar que null casa automaticamente","confundir pattern com cast irrestrito"],"outcomes":["decompor valores com switch exaustivo"]},{"id":"virtual-thread-throughput","name":"Virtual thread para throughput de tarefas bloqueantes","aliases":["virtual thread","thread per task"],"introducedIn":"java-21-profundo","reinforcedIn":["threads"],"prerequisiteConceptIds":["lambda-target-typing","carregamento-execucao-classe","checked-unchecked-contrato"],"commonConfusions":["achar que é thread mais rápida","criar pool pequeno de virtual threads","ignorar estado compartilhado"],"outcomes":["criar, iniciar e aguardar tarefas independentes sem alegar speedup"]},{"id":"versao-preview-lts","name":"Versão, preview e política LTS","aliases":["preview feature","LTS"],"introducedIn":"java-21-profundo","reinforcedIn":[],"prerequisiteConceptIds":["record-invariante-copia"],"commonConfusions":["atribuir LTS à linguagem e não à distribuição","usar preview sem flags e escopo"],"outcomes":["identificar em qual release um recurso ficou permanente"]},{"id":"path-filesystem","name":"Path, filesystem e caminho portável","aliases":["Path","filesystem"],"introducedIn":"java-io","reinforcedIn":["mini-importador-pedidos","process-api-cli"],"prerequisiteConceptIds":["classpath-compilacao"],"commonConfusions":["tratar caminho como String comum","assumir separador fixo do sistema"],"outcomes":["montar caminhos portáveis e distinguir caminho, arquivo e diretório"]},{"id":"byte-char-charset","name":"Bytes, caracteres e Charset","aliases":["UTF-8","encoding"],"introducedIn":"java-io","reinforcedIn":["json","mini-importador-pedidos"],"prerequisiteConceptIds":["string-imutavel"],"commonConfusions":["achar que texto é byte sem codificação","confiar no charset padrão da máquina"],"outcomes":["declarar charset e diagnosticar texto corrompido"]},{"id":"stream-resource-lifecycle","name":"Streams de dados e ciclo de recurso","aliases":["InputStream","Reader","try-with-resources"],"introducedIn":"java-io","reinforcedIn":["process-api-cli","mini-importador-pedidos"],"prerequisiteConceptIds":["try-resource-lifecycle"],"commonConfusions":["achar que GC fecha arquivo","carregar arquivo grande por conveniência"],"outcomes":["fechar recurso e escolher leitura total ou incremental"]},{"id":"files-nio-atomicidade","name":"Files/NIO e operações de arquivo","aliases":["Files","NIO.2"],"introducedIn":"java-io","reinforcedIn":["mini-importador-pedidos"],"prerequisiteConceptIds":["path-filesystem","checked-unchecked-contrato"],"commonConfusions":["assumir atomicidade universal","ignorar falhas parciais de cópia/move"],"outcomes":["escolher create/read/write/move/copy com política de falha"]},{"id":"csv-fronteira-textual","name":"CSV como fronteira textual limitada","aliases":["CSV","delimited text"],"introducedIn":"mini-analisador-vendas","reinforcedIn":["mini-importador-pedidos"],"prerequisiteConceptIds":["byte-char-charset","map-flatmap-cardinalidade"],"commonConfusions":["usar split simples para todo CSV","misturar parsing com regra de negócio"],"outcomes":["definir contrato de colunas, aspas, separador e rejeição"]},{"id":"relatorio-dados-deterministico","name":"Relatório determinístico","aliases":["export","report"],"introducedIn":"mini-analisador-vendas","reinforcedIn":["mini-importador-pedidos","zenith-cli-inicial"],"prerequisiteConceptIds":["reduce-collect-contrato"],"commonConfusions":["deixar ordem dependente de HashMap","não registrar critério de desempate"],"outcomes":["produzir saída ordenada e reproduzível"]},{"id":"json-formato-contrato","name":"JSON como contrato de fronteira","aliases":["JSON"],"introducedIn":"json","reinforcedIn":["mini-importador-pedidos"],"prerequisiteConceptIds":["record-text-block-base","byte-char-charset"],"commonConfusions":["confundir JSON com objeto Java","concatenar JSON manualmente"],"outcomes":["separar representação externa do modelo interno"]},{"id":"serializacao-desserializacao","name":"Serialização e desserialização","aliases":["serialization","deserialization"],"introducedIn":"json","reinforcedIn":["mini-importador-pedidos"],"prerequisiteConceptIds":["json-formato-contrato","checked-unchecked-contrato"],"commonConfusions":["achar que desserializar valida domínio automaticamente","serializar entidade interna por conveniência"],"outcomes":["converter DTO com validação explícita e falha observável"]},{"id":"jackson-introspeccao-records","name":"Jackson, propriedades e records","aliases":["ObjectMapper","JavaBeans"],"introducedIn":"json","reinforcedIn":["mini-importador-pedidos"],"prerequisiteConceptIds":["json-formato-contrato","record-invariante-copia"],"commonConfusions":["achar que getter getX é obrigatório para record","perder invariantes ao criar objeto vazio"],"outcomes":["mapear propriedades sem acoplar contrato externo ao domínio"]},{"id":"processo-filho-contrato","name":"Processo filho como fronteira externa","aliases":["Process","ProcessBuilder"],"introducedIn":"process-api-cli","reinforcedIn":["zenith-cli-inicial"],"prerequisiteConceptIds":["stream-resource-lifecycle","cli-stdin-stdout"],"commonConfusions":["achar que processo é método Java","ignorar exit code"],"outcomes":["executar comando com argumentos, streams, timeout e código de saída"]},{"id":"stdout-stderr-exit-code","name":"stdout, stderr e exit code","aliases":["standard streams","exit status"],"introducedIn":"process-api-cli","reinforcedIn":["zenith-cli-inicial"],"prerequisiteConceptIds":["processo-filho-contrato"],"commonConfusions":["tratar stderr como exceção Java","ignorar saída de erro quando exit code é zero"],"outcomes":["capturar resultado tipado sem perder diagnóstico"]},{"id":"shell-quoting-injection","name":"Shell, quoting e command injection","aliases":["shell expansion","command injection"],"introducedIn":"process-api-cli","reinforcedIn":["zenith-cli-inicial"],"prerequisiteConceptIds":["processo-filho-contrato"],"commonConfusions":["passar comando inteiro como string única","juntar entrada do usuário em shell"],"outcomes":["evitar shell quando possível e passar argumentos separados"]},{"id":"process-timeout-cancelamento","name":"Timeout e encerramento de processo","aliases":["destroy","destroyForcibly","deadline"],"introducedIn":"process-api-cli","reinforcedIn":["zenith-cli-inicial"],"prerequisiteConceptIds":["stdout-stderr-exit-code"],"commonConfusions":["esperar indefinidamente","matar processo sem política de cleanup"],"outcomes":["limitar execução e declarar política de encerramento"]},{"id":"importacao-tolerante","name":"Importação tolerante a registros inválidos","aliases":["partial success","rejection report"],"introducedIn":"mini-importador-pedidos","reinforcedIn":["zenith-cli-inicial"],"prerequisiteConceptIds":["csv-fronteira-textual","serializacao-desserializacao","files-nio-atomicidade"],"commonConfusions":["abortar tudo por uma linha ruim sem contrato","ignorar rejeições silenciosamente"],"outcomes":["separar aceitos, rejeitados e falhas fatais"]},{"id":"zenith-comando-deterministico","name":"Zenith inicial: comandos determinísticos","aliases":["Zenith CLI"],"introducedIn":"zenith-cli-inicial","reinforcedIn":[],"prerequisiteConceptIds":["processo-filho-contrato","relatorio-dados-deterministico","importacao-tolerante"],"commonConfusions":["prometer linguagem natural aberta","executar comando arbitrário"],"outcomes":["criar interface textual restrita, auditável e reproduzível"]},{"id":"complexidade-assintotica","name":"Complexidade assintótica","aliases":["Big O","O(n)"],"introducedIn":"big-o","reinforcedIn":["ordenacao-busca","algoritmos-praticos"],"prerequisiteConceptIds":["contrato-do-laco","contrato-collection-map"],"commonConfusions":["tratar Big O como tempo em segundos","ignorar tamanho e distribuição da entrada"],"outcomes":["explicar crescimento de tempo e espaço sem prometer duração absoluta"]},{"id":"tempo-espaco-tradeoff","name":"Trade-off entre tempo e espaço","aliases":["space complexity"],"introducedIn":"big-o","reinforcedIn":["algoritmos-praticos"],"prerequisiteConceptIds":["complexidade-assintotica"],"commonConfusions":["otimizar tempo sem medir memória","chamar memória auxiliar de erro"],"outcomes":["comparar soluções que trocam memória por menos passos"]},{"id":"medicao-evidencia-performance","name":"Medição e evidência de performance","aliases":["benchmark","JMH"],"introducedIn":"big-o","reinforcedIn":["ordenacao-busca","algoritmos-praticos","debugging"],"prerequisiteConceptIds":["bytecode-jit-perfil"],"commonConfusions":["usar uma execução com currentTimeMillis como prova","comparar sem aquecimento"],"outcomes":["separar análise assintótica, benchmark e profiling"]},{"id":"recursao-caso-base","name":"Caso base e progresso recursivo","aliases":["recursive base case"],"introducedIn":"recursao","reinforcedIn":["ordenacao-busca"],"prerequisiteConceptIds":["complexidade-assintotica","areas-runtime-modelo"],"commonConfusions":["esquecer caso base","não reduzir o problema"],"outcomes":["provar que cada chamada se aproxima da parada"]},{"id":"stack-frame-recursivo","name":"Stack frame recursivo","aliases":["call stack"],"introducedIn":"recursao","reinforcedIn":["debugging"],"prerequisiteConceptIds":["areas-runtime-modelo"],"commonConfusions":["achar que recursão não consome memória","esperar tail-call optimization garantida"],"outcomes":["rastrear empilhamento e StackOverflowError"]},{"id":"memoization-subproblemas","name":"Memoization e subproblemas sobrepostos","aliases":["memoization","dynamic programming"],"introducedIn":"recursao","reinforcedIn":["algoritmos-praticos"],"prerequisiteConceptIds":["recursao-caso-base","contrato-collection-map"],"commonConfusions":["confundir cache com correção automática","guardar resultado com chave mutável"],"outcomes":["evitar recomputação quando há subproblemas repetidos"]},{"id":"busca-binaria-invariante","name":"Busca binária e invariante de intervalo","aliases":["binary search"],"introducedIn":"ordenacao-busca","reinforcedIn":["algoritmos-praticos"],"prerequisiteConceptIds":["recursao-caso-base","complexidade-assintotica"],"commonConfusions":["usar em dados não ordenados","errar condição de avanço"],"outcomes":["manter intervalo fechado/aberto coerente e terminar"]},{"id":"ordenacao-estabilidade","name":"Ordenação, estabilidade e comparador","aliases":["stable sort","Comparator"],"introducedIn":"ordenacao-busca","reinforcedIn":["algoritmos-praticos"],"prerequisiteConceptIds":["ordem-comparable-comparator"],"commonConfusions":["achar que toda ordenação estável é in-place","comparador inconsistente"],"outcomes":["escolher API padrão e declarar critérios/desempate"]},{"id":"dividir-conquistar","name":"Dividir para conquistar","aliases":["divide and conquer","merge sort"],"introducedIn":"ordenacao-busca","reinforcedIn":["algoritmos-praticos"],"prerequisiteConceptIds":["recursao-caso-base"],"commonConfusions":["dividir sem combinar corretamente","reimplementar sort em produção sem motivo"],"outcomes":["separar divisão, caso base e combinação"]},{"id":"estrutura-operacao-dominante","name":"Estrutura pela operação dominante","aliases":["data structure choice"],"introducedIn":"algoritmos-praticos","reinforcedIn":["testes"],"prerequisiteConceptIds":["contrato-collection-map","complexidade-assintotica"],"commonConfusions":["escolher estrutura pelo nome famoso","ignorar operação mais repetida"],"outcomes":["derivar ArrayList, HashMap, TreeMap, ArrayDeque ou PriorityQueue da carga"]},{"id":"grafo-bfs-dfs","name":"Grafos, BFS e DFS","aliases":["graph","breadth-first search","depth-first search"],"introducedIn":"algoritmos-praticos","reinforcedIn":["testes"],"prerequisiteConceptIds":["estrutura-operacao-dominante"],"commonConfusions":["achar que grafo é só árvore","não controlar visitados"],"outcomes":["percorrer vértices com fila ou pilha e evitar ciclos"]},{"id":"heap-priorityqueue-topk","name":"Heap, PriorityQueue e top-k","aliases":["heap","PriorityQueue"],"introducedIn":"algoritmos-praticos","reinforcedIn":["testes"],"prerequisiteConceptIds":["ordenacao-estabilidade"],"commonConfusions":["ordenar tudo para pegar poucos itens","esperar iteração ordenada da PriorityQueue"],"outcomes":["usar heap quando o menor/maior próximo importa"]},{"id":"git-snapshot-index","name":"Git como snapshots e index","aliases":["working tree","staging area","commit"],"introducedIn":"git","reinforcedIn":["build","testes"],"prerequisiteConceptIds":["path-filesystem"],"commonConfusions":["achar que commit salva só diferenças soltas","não entender staging area"],"outcomes":["preparar commits pequenos e verificáveis"]},{"id":"branch-merge-rebase","name":"Branch, merge e rebase","aliases":["branching"],"introducedIn":"git","reinforcedIn":["debugging"],"prerequisiteConceptIds":["git-snapshot-index"],"commonConfusions":["reescrever branch compartilhada","confundir merge com backup"],"outcomes":["trabalhar em paralelo preservando main funcional"]},{"id":"gitignore-secrets","name":".gitignore, artefatos e segredos","aliases":["gitignore","secrets"],"introducedIn":"git","reinforcedIn":["logging"],"prerequisiteConceptIds":["git-snapshot-index"],"commonConfusions":["versionar target ou .class","commitar .env"],"outcomes":["impedir artefatos e credenciais no histórico"]},{"id":"build-lifecycle","name":"Ciclo de build","aliases":["compile","test","package"],"introducedIn":"build","reinforcedIn":["testes"],"prerequisiteConceptIds":["classpath-compilacao","git-snapshot-index"],"commonConfusions":["confundir IDE com build reproduzível","rodar teste fora do ciclo"],"outcomes":["compilar, testar e empacotar por comando reproduzível"]},{"id":"dependencia-escopo-versao","name":"Dependência, escopo e versão","aliases":["Maven scope","Gradle configuration"],"introducedIn":"build","reinforcedIn":["testes","logging"],"prerequisiteConceptIds":["build-lifecycle"],"commonConfusions":["empacotar JUnit em produção","não fixar versão"],"outcomes":["declarar dependências por uso e ambiente"]},{"id":"wrapper-build-reprodutivel","name":"Wrapper e build reproduzível","aliases":["Maven Wrapper","Gradle Wrapper"],"introducedIn":"build","reinforcedIn":["testes"],"prerequisiteConceptIds":["build-lifecycle"],"commonConfusions":["depender da versão global da máquina","não versionar wrapper"],"outcomes":["executar build consistente entre máquinas e CI"]},{"id":"debug-hipotese-breakpoint","name":"Hipótese, breakpoint e inspeção","aliases":["debugger","breakpoint"],"introducedIn":"debugging","reinforcedIn":["logging","testes"],"prerequisiteConceptIds":["stack-frame-recursivo"],"commonConfusions":["clicar step sem hipótese","começar longe do sintoma"],"outcomes":["formular hipótese e pausar onde a evidência aparece"]},{"id":"debug-step-watch-evaluate","name":"Step, watch e evaluate expression","aliases":["step over","watchpoint"],"introducedIn":"debugging","reinforcedIn":["testes"],"prerequisiteConceptIds":["debug-hipotese-breakpoint"],"commonConfusions":["avaliar expressão com efeito colateral sem perceber","entrar em bibliotecas sem necessidade"],"outcomes":["inspecionar estado e avançar execução deliberadamente"]},{"id":"log-nivel-contexto","name":"Nível de log e contexto","aliases":["TRACE","DEBUG","INFO","WARN","ERROR"],"introducedIn":"logging","reinforcedIn":["testes"],"prerequisiteConceptIds":["causa-excecao"],"commonConfusions":["usar ERROR para fluxo esperado","logar sem identificador útil"],"outcomes":["registrar evento com severidade e contexto corretos"]},{"id":"log-parametrizado-causa","name":"Log parametrizado e causa","aliases":["SLF4J placeholders"],"introducedIn":"logging","reinforcedIn":["debugging"],"prerequisiteConceptIds":["log-nivel-contexto"],"commonConfusions":["concatenar string sempre","perder stack trace ao logar exceção"],"outcomes":["logar sem custo desnecessário e preservando Throwable"]},{"id":"configuracao-externa","name":"Configuração externa","aliases":["properties","environment variables"],"introducedIn":"logging","reinforcedIn":["build"],"prerequisiteConceptIds":["gitignore-secrets","stream-resource-lifecycle"],"commonConfusions":["hardcoded secret","misturar config e regra"],"outcomes":["ler configuração fora do código e proteger segredos"]},{"id":"teste-aaa-first","name":"Teste unitário, AAA e FIRST","aliases":["JUnit","Arrange Act Assert"],"introducedIn":"testes","reinforcedIn":[],"prerequisiteConceptIds":["build-lifecycle","checked-unchecked-contrato"],"commonConfusions":["testar muitas coisas no mesmo método","depender de ordem de execução"],"outcomes":["escrever testes rápidos, independentes e auto-verificáveis"]},{"id":"assertions-excecoes-parametrizado","name":"Assertions, exceções e parametrização","aliases":["assertThrows","ParameterizedTest"],"introducedIn":"testes","reinforcedIn":[],"prerequisiteConceptIds":["teste-aaa-first"],"commonConfusions":["comparar double sem delta","não testar falha"],"outcomes":["cobrir caminho feliz, limites e exceções"]},{"id":"tdd-red-green-refactor","name":"TDD: Red, Green, Refactor","aliases":["TDD"],"introducedIn":"testes","reinforcedIn":[],"prerequisiteConceptIds":["teste-aaa-first","git-snapshot-index"],"commonConfusions":["refatorar antes do teste passar","escrever teste depois só para cobertura"],"outcomes":["guiar implementação por comportamento observável"]},{"id":"http-mensagem-recurso","name":"HTTP como mensagem sobre recurso","aliases":["request","response","resource"],"introducedIn":"http","reinforcedIn":["http-wire-contract"],"prerequisiteConceptIds":["json-formato-contrato","cli-stdin-stdout"],"commonConfusions":["confundir URL com recurso em memória","achar que HTTP é Spring"],"outcomes":["separar método, alvo, headers, body e recurso"]},{"id":"http-metodo-semantica","name":"Método HTTP e semântica","aliases":["GET","POST","PUT","PATCH","DELETE"],"introducedIn":"http","reinforcedIn":["http-wire-contract","java-httpclient-json"],"prerequisiteConceptIds":["http-mensagem-recurso"],"commonConfusions":["usar POST para tudo","colocar verbo no caminho sem pensar no recurso"],"outcomes":["escolher método por intenção e efeito"]},{"id":"http-status-classe","name":"Status code como classe de resultado","aliases":["2xx","4xx","5xx"],"introducedIn":"http","reinforcedIn":["java-httpclient-json","unreliable-api-client"],"prerequisiteConceptIds":["http-mensagem-recurso"],"commonConfusions":["retornar 200 para erro de domínio","tratar todo 4xx como exceção técnica"],"outcomes":["interpretar sucesso, erro do cliente e falha do servidor"]},{"id":"http-idempotencia-seguranca","name":"Segurança, idempotência e repetição HTTP","aliases":["safe method","idempotent"],"introducedIn":"http","reinforcedIn":["http-wire-contract","unreliable-api-client"],"prerequisiteConceptIds":["http-metodo-semantica"],"commonConfusions":["achar que idempotente significa resposta igual","repetir POST sem chave"],"outcomes":["decidir quando retry pode ser seguro"]},{"id":"http-header-body-negociacao","name":"Headers, body e negociação de representação","aliases":["Content-Type","Accept","Authorization"],"introducedIn":"http","reinforcedIn":["http-wire-contract","java-httpclient-json"],"prerequisiteConceptIds":["byte-char-charset","json-formato-contrato"],"commonConfusions":["confundir Accept com Content-Type","mandar Authorization em log"],"outcomes":["declarar formato enviado/aceito e metadados sensíveis"]},{"id":"curl-inspecao-http","name":"curl para inspeção HTTP","aliases":["curl","-i","-v"],"introducedIn":"http-wire-contract","reinforcedIn":["java-httpclient-json","pure-java-api-project"],"prerequisiteConceptIds":["http-header-body-negociacao","terminal-diretorio-comando"],"commonConfusions":["usar curl só para baixar arquivo","não observar headers"],"outcomes":["reproduzir request e registrar resposta bruta"]},{"id":"httpclient-reuso-timeout","name":"HttpClient reutilizável e timeouts","aliases":["HttpClient","HttpRequest timeout"],"introducedIn":"java-httpclient-json","reinforcedIn":["unreliable-api-client"],"prerequisiteConceptIds":["http-status-classe","process-timeout-cancelamento"],"commonConfusions":["criar client por request","achar timeout prova que efeito remoto não ocorreu"],"outcomes":["montar cliente com orçamento temporal explícito"]},{"id":"http-response-bodyhandler","name":"HttpResponse e BodyHandler","aliases":["BodyHandlers"],"introducedIn":"java-httpclient-json","reinforcedIn":["pure-java-api-project"],"prerequisiteConceptIds":["httpclient-reuso-timeout","byte-char-charset"],"commonConfusions":["ler body antes de status sem política","ignorar headers"],"outcomes":["separar status, headers e corpo consumido"]},{"id":"dto-mapping-fronteira","name":"DTO e mapeamento de fronteira HTTP","aliases":["request DTO","response DTO"],"introducedIn":"java-httpclient-json","reinforcedIn":["pure-java-api-project"],"prerequisiteConceptIds":["serializacao-desserializacao","json-formato-contrato"],"commonConfusions":["usar JSON externo como domínio","aceitar null como valor válido sem regra"],"outcomes":["mapear representação externa para domínio validado"]},{"id":"timeout-deadline-cancelamento-http","name":"Timeout, deadline e cancelamento em integração","aliases":["deadline","cancellation"],"introducedIn":"unreliable-api-client","reinforcedIn":["pure-java-api-project"],"prerequisiteConceptIds":["httpclient-reuso-timeout"],"commonConfusions":["somar retries sem orçamento total","confundir timeout com rollback remoto"],"outcomes":["limitar chamadas por orçamento total e propagar cancelamento"]},{"id":"retry-backoff-jitter","name":"Retry com backoff e jitter","aliases":["backoff","jitter"],"introducedIn":"unreliable-api-client","reinforcedIn":["pure-java-api-project"],"prerequisiteConceptIds":["http-idempotencia-seguranca","timeout-deadline-cancelamento-http"],"commonConfusions":["repetir toda falha","retry sem limite"],"outcomes":["repetir apenas falhas transitórias sob limite"]},{"id":"rate-limit-paginacao","name":"Rate limit e paginação","aliases":["429","Retry-After","cursor"],"introducedIn":"unreliable-api-client","reinforcedIn":["pure-java-api-project"],"prerequisiteConceptIds":["http-status-classe"],"commonConfusions":["ignorar Retry-After","tratar cursor como número editável"],"outcomes":["respeitar capacidade remota e percorrer páginas com contrato"]},{"id":"secrets-integracao","name":"Secrets em integrações HTTP","aliases":["API key","Bearer token"],"introducedIn":"unreliable-api-client","reinforcedIn":["pure-java-api-project"],"prerequisiteConceptIds":["configuracao-externa","gitignore-secrets"],"commonConfusions":["colocar token em URL ou log","commitar API key"],"outcomes":["fornecer credenciais por ambiente e mascarar logs"]},{"id":"stub-http-deterministico","name":"Stub HTTP determinístico","aliases":["HttpServer","test double"],"introducedIn":"pure-java-api-project","reinforcedIn":[],"prerequisiteConceptIds":["teste-aaa-first","http-response-bodyhandler"],"commonConfusions":["testar contra internet real","aumentar timeout até passar"],"outcomes":["simular status, atraso, headers e payload sem rede externa"]},{"id":"falha-parcial-cache-stale","name":"Falha parcial, cache e stale fallback","aliases":["partial failure","stale cache"],"introducedIn":"pure-java-api-project","reinforcedIn":[],"prerequisiteConceptIds":["retry-backoff-jitter","files-nio-atomicidade"],"commonConfusions":["retornar lista vazia para indisponível","esconder dado velho como fresco"],"outcomes":["representar sucesso parcial, indisponível e cache expirado"]},{"id":"modelo-relacional-tabela-chave","name":"Modelo relacional, tabela e chave","aliases":["relation","primary key","foreign key"],"introducedIn":"sql","reinforcedIn":["postgres","mini-financas-jdbc"],"prerequisiteConceptIds":["contrato-collection-map"],"commonConfusions":["achar que tabela é planilha sem contrato","usar texto livre como identidade"],"outcomes":["modelar entidades, relações, chaves e cardinalidade no banco"]},{"id":"ddl-schema-constraint","name":"DDL, schema e constraint","aliases":["CREATE TABLE","NOT NULL","UNIQUE","CHECK"],"introducedIn":"sql","reinforcedIn":["postgres","migrations"],"prerequisiteConceptIds":["modelo-relacional-tabela-chave"],"commonConfusions":["validar só no Java","criar coluna sem regra de nulidade"],"outcomes":["declarar estrutura e invariantes persistentes"]},{"id":"dml-crud-sql","name":"DML e CRUD em SQL","aliases":["INSERT","SELECT","UPDATE","DELETE"],"introducedIn":"sql","reinforcedIn":["jdbc"],"prerequisiteConceptIds":["ddl-schema-constraint"],"commonConfusions":["esquecer WHERE em update/delete","misturar comando com transação"],"outcomes":["ler e modificar dados com escopo explícito"]},{"id":"join-cardinalidade-sql","name":"JOIN e cardinalidade em SQL","aliases":["INNER JOIN","LEFT JOIN"],"introducedIn":"sql","reinforcedIn":["postgres-concorrencia"],"prerequisiteConceptIds":["modelo-relacional-tabela-chave"],"commonConfusions":["multiplicar linhas sem perceber","usar JOIN como filtro mágico"],"outcomes":["combinar tabelas preservando cardinalidade esperada"]},{"id":"agregacao-groupby-sql","name":"Agregação e GROUP BY","aliases":["COUNT","SUM","AVG"],"introducedIn":"sql","reinforcedIn":["mini-financas-jdbc"],"prerequisiteConceptIds":["dml-crud-sql"],"commonConfusions":["selecionar coluna não agrupada","confundir linha com grupo"],"outcomes":["resumir dados mantendo regra de agrupamento"]},{"id":"sql-transacao-atomicidade","name":"Transação e atomicidade","aliases":["BEGIN","COMMIT","ROLLBACK"],"introducedIn":"sql","reinforcedIn":["postgres-concorrencia","jdbc","mini-financas-jdbc"],"prerequisiteConceptIds":["dml-crud-sql"],"commonConfusions":["achar que rollback desfaz operação já commitada","usar autocommit sem notar"],"outcomes":["delimitar unidade de trabalho tudo-ou-nada"]},{"id":"indice-plano-custo","name":"Índice, plano e custo","aliases":["index","EXPLAIN"],"introducedIn":"sql","reinforcedIn":["postgres","postgres-concorrencia"],"prerequisiteConceptIds":["dml-crud-sql"],"commonConfusions":["criar índice para toda coluna","achar que índice só acelera"],"outcomes":["ler plano e justificar índice por consulta real"]},{"id":"postgres-tipo-dado-dominio","name":"Tipos PostgreSQL e domínio","aliases":["numeric","timestamptz","uuid"],"introducedIn":"postgres","reinforcedIn":["mini-financas-jdbc"],"prerequisiteConceptIds":["ddl-schema-constraint"],"commonConfusions":["usar text para tudo","guardar dinheiro em double"],"outcomes":["escolher tipo por semântica, precisão e consulta"]},{"id":"postgres-explain-analyze","name":"EXPLAIN e EXPLAIN ANALYZE","aliases":["query plan","seq scan","index scan"],"introducedIn":"postgres","reinforcedIn":["postgres-concorrencia"],"prerequisiteConceptIds":["indice-plano-custo"],"commonConfusions":["otimizar sem plano","comparar ambiente sem dados representativos"],"outcomes":["observar plano antes de prometer performance"]},{"id":"mvcc-isolamento-lock","name":"MVCC, isolamento e locks","aliases":["READ COMMITTED","REPEATABLE READ","lock"],"introducedIn":"postgres-concorrencia","reinforcedIn":["jdbc-under-the-hood"],"prerequisiteConceptIds":["sql-transacao-atomicidade","postgres-explain-analyze"],"commonConfusions":["achar que transação sempre serializa tudo","ignorar bloqueio até produção"],"outcomes":["explicar visibilidade, conflito e espera entre transações"]},{"id":"migration-versionada-checksum","name":"Migration versionada e checksum","aliases":["Flyway","V1__","checksum"],"introducedIn":"migrations","reinforcedIn":["mini-financas-jdbc"],"prerequisiteConceptIds":["ddl-schema-constraint","git-snapshot-index"],"commonConfusions":["editar migration aplicada","usar ddl-auto update em produção"],"outcomes":["evoluir schema por histórico revisável"]},{"id":"migration-expand-contract","name":"Evolução expand/contract","aliases":["backward compatible migration"],"introducedIn":"migrations","reinforcedIn":["spring-jpa"],"prerequisiteConceptIds":["migration-versionada-checksum"],"commonConfusions":["renomear coluna em um deploy só","misturar mudança destrutiva com código antigo"],"outcomes":["planejar mudanças compatíveis entre versões"]},{"id":"jdbc-driver-connection","name":"Driver JDBC e Connection","aliases":["DriverManager","Connection"],"introducedIn":"jdbc","reinforcedIn":["jdbc-under-the-hood"],"prerequisiteConceptIds":["sql-transacao-atomicidade","checked-unchecked-contrato"],"commonConfusions":["abrir conexão para cada linha","não fechar recurso"],"outcomes":["abrir, usar e fechar sessão com banco de forma explícita"]},{"id":"preparedstatement-parametro","name":"PreparedStatement e parâmetros","aliases":["bind parameter","SQL injection"],"introducedIn":"jdbc","reinforcedIn":["jdbc-under-the-hood"],"prerequisiteConceptIds":["dml-crud-sql"],"commonConfusions":["concatenar entrada no SQL","tentar parametrizar nome de tabela"],"outcomes":["separar SQL de valores e reduzir injeção"]},{"id":"resultset-mapeamento","name":"ResultSet e mapeamento de linha","aliases":["row mapper","cursor"],"introducedIn":"jdbc","reinforcedIn":["jdbc-under-the-hood"],"prerequisiteConceptIds":["record-invariante-copia"],"commonConfusions":["vazar ResultSet para camada externa","aceitar null sem política"],"outcomes":["converter linha para objeto de domínio/DTO com contrato"]},{"id":"pool-conexao-backpressure","name":"Pool de conexão e backpressure","aliases":["HikariCP","connection pool"],"introducedIn":"jdbc","reinforcedIn":["jdbc-under-the-hood"],"prerequisiteConceptIds":["jdbc-driver-connection"],"commonConfusions":["achar que pool cria capacidade infinita","não devolver conexão"],"outcomes":["limitar recurso escasso e evitar saturação invisível"]},{"id":"jdbc-transacao-rollback","name":"Transação JDBC, commit e rollback","aliases":["setAutoCommit","rollback"],"introducedIn":"jdbc","reinforcedIn":["mini-financas-jdbc","jdbc-under-the-hood"],"prerequisiteConceptIds":["jdbc-driver-connection","sql-transacao-atomicidade"],"commonConfusions":["dar rollback depois de autocommit","perder exceção original no rollback"],"outcomes":["controlar unidade de trabalho e preservar diagnóstico"]},{"id":"financas-ledger-invariante","name":"Ledger financeiro e invariante","aliases":["ledger","saldo","lançamento"],"introducedIn":"mini-financas-jdbc","reinforcedIn":["jdbc-under-the-hood"],"prerequisiteConceptIds":["sql-transacao-atomicidade","postgres-tipo-dado-dominio"],"commonConfusions":["atualizar saldo sem lançamento","usar double para dinheiro"],"outcomes":["registrar eventos financeiros com saldo derivável e auditável"]},{"id":"annotation-metadata-contract","name":"Annotation como metadado de contrato","aliases":["annotation","metadata"],"introducedIn":"anotacoes","reinforcedIn":["di-ioc-profundo","projetospring"],"prerequisiteConceptIds":["classe-instancia-objeto","build-lifecycle"],"commonConfusions":["achar que anotação executa comportamento sozinha","usar anotação para esconder regra de domínio"],"outcomes":["declarar metadado e saber quem o lê em runtime/build"]},{"id":"reflection-runtime-introspection","name":"Reflection e introspecção em runtime","aliases":["Reflection","Class","Method"],"introducedIn":"anotacoes","reinforcedIn":["projetospring","di-ioc-profundo"],"prerequisiteConceptIds":["annotation-metadata-contract"],"commonConfusions":["confundir nome de método com contrato estável","ignorar custo e quebra de encapsulamento"],"outcomes":["inspecionar tipos deliberadamente e limitar uso reflexivo"]},{"id":"srp-coesao-motivo-mudanca","name":"SRP, coesão e motivo de mudança","aliases":["Single Responsibility Principle"],"introducedIn":"solid","reinforcedIn":["clean-code","ddd"],"prerequisiteConceptIds":["encapsulamento-invariante"],"commonConfusions":["contar métodos como responsabilidade","dividir tudo em classes anêmicas"],"outcomes":["separar responsabilidades por motivo real de mudança"]},{"id":"ocp-polimorfismo-extensao","name":"OCP por polimorfismo e composição","aliases":["Open/Closed Principle"],"introducedIn":"solid","reinforcedIn":["padroes","projetospring"],"prerequisiteConceptIds":["interface-contrato","polimorfismo-substituicao"],"commonConfusions":["criar abstração para todo if","editar switch central a cada regra nova"],"outcomes":["estender comportamento sem reabrir regra estável"]},{"id":"lsp-contrato-substituicao","name":"LSP e contrato de substituição","aliases":["Liskov Substitution"],"introducedIn":"solid","reinforcedIn":["ddd"],"prerequisiteConceptIds":["polimorfismo-substituicao"],"commonConfusions":["herdar só para reaproveitar código","subclasse enfraquecer pós-condição"],"outcomes":["garantir que implementação preserve o contrato esperado"]},{"id":"isp-interface-pequena","name":"ISP e interface pequena","aliases":["Interface Segregation"],"introducedIn":"solid","reinforcedIn":["di","arquitetura-software"],"prerequisiteConceptIds":["interface-contrato"],"commonConfusions":["criar interface gigante por camada","forçar método não suportado"],"outcomes":["expor portas pequenas orientadas ao caso de uso"]},{"id":"dip-dependencia-abstracao","name":"DIP e dependência para abstração","aliases":["Dependency Inversion"],"introducedIn":"solid","reinforcedIn":["di","arquitetura-software"],"prerequisiteConceptIds":["interface-contrato"],"commonConfusions":["achar que DIP exige framework","depender de abstração inútil idêntica à implementação"],"outcomes":["fazer política depender de contrato estável, não detalhe externo"]},{"id":"di-composicao-raiz","name":"DI manual e composition root","aliases":["composition root","constructor injection"],"introducedIn":"di","reinforcedIn":["projetospring","di-ioc-profundo"],"prerequisiteConceptIds":["dip-dependencia-abstracao"],"commonConfusions":["new espalhado em regra de negócio","injeção por campo sem necessidade"],"outcomes":["centralizar criação de objetos e injetar dependências explícitas"]},{"id":"ioc-container-registro-resolucao","name":"Container IoC: registro e resolução","aliases":["IoC container","service locator"],"introducedIn":"di","reinforcedIn":["di-ioc-profundo","projetospring"],"prerequisiteConceptIds":["di-composicao-raiz","reflection-runtime-introspection"],"commonConfusions":["usar container como variável global","confundir IoC com DI"],"outcomes":["entender como container registra, resolve e controla ciclo de vida"]},{"id":"strategy-policy-object","name":"Strategy como política substituível","aliases":["Strategy pattern"],"introducedIn":"padroes","reinforcedIn":["projetospring","ddd"],"prerequisiteConceptIds":["ocp-polimorfismo-extensao"],"commonConfusions":["criar Strategy para algoritmo que nunca muda","usar herança quando composição bastava"],"outcomes":["trocar algoritmo por contrato sem mexer no consumidor"]},{"id":"factory-criacao-invariante","name":"Factory para criação com invariantes","aliases":["Factory Method","static factory"],"introducedIn":"padroes","reinforcedIn":["ddd","projetospring"],"prerequisiteConceptIds":["encapsulamento-invariante"],"commonConfusions":["factory que só chama construtor sem motivo","esconder falha de criação"],"outcomes":["centralizar criação quando há validação, variação ou nome de intenção"]},{"id":"adapter-fronteira-externa","name":"Adapter para fronteira externa","aliases":["Adapter pattern","port adapter"],"introducedIn":"padroes","reinforcedIn":["arquitetura-software","projetospring"],"prerequisiteConceptIds":["dto-mapping-fronteira","dip-dependencia-abstracao"],"commonConfusions":["adaptar domínio ao detalhe externo","vazar DTO externo para regra"],"outcomes":["isolar bibliotecas, rede, banco e UI atrás de contrato próprio"]},{"id":"nome-intencao-codigo","name":"Nomes que revelam intenção","aliases":["intention-revealing name"],"introducedIn":"clean-code","reinforcedIn":["ddd"],"prerequisiteConceptIds":["srp-coesao-motivo-mudanca"],"commonConfusions":["nome comprido para compensar função confusa","abreviação tribal"],"outcomes":["nomear estado e comportamento pelo vocabulário do problema"]},{"id":"funcao-pequena-nivel-abstracao","name":"Função pequena e nível de abstração","aliases":["extract method"],"introducedIn":"clean-code","reinforcedIn":["projetospring"],"prerequisiteConceptIds":["srp-coesao-motivo-mudanca"],"commonConfusions":["quebrar em métodos privados sem melhorar leitura","misturar parsing, regra e IO"],"outcomes":["separar passos por nível de abstração observável"]},{"id":"refatoracao-rede-seguranca","name":"Refatoração com rede de segurança","aliases":["refactoring","characterization test"],"introducedIn":"clean-code","reinforcedIn":["projetospring"],"prerequisiteConceptIds":["teste-aaa-first","debug-hipotese-breakpoint"],"commonConfusions":["chamar reescrita de refatoração","mudar comportamento sem teste"],"outcomes":["alterar estrutura preservando comportamento verificável"]},{"id":"mini-framework-dispatcher","name":"Mini-framework: dispatcher e lifecycle","aliases":["dispatcher","front controller"],"introducedIn":"projetospring","reinforcedIn":["spring-core"],"prerequisiteConceptIds":["annotation-metadata-contract","reflection-runtime-introspection","di-composicao-raiz"],"commonConfusions":["achar que framework é mágica","misturar roteamento, criação e regra"],"outcomes":["montar um dispatcher mínimo que conecta metadado, resolução e chamada"]},{"id":"ioc-lifecycle-bean","name":"IoC e lifecycle de objeto gerenciado","aliases":["bean lifecycle","ApplicationContext"],"introducedIn":"di-ioc-profundo","reinforcedIn":["spring-core"],"prerequisiteConceptIds":["ioc-container-registro-resolucao","annotation-metadata-contract"],"commonConfusions":["achar que DI é igual a IoC","ignorar ordem de criação e destruição"],"outcomes":["descrever criação, injeção, inicialização, uso e descarte de componente gerenciado"]},{"id":"dependencia-circular-grafo","name":"Dependência circular como grafo inválido","aliases":["circular dependency"],"introducedIn":"di-ioc-profundo","reinforcedIn":["arquitetura-software"],"prerequisiteConceptIds":["ioc-lifecycle-bean"],"commonConfusions":["resolver ciclo com lazy sem repensar design","não enxergar acoplamento bidirecional"],"outcomes":["quebrar ciclos por responsabilidade, evento ou terceiro serviço"]},{"id":"camadas-direcao-dependencia","name":"Camadas e direção de dependência","aliases":["layered architecture"],"introducedIn":"arquitetura-software","reinforcedIn":["ddd","spring-mvc"],"prerequisiteConceptIds":["dip-dependencia-abstracao","adapter-fronteira-externa"],"commonConfusions":["camada por pasta sem regra de dependência","controller chamar SQL diretamente"],"outcomes":["manter fluxo UI/API -> aplicação -> domínio -> portas"]},{"id":"hexagonal-port-adapter","name":"Arquitetura hexagonal: ports e adapters","aliases":["ports and adapters","hexagonal architecture"],"introducedIn":"arquitetura-software","reinforcedIn":["ddd"],"prerequisiteConceptIds":["camadas-direcao-dependencia","adapter-fronteira-externa"],"commonConfusions":["chamar qualquer interface de porta","isolar domínio só no nome"],"outcomes":["testar caso de uso sem banco, HTTP ou framework reais"]},{"id":"monolito-modular-tradeoff","name":"Monólito modular e trade-off arquitetural","aliases":["modular monolith"],"introducedIn":"arquitetura-software","reinforcedIn":["ddd"],"prerequisiteConceptIds":["camadas-direcao-dependencia"],"commonConfusions":["microserviço como padrão inicial","monólito como sinônimo de bagunça"],"outcomes":["preferir modularidade interna antes de distribuir sem necessidade"]},{"id":"linguagem-ubiqua","name":"Linguagem ubíqua","aliases":["ubiquitous language"],"introducedIn":"ddd","reinforcedIn":["ddd-estrategico"],"prerequisiteConceptIds":["nome-intencao-codigo"],"commonConfusions":["traduzir termo técnico como domínio","usar classe genérica que apaga vocabulário"],"outcomes":["alinhar código, conversa e regra com o mesmo vocabulário"]},{"id":"entidade-value-object","name":"Entity e Value Object","aliases":["Entity","Value Object"],"introducedIn":"ddd","reinforcedIn":["spring-jpa"],"prerequisiteConceptIds":["identidade-referencia-objeto","imutabilidade-objeto"],"commonConfusions":["achar que Entity é sempre JPA","usar identidade onde valor bastava"],"outcomes":["modelar identidade, igualdade e imutabilidade por semântica"]},{"id":"agregado-consistencia","name":"Aggregate e fronteira de consistência","aliases":["Aggregate root"],"introducedIn":"ddd","reinforcedIn":["ddd-estrategico"],"prerequisiteConceptIds":["sql-transacao-atomicidade","consistencia-relacao"],"commonConfusions":["agregado gigante que vira sistema inteiro","transação atravessar tudo por conveniência"],"outcomes":["definir limite onde invariantes são confirmadas juntas"]},{"id":"bounded-context-fronteira","name":"Bounded Context e fronteira de linguagem","aliases":["bounded context"],"introducedIn":"ddd","reinforcedIn":["ddd-estrategico"],"prerequisiteConceptIds":["linguagem-ubiqua","monolito-modular-tradeoff"],"commonConfusions":["contexto = pacote Java","forçar um único modelo para áreas diferentes"],"outcomes":["separar modelos quando palavras e regras mudam de sentido"]},{"id":"spring-component-scan-bean","name":"Spring bean, component scan e injeção oficial","aliases":["@Component","@Service","@Autowired"],"introducedIn":"spring-core","reinforcedIn":["spring-boot-fundamentos","spring-mvc"],"prerequisiteConceptIds":["ioc-lifecycle-bean","annotation-metadata-contract"],"commonConfusions":["achar que anotação cria objeto sozinha","usar campo estático para fugir de DI"],"outcomes":["registrar e injetar componentes entendendo quem controla lifecycle"]},{"id":"spring-bean-scope-configuration","name":"Escopo de bean e configuração explícita","aliases":["singleton","prototype","@Configuration","@Bean"],"introducedIn":"spring-core","reinforcedIn":["spring-boot-fundamentos"],"prerequisiteConceptIds":["spring-component-scan-bean"],"commonConfusions":["singleton Spring igual singleton GoF global","usar @Bean para regra de negócio comum"],"outcomes":["decidir quando componente é descoberto ou declarado manualmente"]},{"id":"boot-bootstrap-autoconfig","name":"Spring Boot bootstrap e auto-configuração","aliases":["@SpringBootApplication","auto-configuration"],"introducedIn":"spring-boot-fundamentos","reinforcedIn":["devtools","mini-helpdesk-api"],"prerequisiteConceptIds":["spring-component-scan-bean","build-lifecycle"],"commonConfusions":["achar que starter é código mágico sem condição","não ler relatório de auto-configuração"],"outcomes":["explicar inicialização e condições de configuração automática"]},{"id":"boot-config-properties","name":"Configuração tipada no Spring Boot","aliases":["@ConfigurationProperties","application.yml"],"introducedIn":"spring-boot-fundamentos","reinforcedIn":["mini-helpdesk-api"],"prerequisiteConceptIds":["configuracao-externa"],"commonConfusions":["espalhar @Value por toda regra","commitar secret em yml"],"outcomes":["carregar configuração externa validável sem vazar segredo"]},{"id":"devtools-restart-boundary","name":"DevTools e limite de restart de desenvolvimento","aliases":["DevTools","restart"],"introducedIn":"devtools","reinforcedIn":["spring-boot-fundamentos"],"prerequisiteConceptIds":["boot-bootstrap-autoconfig"],"commonConfusions":["depender de devtools em produção","confundir restart com hot swap total"],"outcomes":["usar feedback rápido sem esconder build/teste reproduzível"]},{"id":"lombok-generated-code-contract","name":"Lombok como geração em compilação","aliases":["@Getter","@Builder","@RequiredArgsConstructor"],"introducedIn":"lombok","reinforcedIn":["spring-jpa"],"prerequisiteConceptIds":["annotation-metadata-contract","encapsulamento-invariante"],"commonConfusions":["achar que Lombok muda o runtime","gerar setter que quebra invariante"],"outcomes":["usar ou evitar Lombok preservando contrato de domínio"]},{"id":"mvc-controller-binding","name":"Controller MVC, binding e serialização","aliases":["@RestController","@RequestBody","@PathVariable"],"introducedIn":"spring-mvc","reinforcedIn":["validacao-erros","rest-resource-contracts"],"prerequisiteConceptIds":["http-mensagem-recurso","json-formato-contrato","spring-component-scan-bean"],"commonConfusions":["controller conter regra de negócio","confundir binding com validação de domínio"],"outcomes":["mapear HTTP para DTO/caso de uso sem vazar framework"]},{"id":"mvc-response-status-contract","name":"Resposta HTTP explícita no Spring MVC","aliases":["ResponseEntity","@ResponseStatus"],"introducedIn":"spring-mvc","reinforcedIn":["api-design-avancado"],"prerequisiteConceptIds":["http-status-classe"],"commonConfusions":["retornar 200 para todo erro","expor stack trace"],"outcomes":["devolver status, headers e body por contrato"]},{"id":"jpa-entity-identity","name":"JPA Entity e identidade persistente","aliases":["@Entity","@Id"],"introducedIn":"spring-jpa","reinforcedIn":["jpa-transacoes","dto-mapping"],"prerequisiteConceptIds":["entidade-value-object","modelo-relacional-tabela-chave"],"commonConfusions":["Entity JPA igual entidade de domínio sempre","usar setter público em todo campo"],"outcomes":["mapear classe para tabela preservando identidade e invariantes"]},{"id":"springdata-repository-contract","name":"Spring Data repository como porta persistente","aliases":["JpaRepository","repository"],"introducedIn":"spring-jpa","reinforcedIn":["n-mais-1"],"prerequisiteConceptIds":["jdbc-driver-connection","adapter-fronteira-externa"],"commonConfusions":["achar que repository elimina SQL","consultas derivadas sem índice/semântica"],"outcomes":["usar repository entendendo query, transação e custo"]},{"id":"jpa-relationship-loading","name":"Relacionamentos JPA e carregamento","aliases":["@OneToMany","LAZY","EAGER"],"introducedIn":"spring-jpa","reinforcedIn":["n-mais-1","jpa-transacoes"],"prerequisiteConceptIds":["join-cardinalidade-sql","agregado-consistencia"],"commonConfusions":["mapear todo relacionamento bidirecional","usar EAGER para esconder LazyInitializationException"],"outcomes":["modelar relação considerando cardinalidade, consulta e aggregate"]},{"id":"jpa-flush-dirty-checking","name":"Flush, dirty checking e persistence context","aliases":["flush","dirty checking"],"introducedIn":"jpa-transacoes","reinforcedIn":["n-mais-1"],"prerequisiteConceptIds":["jpa-entity-identity","sql-transacao-atomicidade"],"commonConfusions":["flush é commit","entidade detachada continua sincronizando"],"outcomes":["separar estado em memória, SQL enviado e commit confirmado"]},{"id":"transaction-proxy-boundary","name":"@Transactional e limite de proxy","aliases":["@Transactional","proxy"],"introducedIn":"jpa-transacoes","reinforcedIn":["mini-helpdesk-api"],"prerequisiteConceptIds":["jdbc-transacao-rollback","ioc-lifecycle-bean"],"commonConfusions":["self-invocation abre transação","rollback automático para toda exceção checada"],"outcomes":["posicionar transação no caso de uso correto"]},{"id":"dto-entity-boundary-spring","name":"DTO, Entity e boundary no Spring","aliases":["DTO mapping","MapStruct"],"introducedIn":"dto-mapping","reinforcedIn":["rest-resource-contracts","mini-helpdesk-api"],"prerequisiteConceptIds":["dto-mapping-fronteira","jpa-entity-identity"],"commonConfusions":["expor entity como response","usar mapper para esconder regra"],"outcomes":["separar contrato HTTP, entidade persistente e domínio"]},{"id":"bean-validation-boundary","name":"Bean Validation na fronteira","aliases":["@Valid","@NotNull"],"introducedIn":"validacao-erros","reinforcedIn":["mini-helpdesk-api"],"prerequisiteConceptIds":["annotation-metadata-contract","mvc-controller-binding"],"commonConfusions":["@NotNull substitui regra de domínio","validar entity JPA como request público"],"outcomes":["validar formato/entrada e traduzir violações com clareza"]},{"id":"controller-advice-problem-details","name":"ControllerAdvice e Problem Details","aliases":["@ControllerAdvice","ProblemDetail"],"introducedIn":"validacao-erros","reinforcedIn":["api-design-avancado","rest-resource-contracts"],"prerequisiteConceptIds":["mvc-response-status-contract","causa-excecao"],"commonConfusions":["tratar erro com string solta","vazar stack trace para cliente"],"outcomes":["centralizar tradução de exceções para erro HTTP estável"]},{"id":"pagination-sorting-contract","name":"Paginação, filtro e ordenação como contrato","aliases":["Pageable","sort","cursor"],"introducedIn":"paginacao-swagger","reinforcedIn":["api-design-avancado"],"prerequisiteConceptIds":["rate-limit-paginacao","springdata-repository-contract"],"commonConfusions":["página sem ordenação estável","permitir sort por qualquer coluna"],"outcomes":["publicar paginação previsível e limitada"]},{"id":"openapi-documentation-contract","name":"OpenAPI como contrato documentado","aliases":["Swagger","OpenAPI"],"introducedIn":"paginacao-swagger","reinforcedIn":["api-design-avancado"],"prerequisiteConceptIds":["http-header-body-negociacao","mvc-controller-binding"],"commonConfusions":["documentação substitui teste","gerar contrato que não reflete erro real"],"outcomes":["documentar exemplos, schemas e erros verificáveis"]},{"id":"http-optimistic-concurrency","name":"Concorrência otimista HTTP","aliases":["ETag","If-Match","409"],"introducedIn":"api-design-avancado","reinforcedIn":["rest-resource-contracts"],"prerequisiteConceptIds":["http-idempotencia-seguranca","mvcc-isolamento-lock"],"commonConfusions":["última escrita vence sempre","ETag como cache apenas"],"outcomes":["evitar lost update com precondition ou versão"]},{"id":"api-compatible-evolution","name":"Evolução compatível de API","aliases":["backward compatibility","versioning"],"introducedIn":"api-design-avancado","reinforcedIn":["rest-resource-contracts"],"prerequisiteConceptIds":["migration-expand-contract","openapi-documentation-contract"],"commonConfusions":["campo opcional sempre compatível","versionar por mudança interna"],"outcomes":["classificar breaking changes e planejar depreciação"]},{"id":"cors-browser-policy","name":"CORS como política do navegador","aliases":["CORS","preflight"],"introducedIn":"cors-rate-limit","reinforcedIn":["mini-helpdesk-api"],"prerequisiteConceptIds":["http-header-body-negociacao","mvc-controller-binding"],"commonConfusions":["CORS é autenticação","liberar * com credenciais"],"outcomes":["configurar origem, método e header por fronteira real"]},{"id":"api-rate-limit-boundary","name":"Rate limiting na borda da API","aliases":["429","rate limit"],"introducedIn":"cors-rate-limit","reinforcedIn":["mini-helpdesk-api"],"prerequisiteConceptIds":["rate-limit-paginacao","http-status-classe"],"commonConfusions":["rate limit resolve autorização","não retornar Retry-After"],"outcomes":["limitar abuso e comunicar capacidade com status adequado"]},{"id":"nplusone-query-shape","name":"N+1 e forma da consulta","aliases":["N+1","fetch join"],"introducedIn":"n-mais-1","reinforcedIn":["mini-helpdesk-api"],"prerequisiteConceptIds":["jpa-relationship-loading","postgres-explain-analyze"],"commonConfusions":["resolver com EAGER global","não olhar SQL gerado"],"outcomes":["detectar consultas repetidas e escolher fetch/projeção adequada"]},{"id":"helpdesk-api-vertical-slice","name":"API vertical slice com Spring","aliases":["vertical slice","help desk"],"introducedIn":"mini-helpdesk-api","reinforcedIn":["rest-resource-contracts"],"prerequisiteConceptIds":["mvc-controller-binding","springdata-repository-contract","controller-advice-problem-details"],"commonConfusions":["montar todas as camadas sem caso funcionando","projeto sem falhas demonstradas"],"outcomes":["entregar uma fatia API -> caso de uso -> persistência -> erro -> teste"]},{"id":"requisito-para-recurso-rest","name":"Do requisito de negócio ao recurso REST","aliases":["recurso","sub-recurso","hierarquia de URL"],"introducedIn":"arquitetura-api-rest","reinforcedIn":["spring-mvc"],"prerequisiteConceptIds":["http-mensagem-recurso","http-metodo-semantica"],"commonConfusions":["modelar ação como verbo na URL","esconder ação de negócio atrás de PATCH genérico"],"outcomes":["decidir substantivo, hierarquia, verbo e status a partir de um requisito em português"]},{"id":"algoritmo-de-decisao-de-camada","name":"Algoritmo de decisão de camada (Controller/Service/Repository)","aliases":["onde a lógica mora","responsabilidade por camada"],"introducedIn":"arquitetura-api-rest","reinforcedIn":["spring-mvc","dto-mapping"],"prerequisiteConceptIds":["http-status-classe"],"commonConfusions":["colocar regra de negócio no Controller","colocar regra de negócio no Repository"],"outcomes":["aplicar quatro perguntas em ordem para decidir a camada correta"]},{"id":"convencao-de-nome-por-camada","name":"Convenção de nome de método por camada","aliases":["criar/emprestar/findBy","nomenclatura de método"],"introducedIn":"arquitetura-api-rest","reinforcedIn":["spring-mvc","spring-jpa"],"prerequisiteConceptIds":["algoritmo-de-decisao-de-camada"],"commonConfusions":["nomear Service igual ao Controller","nomear Repository como intenção de negócio"],"outcomes":["nomear cada camada de acordo com sua responsabilidade, não com a operação HTTP"]},{"id":"estrutura-de-pacotes","name":"Estrutura de pacotes de uma API","aliases":["controller/service/repository/dto/mapper/exception"],"introducedIn":"arquitetura-api-rest","reinforcedIn":["dto-mapping"],"prerequisiteConceptIds":["convencao-de-nome-por-camada"],"commonConfusions":["colocar tudo em um pacote raiz único","reaproveitar a mesma classe DTO para request e response"],"outcomes":["organizar pacotes refletindo a fronteira do contrato público da API"]},{"id":"auth-session-cookie","name":"Sessão com cookie e estado servidor","aliases":["session","cookie","JSESSIONID"],"introducedIn":"autenticacao-conceitos","reinforcedIn":["spring-security","mini-auth-rbac"],"prerequisiteConceptIds":["http-header-body-negociacao"],"commonConfusions":["cookie autentica sozinho","sessão stateless"],"outcomes":["explicar login com identificador opaco, expiração e estado no servidor"]},{"id":"jwt-claims-signature","name":"JWT: claims, assinatura e expiração","aliases":["JWT","claim","Bearer"],"introducedIn":"autenticacao-conceitos","reinforcedIn":["spring-security","security-oidc"],"prerequisiteConceptIds":["json-formato-contrato","http-header-body-negociacao"],"commonConfusions":["JWT criptografa tudo","decodificar JWT é validar JWT"],"outcomes":["diferenciar payload legível, assinatura, expiração e validação"]},{"id":"oauth2-delegated-authorization","name":"OAuth2 como autorização delegada","aliases":["OAuth2","authorization server"],"introducedIn":"autenticacao-conceitos","reinforcedIn":["security-oidc"],"prerequisiteConceptIds":["http-mensagem-recurso"],"commonConfusions":["OAuth2 é login por si só","access token é senha"],"outcomes":["explicar papéis de cliente, resource owner, authorization server e resource server"]},{"id":"password-hash-verification","name":"Hash de senha com verificação unidirecional","aliases":["BCrypt","PasswordEncoder"],"introducedIn":"spring-security","reinforcedIn":["mini-auth-rbac"],"prerequisiteConceptIds":["secrets-integracao"],"commonConfusions":["hash é criptografia reversível","salt precisa ser secreto"],"outcomes":["armazenar apenas hash adequado e verificar senha sem recuperar original"]},{"id":"security-filter-chain","name":"Spring Security Filter Chain","aliases":["SecurityFilterChain","filter"],"introducedIn":"spring-security","reinforcedIn":["security-oidc"],"prerequisiteConceptIds":["mvc-controller-binding","spring-component-scan-bean"],"commonConfusions":["segurança roda dentro do controller","ordem dos filtros não importa"],"outcomes":["posicionar autenticação/autorização antes do controller"]},{"id":"spring-security-authorization","name":"Autorização por regra, papel e permissão","aliases":["@PreAuthorize","ROLE","authority"],"introducedIn":"spring-security","reinforcedIn":["mini-auth-rbac"],"prerequisiteConceptIds":["security-filter-chain"],"commonConfusions":["autenticado significa autorizado","role resolve regra de ownership"],"outcomes":["separar identidade, papel, permissão e regra de recurso"]},{"id":"oidc-id-token-claims","name":"OpenID Connect ID Token e identidade","aliases":["OIDC","id_token"],"introducedIn":"security-oidc","reinforcedIn":["auth-front-ts"],"prerequisiteConceptIds":["oauth2-delegated-authorization","jwt-claims-signature"],"commonConfusions":["ID token deve chamar API","access token e ID token são iguais"],"outcomes":["separar identidade do usuário de autorização para resource server"]},{"id":"pkce-authorization-code","name":"Authorization Code com PKCE","aliases":["PKCE","code verifier","code challenge"],"introducedIn":"security-oidc","reinforcedIn":["auth-front-ts"],"prerequisiteConceptIds":["oauth2-delegated-authorization"],"commonConfusions":["SPA pode guardar client secret","PKCE substitui redirect URI segura"],"outcomes":["usar code verifier/challenge para reduzir interceptação de authorization code"]},{"id":"resource-server-jwt-validation","name":"Resource Server e validação de token","aliases":["Resource Server","JWK Set","issuer"],"introducedIn":"security-oidc","reinforcedIn":["spring-security"],"prerequisiteConceptIds":["jwt-claims-signature","security-filter-chain"],"commonConfusions":["confiar no payload sem issuer/audience","aceitar algoritmo/chave sem política"],"outcomes":["validar assinatura, issuer, audience, expiração e escopo antes de autorizar"]},{"id":"tls-certificate-chain","name":"TLS, certificado e cadeia de confiança","aliases":["HTTPS","TLS","certificate"],"introducedIn":"https","reinforcedIn":["security-oidc","auth-front-ts"],"prerequisiteConceptIds":["http-mensagem-recurso"],"commonConfusions":["HTTPS autentica usuário","certificado expirado é detalhe visual"],"outcomes":["explicar confidencialidade em trânsito, integridade e autenticação do servidor"]},{"id":"browser-token-storage-risk","name":"Risco de armazenamento de token no navegador","aliases":["localStorage","httpOnly","SameSite"],"introducedIn":"auth-front-ts","reinforcedIn":["frontend-aplicacao"],"prerequisiteConceptIds":["jwt-claims-signature","tls-certificate-chain"],"commonConfusions":["localStorage é cofre","cookie httpOnly elimina CSRF automaticamente"],"outcomes":["comparar XSS, CSRF, cookie httpOnly, SameSite e rotação de sessão"]},{"id":"rbac-permission-boundary","name":"RBAC, permissionamento e ownership","aliases":["RBAC","role","permission"],"introducedIn":"mini-auth-rbac","reinforcedIn":["frontend-aplicacao"],"prerequisiteConceptIds":["spring-security-authorization","jpa-entity-identity"],"commonConfusions":["role ADMIN/USER cobre todo domínio","front esconder botão protege endpoint"],"outcomes":["modelar regra por papel e por recurso no servidor"]},{"id":"frontend-auth-state","name":"Estado autenticado no frontend","aliases":["auth state","login state"],"introducedIn":"auth-front-ts","reinforcedIn":["frontend-aplicacao"],"prerequisiteConceptIds":["browser-token-storage-risk","http-status-classe"],"commonConfusions":["estado visual é autorização","refresh falho deve ser ignorado"],"outcomes":["representar autenticado, expirado, carregando e negado sem vazar segredo"]},{"id":"accessible-form-state","name":"Formulários acessíveis e estados de erro","aliases":["label","aria","form state"],"introducedIn":"frontend-aplicacao","reinforcedIn":["mini-auth-rbac"],"prerequisiteConceptIds":["frontend-auth-state","bean-validation-boundary"],"commonConfusions":["placeholder substitui label","erro visual basta"],"outcomes":["conectar label, validação, foco, loading, erro e recuperação"]},{"id":"frontend-api-error-state","name":"Estado remoto explícito no frontend","aliases":["remote state","loading","empty","error"],"introducedIn":"frontend-aplicacao","reinforcedIn":["api-contract-evolution"],"prerequisiteConceptIds":["controller-advice-problem-details","frontend-auth-state"],"commonConfusions":["null significa qualquer estado","erro 401 é igual a lista vazia"],"outcomes":["renderizar carregando, vazio, erro, expirado e pronto como estados diferentes"]},{"id":"contract-test-evolution","name":"Teste de contrato e evolução verificável","aliases":["contract test","Spring REST Docs"],"introducedIn":"api-contract-evolution","reinforcedIn":["frontend-aplicacao"],"prerequisiteConceptIds":["openapi-documentation-contract","api-compatible-evolution"],"commonConfusions":["documentação gerada é teste completo","status 200 garante compatibilidade"],"outcomes":["provar que representação, erro e status publicados não mudaram sem intenção"]},{"id":"openapi-breaking-change","name":"Breaking change em OpenAPI","aliases":["breaking change","schema diff"],"introducedIn":"api-contract-evolution","reinforcedIn":["frontend-aplicacao"],"prerequisiteConceptIds":["api-compatible-evolution"],"commonConfusions":["adicionar enum sempre é compatível","campo opcional nunca quebra"],"outcomes":["classificar remoção, renome, obrigatoriedade, enum e status por impacto em clientes"]},{"id":"spring-rest-docs-contract","name":"Spring REST Docs como documentação testada","aliases":["Spring REST Docs","snippets"],"introducedIn":"api-contract-evolution","reinforcedIn":["mini-auth-rbac"],"prerequisiteConceptIds":["contract-test-evolution","mvc-controller-binding"],"commonConfusions":["REST Docs substitui design de API","snippet aprovado cobre regra de negócio"],"outcomes":["gerar documentação a partir de testes que verificam request e response"]},{"id":"injecao-preparedstatement-boundary","name":"Injeção e a fronteira do PreparedStatement","aliases":["SQL Injection","command injection","injection"],"introducedIn":"vulnerabilidades-e-dependencias","reinforcedIn":[],"prerequisiteConceptIds":["preparedstatement-parametro"],"commonConfusions":["achar que escapar aspas manualmente é uma defesa completa","achar que usar um ORM elimina toda superfície de injeção mesmo com queries nativas"],"outcomes":["explicar por que PreparedStatement elimina a classe de vulnerabilidade, não apenas mitiga um payload específico"]},{"id":"xss-csrf-classes-de-ataque","name":"XSS e CSRF como classes distintas de ataque","aliases":["Cross-Site Scripting","Cross-Site Request Forgery","XSS","CSRF"],"introducedIn":"vulnerabilidades-e-dependencias","reinforcedIn":[],"prerequisiteConceptIds":["security-filter-chain"],"commonConfusions":["tratar XSS e CSRF como o mesmo tipo de ataque","achar que autenticação por si só impede CSRF"],"outcomes":["diferenciar XSS refletido/armazenado de CSRF e escolher a defesa correta para cada um"]},{"id":"quebra-controle-acesso-idor","name":"Quebra de controle de acesso e IDOR","aliases":["IDOR","Insecure Direct Object Reference","broken access control"],"introducedIn":"vulnerabilidades-e-dependencias","reinforcedIn":["mini-auth-rbac"],"prerequisiteConceptIds":["spring-security-authorization"],"commonConfusions":["achar que autenticação implica autorização correta","achar que ownership do recurso é responsabilidade do frontend"],"outcomes":["identificar ausência de checagem de ownership como IDOR e propor a correção no servidor"]},{"id":"exposicao-dados-sensiveis-log-erro","name":"Exposição de dados sensíveis em log e erro","aliases":["sensitive data exposure","PII em log","stack trace vazando segredo"],"introducedIn":"vulnerabilidades-e-dependencias","reinforcedIn":[],"prerequisiteConceptIds":["log-nivel-contexto","controller-advice-problem-details"],"commonConfusions":["achar que restringir acesso ao log resolve a exposição na origem","achar que mensagem de erro detalhada ajuda o cliente final"],"outcomes":["separar mensagem de erro pública genérica de detalhe técnico correlacionável em log"]},{"id":"higiene-dependencias-cve-sca","name":"Higiene de dependências, CVE e SCA","aliases":["CVE","Software Composition Analysis","Dependabot","supply chain security"],"introducedIn":"vulnerabilidades-e-dependencias","reinforcedIn":[],"prerequisiteConceptIds":[],"commonConfusions":["achar que \"funciona\" prova ausência de vulnerabilidade conhecida na dependência","ignorar um alerta de CVE por não entender o impacto, em vez de investigar"],"outcomes":["rodar e interpretar uma ferramenta de SCA e decidir uma ação diante de um CVE reportado"]},{"id":"test-double-vocabulary","name":"Dublês de teste: dummy, stub, mock, spy e fake","aliases":["test double","mock","stub","fake"],"introducedIn":"mockito","reinforcedIn":["estrategia-testes"],"prerequisiteConceptIds":["teste-aaa-first"],"commonConfusions":["mockar tudo","confundir stub com mock"],"outcomes":["escolher dublê pelo tipo de colaboração que o teste precisa observar"]},{"id":"mockito-behavior-verification","name":"Mockito: comportamento programado e verificação de interação","aliases":["when","thenReturn","verify"],"introducedIn":"mockito","reinforcedIn":["estrategia-testes"],"prerequisiteConceptIds":["test-double-vocabulary"],"commonConfusions":["verify substitui assert de resultado","mockar classe de domínio"],"outcomes":["usar mocks para fronteiras externas sem testar implementação interna por acidente"]},{"id":"container-image-layer","name":"Imagem, layer e container executável","aliases":["image","layer","container"],"introducedIn":"docker-conceitos","reinforcedIn":["dockerfile","compose","testcontainers"],"prerequisiteConceptIds":["build-lifecycle"],"commonConfusions":["imagem é VM","container guarda estado por padrão"],"outcomes":["separar artefato imutável de processo em execução"]},{"id":"docker-volume-persistence","name":"Volume Docker e persistência fora do container","aliases":["volume","bind mount"],"introducedIn":"docker-conceitos","reinforcedIn":["compose","hospedagem-db"],"prerequisiteConceptIds":["path-filesystem"],"commonConfusions":["dados ficam seguros dentro do container removido","bind mount e volume são iguais em qualquer ambiente"],"outcomes":["preservar estado deliberadamente e saber quando descartá-lo"]},{"id":"dockerfile-build-context","name":"Dockerfile, contexto de build e imagem reproduzível","aliases":["Dockerfile","build context"],"introducedIn":"dockerfile","reinforcedIn":["compose"],"prerequisiteConceptIds":["container-image-layer","build-lifecycle"],"commonConfusions":["COPY . . é sempre aceitável","imagem final deve conter ferramenta de build"],"outcomes":["construir imagem pequena, reproduzível e sem arquivos desnecessários"]},{"id":"multistage-runtime-image","name":"Multi-stage build e imagem runtime","aliases":["multi-stage","runtime image"],"introducedIn":"dockerfile","reinforcedIn":["hospedagem-db"],"prerequisiteConceptIds":["dockerfile-build-context"],"commonConfusions":["stage de build vai para produção","JDK completo é sempre necessário no runtime"],"outcomes":["separar build de execução e reduzir superfície da imagem final"]},{"id":"compose-service-network","name":"Compose: serviços, rede e dependências locais","aliases":["docker compose","service","network"],"introducedIn":"compose","reinforcedIn":["testcontainers","hospedagem-db"],"prerequisiteConceptIds":["container-image-layer","docker-volume-persistence"],"commonConfusions":["localhost dentro do container aponta para host","depends_on espera aplicação pronta"],"outcomes":["subir app e banco localmente entendendo nomes de serviço, rede e logs"]},{"id":"testcontainers-disposable-dependency","name":"Testcontainers como dependência real descartável","aliases":["Testcontainers","container descartável"],"introducedIn":"testcontainers","reinforcedIn":["estrategia-testes"],"prerequisiteConceptIds":["compose-service-network","teste-aaa-first","springdata-repository-contract"],"commonConfusions":["testar contra banco de dev","container elimina necessidade de fixture"],"outcomes":["rodar teste com banco real isolado e estado conhecido"]},{"id":"dynamic-test-configuration","name":"Configuração dinâmica de teste","aliases":["DynamicPropertySource","container port"],"introducedIn":"testcontainers","reinforcedIn":["estrategia-testes"],"prerequisiteConceptIds":["testcontainers-disposable-dependency","boot-config-properties"],"commonConfusions":["porta do container é fixa","usar credenciais de dev no teste"],"outcomes":["injetar URL, porta e credenciais efêmeras no contexto de teste"]},{"id":"test-pyramid-slice-contract","name":"Pirâmide de testes, slices e contratos","aliases":["test pyramid","slice test","contract test"],"introducedIn":"estrategia-testes","reinforcedIn":["api-contract-evolution","testcontainers"],"prerequisiteConceptIds":["mockito-behavior-verification","contract-test-evolution"],"commonConfusions":["todo teste precisa subir aplicação inteira","unit test prova contrato HTTP"],"outcomes":["escolher unit, slice, integração ou contrato pelo risco observado"]},{"id":"deterministic-integration-test","name":"Teste de integração determinístico","aliases":["fixture","isolamento","readiness"],"introducedIn":"estrategia-testes","reinforcedIn":["testcontainers"],"prerequisiteConceptIds":["testcontainers-disposable-dependency","teste-aaa-first"],"commonConfusions":["sleep torna teste confiável","ordem de testes pode carregar estado"],"outcomes":["controlar estado, tempo, dependências e evidência de falha"]},{"id":"nosql-access-pattern-modeling","name":"NoSQL por padrão de acesso","aliases":["access pattern","document model"],"introducedIn":"nosql","reinforcedIn":["mongodb","nosql-operacional"],"prerequisiteConceptIds":["json-formato-contrato","modelo-relacional-tabela-chave"],"commonConfusions":["NoSQL é sem modelagem","NoSQL sempre escala melhor"],"outcomes":["escolher modelo orientado ao acesso, consistência e consulta real"]},{"id":"document-embed-reference","name":"Documento: embedding vs referência","aliases":["embedding","referencing"],"introducedIn":"mongodb","reinforcedIn":["nosql-operacional"],"prerequisiteConceptIds":["nosql-access-pattern-modeling","agregado-consistencia"],"commonConfusions":["normalizar tudo como SQL","embedar coleção ilimitada"],"outcomes":["modelar documentos considerando leitura conjunta, crescimento e atualização"]},{"id":"mongodb-query-index-shape","name":"MongoDB: consulta, índice e formato do documento","aliases":["index","aggregation","query shape"],"introducedIn":"mongodb","reinforcedIn":["nosql-operacional"],"prerequisiteConceptIds":["document-embed-reference","postgres-explain-analyze"],"commonConfusions":["índice resolve qualquer filtro","aggregation é substituto de domínio"],"outcomes":["relacionar filtro, ordenação, índice e custo de leitura"]},{"id":"redis-data-structure-choice","name":"Redis como estruturas de dados em memória","aliases":["String","Hash","Set","Sorted Set"],"introducedIn":"redis","reinforcedIn":["nosql-operacional"],"prerequisiteConceptIds":["contrato-collection-map"],"commonConfusions":["Redis é só Map string-string","qualquer dado pode viver só no cache"],"outcomes":["escolher estrutura Redis pelo comando e pela operação esperada"]},{"id":"cache-aside-ttl-invalidation","name":"Cache-aside, TTL e invalidação","aliases":["cache-aside","TTL","invalidation"],"introducedIn":"redis","reinforcedIn":["nosql-operacional"],"prerequisiteConceptIds":["redis-data-structure-choice","api-compatible-evolution"],"commonConfusions":["cache nunca precisa expirar","deletar cache depois do banco é detalhe irrelevante"],"outcomes":["ler/gravar cache sem esconder stale data e inconsistência"]},{"id":"mongodb-operational-indexing","name":"MongoDB operacional: índice, cardinalidade e shard key","aliases":["index","shard key"],"introducedIn":"nosql-operacional","reinforcedIn":["hospedagem-db"],"prerequisiteConceptIds":["mongodb-query-index-shape"],"commonConfusions":["sharding é primeiro passo","índice duplicado não tem custo"],"outcomes":["planejar consulta, índice e escala antes de produção"]},{"id":"redis-persistence-eviction","name":"Redis operacional: persistência, eviction e papel do cache","aliases":["RDB","AOF","eviction"],"introducedIn":"nosql-operacional","reinforcedIn":["hospedagem-db"],"prerequisiteConceptIds":["cache-aside-ttl-invalidation"],"commonConfusions":["Redis cache pode perder tudo sem impacto","persistência Redis é igual banco relacional"],"outcomes":["definir se Redis é cache, fila leve, lock ou dado crítico e configurar risco"]},{"id":"distributed-lock-risk","name":"Locks distribuídos e risco de coordenação","aliases":["distributed lock","Redlock"],"introducedIn":"nosql-operacional","reinforcedIn":["redis"],"prerequisiteConceptIds":["redis-data-structure-choice"],"commonConfusions":["lock distribuído torna tudo serializável","TTL de lock pode ser ignorado"],"outcomes":["evitar lock distribuído quando idempotência, banco ou fila resolvem melhor"]},{"id":"database-environment-boundary","name":"Ambientes de banco: dev, teste, staging e produção","aliases":["dev database","staging","production"],"introducedIn":"hospedagem-db","reinforcedIn":["compose","testcontainers"],"prerequisiteConceptIds":["compose-service-network","sql-transacao-atomicidade"],"commonConfusions":["banco de dev pode servir teste automatizado","produção é só Compose com senha forte"],"outcomes":["separar ambientes, credenciais, backups, migração e observabilidade"]},{"id":"managed-database-operations","name":"Banco gerenciado e responsabilidades operacionais","aliases":["managed database","backup","RDS"],"introducedIn":"hospedagem-db","reinforcedIn":["nosql-operacional"],"prerequisiteConceptIds":["database-environment-boundary"],"commonConfusions":["gerenciado elimina modelagem","backup existe até ser restaurado"],"outcomes":["avaliar backup, restore, upgrade, monitoring, rede e custo antes de escolher hospedagem"]},{"id":"thread-lifecycle-scheduler","name":"Thread, ciclo de vida e escalonamento","aliases":["Thread","scheduler"],"introducedIn":"threads","reinforcedIn":["concorrencia-profunda"],"prerequisiteConceptIds":["stream-nao-interferencia"],"commonConfusions":["thread é sempre paralelismo útil","start e run fazem o mesmo"],"outcomes":["criar trabalho concorrente entendendo ciclo de vida, custo e observabilidade"]},{"id":"race-condition-atomicity","name":"Race condition e atomicidade","aliases":["race condition","atomicidade"],"introducedIn":"threads","reinforcedIn":["concorrencia-profunda"],"prerequisiteConceptIds":["thread-lifecycle-scheduler"],"commonConfusions":["value++ é atômico","teste passando prova ausência de race"],"outcomes":["explicar interleavings e proteger estado compartilhado"]},{"id":"monitor-synchronized-mutual-exclusion","name":"Monitor, synchronized e exclusão mútua","aliases":["synchronized","monitor"],"introducedIn":"threads","reinforcedIn":["concorrencia-profunda"],"prerequisiteConceptIds":["race-condition-atomicity"],"commonConfusions":["synchronized deixa tudo rápido","lock protege qualquer objeto automaticamente"],"outcomes":["usar lock com escopo pequeno e ownership claro"]},{"id":"atomic-concurrent-utilities","name":"Atomic e utilitários java.util.concurrent","aliases":["AtomicInteger","ExecutorService"],"introducedIn":"threads","reinforcedIn":["concorrencia-profunda","tcp-protocol-design"],"prerequisiteConceptIds":["race-condition-atomicity"],"commonConfusions":["atomic resolve invariantes compostas","executor ilimitado é escalável"],"outcomes":["escolher atomic/executor/coleções concorrentes pelo tipo de estado e tarefa"]},{"id":"memory-visibility-happens-before","name":"Visibilidade de memória e happens-before","aliases":["volatile","happens-before"],"introducedIn":"concorrencia-profunda","reinforcedIn":["threads"],"prerequisiteConceptIds":["race-condition-atomicity","bytecode-jit-perfil"],"commonConfusions":["volatile torna incremento atômico","race e visibilidade são o mesmo problema"],"outcomes":["separar atomicidade, visibilidade e ordem de publicação"]},{"id":"deadlock-livelock-starvation","name":"Deadlock, livelock e starvation","aliases":["deadlock","starvation"],"introducedIn":"concorrencia-profunda","reinforcedIn":["tcp-protocol-design"],"prerequisiteConceptIds":["monitor-synchronized-mutual-exclusion"],"commonConfusions":["deadlock é exceção visível","sleep corrige deadlock"],"outcomes":["diagnosticar espera circular, progresso falso e falta de justiça"]},{"id":"completablefuture-composition","name":"CompletableFuture e composição assíncrona","aliases":["CompletableFuture","async"],"introducedIn":"concorrencia-profunda","reinforcedIn":["developer-tools-java"],"prerequisiteConceptIds":["atomic-concurrent-utilities"],"commonConfusions":["async elimina bloqueio externo","join é sempre seguro"],"outcomes":["compor tarefas com timeout, erro e executor explícitos"]},{"id":"websocket-persistent-channel","name":"WebSocket como canal persistente full-duplex","aliases":["WebSocket","full-duplex"],"introducedIn":"websockets","reinforcedIn":["tcp-protocol-design"],"prerequisiteConceptIds":["http-mensagem-recurso","thread-lifecycle-scheduler"],"commonConfusions":["WebSocket substitui REST sempre","canal aberto garante entrega durável"],"outcomes":["usar conexão persistente quando servidor precisa empurrar eventos em tempo real"]},{"id":"stomp-topic-session","name":"STOMP, tópico e sessão WebSocket","aliases":["STOMP","topic","session"],"introducedIn":"websockets","reinforcedIn":["websockets"],"prerequisiteConceptIds":["websocket-persistent-channel","json-formato-contrato"],"commonConfusions":["topic é autorização","mensagem WebSocket não precisa contrato"],"outcomes":["separar conexão, destino, payload e autorização de inscrição"]},{"id":"tcp-stream-framing","name":"TCP stream e framing de mensagem","aliases":["TCP","framing"],"introducedIn":"tcp-protocol-design","reinforcedIn":["websockets"],"prerequisiteConceptIds":["stream-resource-lifecycle","thread-lifecycle-scheduler"],"commonConfusions":["um write vira um read","TCP preserva fronteira de mensagens"],"outcomes":["definir delimitador, tamanho ou protocolo para reconstruir mensagens"]},{"id":"socket-timeout-backpressure","name":"Socket timeout, shutdown e backpressure","aliases":["timeout","backpressure","shutdown"],"introducedIn":"tcp-protocol-design","reinforcedIn":["developer-tools-java"],"prerequisiteConceptIds":["tcp-stream-framing","deadlock-livelock-starvation"],"commonConfusions":["fila infinita resolve pico","read sem timeout é inofensivo"],"outcomes":["limitar conexões, fila, tempo de espera e encerramento coordenado"]},{"id":"tui-terminal-screen-buffer","name":"TUI: Terminal, Screen e buffer visual","aliases":["Lanterna","Terminal","Screen"],"introducedIn":"lanterna-tui","reinforcedIn":["developer-tools-java"],"prerequisiteConceptIds":["stream-resource-lifecycle"],"commonConfusions":["terminal é HTML simples","redraw não precisa estado"],"outcomes":["desenhar UI textual com buffer, refresh e cleanup controlado"]},{"id":"tui-event-loop-state","name":"Event loop e estado testável de TUI","aliases":["event loop","focus","state"],"introducedIn":"lanterna-tui","reinforcedIn":["developer-tools-java"],"prerequisiteConceptIds":["interface-contrato","tui-terminal-screen-buffer"],"commonConfusions":["domínio deve conhecer TextBox","thread por tecla simplifica UI"],"outcomes":["separar input, estado, renderização e domínio testável"]},{"id":"watchservice-filesystem-events","name":"WatchService e eventos de filesystem","aliases":["WatchService","file watcher"],"introducedIn":"developer-tools-java","reinforcedIn":["lanterna-tui"],"prerequisiteConceptIds":["path-filesystem","files-nio-atomicidade"],"commonConfusions":["evento de arquivo é estado final confiável","watcher dispensa rescan"],"outcomes":["monitorar mudanças com rescan, idempotência e backpressure"]},{"id":"jfr-jcmd-diagnostics","name":"JFR, jcmd e diagnóstico por evidência","aliases":["JFR","jcmd"],"introducedIn":"developer-tools-java","reinforcedIn":["concorrencia-profunda"],"prerequisiteConceptIds":["bytecode-jit-perfil","log-parametrizado-causa"],"commonConfusions":["flag copiada é diagnóstico","profiling começa pela solução"],"outcomes":["coletar evidência de JVM antes de otimizar ou culpar threads"]},{"id":"runtime-secret-boundary","name":"Secret como configuração sensível de runtime","aliases":["secret","environment variable","runtime secret"],"introducedIn":"secrets","reinforcedIn":["deploy-backend","spring-profiles"],"prerequisiteConceptIds":["gitignore-secrets","secrets-integracao","configuracao-externa"],"commonConfusions":["secret pode entrar no build","base64 é criptografia","variável de ambiente pode ir para log"],"outcomes":["separar valor sensível de código, imagem e log"]},{"id":"environment-config-contract","name":"Contrato de configuração por ambiente","aliases":["env var","configuration","12-factor"],"introducedIn":"secrets","reinforcedIn":["spring-profiles","praticas-producao"],"prerequisiteConceptIds":["configuracao-externa","database-environment-boundary"],"commonConfusions":["ambiente é branch","config de build e runtime são iguais"],"outcomes":["declarar variáveis obrigatórias, defaults seguros e falha explícita ao iniciar"]},{"id":"ci-pipeline-gate","name":"Pipeline CI como gate reproduzível","aliases":["CI","GitHub Actions","pipeline"],"introducedIn":"cicd","reinforcedIn":["deploy-backend","mini-deploy-observavel"],"prerequisiteConceptIds":["git-snapshot-index","build-lifecycle","deterministic-integration-test"],"commonConfusions":["CI é deploy","pipeline verde prova regra de negócio completa"],"outcomes":["automatizar build, testes, auditorias e artefatos como condição de merge"]},{"id":"artifact-image-provenance","name":"Artefato versionado e proveniência de imagem","aliases":["artifact","image tag","provenance"],"introducedIn":"cicd","reinforcedIn":["hardening-producao","deploy-backend"],"prerequisiteConceptIds":["dockerfile-build-context","multistage-runtime-image"],"commonConfusions":["latest identifica release","imagem pode ser reconstruída livremente em produção"],"outcomes":["ligar commit, workflow, imagem, digest e release implantada"]},{"id":"release-environment-parity","name":"Paridade entre dev, staging e produção","aliases":["parity","staging"],"introducedIn":"praticas-producao","reinforcedIn":["deploy-backend","mini-deploy-observavel"],"prerequisiteConceptIds":["database-environment-boundary","compose-service-network"],"commonConfusions":["staging é produção com dados reais","dev pode usar contrato diferente"],"outcomes":["reduzir diferença operacional entre ambientes sem vazar dados sensíveis"]},{"id":"feature-flag-operational-control","name":"Feature flag como controle operacional","aliases":["feature flag","kill switch"],"introducedIn":"praticas-producao","reinforcedIn":["spring-profiles"],"prerequisiteConceptIds":["environment-config-contract"],"commonConfusions":["flag substitui autorização","flag dispensa teste"],"outcomes":["ativar, desativar e limitar comportamento sem novo deploy"]},{"id":"deploy-rollback-strategy","name":"Estratégia de deploy e rollback","aliases":["rollback","release","deploy"],"introducedIn":"hardening-producao","reinforcedIn":["deploy-backend","mini-deploy-observavel"],"prerequisiteConceptIds":["ci-pipeline-gate","artifact-image-provenance"],"commonConfusions":["rollback sempre desfaz banco","deploy é copiar arquivo"],"outcomes":["planejar release, verificação pós-deploy e caminho de retorno"]},{"id":"health-readiness-liveness","name":"Health check, readiness e liveness","aliases":["health check","readiness","liveness"],"introducedIn":"hardening-producao","reinforcedIn":["mini-deploy-observavel"],"prerequisiteConceptIds":["http-status-classe","log-nivel-contexto"],"commonConfusions":["200 OK da home prova prontidão","liveness e readiness são iguais"],"outcomes":["expor sinais mínimos para roteamento, restart e diagnóstico"]},{"id":"recovery-backup-restore-drill","name":"Backup, restore e ensaio de recuperação","aliases":["backup","restore","recovery drill"],"introducedIn":"hardening-producao","reinforcedIn":["mini-deploy-observavel"],"prerequisiteConceptIds":["managed-database-operations","database-environment-boundary"],"commonConfusions":["ter backup é conseguir recuperar","backup sem teste é garantia"],"outcomes":["provar recuperação com RPO, RTO e evidência de restore"]},{"id":"paas-vps-deployment-boundary","name":"Fronteira operacional entre PaaS e VPS","aliases":["PaaS","VPS","deployment target"],"introducedIn":"deploy-backend","reinforcedIn":["deploy-frontend"],"prerequisiteConceptIds":["container-image-layer","compose-service-network","runtime-secret-boundary"],"commonConfusions":["PaaS elimina operação","VPS é sempre mais profissional"],"outcomes":["escolher alvo de deploy pelo contrato operacional assumido"]},{"id":"spring-profile-config-activation","name":"Spring Profile e ativação de configuração","aliases":["Spring Profiles","profile"],"introducedIn":"spring-profiles","reinforcedIn":["deploy-backend"],"prerequisiteConceptIds":["boot-config-properties","environment-config-contract"],"commonConfusions":["profile é feature flag de usuário","profile pode guardar secret no código"],"outcomes":["ativar beans/configuração por ambiente sem acoplar segredo ao artefato"]},{"id":"frontend-static-deploy-cache","name":"Deploy front-end estático e cache de assets","aliases":["static deploy","CDN","cache"],"introducedIn":"deploy-frontend","reinforcedIn":["mini-deploy-observavel"],"prerequisiteConceptIds":["http-header-body-negociacao","artifact-image-provenance"],"commonConfusions":["front-end não precisa release","cache só melhora performance"],"outcomes":["publicar build estático versionado considerando roteamento, cache e variáveis públicas"]},{"id":"repo-topology-monorepo-polyrepo","name":"Topologia de repositório: monorepo e poli-repo","aliases":["monorepo","polyrepo"],"introducedIn":"deploy-frontend","reinforcedIn":["cicd"],"prerequisiteConceptIds":["git-snapshot-index","build-lifecycle"],"commonConfusions":["monorepo é bagunça","poli-repo desacopla domínio automaticamente"],"outcomes":["decidir organização por ownership, CI, versionamento e fronteiras de mudança"]},{"id":"production-evidence-checklist","name":"Evidência operacional de produção","aliases":["deployment evidence","runbook","definition of done"],"introducedIn":"mini-deploy-observavel","reinforcedIn":["deploy-backend","hardening-producao"],"prerequisiteConceptIds":["deploy-rollback-strategy","health-readiness-liveness","recovery-backup-restore-drill"],"commonConfusions":["print de tela basta","deploy concluído é o app abrir uma vez"],"outcomes":["entregar evidências reproduzíveis de deploy, health, rollback e restore"]},{"id":"spring-webclient-reactive-client","name":"WebClient como cliente HTTP moderno no Spring","aliases":["WebClient","reactive client"],"introducedIn":"webclient","reinforcedIn":["resilience","coupled-services-lab"],"prerequisiteConceptIds":["httpclient-reuso-timeout","timeout-deadline-cancelamento-http","json-formato-contrato"],"commonConfusions":["WebClient torna toda falha assíncrona resolvida","reativo dispensa timeout"],"outcomes":["chamar APIs externas com contrato, timeout, erro e tipo explícito"]},{"id":"resttemplate-legacy-client","name":"RestTemplate como cliente legado bloqueante","aliases":["RestTemplate","blocking client"],"introducedIn":"webclient","reinforcedIn":["coupled-services-lab"],"prerequisiteConceptIds":["http-mensagem-recurso","http-status-classe"],"commonConfusions":["legado significa proibido","cliente bloqueante não precisa limite"],"outcomes":["manter e migrar clientes bloqueantes entendendo seus limites"]},{"id":"bounded-context-context-map","name":"Bounded Context e Context Map","aliases":["bounded context","context map"],"introducedIn":"ddd-estrategico","reinforcedIn":["coupled-services-lab"],"prerequisiteConceptIds":["abstracao-modelagem","dto-mapping-fronteira"],"commonConfusions":["contexto é pacote Java","um banco compartilhado define o modelo"],"outcomes":["mapear fronteiras de linguagem, ownership e integração entre domínios"]},{"id":"anti-corruption-integration-contract","name":"Anti-corruption layer e contrato de integração","aliases":["ACL","anti-corruption layer"],"introducedIn":"ddd-estrategico","reinforcedIn":["webclient","coupled-services-lab"],"prerequisiteConceptIds":["bounded-context-context-map","dto-entity-boundary-spring"],"commonConfusions":["traduzir DTO é duplicação inútil","integração direta sempre simplifica"],"outcomes":["proteger modelo interno contra contratos externos instáveis"]},{"id":"aggregate-transaction-boundary","name":"Agregado como fronteira transacional","aliases":["aggregate","transaction boundary"],"introducedIn":"ddd-estrategico","reinforcedIn":["coupled-services-lab"],"prerequisiteConceptIds":["encapsulamento-invariante","transaction-proxy-boundary"],"commonConfusions":["agregado é tabela","transação deve atravessar todos os serviços"],"outcomes":["decidir consistência local antes de integração entre contextos"]},{"id":"frontend-backend-contract","name":"Contrato entre front-end e back-end","aliases":["frontend-backend contract","API contract"],"introducedIn":"conectando-front-back","reinforcedIn":["websockets","deploy-frontend"],"prerequisiteConceptIds":["json-formato-contrato","mvc-response-status-contract","openapi-documentation-contract"],"commonConfusions":["front escolhe qualquer shape","status HTTP não importa para UX"],"outcomes":["alinhar payload, status, erro e autenticação entre UI e API"]},{"id":"browser-fetch-lifecycle","name":"fetch no navegador: request, response e erro","aliases":["fetch","browser request"],"introducedIn":"conectando-front-back","reinforcedIn":["auth-front-ts"],"prerequisiteConceptIds":["http-header-body-negociacao","cors-browser-policy"],"commonConfusions":["fetch rejeita promise em todo 4xx","CORS é regra do servidor Java"],"outcomes":["tratar resposta, falha de rede, CORS e parsing explicitamente"]},{"id":"temporal-coupling-sync","name":"Acoplamento temporal em integração síncrona","aliases":["temporal coupling","synchronous coupling"],"introducedIn":"coupled-services-lab","reinforcedIn":["resilience","messaging-model"],"prerequisiteConceptIds":["spring-webclient-reactive-client","frontend-backend-contract"],"commonConfusions":["HTTP separa disponibilidade automaticamente","serviço separado elimina acoplamento"],"outcomes":["medir dependência simultânea, latência e caminho crítico"]},{"id":"partial-failure-unknown-state","name":"Falha parcial e estado desconhecido","aliases":["partial failure","unknown state"],"introducedIn":"coupled-services-lab","reinforcedIn":["resilience"],"prerequisiteConceptIds":["timeout-deadline-cancelamento-http","http-idempotencia-seguranca"],"commonConfusions":["timeout sempre significa falha definitiva","retry infinito aumenta confiabilidade"],"outcomes":["modelar resultado desconhecido, reconciliação e idempotência"]},{"id":"synchronous-deadline-budget","name":"Deadline budget em chamada síncrona","aliases":["deadline budget","latency budget"],"introducedIn":"coupled-services-lab","reinforcedIn":["resilience","observabilidade-pratica"],"prerequisiteConceptIds":["timeout-deadline-cancelamento-http","temporal-coupling-sync"],"commonConfusions":["cada chamada pode ter timeout isolado alto","latência média basta"],"outcomes":["dividir orçamento de tempo entre chamadas, retries e fallback"]},{"id":"circuit-breaker-state-machine","name":"Circuit breaker como máquina de estados","aliases":["circuit breaker","closed open half-open"],"introducedIn":"resilience","reinforcedIn":["observabilidade-pratica"],"prerequisiteConceptIds":["partial-failure-unknown-state","synchronous-deadline-budget"],"commonConfusions":["circuit breaker corrige serviço remoto","fallback é sempre resposta correta"],"outcomes":["abrir, meia-abrir e fechar circuito a partir de sinais e política explícita"]},{"id":"resilience4j-policy-composition","name":"Composição de políticas Resilience4j","aliases":["retry","bulkhead","time limiter"],"introducedIn":"resilience","reinforcedIn":["webclient"],"prerequisiteConceptIds":["retry-backoff-jitter","circuit-breaker-state-machine"],"commonConfusions":["retry antes de tudo é seguro","políticas independentes não interagem"],"outcomes":["combinar timeout, retry, circuit breaker e bulkhead sem amplificar falha"]},{"id":"aop-proxy-advice-joinpoint","name":"AOP proxy, advice e join point","aliases":["AOP","advice","join point"],"introducedIn":"spring-aop","reinforcedIn":["spring-cache","observabilidade-pratica"],"prerequisiteConceptIds":["annotation-metadata-contract","transaction-proxy-boundary"],"commonConfusions":["AOP altera bytecode sempre","self-invocation passa pelo proxy"],"outcomes":["identificar onde advice executa e onde o proxy não intercepta"]},{"id":"cross-cutting-concern-boundary","name":"Fronteira de cross-cutting concern","aliases":["cross-cutting concern","aspect"],"introducedIn":"spring-aop","reinforcedIn":["observabilidade-pratica"],"prerequisiteConceptIds":["aop-proxy-advice-joinpoint","log-parametrizado-causa"],"commonConfusions":["aspect deve conter regra de negócio","log genérico já é observabilidade"],"outcomes":["separar observabilidade, segurança e transação sem esconder domínio"]},{"id":"spring-cache-key-ttl","name":"Spring Cache: chave, TTL e escopo","aliases":["Spring Cache","cache key","TTL"],"introducedIn":"spring-cache","reinforcedIn":["observabilidade-pratica"],"prerequisiteConceptIds":["cache-aside-ttl-invalidation","aop-proxy-advice-joinpoint"],"commonConfusions":["cache sempre acelera sem custo","chave padrão serve para todo domínio"],"outcomes":["definir chave, TTL, invalidação e escopo de cache por contrato"]},{"id":"cache-stale-invalidation-risk","name":"Stale data e risco de invalidação","aliases":["stale cache","invalidation"],"introducedIn":"spring-cache","reinforcedIn":["resilience"],"prerequisiteConceptIds":["spring-cache-key-ttl","redis-persistence-eviction"],"commonConfusions":["dado cacheado é sempre verdadeiro","evict manual resolve todos os casos"],"outcomes":["avaliar quando aceitar dado antigo e quando invalidar de forma segura"]},{"id":"actuator-operational-endpoints","name":"Actuator endpoints e exposição operacional","aliases":["Actuator","management endpoints"],"introducedIn":"actuator","reinforcedIn":["observabilidade-pratica"],"prerequisiteConceptIds":["health-readiness-liveness","boot-bootstrap-autoconfig"],"commonConfusions":["expor todos endpoints é inofensivo","endpoint operacional é API de produto"],"outcomes":["expor health, metrics e info com segurança e escopo"]},{"id":"health-indicator-dependency-signal","name":"HealthIndicator como sinal de dependência","aliases":["HealthIndicator","dependency health"],"introducedIn":"actuator","reinforcedIn":["observabilidade-pratica"],"prerequisiteConceptIds":["actuator-operational-endpoints","managed-database-operations"],"commonConfusions":["health deve testar regra complexa","dependência lenta deve travar health sem limite"],"outcomes":["criar sinais simples, limitados e úteis para readiness"]},{"id":"structured-log-correlation-id","name":"Log estruturado e correlation ID","aliases":["structured log","correlation ID"],"introducedIn":"observabilidade-pratica","reinforcedIn":["coupled-services-lab"],"prerequisiteConceptIds":["log-nivel-contexto","log-parametrizado-causa"],"commonConfusions":["log livre é suficiente","correlation ID é dado sensível por padrão"],"outcomes":["propagar contexto de requisição sem vazar segredo"]},{"id":"metrics-cardinality-labels","name":"Métricas, labels e cardinalidade","aliases":["metrics","cardinality","labels"],"introducedIn":"observabilidade-pratica","reinforcedIn":["actuator"],"prerequisiteConceptIds":["actuator-operational-endpoints"],"commonConfusions":["label com userId é ótimo para debug","métrica substitui log"],"outcomes":["modelar métricas com labels de baixa cardinalidade e sinal acionável"]},{"id":"trace-span-boundary","name":"Trace, span e fronteira de chamada","aliases":["trace","span"],"introducedIn":"observabilidade-pratica","reinforcedIn":["webclient","coupled-services-lab"],"prerequisiteConceptIds":["temporal-coupling-sync","structured-log-correlation-id"],"commonConfusions":["trace explica regra de negócio sozinho","span sem propagação basta"],"outcomes":["enxergar caminho crítico entre serviços e dependências"]},{"id":"sli-slo-error-budget","name":"SLI, SLO e orçamento de erro","aliases":["SLI","SLO","error budget"],"introducedIn":"observabilidade-pratica","reinforcedIn":["resilience"],"prerequisiteConceptIds":["metrics-cardinality-labels","synchronous-deadline-budget"],"commonConfusions":["SLO é desejo de 100%","alerta em todo erro melhora operação"],"outcomes":["definir objetivo mensurável e alerta baseado em impacto"]},{"id":"message-envelope-payload-metadata","name":"Envelope de mensagem, payload e metadados","aliases":["message envelope","payload","metadata"],"introducedIn":"mensageria","reinforcedIn":["in-memory-event-bus","aws-sns-sqs"],"prerequisiteConceptIds":["json-formato-contrato","temporal-coupling-sync"],"commonConfusions":["mensagem é só o JSON do domínio","metadados são detalhe opcional"],"outcomes":["separar dado de negócio, identidade, tipo, versão, correlação e metadados operacionais"]},{"id":"event-command-message-intent","name":"Evento, comando e mensagem por intenção","aliases":["event","command","message"],"introducedIn":"mensageria","reinforcedIn":["in-memory-event-bus","messaging-model","saga-schema-ordering"],"prerequisiteConceptIds":["bounded-context-context-map"],"commonConfusions":["todo nome no passado é evento correto","evento é chamada remota sem resposta"],"outcomes":["nomear mensagens pelo tempo e pela responsabilidade esperada"]},{"id":"queue-work-competing-consumers","name":"Queue e consumidores competindo por trabalho","aliases":["queue","competing consumers"],"introducedIn":"mensageria","reinforcedIn":["messaging-model","aws-sns-sqs"],"prerequisiteConceptIds":["temporal-coupling-sync"],"commonConfusions":["queue entrega cópia para todos","mais consumidores sempre preservam ordem"],"outcomes":["distribuir trabalho com redelivery, backlog e ownership claro"]},{"id":"pubsub-fanout-subscription","name":"Pub/sub, fan-out e assinatura durável","aliases":["pub/sub","fan-out","subscription"],"introducedIn":"mensageria","reinforcedIn":["messaging-model","aws-sns-sqs"],"prerequisiteConceptIds":["event-command-message-intent"],"commonConfusions":["fan-out é o mesmo que vários workers na mesma fila","publicar evento garante que todo consumidor já processou"],"outcomes":["desenhar consumidores independentes com cópias e ritmos próprios"]},{"id":"delivery-at-least-once-idempotency","name":"At-least-once e idempotência do consumidor","aliases":["at-least-once","idempotent consumer"],"introducedIn":"mensageria","reinforcedIn":["delivery-failure-lab","kafka-confiavel"],"prerequisiteConceptIds":["http-idempotencia-seguranca","partial-failure-unknown-state"],"commonConfusions":["at-least-once significa exatamente uma vez","DLQ elimina necessidade de idempotência"],"outcomes":["absorver duplicação e reentrega sem corromper estado"]},{"id":"broker-backlog-backpressure","name":"Broker, backlog e backpressure operacional","aliases":["broker","backlog","backpressure"],"introducedIn":"messaging-model","reinforcedIn":["aws-sns-sqs","eda-observability-security","kafka"],"prerequisiteConceptIds":["metrics-cardinality-labels"],"commonConfusions":["broker é só transporte invisível","backlog alto sempre é incidente"],"outcomes":["medir diferença entre produção e consumo com limites, retenção e alertas"]},{"id":"sns-topic-fanout-policy","name":"SNS topic, assinatura e policy de fan-out","aliases":["SNS topic","subscription policy"],"introducedIn":"aws-sns-sqs","reinforcedIn":["delivery-failure-lab"],"prerequisiteConceptIds":["pubsub-fanout-subscription","runtime-secret-boundary"],"commonConfusions":["SNS guarda mensagens para qualquer consumo futuro","policy permissiva é detalhe de laboratório"],"outcomes":["publicar em tópico e autorizar filas específicas com menor privilégio"]},{"id":"sqs-queue-visibility-receipt","name":"SQS queue, receipt handle e visibility timeout","aliases":["SQS queue","receipt handle","visibility timeout"],"introducedIn":"aws-sns-sqs","reinforcedIn":["delivery-failure-lab","async-integration-tests"],"prerequisiteConceptIds":["queue-work-competing-consumers"],"commonConfusions":["receber apaga a mensagem","visibility timeout é lock permanente"],"outcomes":["processar, apagar após sucesso e aceitar reentrega como comportamento normal"]},{"id":"fifo-deduplication-group","name":"FIFO, deduplicação e message group","aliases":["FIFO queue","message group","deduplication"],"introducedIn":"aws-sns-sqs","reinforcedIn":["delivery-failure-lab"],"prerequisiteConceptIds":["delivery-at-least-once-idempotency"],"commonConfusions":["FIFO dá ordenação global gratuita","deduplication resolve efeito externo duplicado"],"outcomes":["limitar ordenação e deduplicação ao contrato real do grupo de mensagens"]},{"id":"dlq-redrive-policy","name":"DLQ, redrive e isolamento de falha","aliases":["DLQ","dead-letter queue","redrive"],"introducedIn":"delivery-failure-lab","reinforcedIn":["eda-observability-security","async-integration-tests"],"prerequisiteConceptIds":["delivery-at-least-once-idempotency","sqs-queue-visibility-receipt"],"commonConfusions":["DLQ é lixeira eterna","redrive automático corrige bug de schema"],"outcomes":["isolar poison messages, corrigir causa e reprocessar com evidência"]},{"id":"visibility-timeout-retry-window","name":"Janela de retry e visibility timeout","aliases":["retry window","visibility timeout"],"introducedIn":"delivery-failure-lab","reinforcedIn":["async-integration-tests"],"prerequisiteConceptIds":["sqs-queue-visibility-receipt","retry-backoff-jitter"],"commonConfusions":["timeout maior sempre aumenta confiabilidade","retry sem limite é seguro em mensageria"],"outcomes":["dimensionar timeout, backoff e maxReceiveCount pelo tempo real do handler"]},{"id":"kafka-topic-partition-offset","name":"Kafka topic, partition e offset","aliases":["Kafka topic","partition","offset"],"introducedIn":"kafka","reinforcedIn":["spring-kafka","event-driven-profundo"],"prerequisiteConceptIds":["broker-backlog-backpressure","pubsub-fanout-subscription"],"commonConfusions":["Kafka é fila única","offset confirma efeito de negócio automaticamente"],"outcomes":["ler Kafka como log particionado com posição por consumidor"]},{"id":"kafka-producer-consumer-record","name":"Producer, consumer e record Kafka","aliases":["producer","consumer","record"],"introducedIn":"kafka","reinforcedIn":["spring-kafka"],"prerequisiteConceptIds":["message-envelope-payload-metadata"],"commonConfusions":["record não precisa key","serialização é detalhe sem contrato"],"outcomes":["publicar e consumir records com key, value, headers e serialização explícitos"]},{"id":"consumer-group-rebalance-offset","name":"Consumer group, rebalance e commit de offset","aliases":["consumer group","rebalance","offset commit"],"introducedIn":"kafka","reinforcedIn":["spring-kafka","kafka-confiavel"],"prerequisiteConceptIds":["kafka-topic-partition-offset","delivery-at-least-once-idempotency"],"commonConfusions":["grupo replica evento para todos","commit antes do efeito é sempre seguro"],"outcomes":["controlar divisão de partições, reprocessamento e pontos de commit"]},{"id":"spring-kafka-listener-template","name":"Spring Kafka listener e KafkaTemplate","aliases":["@KafkaListener","KafkaTemplate"],"introducedIn":"spring-kafka","reinforcedIn":["kafka-confiavel"],"prerequisiteConceptIds":["kafka-producer-consumer-record","boot-bootstrap-autoconfig"],"commonConfusions":["Spring esconde semântica de offset","listener sem tratamento de erro já é confiável"],"outcomes":["integrar Kafka ao Spring preservando contratos do broker"]},{"id":"kafka-schema-evolution-contract","name":"Schema evolution em eventos Kafka","aliases":["schema evolution","event version"],"introducedIn":"kafka-confiavel","reinforcedIn":["event-driven-profundo","saga-schema-ordering"],"prerequisiteConceptIds":["json-formato-contrato","contract-test-evolution"],"commonConfusions":["consumidor novo apaga compatibilidade antiga","retry corrige incompatibilidade permanente"],"outcomes":["evoluir evento com versionamento, compatibilidade e testes de contrato"]},{"id":"kafka-retry-dlt-policy","name":"Retry topic e DLT no Kafka","aliases":["retry topic","DLT","dead-letter topic"],"introducedIn":"kafka-confiavel","reinforcedIn":["event-driven-profundo"],"prerequisiteConceptIds":["consumer-group-rebalance-offset"],"commonConfusions":["reprocessar infinitamente melhora disponibilidade","DLT é destino final sem diagnóstico"],"outcomes":["separar falha transitória de permanente e preservar evidência para reprocessamento"]},{"id":"kafka-idempotent-consumer","name":"Consumidor Kafka idempotente","aliases":["idempotent Kafka consumer"],"introducedIn":"kafka-confiavel","reinforcedIn":["outbox-inbox"],"prerequisiteConceptIds":["delivery-at-least-once-idempotency","aggregate-transaction-boundary"],"commonConfusions":["producer idempotent resolve o banco do consumidor","commit de offset equivale a transação local"],"outcomes":["registrar event ID e efeito em fronteira transacional local"]},{"id":"kafka-log-replication-ordering","name":"Log replicado, ordering e retenção no Kafka","aliases":["replicated log","ordering","retention"],"introducedIn":"event-driven-profundo","reinforcedIn":["saga-schema-ordering"],"prerequisiteConceptIds":["kafka-topic-partition-offset","artifact-image-provenance"],"commonConfusions":["Kafka ordena globalmente todos os eventos","retenção substitui auditoria de domínio"],"outcomes":["usar partições e retenção respeitando semântica, custo e replay"]},{"id":"partition-key-ordering-contract","name":"Chave de partição como contrato de ordenação","aliases":["partition key","ordering contract"],"introducedIn":"event-driven-profundo","reinforcedIn":["saga-schema-ordering"],"prerequisiteConceptIds":["kafka-log-replication-ordering","aggregate-transaction-boundary"],"commonConfusions":["key aleatória sempre distribui melhor","ordenar globalmente simplifica tudo"],"outcomes":["escolher key pelo aggregate/fluxo que exige ordem observável"]},{"id":"kafka-offset-commit-replay","name":"Offset commit, replay e recuperação","aliases":["replay","offset reset","commit"],"introducedIn":"event-driven-profundo","reinforcedIn":["testes-integracao-avancados"],"prerequisiteConceptIds":["consumer-group-rebalance-offset","kafka-idempotent-consumer"],"commonConfusions":["replay é sempre seguro sem idempotência","reset de offset corrige bug automaticamente"],"outcomes":["reprocessar com isolamento, evidência e efeitos idempotentes"]},{"id":"eda-correlation-causality","name":"Correlação, causalidade e identidade em EDA","aliases":["correlationId","causationId","eventId"],"introducedIn":"eda-observability-security","reinforcedIn":[],"prerequisiteConceptIds":["structured-log-correlation-id","message-envelope-payload-metadata"],"commonConfusions":["um único ID serve para tudo","payload inteiro é necessário no log"],"outcomes":["rastrear fluxo assíncrono sem vazar dados sensíveis"]},{"id":"async-trace-boundary","name":"Fronteira de trace assíncrono","aliases":["async span","messaging trace"],"introducedIn":"eda-observability-security","reinforcedIn":["async-integration-tests"],"prerequisiteConceptIds":["trace-span-boundary","eda-correlation-causality"],"commonConfusions":["trace assíncrono deve parecer chamada HTTP contínua","span sem contexto basta"],"outcomes":["propagar contexto em producer/consumer sem falsear causalidade"]},{"id":"message-security-iam-policy","name":"Segurança de mensagem e policy IAM mínima","aliases":["message security","IAM policy"],"introducedIn":"eda-observability-security","reinforcedIn":["aws-sns-sqs"],"prerequisiteConceptIds":["runtime-secret-boundary","spring-security-authorization"],"commonConfusions":["ambiente de mensageria não tem autorização","payload pode carregar segredo por conveniência"],"outcomes":["limitar publish/consume, proteger payload e evitar vazamento operacional"]},{"id":"localstack-container-boundary","name":"LocalStack/Testcontainers como fronteira de teste","aliases":["LocalStack","Testcontainers AWS"],"introducedIn":"async-integration-tests","reinforcedIn":["testes-integracao-avancados"],"prerequisiteConceptIds":["testcontainers-disposable-dependency","sqs-queue-visibility-receipt"],"commonConfusions":["emulador local é igual à cloud","container resolve desenho de teste ruim"],"outcomes":["testar contrato local documentando limites entre emulação e serviço real"]},{"id":"async-awaitility-eventual-assertion","name":"Assertiva eventual com Awaitility","aliases":["Awaitility","eventual assertion"],"introducedIn":"async-integration-tests","reinforcedIn":["testes-integracao-avancados"],"prerequisiteConceptIds":["deterministic-integration-test","visibility-timeout-retry-window"],"commonConfusions":["sleep maior deixa teste determinístico","Awaitility corrige race condition"],"outcomes":["esperar condição observável com deadline e diagnóstico"]},{"id":"springboot-test-slice-contract","name":"Contrato de slice test no Spring Boot","aliases":["Spring Boot test slice","@SpringBootTest"],"introducedIn":"testes-integracao-avancados","reinforcedIn":["async-integration-tests"],"prerequisiteConceptIds":["spring-kafka-listener-template","deterministic-integration-test"],"commonConfusions":["todo teste precisa subir aplicação inteira","slice simula broker real"],"outcomes":["escolher escopo de teste conforme fronteira real a validar"]},{"id":"kafka-testcontainer-determinism","name":"Kafka Testcontainer determinístico","aliases":["Kafka Testcontainer"],"introducedIn":"testes-integracao-avancados","reinforcedIn":["async-integration-tests"],"prerequisiteConceptIds":["testcontainers-disposable-dependency","kafka-topic-partition-offset"],"commonConfusions":["container torna teste rápido por padrão","topic compartilhado não vaza entre testes"],"outcomes":["subir Kafka descartável com readiness, tópicos isolados e assertivas por efeito"]},{"id":"distributed-system-failure-boundary","name":"Sistema distribuído e fronteira de falha parcial","aliases":["distributed system","partial failure boundary"],"introducedIn":"sistemas-distribuidos","reinforcedIn":["consistencia-distribuida","mini-pedidos-eventos"],"prerequisiteConceptIds":["temporal-coupling-sync","partial-failure-unknown-state"],"commonConfusions":["serviços separados falham como uma aplicação local","rede é detalhe de infraestrutura"],"outcomes":["modelar comunicação remota como falha parcial, latência e estado desconhecido"]},{"id":"cap-partition-tradeoff","name":"CAP sob partição de rede","aliases":["CAP","partition tolerance"],"introducedIn":"sistemas-distribuidos","reinforcedIn":["consistencia-distribuida"],"prerequisiteConceptIds":["distributed-system-failure-boundary"],"commonConfusions":["CAP permite escolher dois em qualquer momento","P é opcional em produção"],"outcomes":["explicar que, sob partição, o sistema precisa preferir consistência ou disponibilidade por fronteira"]},{"id":"consistency-availability-scope","name":"Escopo local da escolha CP/AP","aliases":["CP","AP","consistency scope"],"introducedIn":"sistemas-distribuidos","reinforcedIn":["consistencia-distribuida"],"prerequisiteConceptIds":["cap-partition-tradeoff"],"commonConfusions":["o sistema inteiro é CP ou AP para tudo","uma escolha de banco resolve todas as operações"],"outcomes":["fazer trade-off por operação, dado e impacto de negócio"]},{"id":"eventual-consistency-reconciliation","name":"Consistência eventual e reconciliação","aliases":["eventual consistency","reconciliation"],"introducedIn":"sistemas-distribuidos","reinforcedIn":["mini-pedidos-eventos","saga-schema-ordering"],"prerequisiteConceptIds":["delivery-at-least-once-idempotency","eda-correlation-causality"],"commonConfusions":["eventual significa inconsistente para sempre","retry substitui reconciliação"],"outcomes":["aceitar atraso controlado com estado, correlação, compensação e rotina de reparo"]},{"id":"pacelc-latency-consistency","name":"PACELC: partição, latência e consistência","aliases":["PACELC"],"introducedIn":"consistencia-distribuida","reinforcedIn":["sistemas-distribuidos"],"prerequisiteConceptIds":["cap-partition-tradeoff"],"commonConfusions":["CAP explica somente incidentes extremos","latência normal não é trade-off de consistência"],"outcomes":["avaliar consistência versus latência também em operação normal"]},{"id":"read-consistency-models","name":"Modelos de leitura: forte, monotônica e read-your-writes","aliases":["strong consistency","read-your-writes","monotonic reads"],"introducedIn":"consistencia-distribuida","reinforcedIn":["mini-pedidos-eventos"],"prerequisiteConceptIds":["pacelc-latency-consistency","transaction-proxy-boundary"],"commonConfusions":["consistência eventual é um único modelo","cache sempre preserva read-your-writes"],"outcomes":["escolher garantia de leitura pelo fluxo do usuário e pelo custo operacional"]},{"id":"time-timeout-uncertainty","name":"Tempo, timeout e incerteza distribuída","aliases":["timeout","clock uncertainty"],"introducedIn":"consistencia-distribuida","reinforcedIn":["outbox-inbox","saga-schema-ordering"],"prerequisiteConceptIds":["partial-failure-unknown-state","synchronous-deadline-budget"],"commonConfusions":["timeout prova que o efeito remoto não aconteceu","clock local decide ordem global"],"outcomes":["tratar timeout como incerteza e projetar reconciliação/idempotência"]},{"id":"outbox-atomic-publish-intent","name":"Outbox como intenção durável de publicação","aliases":["transactional outbox","publish intent"],"introducedIn":"outbox-inbox","reinforcedIn":["mini-pedidos-eventos"],"prerequisiteConceptIds":["aggregate-transaction-boundary","event-command-message-intent"],"commonConfusions":["outbox faz transação distribuída","publicar dentro da transação local é seguro por si só"],"outcomes":["gravar estado e intenção de evento na mesma transação local"]},{"id":"inbox-dedup-effect-transaction","name":"Inbox e deduplicação com efeito local","aliases":["inbox","deduplication table"],"introducedIn":"outbox-inbox","reinforcedIn":["mini-pedidos-eventos"],"prerequisiteConceptIds":["kafka-idempotent-consumer","delivery-at-least-once-idempotency"],"commonConfusions":["deduplicar em memória basta","marcar processado antes do efeito é seguro"],"outcomes":["registrar eventId e efeito numa fronteira transacional coerente"]},{"id":"relay-retry-duplication-window","name":"Relay, retry e janela de duplicação","aliases":["outbox relay","polling publisher","CDC"],"introducedIn":"outbox-inbox","reinforcedIn":["mini-pedidos-eventos"],"prerequisiteConceptIds":["outbox-atomic-publish-intent","broker-backlog-backpressure"],"commonConfusions":["relay nunca publica duplicado","CDC remove necessidade de idempotência"],"outcomes":["provar recuperação sem evento perdido aceitando republicação controlada"]},{"id":"saga-choreography-orchestration","name":"Saga por coreografia ou orquestração","aliases":["saga choreography","saga orchestration"],"introducedIn":"saga-schema-ordering","reinforcedIn":["mini-pedidos-eventos"],"prerequisiteConceptIds":["event-command-message-intent","bounded-context-context-map"],"commonConfusions":["coreografia é sempre mais simples","orquestrador é monólito por definição"],"outcomes":["escolher coordenação distribuída pelo tamanho, visibilidade e ownership do fluxo"]},{"id":"compensating-action-domain-state","name":"Compensação como ação de domínio","aliases":["compensating action","compensation"],"introducedIn":"saga-schema-ordering","reinforcedIn":["mini-pedidos-eventos"],"prerequisiteConceptIds":["eventual-consistency-reconciliation","aggregate-transaction-boundary"],"commonConfusions":["compensação é rollback ACID","toda ação pode ser desfeita"],"outcomes":["modelar compensação, falha de compensação e estado intermediário explicitamente"]},{"id":"event-schema-ordering-evolution","name":"Evolução de schema e ordering por aggregate","aliases":["event schema","ordering by aggregate"],"introducedIn":"saga-schema-ordering","reinforcedIn":["mini-pedidos-eventos"],"prerequisiteConceptIds":["kafka-schema-evolution-contract","partition-key-ordering-contract"],"commonConfusions":["retry corrige breaking change","ordem global simplifica o domínio"],"outcomes":["evoluir eventos com compatibilidade e limitar ordering ao fluxo que precisa dele"]},{"id":"event-driven-project-evidence","name":"Evidência de projeto orientado a eventos","aliases":["EDA evidence","kill test","project evidence"],"introducedIn":"mini-pedidos-eventos","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["outbox-atomic-publish-intent","saga-choreography-orchestration","async-awaitility-eventual-assertion"],"commonConfusions":["caminho feliz aprova sistema distribuído","print substitui teste de crash"],"outcomes":["entregar provas reprodutíveis de duplicação, atraso, replay, compensação e observabilidade"]},{"id":"lifo-stack-discipline","name":"Pilha LIFO e disciplina de topo","aliases":["stack","LIFO","Deque push pop"],"introducedIn":"estruturas-avancadas","reinforcedIn":["projeto"],"prerequisiteConceptIds":["array-indice-length","recursao-caso-base"],"commonConfusions":["Stack antiga é sempre a melhor API Java","pilha permite remover qualquer elemento"],"outcomes":["usar LIFO para desfazer, chamadas e processamento por topo sem violar invariantes"]},{"id":"fifo-queue-discipline","name":"Fila FIFO e disciplina de atendimento","aliases":["queue","FIFO","offer poll"],"introducedIn":"estruturas-avancadas","reinforcedIn":["mini-reservas-testadas","mensageria"],"prerequisiteConceptIds":["contrato-collection-map"],"commonConfusions":["fila garante prioridade automaticamente","Queue e List têm o mesmo contrato semântico"],"outcomes":["modelar atendimento por ordem de chegada usando operações de fila e casos vazios"]},{"id":"tree-invariant-search-cost","name":"Árvore, invariante e custo de busca","aliases":["binary tree","binary search tree"],"introducedIn":"estruturas-avancadas","reinforcedIn":["projeto"],"prerequisiteConceptIds":["recursao-caso-base","complexidade-assintotica"],"commonConfusions":["toda árvore binária busca em O(log n)","árvore é só lista com nós"],"outcomes":["preservar invariante esquerda/direita e relacionar altura ao custo real"]},{"id":"graph-adjacency-traversal","name":"Grafo, lista de adjacência e travessia","aliases":["graph","adjacency list","BFS"],"introducedIn":"estruturas-avancadas","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["contrato-collection-map","complexidade-assintotica"],"commonConfusions":["grafo precisa ser hierárquico","visited é detalhe opcional"],"outcomes":["representar relações não hierárquicas e percorrer sem laço infinito"]},{"id":"behavior-test-matrix","name":"Matriz de comportamento testável","aliases":["test matrix","behavior matrix"],"introducedIn":"mini-reservas-testadas","reinforcedIn":["projeto","projeto-integrador"],"prerequisiteConceptIds":["teste-aaa-first"],"commonConfusions":["testar método privado prova comportamento","um teste feliz cobre a regra"],"outcomes":["mapear caminho feliz, limites, conflito, erro e evidência por comportamento observável"]},{"id":"reservation-invariant-capacity","name":"Invariante de reserva e capacidade","aliases":["reservation invariant","capacity"],"introducedIn":"mini-reservas-testadas","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["encapsulamento-invariante","teste-aaa-first"],"commonConfusions":["validar na UI basta","capacidade é só campo público"],"outcomes":["impedir overbooking e estados impossíveis por regra de domínio testada"]},{"id":"test-double-boundary","name":"Fronteira de dublê de teste","aliases":["stub","mock","fake"],"introducedIn":"mini-reservas-testadas","reinforcedIn":["projeto"],"prerequisiteConceptIds":["test-double-vocabulary","mockito-behavior-verification"],"commonConfusions":["mockar tudo deixa teste melhor","mock substitui regra de domínio"],"outcomes":["usar dublês apenas em fronteiras externas e preservar teste de regra real"]},{"id":"library-domain-slice","name":"Slice de domínio da biblioteca","aliases":["library domain","vertical slice"],"introducedIn":"projeto","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["associacao-direcao","cardinalidade-objeto","encapsulamento-invariante"],"commonConfusions":["projeto final é juntar todos os recursos sem limite","modelo de biblioteca precisa começar com banco"],"outcomes":["entregar empréstimo/devolução/consulta como fatias testáveis de domínio antes de infraestrutura"]},{"id":"project-evidence-readme","name":"Evidência de projeto em README","aliases":["project evidence","README evidence"],"introducedIn":"projeto","reinforcedIn":["mini-pedidos-eventos","projeto-integrador"],"prerequisiteConceptIds":["evidencia-manual","teste-aaa-first"],"commonConfusions":["README é enfeite depois do código","print substitui teste reproduzível"],"outcomes":["registrar comandos, decisões, casos-limite e critérios de aceite reproduzíveis"]},{"id":"review-intent-risk-check","name":"Code review por intenção e risco","aliases":["code review","review checklist"],"introducedIn":"code-review-adr","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["git-snapshot-index","nome-intencao-codigo"],"commonConfusions":["review é caça a erro pessoal","aprovar porque compilou basta"],"outcomes":["revisar mudança por comportamento, risco, teste, contrato e legibilidade"]},{"id":"adr-context-decision-consequence","name":"ADR: contexto, decisão e consequência","aliases":["ADR","Architecture Decision Record"],"introducedIn":"code-review-adr","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["hexagonal-port-adapter","bounded-context-context-map"],"commonConfusions":["ADR é documentação decorativa","decisão sem trade-off não precisa registro"],"outcomes":["registrar decisão arquitetural curta com alternativas e consequências verificáveis"]},{"id":"scrum-feedback-cadence","name":"Scrum como cadência de feedback","aliases":["Scrum","Sprint"],"introducedIn":"agile","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["project-evidence-readme"],"commonConfusions":["Scrum é reunião diária","sprint serve para esconder atraso até o fim"],"outcomes":["planejar ciclos curtos com objetivo, revisão, retrospectiva e incremento demonstrável"]},{"id":"kanban-wip-flow","name":"Kanban, WIP e fluxo contínuo","aliases":["Kanban","WIP","flow"],"introducedIn":"agile","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["branch-merge-rebase"],"commonConfusions":["Kanban é só quadro com colunas","mais tarefas em progresso acelera entrega"],"outcomes":["limitar trabalho em progresso, medir bloqueio e reduzir troca de contexto"]},{"id":"end-to-end-request-map","name":"Mapa ponta-a-ponta de uma requisição","aliases":["request map","end-to-end"],"introducedIn":"mapa-sistema","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["frontend-backend-contract","spring-security-authorization","trace-span-boundary"],"commonConfusions":["controller é o sistema inteiro","cache/evento/log são detalhes fora do fluxo"],"outcomes":["seguir uma ação da UI até domínio, banco, cache, evento, observabilidade e resposta"]},{"id":"system-knowledge-map","name":"Mapa de conhecimento do sistema","aliases":["system map","knowledge map"],"introducedIn":"mapa-sistema","reinforcedIn":["quiz"],"prerequisiteConceptIds":["project-evidence-readme"],"commonConfusions":["glossário é lista decorada","mapa dispensa pré-requisito"],"outcomes":["relacionar conceitos por dependência e usar o mapa para revisar lacunas"]},{"id":"capstone-reference-architecture","name":"Arquitetura de referência verificável","aliases":["capstone architecture"],"introducedIn":"projeto-integrador","reinforcedIn":["quiz"],"prerequisiteConceptIds":["production-evidence-checklist","event-driven-project-evidence","health-readiness-liveness"],"commonConfusions":["arquitetura final é desenho sem execução","usar todas as tecnologias é obrigatório"],"outcomes":["entregar uma arquitetura ponta-a-ponta com critérios de aceite, evidência e limites"]},{"id":"capstone-operational-evidence","name":"Evidência operacional do projeto final","aliases":["operational evidence","capstone evidence"],"introducedIn":"projeto-integrador","reinforcedIn":["quiz"],"prerequisiteConceptIds":["sli-slo-error-budget","deploy-rollback-strategy","recovery-backup-restore-drill"],"commonConfusions":["deploy funcionando uma vez prova produção","log solto substitui runbook"],"outcomes":["provar build, teste, deploy, rollback, observabilidade, backup e falhas controladas"]},{"id":"final-review-diagnostic-loop","name":"Revisão final como diagnóstico de lacunas","aliases":["final review","diagnostic quiz"],"introducedIn":"quiz","reinforcedIn":["projeto-integrador"],"prerequisiteConceptIds":["system-knowledge-map","project-evidence-readme"],"commonConfusions":["quiz final mede memorização isolada","errar significa voltar ao começo"],"outcomes":["usar erros de revisão para voltar aos capítulos/conceitos certos e fortalecer projeto"]}],"glossary":[{"id":"term-0","term":"JDK","definition":"Kit de desenvolvimento que reúne compilador, ferramentas e runtime necessários para criar aplicações Java.","chapterId":"intro","aliases":["Java Development Kit"]},{"id":"term-1","term":"JRE","definition":"Ambiente necessário para executar aplicações Java: JVM mais bibliotecas de runtime.","chapterId":"intro","aliases":["Java Runtime Environment"]},{"id":"term-2","term":"JVM","definition":"Máquina virtual que carrega e executa bytecode Java, gerenciando memória, threads e otimizações em runtime.","chapterId":"intro","aliases":["Java Virtual Machine"]},{"id":"term-3","term":"Bytecode","definition":"Representação intermediária gerada pelo compilador Java e executada pela JVM, normalmente armazenada em arquivos .class.","chapterId":"primeiro-programa","aliases":[]},{"id":"term-4","term":"Terminal","definition":"Interface textual que exibe a saída de um processo e encaminha a ele comandos e dados digitados.","chapterId":"entrada-console","aliases":[]},{"id":"term-5","term":"CLI","definition":"Interface de linha de comando: interação por texto, argumentos e comandos em vez de controles gráficos.","chapterId":"entrada-console","aliases":["Command-line interface","interface de linha de comando"]},{"id":"term-6","term":"Entrada padrão","definition":"Canal de entrada do processo representado em Java por System.in; no terminal, normalmente recebe o que a pessoa digita.","chapterId":"entrada-console","aliases":["standard input","stdin","System.in"]},{"id":"term-7","term":"Scanner","definition":"Classe da biblioteca Java que divide uma entrada em linhas ou tokens e converte representações textuais.","chapterId":"entrada-console","aliases":[]},{"id":"term-8","term":"Buffer","definition":"Área temporária que mantém dados recebidos ou preparados até que sejam consumidos ou enviados.","chapterId":"entrada-console","aliases":[]},{"id":"term-9","term":"Parsing","definition":"Conversão de uma representação textual segundo uma sintaxe para um valor estruturado ou tipado.","chapterId":"entrada-console","aliases":["parse"]},{"id":"term-10","term":"EOF","definition":"Fim da entrada: sinal de que não existe outra linha ou byte disponível para leitura.","chapterId":"entrada-console","aliases":["end of file","fim de arquivo"]},{"id":"term-11","term":"BigDecimal","definition":"Tipo Java para números decimais com precisão e escala controladas, indicado quando arredondamento exato importa.","chapterId":"entrada-console","aliases":[]},{"id":"term-12","term":"Locale","definition":"Conjunto de convenções regionais de idioma, números, moeda, datas e ordenação.","chapterId":"entrada-console","aliases":[]},{"id":"term-13","term":"Socket","definition":"Extremidade de comunicação entre processos que transporta bytes, normalmente através de uma rede.","chapterId":"entrada-console","aliases":["sockets"]},{"id":"term-14","term":"Protocolo","definition":"Acordo que define formato, ordem, significado, erros e encerramento das mensagens trocadas.","chapterId":"entrada-console","aliases":["protocolos"]},{"id":"term-15","term":"JIT","definition":"Compilador Just-In-Time que transforma trechos frequentes de bytecode em código de máquina durante a execução.","chapterId":"jvm-profundo","aliases":["Just-In-Time"]},{"id":"term-16","term":"Garbage Collector","definition":"Componente da JVM que identifica objetos sem referências alcançáveis e recupera sua memória automaticamente.","chapterId":"jvm-profundo","aliases":["GC","coletor de lixo"]},{"id":"term-17","term":"Stack","definition":"Região associada a cada thread que armazena frames de métodos, parâmetros e variáveis locais.","chapterId":"jvm-profundo","aliases":["pilha"]},{"id":"term-18","term":"Heap","definition":"Região compartilhada da memória onde objetos e arrays são normalmente alocados.","chapterId":"jvm-profundo","aliases":[]},{"id":"term-19","term":"Classe","definition":"Definição de estrutura e comportamento usada para criar objetos.","chapterId":"classes","aliases":["class"]},{"id":"term-20","term":"Objeto","definition":"Instância concreta de uma classe, com identidade, estado e comportamento próprios.","chapterId":"classes","aliases":[]},{"id":"term-21","term":"Encapsulamento","definition":"Proteção do estado interno de um objeto por meio de uma interface controlada de operações.","chapterId":"encapsulamento","aliases":[]},{"id":"term-22","term":"Herança","definition":"Mecanismo pelo qual uma classe especializa outra e reutiliza membros compatíveis.","chapterId":"heranca","aliases":[]},{"id":"term-23","term":"Polimorfismo","definition":"Capacidade de tratar implementações diferentes por um contrato comum, com o comportamento decidido em runtime.","chapterId":"polimorfismo","aliases":[]},{"id":"term-24","term":"Abstração","definition":"Modelagem que destaca características essenciais e esconde detalhes desnecessários para quem usa o componente.","chapterId":"abstracao","aliases":[]},{"id":"term-25","term":"Interface","definition":"Contrato de tipos que declara comportamentos sem exigir uma implementação concreta única.","chapterId":"interfaces","aliases":[]},{"id":"term-26","term":"Exceção","definition":"Objeto que representa uma condição anormal e altera o fluxo normal de execução.","chapterId":"excecoes","aliases":["exception"]},{"id":"term-27","term":"Collection","definition":"Família de interfaces e implementações para armazenar e manipular grupos de objetos.","chapterId":"colecoes","aliases":["Collections","coleção"]},{"id":"term-28","term":"Generics","definition":"Sistema de parametrização de tipos que aumenta reutilização e segurança em tempo de compilação.","chapterId":"generics","aliases":["genéricos"]},{"id":"term-29","term":"Lambda","definition":"Expressão compacta que fornece a implementação de uma interface funcional.","chapterId":"streams","aliases":["lambdas"]},{"id":"term-30","term":"Stream","definition":"Pipeline declarativo para processar sequências de elementos sem representar uma coleção de armazenamento.","chapterId":"streams","aliases":["streams"]},{"id":"term-31","term":"Optional","definition":"Contêiner que expressa explicitamente a presença ou ausência de um valor de retorno.","chapterId":"javamoderno","aliases":[]},{"id":"term-32","term":"Record","definition":"Tipo Java compacto voltado a carregar um conjunto fixo de dados, com acessores, igualdade, hash e representação gerados.","chapterId":"java-21-profundo","aliases":["records"]},{"id":"term-33","term":"Sealed type","definition":"Classe ou interface Java que restringe explicitamente quais tipos podem estendê-la ou implementá-la.","chapterId":"java-21-profundo","aliases":["sealed","tipo selado"]},{"id":"term-34","term":"Virtual thread","definition":"Thread leve gerenciada pela JVM, adequada a grandes quantidades de tarefas que passam tempo bloqueadas em entrada e saída.","chapterId":"java-21-profundo","aliases":["virtual threads","thread virtual","threads virtuais"]},{"id":"term-35","term":"Big O","definition":"Notação que descreve como o custo de um algoritmo cresce conforme aumenta o tamanho da entrada.","chapterId":"big-o","aliases":["complexidade assintótica"]},{"id":"term-36","term":"Recursão","definition":"Técnica em que uma função resolve o problema chamando a si mesma sobre uma entrada menor, até alcançar um caso-base.","chapterId":"recursao","aliases":[]},{"id":"term-37","term":"Git","definition":"Sistema distribuído de controle de versão que registra a evolução do código e permite trabalho paralelo.","chapterId":"git","aliases":[]},{"id":"term-38","term":"Commit","definition":"Registro imutável de um conjunto coerente de alterações no histórico do Git.","chapterId":"git","aliases":[]},{"id":"term-39","term":"Maven","definition":"Ferramenta de build e gestão de dependências baseada em convenções e no arquivo pom.xml.","chapterId":"build","aliases":[]},{"id":"term-40","term":"Gradle","definition":"Ferramenta de automação de build que usa uma DSL para configurar tarefas e dependências.","chapterId":"build","aliases":[]},{"id":"term-41","term":"Reflection","definition":"API que permite inspecionar e manipular tipos, membros e anotações em tempo de execução.","chapterId":"anotacoes","aliases":["reflexão"]},{"id":"term-42","term":"JSON","definition":"Formato textual de troca de dados baseado em objetos, arrays e valores simples.","chapterId":"json","aliases":[]},{"id":"term-43","term":"Logging","definition":"Registro estruturado de eventos da aplicação para diagnóstico, auditoria e observabilidade.","chapterId":"logging","aliases":["logs"]},{"id":"term-44","term":"SOLID","definition":"Conjunto de cinco princípios de design orientado a objetos voltados a coesão, extensão e baixo acoplamento.","chapterId":"solid","aliases":[]},{"id":"term-45","term":"Injeção de dependência","definition":"Técnica em que um objeto recebe as colaborações de que precisa em vez de construí-las internamente.","chapterId":"di","aliases":["DI","dependency injection"]},{"id":"term-46","term":"IoC","definition":"Inversão de Controle: o fluxo de criação e coordenação de componentes é transferido para um contêiner ou framework.","chapterId":"spring-core","aliases":["inversão de controle"]},{"id":"term-47","term":"HTTP","definition":"Protocolo de requisição e resposta usado na comunicação entre clientes e servidores web.","chapterId":"http","aliases":[]},{"id":"term-48","term":"REST","definition":"Estilo arquitetural que modela recursos e usa as semânticas do HTTP para manipulá-los.","chapterId":"http","aliases":[]},{"id":"term-49","term":"Endpoint","definition":"Combinação de endereço e operação exposta por uma API para atender determinada interação.","chapterId":"http","aliases":[]},{"id":"term-50","term":"Status HTTP","definition":"Código numérico que comunica o resultado de uma requisição, como 200, 404 ou 409.","chapterId":"http","aliases":["status code"]},{"id":"term-51","term":"SQL","definition":"Linguagem declarativa usada para definir, consultar e modificar dados relacionais.","chapterId":"sql","aliases":[]},{"id":"term-52","term":"Transação","definition":"Unidade lógica de trabalho que deve ser confirmada integralmente ou revertida.","chapterId":"sql","aliases":["transaction"]},{"id":"term-53","term":"ACID","definition":"Propriedades de atomicidade, consistência, isolamento e durabilidade esperadas de transações confiáveis.","chapterId":"sql","aliases":[]},{"id":"term-54","term":"Índice","definition":"Estrutura auxiliar que acelera buscas no banco ao custo de espaço e manutenção nas escritas.","chapterId":"postgres","aliases":["index"]},{"id":"term-55","term":"MVCC","definition":"Controle de concorrência por múltiplas versões: mantém versões de linhas para oferecer snapshots e reduzir bloqueio entre leituras e escritas.","chapterId":"postgres-concorrencia","aliases":["Multi-Version Concurrency Control"]},{"id":"term-56","term":"Migration","definition":"Alteração versionada e reproduzível aplicada ao schema do banco de dados.","chapterId":"migrations","aliases":["migrations","migração"]},{"id":"term-57","term":"JDBC","definition":"API padrão do Java para abrir conexões, enviar SQL e processar resultados de bancos relacionais.","chapterId":"jdbc","aliases":[]},{"id":"term-58","term":"Bean","definition":"Objeto cujo ciclo de vida e dependências são gerenciados pelo contêiner Spring.","chapterId":"spring-core","aliases":["beans"]},{"id":"term-59","term":"Spring MVC","definition":"Módulo web do Spring que organiza requisições HTTP, controllers, conversão de dados e respostas.","chapterId":"spring-mvc","aliases":[]},{"id":"term-60","term":"JPA","definition":"Especificação Java de mapeamento objeto-relacional; implementações como Hibernate executam o trabalho concreto.","chapterId":"spring-jpa","aliases":["Java Persistence API"]},{"id":"term-61","term":"Entity","definition":"Classe persistente mapeada para dados relacionais e identificada no contexto do JPA.","chapterId":"spring-jpa","aliases":["entidade"]},{"id":"term-62","term":"DTO","definition":"Objeto criado para transportar dados através de uma fronteira sem expor diretamente o modelo interno.","chapterId":"dto-mapping","aliases":["Data Transfer Object"]},{"id":"term-63","term":"Bean Validation","definition":"Modelo declarativo de validação baseado em constraints como @NotNull e @Size.","chapterId":"validacao-erros","aliases":["validação"]},{"id":"term-64","term":"N+1","definition":"Problema em que uma consulta inicial dispara várias consultas adicionais, geralmente uma para cada item retornado.","chapterId":"n-mais-1","aliases":[]},{"id":"term-65","term":"CORS","definition":"Política do navegador que controla quais origens podem acessar recursos de outra origem.","chapterId":"cors-rate-limit","aliases":["Cross-Origin Resource Sharing"]},{"id":"term-66","term":"JUnit","definition":"Framework usado para escrever e executar testes automatizados em Java.","chapterId":"testes","aliases":[]},{"id":"term-67","term":"Mock","definition":"Dublê configurável usado para controlar dependências e verificar interações durante um teste.","chapterId":"mockito","aliases":["mocks"]},{"id":"term-68","term":"JWT","definition":"Token assinado composto por header, payload e assinatura; carrega claims, mas não é criptografado por padrão.","chapterId":"autenticacao-conceitos","aliases":["JSON Web Token"]},{"id":"term-69","term":"OAuth2","definition":"Framework de autorização delegada que permite conceder acesso limitado sem compartilhar a senha do usuário.","chapterId":"autenticacao-conceitos","aliases":["OAuth 2.0"]},{"id":"term-70","term":"OIDC","definition":"OpenID Connect: camada de identidade sobre OAuth 2.0 que padroniza autenticação e ID token.","chapterId":"security-oidc","aliases":["OpenID Connect"]},{"id":"term-71","term":"PKCE","definition":"Proteção do fluxo Authorization Code que vincula cada código a um segredo temporário criado pelo cliente.","chapterId":"security-oidc","aliases":["Proof Key for Code Exchange"]},{"id":"term-72","term":"RBAC","definition":"Modelo de autorização no qual permissões são atribuídas a papéis associados aos usuários.","chapterId":"mini-auth-rbac","aliases":["Role-Based Access Control"]},{"id":"term-73","term":"TLS","definition":"Protocolo criptográfico que oferece confidencialidade, integridade e autenticação para conexões como HTTPS.","chapterId":"https","aliases":[]},{"id":"term-74","term":"Docker","definition":"Plataforma para empacotar e executar aplicações em containers reproduzíveis.","chapterId":"docker-conceitos","aliases":[]},{"id":"term-75","term":"Imagem Docker","definition":"Artefato imutável em camadas que contém aplicação, runtime e configuração necessários para criar containers.","chapterId":"dockerfile","aliases":["Docker image"]},{"id":"term-76","term":"Container","definition":"Processo isolado iniciado a partir de uma imagem e compartilhando o kernel do host.","chapterId":"docker-conceitos","aliases":["containers"]},{"id":"term-77","term":"Docker Compose","definition":"Ferramenta declarativa para definir e executar vários serviços relacionados.","chapterId":"compose","aliases":["Compose"]},{"id":"term-78","term":"Testcontainers","definition":"Biblioteca que inicia containers descartáveis durante testes de integração.","chapterId":"testcontainers","aliases":[]},{"id":"term-79","term":"NoSQL","definition":"Família de bancos não relacionais com modelos como documentos, chave-valor, colunas ou grafos.","chapterId":"nosql","aliases":[]},{"id":"term-80","term":"MongoDB","definition":"Banco orientado a documentos que armazena estruturas semelhantes a JSON no formato BSON.","chapterId":"mongodb","aliases":[]},{"id":"term-81","term":"Redis","definition":"Armazenamento em memória baseado em estruturas de dados, frequentemente usado para cache e coordenação.","chapterId":"redis","aliases":[]},{"id":"term-82","term":"Cache","definition":"Cópia temporária de dados criada para reduzir latência e trabalho repetido.","chapterId":"spring-cache","aliases":[]},{"id":"term-83","term":"Circuit Breaker","definition":"Padrão que interrompe chamadas a uma dependência instável para evitar falhas em cascata.","chapterId":"resilience","aliases":[]},{"id":"term-84","term":"WebClient","definition":"Cliente HTTP reativo e não bloqueante fornecido pelo ecossistema Spring.","chapterId":"webclient","aliases":[]},{"id":"term-85","term":"AOP","definition":"Programação orientada a aspectos, usada para aplicar comportamentos transversais como logs e transações.","chapterId":"spring-aop","aliases":["Aspect-Oriented Programming"]},{"id":"term-86","term":"Actuator","definition":"Conjunto de endpoints operacionais do Spring Boot para saúde, métricas e diagnóstico.","chapterId":"actuator","aliases":[]},{"id":"term-87","term":"CI/CD","definition":"Automação contínua de integração, testes e entrega ou implantação de software.","chapterId":"cicd","aliases":["pipeline"]},{"id":"term-88","term":"Observabilidade","definition":"Capacidade de compreender o estado interno de um sistema por métricas, logs e traces.","chapterId":"mini-deploy-observavel","aliases":[]},{"id":"term-89","term":"SLO","definition":"Objetivo mensurável de confiabilidade de um serviço em uma janela, como 99,9% de sucesso em 30 dias.","chapterId":"observabilidade-pratica","aliases":["Service Level Objective"]},{"id":"term-90","term":"SBOM","definition":"Inventário dos componentes e versões presentes em um artefato de software.","chapterId":"hardening-producao","aliases":["Software Bill of Materials"]},{"id":"term-91","term":"Thread","definition":"Fluxo de execução dentro de um processo, com stack própria e memória compartilhada com outras threads.","chapterId":"threads","aliases":["threads"]},{"id":"term-92","term":"Race condition","definition":"Falha dependente da ordem de execução concorrente quando acessos compartilhados não são coordenados.","chapterId":"concorrencia-profunda","aliases":["condição de corrida"]},{"id":"term-93","term":"Deadlock","definition":"Situação em que fluxos concorrentes aguardam indefinidamente recursos mantidos uns pelos outros.","chapterId":"concorrencia-profunda","aliases":[]},{"id":"term-94","term":"Mensageria","definition":"Comunicação assíncrona baseada no envio de mensagens por meio de um broker ou canal.","chapterId":"mensageria","aliases":[]},{"id":"term-95","term":"Kafka","definition":"Plataforma distribuída de streaming de eventos baseada em logs particionados e persistentes.","chapterId":"kafka","aliases":["Apache Kafka"]},{"id":"term-96","term":"Tópico Kafka","definition":"Fluxo nomeado de registros no Kafka, dividido em partições.","chapterId":"kafka","aliases":["topic"]},{"id":"term-97","term":"Partição","definition":"Unidade ordenada e paralelizável de armazenamento de registros dentro de um tópico Kafka.","chapterId":"kafka","aliases":["partition"]},{"id":"term-98","term":"Consumer group","definition":"Grupo de consumidores que divide entre si as partições de um tópico.","chapterId":"kafka","aliases":["grupo de consumidores"]},{"id":"term-99","term":"DLT","definition":"Dead-letter topic: tópico que recebe mensagens não processadas após a política de tentativas definida.","chapterId":"kafka-confiavel","aliases":["dead-letter topic"]},{"id":"term-100","term":"Outbox","definition":"Tabela gravada na mesma transação do domínio para que eventos pendentes sejam publicados de forma recuperável.","chapterId":"kafka-confiavel","aliases":["transactional outbox"]},{"id":"term-101","term":"DDD","definition":"Abordagem de modelagem que aproxima o software do domínio e da linguagem usada pelos especialistas.","chapterId":"ddd","aliases":["Domain-Driven Design"]},{"id":"term-102","term":"Agregado","definition":"Fronteira de consistência no DDD, controlada por uma raiz que protege suas invariantes.","chapterId":"ddd","aliases":["aggregate"]},{"id":"term-103","term":"CAP","definition":"Princípio segundo o qual, diante de uma partição de rede, um sistema distribuído escolhe entre consistência e disponibilidade.","chapterId":"sistemas-distribuidos","aliases":["teorema CAP"]},{"id":"term-104","term":"PACELC","definition":"Modelo que acrescenta ao CAP a troca entre latência e consistência quando não existe partição.","chapterId":"consistencia-distribuida","aliases":[]},{"id":"term-105","term":"Idempotência","definition":"Propriedade de uma operação que produz o mesmo efeito observável quando repetida com a mesma intenção.","chapterId":"sistemas-distribuidos","aliases":["idempotency"]},{"id":"term-106","term":"Event-driven","definition":"Estilo arquitetural no qual componentes publicam e reagem a eventos, reduzindo o acoplamento temporal.","chapterId":"event-driven-profundo","aliases":["arquitetura orientada a eventos"]},{"id":"term-107","term":"WebSocket","definition":"Protocolo de conexão persistente e bidirecional entre cliente e servidor.","chapterId":"websockets","aliases":["WebSockets"]},{"id":"term-108","term":"BFF","definition":"Backend for Frontend: backend dedicado às necessidades de uma interface, capaz de intermediar APIs, sessão e tokens.","chapterId":"frontend-aplicacao","aliases":["Backend for Frontend"]},{"id":"term-109","term":"ADR","definition":"Registro curto que documenta uma decisão arquitetural, seu contexto e suas consequências.","chapterId":"code-review-adr","aliases":["Architecture Decision Record"]},{"id":"term-110","term":"WebHook","definition":"Chamada HTTP enviada automaticamente quando um evento ocorre em outro sistema.","chapterId":"conectando-front-back","aliases":["webhook","webhooks"]}],"modules":[{"id":"orientation","title":"Orientacao e ambiente","summary":"Constrói a base necessária para orientacao e ambiente sem antecipar abstrações.","order":0,"prerequisiteModuleIds":[],"chapterIds":["terminal-shell-fundamentos","intro","primeiro-programa"],"englishLevel":0,"projectGuidance":"supported"},{"id":"programming-fundamentals","title":"Fundamentos de programacao","summary":"Constrói a base necessária para fundamentos de programacao sem antecipar abstrações.","order":1,"prerequisiteModuleIds":["orientation"],"chapterIds":["variaveis-tipos","operadores-expressoes","controle-fluxo","lacos-repeticao","arrays-matrizes","strings-wrapper","metodos-escopo","entrada-console","logica-programacao","mini-caixa-eletronico"],"englishLevel":0,"projectGuidance":"supported"},{"id":"oop-modeling","title":"POO, modelagem e associacoes","summary":"Constrói a base necessária para poo, modelagem e associacoes sem antecipar abstrações.","order":2,"prerequisiteModuleIds":["programming-fundamentals"],"chapterIds":["classes","atributos","construtores","encapsulamento","static","heranca","polimorfismo","abstracao","interfaces","pilares-profundo","mini-biblioteca-cli"],"englishLevel":0,"projectGuidance":"supported"},{"id":"java-core","title":"Java essencial e programacao funcional","summary":"Constrói a base necessária para java essencial e programacao funcional sem antecipar abstrações.","order":3,"prerequisiteModuleIds":["oop-modeling"],"chapterIds":["pacotes","excecoes","colecoes","generics","streams","javamoderno","jvm-profundo","java-21-profundo"],"englishLevel":1,"projectGuidance":"guided"},{"id":"io-cli-serialization","title":"I/O, serializacao e aplicacoes CLI","summary":"Constrói a base necessária para i/o, serializacao e aplicacoes cli sem antecipar abstrações.","order":4,"prerequisiteModuleIds":["java-core"],"chapterIds":["java-io","mini-analisador-vendas","json","mini-importador-pedidos"],"englishLevel":1,"projectGuidance":"guided"},{"id":"algorithms-data-structures","title":"Algoritmos e estruturas de dados","summary":"Constrói a base necessária para algoritmos e estruturas de dados sem antecipar abstrações.","order":5,"prerequisiteModuleIds":["java-core"],"chapterIds":["big-o","recursao","ordenacao-busca","algoritmos-praticos","estruturas-avancadas"],"englishLevel":1,"projectGuidance":"guided"},{"id":"testing-engineering","title":"Build, Git, debugging e testes fundamentais","summary":"Constrói a base necessária para build, git, debugging e testes fundamentais sem antecipar abstrações.","order":6,"prerequisiteModuleIds":["java-core"],"chapterIds":["git","build","debugging","logging","testes","mockito","mini-reservas-testadas","projeto"],"englishLevel":1,"projectGuidance":"guided"},{"id":"http-api-clients","title":"HTTP, JSON e consumo de APIs com Java","summary":"Constrói a base necessária para http, json e consumo de apis com java sem antecipar abstrações.","order":7,"prerequisiteModuleIds":["io-cli-serialization","testing-engineering"],"chapterIds":["http"],"englishLevel":1,"projectGuidance":"guided"},{"id":"relational-data-jdbc","title":"SQL, PostgreSQL, JDBC e transacoes","summary":"Constrói a base necessária para sql, postgresql, jdbc e transacoes sem antecipar abstrações.","order":8,"prerequisiteModuleIds":["io-cli-serialization","testing-engineering"],"chapterIds":["sql","postgres","postgres-concorrencia","migrations","jdbc","mini-financas-jdbc"],"englishLevel":1,"projectGuidance":"guided"},{"id":"application-design","title":"Design de aplicacao antes de frameworks","summary":"Constrói a base necessária para design de aplicacao antes de frameworks sem antecipar abstrações.","order":9,"prerequisiteModuleIds":["oop-modeling","testing-engineering"],"chapterIds":["anotacoes","solid","di","padroes","clean-code","projetospring","di-ioc-profundo","arquitetura-software","ddd"],"englishLevel":1,"projectGuidance":"bounded"},{"id":"spring-api","title":"Spring e primeira API completa","summary":"Constrói a base necessária para spring e primeira api completa sem antecipar abstrações.","order":10,"prerequisiteModuleIds":["http-api-clients","relational-data-jdbc","application-design"],"chapterIds":["spring-core","spring-boot-fundamentos","devtools","lombok","arquitetura-api-rest","spring-mvc","spring-jpa","jpa-transacoes","dto-mapping","validacao-erros","paginacao-swagger","api-design-avancado","n-mais-1","cors-rate-limit","mini-helpdesk-api"],"englishLevel":2,"projectGuidance":"bounded"},{"id":"api-security-quality","title":"Contratos, seguranca e qualidade de APIs","summary":"Constrói a base necessária para contratos, seguranca e qualidade de apis sem antecipar abstrações.","order":11,"prerequisiteModuleIds":["spring-api"],"chapterIds":["autenticacao-conceitos","spring-security","security-oidc","https","mini-auth-rbac","auth-front-ts","frontend-aplicacao"],"englishLevel":2,"projectGuidance":"bounded"},{"id":"containers-integration-data","title":"Containers, testes de integracao e dados avancados","summary":"Constrói a base necessária para containers, testes de integracao e dados avancados sem antecipar abstrações.","order":12,"prerequisiteModuleIds":["spring-api","testing-engineering"],"chapterIds":["docker-conceitos","dockerfile","compose","testcontainers","estrategia-testes","nosql","mongodb","redis","nosql-operacional","hospedagem-db"],"englishLevel":2,"projectGuidance":"bounded"},{"id":"concurrency-network-tui","title":"Concorrencia, redes e interfaces de terminal","summary":"Constrói a base necessária para concorrencia, redes e interfaces de terminal sem antecipar abstrações.","order":13,"prerequisiteModuleIds":["io-cli-serialization","testing-engineering"],"chapterIds":["threads","concorrencia-profunda","websockets"],"englishLevel":2,"projectGuidance":"bounded"},{"id":"production-delivery","title":"Producao, CI/CD e operacao","summary":"Constrói a base necessária para producao, ci/cd e operacao sem antecipar abstrações.","order":14,"prerequisiteModuleIds":["api-security-quality","containers-integration-data"],"chapterIds":["secrets","cicd","praticas-producao","hardening-producao","deploy-backend","spring-profiles","mini-deploy-observavel","deploy-frontend"],"englishLevel":2,"projectGuidance":"bounded"},{"id":"synchronous-integration","title":"Integracao sincrona entre servicos","summary":"Constrói a base necessária para integracao sincrona entre servicos sem antecipar abstrações.","order":15,"prerequisiteModuleIds":["api-security-quality","concurrency-network-tui"],"chapterIds":["webclient","ddd-estrategico","conectando-front-back"],"englishLevel":2,"projectGuidance":"independent"},{"id":"resilience-observability","title":"Falhas, resiliencia e observabilidade","summary":"Constrói a base necessária para falhas, resiliencia e observabilidade sem antecipar abstrações.","order":16,"prerequisiteModuleIds":["synchronous-integration","production-delivery"],"chapterIds":["resilience","spring-aop","spring-cache","actuator","observabilidade-pratica"],"englishLevel":3,"projectGuidance":"independent"},{"id":"messaging-eda","title":"Mensageria e arquitetura orientada a eventos","summary":"Constrói a base necessária para mensageria e arquitetura orientada a eventos sem antecipar abstrações.","order":17,"prerequisiteModuleIds":["synchronous-integration","relational-data-jdbc","containers-integration-data","concurrency-network-tui"],"chapterIds":["mensageria","kafka","spring-kafka","kafka-confiavel","event-driven-profundo","testes-integracao-avancados"],"englishLevel":3,"projectGuidance":"independent"},{"id":"distributed-consistency","title":"Consistencia e sistemas distribuidos","summary":"Constrói a base necessária para consistencia e sistemas distribuidos sem antecipar abstrações.","order":18,"prerequisiteModuleIds":["messaging-eda","resilience-observability"],"chapterIds":["sistemas-distribuidos","consistencia-distribuida","mini-pedidos-eventos"],"englishLevel":3,"projectGuidance":"independent"},{"id":"professional-final","title":"Trabalho em equipe e projeto integrador","summary":"Constrói a base necessária para trabalho em equipe e projeto integrador sem antecipar abstrações.","order":19,"prerequisiteModuleIds":["distributed-consistency","production-delivery"],"chapterIds":["code-review-adr","agile","mapa-sistema","projeto-integrador","quiz"],"englishLevel":3,"projectGuidance":"independent"}],"chapters":[{"id":"terminal-shell-fundamentos","moduleId":"orientation","order":0,"title":"Terminal e shell — antes de instalar qualquer coisa","summary":"O resto do curso vai pedir que você digite comandos em uma janela preta várias vezes por capítulo — para compilar com javac, rodar com java, usar Maven, Git, Docker. Nenhum desses capítulos vai parar para explicar o que é essa janela. Este capítulo existe para fechar essa lacuna antes que ela vire um obstáculo silencioso: você vai aprender a navegar, manipular arquivos, entender o que acontece quando aperta Enter, e reconhecer os riscos reais de comandos destrutivos.","objectives":["Diferenciar terminal, terminal emulator, shell e CLI","Navegar e manipular arquivos usando pwd, ls, cd, mkdir, cp, mv, rm","Diagnosticar problemas de PATH com command -v","Compor comandos com pipes, redirecionamento e exit status"],"whyItExists":"Do capítulo de introdução em diante, o curso pede javac, java, git, docker e outras ferramentas de linha de comando sem nunca ensinar o terminal em si. Este capítulo fecha essa lacuna antes que ela vire um obstáculo silencioso.","prerequisiteChapterIds":[],"conceptIds":["terminal-terminal-emulator-e-shell-nao-sao-a-mesma-coisa","onde-voce-esta-diretorio-atual-home-e-caminhos","arquivos-criar-copiar-mover-apagar-ler","buscando-coisas-grep-e-find","path-como-o-shell-acha-o-programa-que-voce-pediu","quoting-por-que-espacos-quebram-comandos","redirecionamento-mandando-saida-para-um-arquivo","pipes-conectando-um-comando-ao-outro","composicao-de-comandos-e-exit-status","variaveis-de-ambiente","permissoes-quem-pode-executar-o-que","processos-em-execucao"],"introducedConceptIds":["terminal-vs-shell","working-directory-path","path-env-var-resolution","pipe-redirection-exit-status"],"usedConceptIds":[],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"terminal-shell-intuition","type":"intuition","authorship":"authored","title":"O terminal só desenha; o shell decide","body":"A janela preta (terminal emulator) apenas envia o que você digita para dentro e desenha o que volta. Quem interpreta comandos, expande caminhos e decide o que executar é o shell -- um programa à parte, que pode ser trocado (bash, zsh, fish, PowerShell) sem trocar a janela.","analogyLimit":"Pense no terminal como o telefone e o shell como a pessoa do outro lado da linha: o telefone não entende o que você fala, só transmite."},{"id":"terminal-shell-fundamentos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div>\n        <div class=\"meta-item\">Pré-requisito: <span class=\"prereq-none\">nenhum</span></div>\n      </div>","fidelityText":"Dificuldade: Iniciante Pré-requisito: nenhum"},{"id":"terminal-shell-fundamentos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O resto do curso vai pedir que você digite comandos em uma janela preta várias vezes por capítulo — para compilar com <code>javac</code>, rodar com <code>java</code>, usar Maven, Git, Docker. Nenhum desses capítulos vai parar para explicar <em>o que é</em> essa janela. Este capítulo existe para fechar essa lacuna antes que ela vire um obstáculo silencioso: você vai aprender a navegar, manipular arquivos, entender o que acontece quando aperta Enter, e reconhecer os riscos reais de comandos destrutivos.</p>","fidelityText":"O resto do curso vai pedir que você digite comandos em uma janela preta várias vezes por capítulo — para compilar com javac, rodar com java, usar Maven, Git, Docker. Nenhum desses capítulos vai parar para explicar o que é essa janela. Este capítulo existe para fechar essa lacuna antes que ela vire um obstáculo silencioso: você vai aprender a navegar, manipular arquivos, entender o que acontece quando aperta Enter, e reconhecer os riscos reais de comandos destrutivos."},{"id":"terminal-shell-fundamentos-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Terminal, terminal emulator e shell não são a mesma coisa</h2>","fidelityText":"Terminal, terminal emulator e shell não são a mesma coisa"},{"id":"terminal-shell-fundamentos-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Termo</th><th>O que é de fato</th></tr>\n        <tr><td><b>Terminal emulator</b></td><td>O programa com janela (iTerm, Windows Terminal, GNOME Terminal, o terminal integrado da sua IDE). Só desenha texto na tela e envia o que você digita para dentro.</td></tr>\n        <tr><td><b>Shell</b></td><td>O programa que de fato interpreta o que você digita e decide o que fazer — é ele quem entende <code>cd</code>, expande <code>*</code>, resolve <code>$PATH</code>. Exemplos: <code>bash</code>, <code>zsh</code>, <code>fish</code>, <code>PowerShell</code>, <code>cmd.exe</code>.</td></tr>\n        <tr><td><b>CLI</b></td><td><em>Command-line interface</em> — qualquer programa (incluindo o shell) que você opera digitando comandos, em vez de clicar.</td></tr>\n        <tr><td><b>Comando</b></td><td>O nome do que você quer executar (<code>ls</code>, <code>git</code>, <code>java</code>) — pode ser um programa instalado ou um <em>builtin</em> do próprio shell.</td></tr>\n        <tr><td><b>Argumento / opção (flag)</b></td><td>Informação extra depois do comando. <code>ls -la meu-projeto</code>: <code>-la</code> é uma opção (modifica o comportamento), <code>meu-projeto</code> é um argumento (o alvo).</td></tr>\n        <tr><td><b>Processo</b></td><td>Uma execução em andamento de um programa — cada comando que roda até terminar é um processo, com seu próprio código de saída (mais adiante).</td></tr>\n      </tbody></table>","fidelityText":"TermoO que é de fato Terminal emulatorO programa com janela (iTerm, Windows Terminal, GNOME Terminal, o terminal integrado da sua IDE). Só desenha texto na tela e envia o que você digita para dentro. ShellO programa que de fato interpreta o que você digita e decide o que fazer — é ele quem entende cd, expande *, resolve $PATH. Exemplos: bash, zsh, fish, PowerShell, cmd.exe. CLICommand-line interface — qualquer programa (incluindo o shell) que você opera digitando comandos, em vez de clicar. ComandoO nome do que você quer executar (ls, git, java) — pode ser um programa instalado ou um builtin do próprio shell. Argumento / opção (flag)Informação extra depois do comando. ls -la meu-projeto: -la é uma opção (modifica o comportamento), meu-projeto é um argumento (o alvo). ProcessoUma execução em andamento de um programa — cada comando que roda até terminar é um processo, com seu próprio código de saída (mais adiante)."},{"id":"terminal-shell-fundamentos-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Um erro comum: achar que \"terminal\" e \"shell\" são sinônimos, ou que existe só um shell (\"o\" terminal). Você pode abrir o mesmo terminal emulator hoje com <code>bash</code> e amanhã com <code>zsh</code> — a janela é a mesma, o intérprete por trás muda, e algumas sintaxes (como quoting e scripts) não são 100% intercambiáveis entre eles.</div>","fidelityText":"Um erro comum: achar que \"terminal\" e \"shell\" são sinônimos, ou que existe só um shell (\"o\" terminal). Você pode abrir o mesmo terminal emulator hoje com bash e amanhã com zsh — a janela é a mesma, o intérprete por trás muda, e algumas sintaxes (como quoting e scripts) não são 100% intercambiáveis entre eles."},{"id":"terminal-shell-fundamentos-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Onde você está: diretório atual, home e caminhos</h2>","fidelityText":"Onde você está: diretório atual, home e caminhos"},{"id":"terminal-shell-fundamentos-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Todo shell mantém um <strong>diretório atual</strong> (também chamado <em>working directory</em>) — é a pasta \"onde você está\" quando roda um comando sem caminho explícito. <strong>Home</strong> é a pasta pessoal do seu usuário (<code>~</code> é um atalho universal para ela). Um <strong>caminho absoluto</strong> começa da raiz do sistema de arquivos (<code>/home/ana/projetos</code> no Linux/macOS, <code>C:\\Users\\Ana\\projetos</code> no Windows); um <strong>caminho relativo</strong> é resolvido a partir do diretório atual (<code>projetos/exercicios</code>, <code>../outra-pasta</code>).</p>","fidelityText":"Todo shell mantém um diretório atual (também chamado working directory) — é a pasta \"onde você está\" quando roda um comando sem caminho explícito. Home é a pasta pessoal do seu usuário (~ é um atalho universal para ela). Um caminho absoluto começa da raiz do sistema de arquivos (/home/ana/projetos no Linux/macOS, C:\\Users\\Ana\\projetos no Windows); um caminho relativo é resolvido a partir do diretório atual (projetos/exercicios, ../outra-pasta)."},{"id":"terminal-shell-fundamentos-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"pwd          # print working directory -- mostra o caminho absoluto de onde você está agora\nls           # lista o conteúdo do diretório atual\nls -la       # -l: formato detalhado (permissões, dono, tamanho); -a: inclui arquivos ocultos (começam com .)\ncd projetos  # entra na subpasta \"projetos\" (caminho relativo)\ncd ..        # sobe um nível -- \"..\" sempre significa \"pasta pai\"\ncd ~         # volta direto para a home, de qualquer lugar\nmkdir estudos           # cria uma pasta\nmkdir -p a/b/c           # -p: cria a cadeia inteira de pastas intermediárias que ainda não existem","fidelityText":"pwd # print working directory -- mostra o caminho absoluto de onde você está agora ls # lista o conteúdo do diretório atual ls -la # -l: formato detalhado (permissões, dono, tamanho); -a: inclui arquivos ocultos (começam com .) cd projetos # entra na subpasta \"projetos\" (caminho relativo) cd .. # sobe um nível -- \"..\" sempre significa \"pasta pai\" cd ~ # volta direto para a home, de qualquer lugar mkdir estudos # cria uma pasta mkdir -p a/b/c # -p: cria a cadeia inteira de pastas intermediárias que ainda não existem","highlightedHtml":"pwd          <span class=\"com\"># print working directory -- mostra o caminho absoluto de onde você está agora</span>\nls           <span class=\"com\"># lista o conteúdo do diretório atual</span>\nls -la       <span class=\"com\"># -l: formato detalhado (permissões, dono, tamanho); -a: inclui arquivos ocultos (começam com .)</span>\ncd projetos  <span class=\"com\"># entra na subpasta \"projetos\" (caminho relativo)</span>\ncd ..        <span class=\"com\"># sobe um nível -- \"..\" sempre significa \"pasta pai\"</span>\ncd ~         <span class=\"com\"># volta direto para a home, de qualquer lugar</span>\nmkdir estudos           <span class=\"com\"># cria uma pasta</span>\nmkdir -p a/b/c           <span class=\"com\"># -p: cria a cadeia inteira de pastas intermediárias que ainda não existem</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["pwd mostra o caminho absoluto do diretório atual antes de qualquer navegação relativa."],"commonMistakes":["Rodar cd com caminho relativo sem antes confirmar onde está com pwd"]},{"id":"terminal-shell-fundamentos-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Arquivos: criar, copiar, mover, apagar, ler</h2>","fidelityText":"Arquivos: criar, copiar, mover, apagar, ler"},{"id":"terminal-shell-fundamentos-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"touch notas.txt        # cria um arquivo vazio (ou só atualiza a data de modificação, se já existir)\ncp notas.txt copia.txt # copia -- o original continua existindo\nmv copia.txt final.txt # move OU renomeia -- no shell, renomear é só \"mover para o mesmo lugar com outro nome\"\nrm final.txt           # remove um arquivo -- não existe lixeira aqui\nrmdir pasta-vazia      # remove uma pasta, só se estiver vazia\ncat notas.txt          # despeja o conteúdo inteiro no terminal\nless notas.txt         # abre em modo paginado/navegável -- melhor para arquivos grandes; \"q\" para sair\nhead -n 5 notas.txt    # só as 5 primeiras linhas\ntail -n 5 notas.txt    # só as 5 últimas linhas","fidelityText":"touch notas.txt # cria um arquivo vazio (ou só atualiza a data de modificação, se já existir) cp notas.txt copia.txt # copia -- o original continua existindo mv copia.txt final.txt # move OU renomeia -- no shell, renomear é só \"mover para o mesmo lugar com outro nome\" rm final.txt # remove um arquivo -- não existe lixeira aqui rmdir pasta-vazia # remove uma pasta, só se estiver vazia cat notas.txt # despeja o conteúdo inteiro no terminal less notas.txt # abre em modo paginado/navegável -- melhor para arquivos grandes; \"q\" para sair head -n 5 notas.txt # só as 5 primeiras linhas tail -n 5 notas.txt # só as 5 últimas linhas","highlightedHtml":"touch grades.txt        <span class=\"com\"># cria um arquivo vazio (ou só atualiza a data de modificação, se já existir)</span>\ncp grades.txt copy.txt <span class=\"com\"># copia -- o original continua existindo</span>\nmv copy.txt final.txt <span class=\"com\"># move OU renomeia -- no shell, renomear é só \"mover para o mesmo lugar com outro nome\"</span>\nrm final.txt           <span class=\"com\"># remove um arquivo -- não existe lixeira aqui</span>\nrmdir folder-empty      <span class=\"com\"># remove uma pasta, só se estiver vazia</span>\ncat grades.txt          <span class=\"com\"># despeja o conteúdo inteiro no terminal</span>\nless grades.txt         <span class=\"com\"># abre em modo paginado/navegável -- melhor para arquivos grandes; \"q\" para sair</span>\nhead -n 5 grades.txt    <span class=\"com\"># só as 5 primeiras linhas</span>\ntail -n 5 grades.txt    <span class=\"com\"># só as 5 últimas linhas</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["touch cria um arquivo vazio (ou só atualiza a data de modificação); cp/mv/rm operam sobre arquivos existentes.","rm não tem desfazer -- diferente de apagar pela interface gráfica."],"commonMistakes":["Rodar rm -rf sem conferir o caminho com pwd/ls antes"]},{"id":"terminal-shell-fundamentos-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>rm</code> não pergunta e não tem desfazer.</b> Diferente de apagar um arquivo pela interface gráfica (que geralmente vai para uma lixeira), <code>rm</code> remove de verdade, imediatamente. <code>rm -rf alguma-pasta</code> é ainda mais perigoso: <code>-r</code> torna a remoção recursiva (apaga o conteúdo inteiro de uma pasta) e <code>-f</code> força, sem pedir confirmação nem avisar sobre erros. Nunca rode <code>rm -rf</code> copiando de um tutorial sem entender exatamente qual caminho está sendo apagado — e nunca rode como usuário administrador \"só para garantir que funciona\". Confira o caminho com <code>pwd</code> e <code>ls</code> antes de apagar qualquer coisa recursivamente.</div>","fidelityText":"rm não pergunta e não tem desfazer. Diferente de apagar um arquivo pela interface gráfica (que geralmente vai para uma lixeira), rm remove de verdade, imediatamente. rm -rf alguma-pasta é ainda mais perigoso: -r torna a remoção recursiva (apaga o conteúdo inteiro de uma pasta) e -f força, sem pedir confirmação nem avisar sobre erros. Nunca rode rm -rf copiando de um tutorial sem entender exatamente qual caminho está sendo apagado — e nunca rode como usuário administrador \"só para garantir que funciona\". Confira o caminho com pwd e ls antes de apagar qualquer coisa recursivamente."},{"id":"terminal-shell-fundamentos-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Buscando coisas: grep e find</h2>","fidelityText":"Buscando coisas: grep e find"},{"id":"terminal-shell-fundamentos-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"grep \"error\" log.txt          # mostra as linhas de log.txt que contêm \"erro\"\ngrep -r \"TODO\" src/          # -r: procura recursivamente dentro da pasta src/\nfind . -name \"*.java\"        # procura, a partir do diretório atual (.), arquivos cujo nome termina em .java","fidelityText":"grep \"erro\" log.txt # mostra as linhas de log.txt que contêm \"erro\" grep -r \"TODO\" src/ # -r: procura recursivamente dentro da pasta src/ find . -name \"*.java\" # procura, a partir do diretório atual (.), arquivos cujo nome termina em .java","highlightedHtml":"grep \"error\" log.txt          <span class=\"com\"># mostra as linhas de log.txt que contêm \"erro\"</span>\ngrep -r \"TODO\" src/          <span class=\"com\"># -r: procura recursivamente dentro da pasta src/</span>\nfind . -name \"*.java\"        <span class=\"com\"># procura, a partir do diretório atual (.), arquivos cujo nome termina em .java</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["grep busca dentro do conteúdo de arquivos; find busca arquivos/pastas pelo nome -- são complementares."],"commonMistakes":["Usar grep quando o objetivo é achar um arquivo pelo nome (isso é find)"]},{"id":"terminal-shell-fundamentos-content-14","type":"html","authorship":"legacy-preserved","html":"<p><code>grep</code> busca <em>dentro</em> do conteúdo de arquivos; <code>find</code> busca arquivos e pastas pelo <em>nome/atributos</em> na árvore de diretórios — são complementares, não concorrentes. Ferramentas mais modernas como <code>ripgrep</code> (<code>rg</code>) fazem o mesmo trabalho de <code>grep -r</code> com mais velocidade e melhores padrões; são um complemento útil de instalar depois, não um pré-requisito deste curso.</p>","fidelityText":"grep busca dentro do conteúdo de arquivos; find busca arquivos e pastas pelo nome/atributos na árvore de diretórios — são complementares, não concorrentes. Ferramentas mais modernas como ripgrep (rg) fazem o mesmo trabalho de grep -r com mais velocidade e melhores padrões; são um complemento útil de instalar depois, não um pré-requisito deste curso."},{"id":"terminal-shell-fundamentos-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>PATH: como o shell acha o programa que você pediu</h2>","fidelityText":"PATH: como o shell acha o programa que você pediu"},{"id":"terminal-shell-fundamentos-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Quando você digita <code>java</code>, o shell não sabe magicamente onde esse programa está — ele procura, em ordem, em cada pasta listada na variável de ambiente <code>PATH</code>, até achar um executável com esse nome. Se nenhuma pasta do <code>PATH</code> tiver <code>java</code>, o shell responde algo como <code>command not found</code> — quase sempre um problema de instalação ou de <code>PATH</code> mal configurado, não um bug do Java.</p>","fidelityText":"Quando você digita java, o shell não sabe magicamente onde esse programa está — ele procura, em ordem, em cada pasta listada na variável de ambiente PATH, até achar um executável com esse nome. Se nenhuma pasta do PATH tiver java, o shell responde algo como command not found — quase sempre um problema de instalação ou de PATH mal configurado, não um bug do Java."},{"id":"terminal-shell-fundamentos-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"command -v java     # mostra o caminho completo do executável \"java\" que o shell usaria\ncommand -v javac\necho $PATH          # lista as pastas onde o shell procura (Linux/macOS/bash-like)","fidelityText":"command -v java # mostra o caminho completo do executável \"java\" que o shell usaria command -v javac echo $PATH # lista as pastas onde o shell procura (Linux/macOS/bash-like)","highlightedHtml":"command -v java     <span class=\"com\"># mostra o caminho completo do executável \"java\" que o shell usaria</span>\ncommand -v javac\necho $PATH          <span class=\"com\"># lista as pastas onde o shell procura (Linux/macOS/bash-like)</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["command -v segue o mesmo mecanismo de busca no PATH que o shell usa para qualquer comando digitado."],"commonMistakes":["Usar which esperando comportamento idêntico em todo shell/sistema"]},{"id":"terminal-shell-fundamentos-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>command -v</code> é a forma recomendada pelo padrão POSIX para essa pergunta, e funciona de modo previsível em qualquer shell compatível — ela também reconhece corretamente comandos <em>builtin</em> (embutidos no próprio shell, sem executável separado), o que <code>which</code> nem sempre faz de forma consistente entre sistemas. <code>which</code> ainda é comum e você vai vê-lo em tutoriais, mas prefira <code>command -v</code> quando o objetivo é um script confiável.</div>","fidelityText":"command -v é a forma recomendada pelo padrão POSIX para essa pergunta, e funciona de modo previsível em qualquer shell compatível — ela também reconhece corretamente comandos builtin (embutidos no próprio shell, sem executável separado), o que which nem sempre faz de forma consistente entre sistemas. which ainda é comum e você vai vê-lo em tutoriais, mas prefira command -v quando o objetivo é um script confiável."},{"id":"terminal-shell-fundamentos-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Quoting: por que espaços quebram comandos</h2>","fidelityText":"Quoting: por que espaços quebram comandos"},{"id":"terminal-shell-fundamentos-content-20","type":"html","authorship":"legacy-preserved","html":"<p>O shell separa o que você digita em pedaços usando espaço como delimitador — então <code>cd Meus Documentos</code> vira dois argumentos (<code>Meus</code> e <code>Documentos</code>), não um caminho com espaço. Aspas dizem ao shell \"trate isto como um único pedaço\":</p>","fidelityText":"O shell separa o que você digita em pedaços usando espaço como delimitador — então cd Meus Documentos vira dois argumentos (Meus e Documentos), não um caminho com espaço. Aspas dizem ao shell \"trate isto como um único pedaço\":"},{"id":"terminal-shell-fundamentos-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"cd \"Meus Documentos\"     # aspas duplas -- trata o espaço como parte do nome, não como separador\necho 'text literal $HOME' # aspas simples -- normalmente impedem expansão de variáveis (varia por shell)\necho \"user: $HOME\"      # aspas duplas -- normalmente permitem expansão de variáveis (varia por shell)","fidelityText":"cd \"Meus Documentos\" # aspas duplas -- trata o espaço como parte do nome, não como separador echo 'texto literal $HOME' # aspas simples -- normalmente impedem expansão de variáveis (varia por shell) echo \"usuário: $HOME\" # aspas duplas -- normalmente permitem expansão de variáveis (varia por shell)","highlightedHtml":"cd \"Meus Documentos\"     <span class=\"com\"># aspas duplas -- trata o espaço como parte do nome, não como separador</span>\necho 'text literal $HOME' <span class=\"com\"># aspas simples -- normalmente impedem expansão de variáveis (varia por shell)</span>\necho \"user: $HOME\"      <span class=\"com\"># aspas duplas -- normalmente permitem expansão de variáveis (varia por shell)</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["Aspas agrupam texto com espaço como um único argumento -- sem elas, o shell separa por espaço."],"commonMistakes":["Esquecer aspas em caminhos com espaço e obter dois argumentos em vez de um"]},{"id":"terminal-shell-fundamentos-content-22","type":"html","authorship":"legacy-preserved","html":"<p>As regras exatas de quoting (o que expande dentro de aspas simples vs. duplas, como escapar um caractere especial) variam entre <code>bash</code>/<code>zsh</code>, <code>fish</code> e <code>PowerShell</code> — o princípio (\"agrupar texto com espaço como se fosse um só argumento\") é portável; a sintaxe exata não é.</p>","fidelityText":"As regras exatas de quoting (o que expande dentro de aspas simples vs. duplas, como escapar um caractere especial) variam entre bash/zsh, fish e PowerShell — o princípio (\"agrupar texto com espaço como se fosse um só argumento\") é portável; a sintaxe exata não é."},{"id":"terminal-shell-fundamentos-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>Redirecionamento: mandando saída para um arquivo</h2>","fidelityText":"Redirecionamento: mandando saída para um arquivo"},{"id":"terminal-shell-fundamentos-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"java Main > saida.txt      # manda o stdout (saída normal) para dentro do arquivo, sobrescrevendo\njava Main >> saida.txt     # mesma coisa, mas ANEXA ao final em vez de sobrescrever\njava Main < entrada.txt    # o conteúdo de entrada.txt vira o stdin (entrada) do programa, como se você tivesse digitado\njava Main 2> errors.txt     # manda só o stderr (saída de erro) para o arquivo\njava Main > saida.txt 2>&1 # manda stdout E stderr para o MESMO arquivo","fidelityText":"java Main > saida.txt # manda o stdout (saída normal) para dentro do arquivo, sobrescrevendo java Main >> saida.txt # mesma coisa, mas ANEXA ao final em vez de sobrescrever java Main < entrada.txt # o conteúdo de entrada.txt vira o stdin (entrada) do programa, como se você tivesse digitado java Main 2> erros.txt # manda só o stderr (saída de erro) para o arquivo java Main > saida.txt 2>&1 # manda stdout E stderr para o MESMO arquivo","highlightedHtml":"java Main &gt; saida.txt      <span class=\"com\"># manda o stdout (saída normal) para dentro do arquivo, sobrescrevendo</span>\njava Main &gt;&gt; saida.txt     <span class=\"com\"># mesma coisa, mas ANEXA ao final em vez de sobrescrever</span>\njava Main &lt; entrada.txt    <span class=\"com\"># o conteúdo de entrada.txt vira o stdin (entrada) do programa, como se você tivesse digitado</span>\njava Main 2&gt; errors.txt     <span class=\"com\"># manda só o stderr (saída de erro) para o arquivo</span>\njava Main &gt; saida.txt 2&gt;&amp;1 <span class=\"com\"># manda stdout E stderr para o MESMO arquivo</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["> sobrescreve o arquivo de destino; >> anexa ao final -- a diferença é destrutiva se trocada por engano."],"commonMistakes":["Usar > quando a intenção era >> e perder conteúdo anterior"]},{"id":"terminal-shell-fundamentos-content-25","type":"html","authorship":"legacy-preserved","html":"<p>Todo processo tem três canais padrão: <strong>stdin</strong> (entrada), <strong>stdout</strong> (saída normal) e <strong>stderr</strong> (saída de erro) — os mesmos três canais que a Process API do Java (capítulo dedicado, mais adiante) expõe para processos filhos que você mesmo iniciar do seu código.</p>","fidelityText":"Todo processo tem três canais padrão: stdin (entrada), stdout (saída normal) e stderr (saída de erro) — os mesmos três canais que a Process API do Java (capítulo dedicado, mais adiante) expõe para processos filhos que você mesmo iniciar do seu código."},{"id":"terminal-shell-fundamentos-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>Pipes: conectando um comando ao outro</h2>","fidelityText":"Pipes: conectando um comando ao outro"},{"id":"terminal-shell-fundamentos-code-27","type":"code","authorship":"legacy-preserved","language":"java","source":"cat log.txt | grep \"error\" | head -n 10","fidelityText":"cat log.txt | grep \"erro\" | head -n 10","highlightedHtml":"cat log.txt | grep \"error\" | head -n 10","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["| conecta o stdout do processo à esquerda ao stdin do processo à direita, sem arquivo intermediário."],"commonMistakes":["Achar que os dois lados do pipe rodam de forma totalmente independente, sem relação de dados"]},{"id":"terminal-shell-fundamentos-content-28","type":"html","authorship":"legacy-preserved","html":"<p>O caractere <code>|</code> (pipe) conecta o <strong>stdout do processo à esquerda</strong> diretamente ao <strong>stdin do processo à direita</strong> — sem passar por um arquivo intermediário. No exemplo, <code>cat</code> despeja o arquivo, <code>grep</code> filtra as linhas com \"erro\" recebendo isso como entrada, e <code>head</code> pega só as 10 primeiras dessas linhas filtradas. Cada comando não sabe que está em um pipe — ele só lê do stdin e escreve no stdout normalmente; é o shell quem conecta os dois.</p>","fidelityText":"O caractere | (pipe) conecta o stdout do processo à esquerda diretamente ao stdin do processo à direita — sem passar por um arquivo intermediário. No exemplo, cat despeja o arquivo, grep filtra as linhas com \"erro\" recebendo isso como entrada, e head pega só as 10 primeiras dessas linhas filtradas. Cada comando não sabe que está em um pipe — ele só lê do stdin e escreve no stdout normalmente; é o shell quem conecta os dois."},{"id":"terminal-shell-fundamentos-content-29","type":"html","authorship":"legacy-preserved","html":"<h2>Composição de comandos e exit status</h2>","fidelityText":"Composição de comandos e exit status"},{"id":"terminal-shell-fundamentos-code-30","type":"code","authorship":"legacy-preserved","language":"java","source":"javac Main.java ; java Main    # \";\" -- roda o segundo comando DEPOIS do primeiro, não importa o resultado\njavac Main.java && java Main   # \"&&\" -- só roda o segundo se o primeiro teve SUCESSO\njavac Main.java || echo \"failed the compilation\" # \"||\" -- só roda o segundo se o primeiro FALHOU","fidelityText":"javac Main.java ; java Main # \";\" -- roda o segundo comando DEPOIS do primeiro, não importa o resultado javac Main.java && java Main # \"&&\" -- só roda o segundo se o primeiro teve SUCESSO javac Main.java || echo \"falhou a compilação\" # \"||\" -- só roda o segundo se o primeiro FALHOU","highlightedHtml":"javac Main.java ; java Main    <span class=\"com\"># \";\" -- roda o segundo comando DEPOIS do primeiro, não importa o resultado</span>\njavac Main.java &amp;&amp; java Main   <span class=\"com\"># \"&amp;&amp;\" -- só roda o segundo se o primeiro teve SUCESSO</span>\njavac Main.java || echo \"failed the compilation\" <span class=\"com\"># \"||\" -- só roda o segundo se o primeiro FALHOU</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":[";, && e || compõem comandos com semânticas diferentes baseadas no exit status do comando anterior."],"commonMistakes":["Usar ; quando a intenção era só continuar se o comando anterior desse certo (isso é &&)"]},{"id":"terminal-shell-fundamentos-content-31","type":"html","authorship":"legacy-preserved","html":"<p>Essa composição depende do <strong>exit status</strong> (código de saída) de cada processo: por convenção universal em programas de linha de comando, <code>0</code> significa sucesso, e qualquer valor diferente de <code>0</code> significa falha — o significado exato de cada código diferente de zero é definido pelo próprio programa. Guarde essa ideia: o método <code>Process.exitValue()</code> da Process API, mais adiante no curso, expõe exatamente esse mesmo número para processos que você iniciar via Java.</p>","fidelityText":"Essa composição depende do exit status (código de saída) de cada processo: por convenção universal em programas de linha de comando, 0 significa sucesso, e qualquer valor diferente de 0 significa falha — o significado exato de cada código diferente de zero é definido pelo próprio programa. Guarde essa ideia: o método Process.exitValue() da Process API, mais adiante no curso, expõe exatamente esse mesmo número para processos que você iniciar via Java."},{"id":"terminal-shell-fundamentos-content-32","type":"html","authorship":"legacy-preserved","html":"<h2>Variáveis de ambiente</h2>","fidelityText":"Variáveis de ambiente"},{"id":"terminal-shell-fundamentos-content-33","type":"html","authorship":"legacy-preserved","html":"<p><code>PATH</code> e <code>HOME</code> são exemplos de <strong>variáveis de ambiente</strong> — pares nome/valor que o sistema operacional e os programas conseguem ler. Você já viu <code>echo $PATH</code> acima; definir uma variável temporariamente (só para a sessão atual do terminal) segue uma sintaxe que muda por shell (em bash/zsh: <code>export NOME=valor</code>). Este curso não é um curso de Bash — o que importa aqui é entender que ferramentas como Maven, Docker e o próprio <code>java</code> leem configuração do ambiente, não decorar toda a sintaxe de shell scripting.</p>","fidelityText":"PATH e HOME são exemplos de variáveis de ambiente — pares nome/valor que o sistema operacional e os programas conseguem ler. Você já viu echo $PATH acima; definir uma variável temporariamente (só para a sessão atual do terminal) segue uma sintaxe que muda por shell (em bash/zsh: export NOME=valor). Este curso não é um curso de Bash — o que importa aqui é entender que ferramentas como Maven, Docker e o próprio java leem configuração do ambiente, não decorar toda a sintaxe de shell scripting."},{"id":"terminal-shell-fundamentos-content-34","type":"html","authorship":"legacy-preserved","html":"<h2>Permissões: quem pode executar o quê</h2>","fidelityText":"Permissões: quem pode executar o quê"},{"id":"terminal-shell-fundamentos-code-35","type":"code","authorship":"legacy-preserved","language":"java","source":"ls -l script.sh\n# -rw-r--r-- 1 ana ana 120 ago 22 10:00 script.sh\n#  ^^^^^^^^^ permissões: dono pode ler/escrever, grupo e outros só leem -- ninguém pode EXECUTAR ainda\nchmod +x script.sh   # adiciona permissão de execução para o arquivo","fidelityText":"ls -l script.sh # -rw-r--r-- 1 ana ana 120 ago 22 10:00 script.sh # ^^^^^^^^^ permissões: dono pode ler/escrever, grupo e outros só leem -- ninguém pode EXECUTAR ainda chmod +x script.sh # adiciona permissão de execução para o arquivo","highlightedHtml":"ls -l script.sh\n<span class=\"com\"># -rw-r--r-- 1 ana ana 120 ago 22 10:00 script.sh\n#  ^^^^^^^^^ permissões: dono pode ler/escrever, grupo e outros só leem -- ninguém pode EXECUTAR ainda</span>\nchmod +x script.sh   <span class=\"com\"># adiciona permissão de execução para o arquivo</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["ls -l mostra permissões; um arquivo de texto comum não é executável até receber chmod +x."],"commonMistakes":["Esperar que um script rode direto sem permissão de execução"]},{"id":"terminal-shell-fundamentos-content-36","type":"html","authorship":"legacy-preserved","html":"<p>Um arquivo de texto comum não é executável por padrão, mesmo que o conteúdo seja um script válido — o sistema operacional distingue \"posso ler/escrever este arquivo\" de \"posso rodar este arquivo como programa\". <code>chmod +x</code> resolve o segundo caso.</p>","fidelityText":"Um arquivo de texto comum não é executável por padrão, mesmo que o conteúdo seja um script válido — o sistema operacional distingue \"posso ler/escrever este arquivo\" de \"posso rodar este arquivo como programa\". chmod +x resolve o segundo caso."},{"id":"terminal-shell-fundamentos-content-37","type":"html","authorship":"legacy-preserved","html":"<h2>Processos em execução</h2>","fidelityText":"Processos em execução"},{"id":"terminal-shell-fundamentos-code-38","type":"code","authorship":"legacy-preserved","language":"java","source":"ps          # lista processos em execução (do terminal atual, dependendo das opções)\nkill 4821   # pede para o processo de PID 4821 terminar (não é administração de sistema ainda -- só o essencial)","fidelityText":"ps # lista processos em execução (do terminal atual, dependendo das opções) kill 4821 # pede para o processo de PID 4821 terminar (não é administração de sistema ainda -- só o essencial)","highlightedHtml":"ps          <span class=\"com\"># lista processos em execução (do terminal atual, dependendo das opções)</span>\nkill 4821   <span class=\"com\"># pede para o processo de PID 4821 terminar (não é administração de sistema ainda -- só o essencial)</span>","caption":"Exemplo executável de terminal-shell-fundamentos.","explanation":["ps lista processos em execução; kill pede que um processo termine, identificado pelo PID."],"commonMistakes":["Confundir kill com uma remoção segura -- ele apenas sinaliza o processo, que pode ignorar sinais não-forçados"]},{"id":"terminal-shell-fundamentos-content-39","type":"html","authorship":"legacy-preserved","html":"<h2>Portabilidade: nem todo mundo usa Bash</h2>","fidelityText":"Portabilidade: nem todo mundo usa Bash"},{"id":"terminal-shell-fundamentos-content-40","type":"html","authorship":"legacy-preserved","html":"<p>Este curso usa sintaxe estilo Unix (Bash/Zsh) na maior parte dos exemplos, porque é a mais comum em documentação técnica e em servidores Linux — mas isso não significa que só existe um shell, nem que <code>fish</code> ou <code>PowerShell</code> são \"Bash com roupa diferente\":</p>","fidelityText":"Este curso usa sintaxe estilo Unix (Bash/Zsh) na maior parte dos exemplos, porque é a mais comum em documentação técnica e em servidores Linux — mas isso não significa que só existe um shell, nem que fish ou PowerShell são \"Bash com roupa diferente\":"},{"id":"terminal-shell-fundamentos-content-41","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Ambiente</th><th>Onde aparece</th><th>Observação</th></tr>\n        <tr><td>Bash / Zsh (POSIX-like)</td><td>Linux, macOS, WSL</td><td>Sintaxe deste capítulo funciona nos dois com pouquíssima diferença prática neste nível.</td></tr>\n        <tr><td>fish</td><td>Instalado por escolha em Linux/macOS</td><td>Sintaxe de variáveis e scripts é deliberadamente diferente de Bash — os <em>conceitos</em> (PATH, pipe, exit status) são os mesmos.</td></tr>\n        <tr><td>PowerShell</td><td>Windows (padrão moderno)</td><td>Comandos nativos têm nomes próprios (<code>Get-ChildItem</code> em vez de <code>ls</code>, embora aliases existam); pipe conecta <em>objetos</em>, não só texto.</td></tr>\n        <tr><td>cmd.exe</td><td>Windows (legado)</td><td>Ainda existe, mas PowerShell é a opção recomendada para uso atual no Windows.</td></tr>\n      </tbody></table>","fidelityText":"AmbienteOnde apareceObservação Bash / Zsh (POSIX-like)Linux, macOS, WSLSintaxe deste capítulo funciona nos dois com pouquíssima diferença prática neste nível. fishInstalado por escolha em Linux/macOSSintaxe de variáveis e scripts é deliberadamente diferente de Bash — os conceitos (PATH, pipe, exit status) são os mesmos. PowerShellWindows (padrão moderno)Comandos nativos têm nomes próprios (Get-ChildItem em vez de ls, embora aliases existam); pipe conecta objetos, não só texto. cmd.exeWindows (legado)Ainda existe, mas PowerShell é a opção recomendada para uso atual no Windows."},{"id":"terminal-shell-fundamentos-content-42","type":"html","authorship":"legacy-preserved","html":"<p>Quando um comando deste curso for específico de um shell, trate-o como <strong>sintaxe específica</strong>; o <strong>conceito</strong> por trás (diretório atual, PATH, pipe, exit status) é portável entre todos eles.</p>","fidelityText":"Quando um comando deste curso for específico de um shell, trate-o como sintaxe específica; o conceito por trás (diretório atual, PATH, pipe, exit status) é portável entre todos eles."},{"id":"terminal-shell-fundamentos-exercise-43","type":"exercise","authorship":"legacy-preserved","title":"Exercício 0.0 — reconhecimento de terminal","prompt":"Abra seu terminal. Rode pwd e anote onde você está. Crie uma pasta estudos-java com mkdir, entre nela com cd, crie um arquivo de texto com touch notas.txt, escreva algo nele com o editor de sua preferência, e confirme o conteúdo com cat notas.txt. Rode command -v java e command -v javac — se algum não responder com um caminho, resolva a instalação/PATH antes de seguir para o próximo capítulo.","difficulty":"foundation","criteria":["pwd mostra o caminho absoluto do diretório atual antes de qualquer navegação.","mkdir estudos-java && cd estudos-java cria e entra na pasta em sequência (usando &&, já praticado acima).","touch notas.txt cria o arquivo vazio; o editor grava o conteúdo; cat notas.txt confirma o que foi salvo.","Se command -v java não retornar um caminho, o problema é de instalação do JDK ou de configuração do PATH — resolva isso antes do capítulo \"Introdução & ambiente\", que pressupõe java/javac já funcionando."],"fidelityText":"Exercício 0.0 — reconhecimento de terminalfácil Abra seu terminal. Rode pwd e anote onde você está. Crie uma pasta estudos-java com mkdir, entre nela com cd, crie um arquivo de texto com touch notas.txt, escreva algo nele com o editor de sua preferência, e confirme o conteúdo com cat notas.txt. Rode command -v java e command -v javac — se algum não responder com um caminho, resolva a instalação/PATH antes de seguir para o próximo capítulo. Ver solução pwd mostra o caminho absoluto do diretório atual antes de qualquer navegação. mkdir estudos-java && cd estudos-java cria e entra na pasta em sequência (usando &&, já praticado acima). touch notas.txt cria o arquivo vazio; o editor grava o conteúdo; cat notas.txt confirma o que foi salvo. Se command -v java não retornar um caminho, o problema é de instalação do JDK ou de configuração do PATH — resolva isso antes do capítulo \"Introdução & ambiente\", que pressupõe java/javac já funcionando.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 0.0 — reconhecimento de terminal</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Abra seu terminal. Rode <code>pwd</code> e anote onde você está. Crie uma pasta <code>estudos-java</code> com <code>mkdir</code>, entre nela com <code>cd</code>, crie um arquivo de texto com <code>touch notas.txt</code>, escreva algo nele com o editor de sua preferência, e confirme o conteúdo com <code>cat notas.txt</code>. Rode <code>command -v java</code> e <code>command -v javac</code> — se algum não responder com um caminho, resolva a instalação/PATH antes de seguir para o próximo capítulo.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <ul>\n            <li><code>pwd</code> mostra o caminho absoluto do diretório atual antes de qualquer navegação.</li>\n            <li><code>mkdir estudos-java &amp;&amp; cd estudos-java</code> cria e entra na pasta em sequência (usando <code>&amp;&amp;</code>, já praticado acima).</li>\n            <li><code>touch notas.txt</code> cria o arquivo vazio; o editor grava o conteúdo; <code>cat notas.txt</code> confirma o que foi salvo.</li>\n            <li>Se <code>command -v java</code> não retornar um caminho, o problema é de instalação do JDK ou de configuração do <code>PATH</code> — resolva isso antes do capítulo \"Introdução &amp; ambiente\", que pressupõe <code>java</code>/<code>javac</code> já funcionando.</li>\n          </ul>\n        </div>\n      </div>"},{"id":"terminal-shell-quiz","type":"quiz","authorship":"authored","conceptId":"path-env-var-resolution","prompt":"Você digita `meuprograma` no shell e recebe `command not found`, mas o arquivo do programa existe na pasta atual. Qual é a explicação mais provável?","options":[{"id":"a","label":"O shell só executa programas cujo diretório esteja listado em PATH (ou que sejam chamados com caminho explícito, como ./meuprograma)","correct":true,"explanation":"Correto: sem estar no PATH e sem caminho explícito, o shell não sabe procurar no diretório atual por padrão."},{"id":"b","label":"O programa está corrompido e precisa ser reinstalado","correct":false,"explanation":"Corrompido geraria outro tipo de erro (permissão negada, erro de execução) -- \"command not found\" é especificamente sobre não achar o comando, não sobre executá-lo."},{"id":"c","label":"Todo comando precisa ser digitado com letras maiúsculas no shell","correct":false,"explanation":"Case sensitivity varia por sistema de arquivos, mas não é a causa típica de \"command not found\" para um nome de comando digitado corretamente."}]},{"id":"terminal-shell-error-case","type":"error-case","authorship":"authored","title":"rm -rf apagou a pasta errada","scenario":"Um aluno queria apagar uma pasta de testes vazia dentro do projeto, mas rodou rm -rf a partir do diretório pai, um nível acima do pretendido.","symptom":"A pasta do projeto inteira (incluindo código-fonte não commitado) desapareceu sem aviso nem confirmação.","cause":"rm -rf não pede confirmação e opera recursivamente a partir do diretório atual -- um pwd/ls não conferido antes do comando deixou passar o caminho errado.","diagnosis":["Rodar pwd antes de qualquer rm -rf para confirmar onde você está","Rodar ls no alvo antes de apagar, para confirmar que é a pasta certa"],"correction":"Sem backup ou controle de versão, a recuperação depende de um sistema de arquivos com suporte a desfazer (raro) -- na prática, o conteúdo se perde.","prevention":"Faça commit frequente em Git antes de qualquer operação destrutiva, e trate rm -rf como uma operação que exige conferência explícita do caminho, nunca copiada de um tutorial sem adaptação."}],"resources":[{"id":"terminal-shell-fundamentos-gnu-bash-manual","type":"official-docs","title":"GNU Bash Reference Manual","url":"https://www.gnu.org/software/bash/manual/bash.html","reinforces":"Referência oficial e completa do shell mais usado em sistemas Linux/macOS -- navegação, quoting, redirecionamento e composição de comandos.","language":"en","publisher":"Free Software Foundation","official":true,"expectedLevel":"beginner","auditStatus":"approved","verifiedAt":"2026-08-23"},{"id":"terminal-shell-fundamentos-posix-shell-spec","type":"official-docs","title":"POSIX Shell & Utilities -- Shell Command Language","url":"https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html","reinforces":"Especificação formal da linguagem de comando de shell compatível com POSIX -- base para entender por que bash/zsh se comportam de forma semelhante.","language":"en","publisher":"The Open Group","official":true,"expectedLevel":"intermediate","auditStatus":"approved","verifiedAt":"2026-08-23"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The terminal shell fundamentals example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"pwd          # print working directory -- mostra o caminho absoluto de onde você está agora","instruction":"The terminal shell fundamentals example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22","notes":["Capítulo novo criado na reconstrução editorial de agosto de 2026 para fechar o pré-requisito oculto de terminal/shell.","Revisão factual: comandos e conceitos conferidos contra GNU Bash Reference Manual e POSIX Shell Command Language."]},"editorialReview":{"requiredTopics":["terminal-vs-shell","navegacao-diretorios","arquivos-crud-shell","busca-grep-find","path-resolucao-comandos","quoting","redirecionamento","pipes","composicao-exit-status","variaveis-ambiente","permissoes","processos","portabilidade-shell"],"evidenceBlocks":{"terminal-vs-shell":["terminal-shell-intuition"],"navegacao-diretorios":["terminal-shell-fundamentos-code-8"],"arquivos-crud-shell":["terminal-shell-fundamentos-code-10","terminal-shell-error-case"],"busca-grep-find":["terminal-shell-fundamentos-code-13"],"path-resolucao-comandos":["terminal-shell-fundamentos-code-17","terminal-shell-quiz"],"quoting":["terminal-shell-fundamentos-code-21"],"redirecionamento":["terminal-shell-fundamentos-code-24"],"pipes":["terminal-shell-fundamentos-code-27"],"composicao-exit-status":["terminal-shell-fundamentos-code-30"],"variaveis-ambiente":["terminal-shell-fundamentos-code-17"],"permissoes":["terminal-shell-fundamentos-code-35"],"processos":["terminal-shell-fundamentos-code-38"],"portabilidade-shell":["terminal-shell-fundamentos-content-39","terminal-shell-fundamentos-content-40","terminal-shell-fundamentos-content-41"]},"primarySources":["GNU Bash Reference Manual -- https://www.gnu.org/software/bash/manual/bash.html","POSIX Shell & Utilities -- Shell Command Language -- https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html"],"factualReviewedAt":"2026-08-22","pedagogicalReviewedAt":"2026-08-22","openIssues":[]}},{"id":"intro","moduleId":"orientation","order":1,"title":"Introdução & ambiente","summary":"Este primeiro capítulo não presume experiência com programação, terminal ou orientação a objetos. O objetivo é montar o ambiente e entender o caminho mais básico: você salva texto em um arquivo, uma ferramenta verifica e traduz esse texto, e outra executa o resultado.","objectives":["Distinguir código-fonte, compilação, bytecode e execução","Verificar JDK e JVM pelo terminal","Executar os primeiros comandos sabendo em qual diretório está"],"whyItExists":"Antes de escrever Java, você precisa enxergar as ferramentas que transformam texto em um programa executável; sem esse mapa, qualquer falha da instalação parece uma falha do código.","prerequisiteChapterIds":[],"conceptIds":["jdk-jre-e-jvm-o-que-cada-um-faz","do-codigo-fonte-ao-programa-rodando","verifique-o-ambiente-antes-de-programar"],"introducedConceptIds":["source-bytecode-runtime","jdk-jvm","terminal-diretorio-comando"],"usedConceptIds":[],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"intro-intuition","type":"intuition","authorship":"authored","title":"Java começa como texto","body":"Um arquivo .java é texto. O JDK fornece o compilador que verifica esse texto e produz bytecode; a JVM executa o bytecode. A IDE apenas aciona essas mesmas etapas por você.","analogyLimit":"A ideia de tradução ajuda a separar etapas, mas bytecode não é uma língua humana nem o javac executa o programa."},{"id":"intro-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#terminal-shell-fundamentos\">00 · Terminal e shell</a></div>\n      </div>","fidelityText":"Dificuldade: Iniciante Pré-requisito: 00 · Terminal e shell"},{"id":"intro-content-2","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Baseline verificável do curso</h2><ul>\n    <li><strong>Java:</strong> JDK 21 LTS; recursos preview ficam explicitamente identificados e não são exigidos.</li>\n    <li><strong>Spring:</strong> exemplos tradicionais são compatíveis com a linha Spring Boot 3.5 / Framework 6.2; notas indicam diferenças relevantes do Framework 7, como a preferência por <code>RestClient</code>.</li>\n    <li><strong>Dados e infraestrutura:</strong> PostgreSQL 16 nos exemplos, Redis 7 e imagens fixadas no projeto executável. Para Kafka, Springdoc e Testcontainers, use a versão administrada pelo BOM adotado e registre a imagem por tag e digest.</li>\n    <li><strong>Última revisão técnica integral:</strong> agosto de 2026. Antes de copiar dependências, confira a matriz oficial de compatibilidade da versão do projeto.</li>\n    </ul></div>","fidelityText":"Baseline verificável do curso Java: JDK 21 LTS; recursos preview ficam explicitamente identificados e não são exigidos. Spring: exemplos tradicionais são compatíveis com a linha Spring Boot 3.5 / Framework 6.2; notas indicam diferenças relevantes do Framework 7, como a preferência por RestClient. Dados e infraestrutura: PostgreSQL 16 nos exemplos, Redis 7 e imagens fixadas no projeto executável. Para Kafka, Springdoc e Testcontainers, use a versão administrada pelo BOM adotado e registre a imagem por tag e digest. Última revisão técnica integral: agosto de 2026. Antes de copiar dependências, confira a matriz oficial de compatibilidade da versão do projeto."},{"id":"intro-content-3","type":"html","authorship":"legacy-preserved","html":"<p>Este primeiro capítulo não presume experiência com programação, terminal ou orientação a objetos. O objetivo é montar o ambiente e entender o caminho mais básico: você salva texto em um arquivo, uma ferramenta verifica e traduz esse texto, e outra executa o resultado.</p>","fidelityText":"Este primeiro capítulo não presume experiência com programação, terminal ou orientação a objetos. O objetivo é montar o ambiente e entender o caminho mais básico: você salva texto em um arquivo, uma ferramenta verifica e traduz esse texto, e outra executa o resultado."},{"id":"intro-content-4","type":"html","authorship":"legacy-preserved","html":"<p><strong>Terminal</strong> é uma janela de texto onde você envia comandos ao sistema. Um <strong>comando</strong> pede uma ação, como mostrar a versão do Java. O terminal trabalha em um <strong>diretório atual</strong>; por isso, antes de compilar, você precisa estar na pasta onde salvou o arquivo. CLI significa <em>command-line interface</em>, ou interface de linha de comando. A entrada interativa pelo console será ensinada somente depois de variáveis, decisões, laços, Strings e métodos.</p>","fidelityText":"Terminal é uma janela de texto onde você envia comandos ao sistema. Um comando pede uma ação, como mostrar a versão do Java. O terminal trabalha em um diretório atual; por isso, antes de compilar, você precisa estar na pasta onde salvou o arquivo. CLI significa command-line interface, ou interface de linha de comando. A entrada interativa pelo console será ensinada somente depois de variáveis, decisões, laços, Strings e métodos."},{"id":"intro-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>JDK, JRE e JVM — o que cada um faz</h2>","fidelityText":"JDK, JRE e JVM — o que cada um faz"},{"id":"intro-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Sigla</th><th>O que é</th><th>Quando você usa</th></tr>\n        <tr><td><b style=\"color:var(--ink)\">JVM</b></td><td>Java Virtual Machine — a máquina virtual que executa bytecode Java, independente de sistema operacional</td><td>Sempre, é ela quem roda o <code>.class</code></td></tr>\n        <tr><td><b style=\"color:var(--ink)\">JRE</b></td><td>Java Runtime Environment — JVM + bibliotecas padrão, só para <em>executar</em> programas</td><td>Ambientes que só rodam Java, sem compilar</td></tr>\n        <tr><td><b style=\"color:var(--ink)\">JDK</b></td><td>Java Development Kit — JRE + compilador (<code>javac</code>), depurador e ferramentas</td><td>Para desenvolver — é o que você instala para programar</td></tr>\n      </tbody></table>","fidelityText":"SiglaO que éQuando você usa JVMJava Virtual Machine — a máquina virtual que executa bytecode Java, independente de sistema operacionalSempre, é ela quem roda o .class JREJava Runtime Environment — JVM + bibliotecas padrão, só para executar programasAmbientes que só rodam Java, sem compilar JDKJava Development Kit — JRE + compilador (javac), depurador e ferramentasPara desenvolver — é o que você instala para programar"},{"id":"intro-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Do código-fonte ao programa rodando</h2>","fidelityText":"Do código-fonte ao programa rodando"},{"id":"intro-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"// 1. Você escreve Main.java (código-fonte, texto legível)\n// 2. javac compila para bytecode:\njavac Main.java   // gera Main.class\n\n// 3. a JVM interpreta/compila-JIT o bytecode e executa:\njava Main","fidelityText":"// 1. Você escreve Main.java (código-fonte, texto legível) // 2. javac compila para bytecode: javac Main.java // gera Main.class // 3. a JVM interpreta/compila-JIT o bytecode e executa: java Main","highlightedHtml":"<span class=\"com\">// 1. Você escreve Main.java (código-fonte, texto legível)\n// 2. javac compila para bytecode:</span>\njavac Main.java   <span class=\"com\">// gera Main.class</span>\n\n<span class=\"com\">// 3. a JVM interpreta/compila-JIT o bytecode e executa:</span>\njava Main","caption":"Exemplo executável de intro.","explanation":["javac recebe o arquivo-fonte e produz Main.class somente se a verificação de compilação passar.","java recebe o nome da classe, carrega o bytecode e inicia a JVM; não se escreve a extensão .class."],"commonMistakes":["Executar o comando em outro diretório","Usar java Main.class"]},{"id":"intro-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Por que isso importa:</b> o bytecode gerado pelo <code>javac</code> não é código de máquina x86 nem ARM — é um formato intermediário que qualquer JVM (Windows, Linux, macOS) sabe interpretar. É esse desenho que dá origem ao lema <em>\"write once, run anywhere\"</em>.</div>","fidelityText":"Por que isso importa: o bytecode gerado pelo javac não é código de máquina x86 nem ARM — é um formato intermediário que qualquer JVM (Windows, Linux, macOS) sabe interpretar. É esse desenho que dá origem ao lema \"write once, run anywhere\"."},{"id":"intro-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Verifique o ambiente antes de programar</h2>","fidelityText":"Verifique o ambiente antes de programar"},{"id":"intro-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"java --version\njavac --version","fidelityText":"java --version javac --version","highlightedHtml":"java --version\njavac --version","caption":"Exemplo executável de intro.","explanation":["java --version verifica a ferramenta que inicia a JVM.","javac --version verifica o compilador do JDK; os dois resultados precisam indicar a baseline 21."],"commonMistakes":["Testar apenas java e descobrir depois que não há compilador","Copiar o símbolo do prompt junto com o comando"]},{"id":"intro-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Os dois comandos devem responder com a versão 21. <code>java</code> inicia a execução; <code>javac</code> é o compilador incluído no JDK. Se um comando não for reconhecido, o problema ainda é de instalação ou de configuração do <code>PATH</code>, a lista de diretórios em que o sistema procura programas.</p>","fidelityText":"Os dois comandos devem responder com a versão 21. java inicia a execução; javac é o compilador incluído no JDK. Se um comando não for reconhecido, o problema ainda é de instalação ou de configuração do PATH, a lista de diretórios em que o sistema procura programas."},{"id":"intro-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Você verá palavras ainda não ensinadas no primeiro programa, como <code>class</code>, <code>public</code> e <code>static</code>. Neste momento, use a forma completa como um contrato de inicialização. Os capítulos seguintes explicarão cada responsabilidade quando você já tiver os pré-requisitos para compreendê-la.</div>","fidelityText":"Você verá palavras ainda não ensinadas no primeiro programa, como class, public e static. Neste momento, use a forma completa como um contrato de inicialização. Os capítulos seguintes explicarão cada responsabilidade quando você já tiver os pré-requisitos para compreendê-la."},{"id":"intro-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 0.1 — ambiente observável","prompt":"Abra o terminal, execute java --version e javac --version e anote a responsabilidade de cada comando. Depois crie uma pasta vazia para os exercícios e confirme que o terminal está nela. Você ainda não precisa explicar os modificadores do método main.","difficulty":"foundation","criteria":["java --version mostra a ferramenta de execução e sua versão.","javac --version mostra o compilador do JDK.","Os dois comandos indicam Java 21.","Você sabe localizar a pasta atual antes de criar Main.java no próximo capítulo."],"fidelityText":"Exercício 0.1 — ambiente observávelfácil Abra o terminal, execute java --version e javac --version e anote a responsabilidade de cada comando. Depois crie uma pasta vazia para os exercícios e confirme que o terminal está nela. Você ainda não precisa explicar os modificadores do método main. Ver solução java --version mostra a ferramenta de execução e sua versão.javac --version mostra o compilador do JDK.Os dois comandos indicam Java 21.Você sabe localizar a pasta atual antes de criar Main.java no próximo capítulo.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 0.1 — ambiente observável</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Abra o terminal, execute <code>java --version</code> e <code>javac --version</code> e anote a responsabilidade de cada comando. Depois crie uma pasta vazia para os exercícios e confirme que o terminal está nela. Você ainda não precisa explicar os modificadores do método <code>main</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<ul><li><code>java --version</code> mostra a ferramenta de execução e sua versão.</li><li><code>javac --version</code> mostra o compilador do JDK.</li><li>Os dois comandos indicam Java 21.</li><li>Você sabe localizar a pasta atual antes de criar <code>Main.java</code> no próximo capítulo.</li></ul>\n        </div>\n      </div>"},{"id":"intro-flow","type":"diagram","authorship":"authored","title":"O ciclo observável","description":"Cada seta corresponde a uma ação que você consegue executar ou inspecionar.","steps":["Salvar Main.java","Executar javac Main.java","Confirmar que Main.class foi criado","Executar java Main","Observar a saída ou diagnosticar a etapa que falhou"]},{"id":"intro-error","type":"error-case","authorship":"authored","title":"java existe, mas javac não","scenario":"java --version funciona e javac --version não.","symptom":"O sistema executa Java, mas não consegue compilar seu arquivo.","cause":"Há somente um runtime no PATH ou o JDK foi instalado/configurado incorretamente.","diagnosis":["Execute os dois comandos separadamente","Confira se ambos apontam para a versão 21"],"correction":"Instale um JDK 21 e ajuste o PATH seguindo a documentação da distribuição escolhida.","prevention":"Valide as duas ferramentas antes do primeiro exercício."},{"id":"intro-quiz","type":"quiz","authorship":"authored","conceptId":"source-bytecode-runtime","prompt":"Main.java foi salvo, javac Main.java terminou sem erro e java Main informa que não encontrou a classe. Qual verificação vem primeiro?","options":[{"id":"intro-q-a","label":"Confirmar se o terminal está no diretório que contém Main.class.","correct":true,"explanation":"O launcher procura a classe no classpath atual por padrão; diretório incorreto explica a compilação bem-sucedida e a falha de execução."},{"id":"intro-q-b","label":"Reescrever o programa usando Spring.","correct":false,"explanation":"Um framework não corrige localização de classe nem faz parte dos pré-requisitos."},{"id":"intro-q-c","label":"Apagar o JDK porque compilação e execução são a mesma etapa.","correct":false,"explanation":"javac e java têm responsabilidades distintas; a compilação já produziu o artefato."}]}],"resources":[{"id":"intro-java-launcher","type":"official-docs","title":"Ferramenta java: modos de execução","url":"https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html","reinforces":"Distingue lançamento de classe e execução direta de arquivo-fonte.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"intro-javac","type":"official-docs","title":"Ferramenta javac","url":"https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html","reinforces":"Confirma a função do compilador e seus arquivos de entrada e saída.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The Java development environment example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"// 1. Você escreve Main.java (código-fonte, texto legível)","instruction":"The Java development environment example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["Fluxo de ferramentas revisado com aluno sem pré-requisitos."]}},{"id":"primeiro-programa","moduleId":"orientation","order":2,"title":"Primeiro programa: escrever, compilar e executar","summary":"Antes de estudar abstrações, você precisa dominar o ciclo concreto: um arquivo .java contém código-fonte; o compilador javac verifica regras e produz bytecode; a JVM executa esse bytecode. IDEs escondem parte desse caminho, mas não o eliminam.","objectives":["Criar Main.java com o ponto de entrada tradicional","Compilar e executar sem depender da IDE","Ler a primeira mensagem de diagnóstico antes de alterar o código"],"whyItExists":"O primeiro programa torna visível o ciclo apresentado na orientação e cria uma rotina de diagnóstico que será usada em todos os exercícios.","prerequisiteChapterIds":["intro"],"conceptIds":["o-menor-programa-tradicional","terminal-sem-magica","leitura-de-uma-mensagem-do-compilador","um-exemplo-de-cada-categoria-de-erro","argumentos-de-linha-de-comando"],"introducedConceptIds":["classe-main","stdout-println","categorias-de-falha"],"usedConceptIds":["source-bytecode-runtime","jdk-jvm","terminal-diretorio-comando"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"primeiro-intuition","type":"intuition","authorship":"authored","title":"Um ponto de partida combinado","body":"A JVM precisa de uma entrada conhecida para iniciar um programa tradicional. Por enquanto, trate public static void main(String[] args) como o contrato de inicialização; cada palavra será explicada quando seus conceitos existirem.","analogyLimit":"É um contrato de chamada, não uma frase mágica: alterar sua forma pode impedir que o launcher a reconheça."},{"id":"primeiro-programa-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#intro\">Ambiente Java</a></div></div>","fidelityText":"Dificuldade: IniciantePré-requisito: Ambiente Java"},{"id":"primeiro-programa-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Antes de estudar abstrações, você precisa dominar o ciclo concreto: um arquivo <code>.java</code> contém código-fonte; o compilador <code>javac</code> verifica regras e produz bytecode; a JVM executa esse bytecode. IDEs escondem parte desse caminho, mas não o eliminam.</p>","fidelityText":"Antes de estudar abstrações, você precisa dominar o ciclo concreto: um arquivo .java contém código-fonte; o compilador javac verifica regras e produz bytecode; a JVM executa esse bytecode. IDEs escondem parte desse caminho, mas não o eliminam."},{"id":"primeiro-programa-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>O menor programa tradicional</h2>","fidelityText":"O menor programa tradicional"},{"id":"primeiro-programa-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Main {\n    public static void main(String[] args) {\n        System.out.println(\"Hello, Java!\");\n    }\n}","fidelityText":"public class Main { public static void main(String[] args) { System.out.println(\"Olá, Java!\"); } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Main</span> {\n    <span class=\"kw\">public static void</span> <span class=\"fn\">main</span>(String[] args) {\n        System.out.println(<span class=\"str\">\"Hello, Java!\"</span>);\n    }\n}","caption":"Exemplo executável de primeiro-programa.","explanation":["O nome do arquivo precisa coincidir com o nome da classe pública.","main é o ponto de entrada que a JVM procura para começar a executar."],"commonMistakes":["Nomear o arquivo diferente da classe pública"]},{"id":"primeiro-programa-content-5","type":"html","authorship":"legacy-preserved","html":"<p>O nome do arquivo deve ser <code>Main.java</code> porque a classe pública se chama <code>Main</code>. <code>main</code> é o ponto de entrada. <code>String[] args</code> recebe argumentos da linha de comando. <code>System.out</code> é a saída padrão e <code>println</code> imprime uma linha.</p>","fidelityText":"O nome do arquivo deve ser Main.java porque a classe pública se chama Main. main é o ponto de entrada. String[] args recebe argumentos da linha de comando. System.out é a saída padrão e println imprime uma linha."},{"id":"primeiro-programa-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Terminal sem mágica</h2>","fidelityText":"Terminal sem mágica"},{"id":"primeiro-programa-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"javac Main.java   # compila e cria Main.class\njava Main         # pede à JVM para executar Main (não leva a extensão .class)\njava Main.java    # source-file mode: compila em memória e executa numa única etapa","fidelityText":"javac Main.java # compila e cria Main.class java Main # pede à JVM para executar Main (não leva a extensão .class) java Main.java # source-file mode: compila em memória e executa numa única etapa","highlightedHtml":"javac Main.java   <span class=\"com\"># compila e cria Main.class</span>\njava Main         <span class=\"com\"># pede à JVM para executar Main (não leva a extensão .class)</span>\njava Main.java    <span class=\"com\"># source-file mode: compila em memória e executa numa única etapa</span>","caption":"Exemplo executável de primeiro-programa.","explanation":["javac compila; java executa o .class; o source-file mode faz os dois em uma chamada, sem gerar arquivo .class visível."],"commonMistakes":["Achar que java Main.java grava um .class no disco"]},{"id":"primeiro-programa-content-8","type":"html","authorship":"legacy-preserved","html":"<p>O nome do arquivo <strong>precisa</strong> coincidir com o nome da classe pública que ele contém — <code>Main.java</code> para uma classe <code>public class Main</code>. Isso não é convenção, é regra do compilador: <code>javac</code> recusa um arquivo em que o nome não bate com a classe pública. O <strong>source-file mode</strong> (<code>java Main.java</code>) existe para reduzir fricção em scripts pequenos e no primeiro contato com a linguagem: ele compila o arquivo internamente e roda o resultado numa única chamada, sem deixar um <code>.class</code> no disco — mas continua sendo os mesmos dois passos (compilar, depois executar), só que escondidos. Em projetos reais, com múltiplas classes, você volta a usar o par explícito <code>javac</code>/<code>java</code>.</p>","fidelityText":"O nome do arquivo precisa coincidir com o nome da classe pública que ele contém — Main.java para uma classe public class Main. Isso não é convenção, é regra do compilador: javac recusa um arquivo em que o nome não bate com a classe pública. O source-file mode (java Main.java) existe para reduzir fricção em scripts pequenos e no primeiro contato com a linguagem: ele compila o arquivo internamente e roda o resultado numa única chamada, sem deixar um .class no disco — mas continua sendo os mesmos dois passos (compilar, depois executar), só que escondidos. Em projetos reais, com múltiplas classes, você volta a usar o par explícito javac/java."},{"id":"primeiro-programa-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Erro não é uma categoria única.</b> Erro de compilação impede gerar bytecode (o programa nunca chega a rodar); exceção ocorre durante a execução (o programa rodou até um ponto e parou); erro lógico permite rodar do início ao fim, mas produz um resultado incorreto silenciosamente — o mais perigoso dos três, porque nada avisa que algo deu errado. Aprenda a classificar antes de tentar corrigir: a estratégia de correção é diferente para cada categoria.</div>","fidelityText":"Erro não é uma categoria única. Erro de compilação impede gerar bytecode (o programa nunca chega a rodar); exceção ocorre durante a execução (o programa rodou até um ponto e parou); erro lógico permite rodar do início ao fim, mas produz um resultado incorreto silenciosamente — o mais perigoso dos três, porque nada avisa que algo deu errado. Aprenda a classificar antes de tentar corrigir: a estratégia de correção é diferente para cada categoria."},{"id":"primeiro-programa-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Leitura de uma mensagem do compilador</h2>","fidelityText":"Leitura de uma mensagem do compilador"},{"id":"primeiro-programa-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Leia de cima para baixo: arquivo, linha, símbolo indicado por <code>^</code> e descrição. Conserte <strong>o primeiro</strong> erro e compile outra vez — um caractere ausente (uma chave, um ponto e vírgula) pode gerar vários erros em cascata nas linhas seguintes, porque o compilador perde a referência de onde uma instrução termina e a próxima começa. Corrigir o quinto erro listado antes do primeiro geralmente não resolve nada, porque ele pode ser só um efeito colateral do primeiro.</p>","fidelityText":"Leia de cima para baixo: arquivo, linha, símbolo indicado por ^ e descrição. Conserte o primeiro erro e compile outra vez — um caractere ausente (uma chave, um ponto e vírgula) pode gerar vários erros em cascata nas linhas seguintes, porque o compilador perde a referência de onde uma instrução termina e a próxima começa. Corrigir o quinto erro listado antes do primeiro geralmente não resolve nada, porque ele pode ser só um efeito colateral do primeiro."},{"id":"primeiro-programa-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Um exemplo de cada categoria de erro</h2>","fidelityText":"Um exemplo de cada categoria de erro"},{"id":"primeiro-programa-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Main {\n    public static void main(String[] args) {\n        int total = 10 / 0; // compila normalmente -- divisão por zero é um erro de EXECUÇÃO em int\n        System.out.println(total);\n    }\n}","fidelityText":"public class Main { public static void main(String[] args) { int total = 10 / 0; // compila normalmente -- divisão por zero é um erro de EXECUÇÃO em int System.out.println(total); } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Main</span> {\n    <span class=\"kw\">public static void</span> <span class=\"fn\">main</span>(String[] args) {\n        <span class=\"kw\">int</span> total = <span class=\"num\">10</span> / <span class=\"num\">0</span>; <span class=\"com\">// compila normalmente -- divisão por zero é um erro de EXECUÇÃO em int</span>\n        System.out.println(total);\n    }\n}","caption":"Exemplo executável de primeiro-programa.","explanation":["Divisão por zero em int compila normalmente -- só falha em tempo de execução, como ArithmeticException."],"commonMistakes":["Confundir erro de compilação com exceção de runtime"]},{"id":"primeiro-programa-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Esse programa compila sem nenhum aviso: para o compilador, <code>10 / 0</code> é uma expressão válida sintaticamente. É só ao executar que a JVM lança <code>ArithmeticException: / by zero</code> — uma exceção, não um erro de compilação. Já um exemplo de erro lógico: <code>if (idade = 18)</code> em vez de <code>if (idade == 18)</code> às vezes nem compila (dependendo do tipo), mas trocar <code>&gt;</code> por <code>&gt;=</code> numa condição de aprovação compila, roda e produz nota de corte errada sem avisar nada — só um teste com o valor de fronteira (exatamente 18) revela o problema.</p>","fidelityText":"Esse programa compila sem nenhum aviso: para o compilador, 10 / 0 é uma expressão válida sintaticamente. É só ao executar que a JVM lança ArithmeticException: / by zero — uma exceção, não um erro de compilação. Já um exemplo de erro lógico: if (idade = 18) em vez de if (idade == 18) às vezes nem compila (dependendo do tipo), mas trocar > por >= numa condição de aprovação compila, roda e produz nota de corte errada sem avisar nada — só um teste com o valor de fronteira (exatamente 18) revela o problema."},{"id":"primeiro-programa-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Argumentos de linha de comando</h2>","fidelityText":"Argumentos de linha de comando"},{"id":"primeiro-programa-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Main {\n    public static void main(String[] args) {\n        if (args.length == 0) {\n            System.out.println(\"Uso: java Main <name>\");\n            return;\n        }\n        System.out.println(\"Hello, \" + args[0] + \"!\");\n    }\n}","fidelityText":"public class Main { public static void main(String[] args) { if (args.length == 0) { System.out.println(\"Uso: java Main <nome>\"); return; } System.out.println(\"Olá, \" + args[0] + \"!\"); } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Main</span> {\n    <span class=\"kw\">public static void</span> <span class=\"fn\">main</span>(String[] args) {\n        <span class=\"kw\">if</span> (args.length == <span class=\"num\">0</span>) {\n            System.out.println(<span class=\"str\">\"Uso: java Main &lt;name&gt;\"</span>);\n            <span class=\"kw\">return</span>;\n        }\n        System.out.println(<span class=\"str\">\"Hello, \"</span> + args[<span class=\"num\">0</span>] + <span class=\"str\">\"!\"</span>);\n    }\n}","caption":"Exemplo executável de primeiro-programa.","explanation":["Validar args.length antes de acessar uma posição evita ArrayIndexOutOfBoundsException quando o programa roda sem argumentos."],"commonMistakes":["Acessar args[0] sem checar se algum argumento foi passado"]},{"id":"primeiro-programa-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"java Main Ana   # args = [\"Ana\"] -- Ana não faz parte do comando \"java\", é um argumento do PROGRAMA","fidelityText":"java Main Ana # args = [\"Ana\"] -- Ana não faz parte do comando \"java\", é um argumento do PROGRAMA","highlightedHtml":"java Main Ana   <span class=\"com\"># args = [\"Ana\"] -- Ana não faz parte do comando \"java\", é um argumento do PROGRAMA</span>","caption":"Exemplo executável de primeiro-programa.","explanation":["args recebe os argumentos passados depois do nome da classe no comando java -- não fazem parte do comando em si."],"commonMistakes":["Confundir argumento do programa com opção do comando java"]},{"id":"primeiro-programa-content-18","type":"html","authorship":"legacy-preserved","html":"<p><code>args</code> é o mesmo conceito de argumento que você já viu no capítulo de terminal — só que aqui quem recebe é o método <code>main</code> do seu programa Java, não um comando de shell.</p>","fidelityText":"args é o mesmo conceito de argumento que você já viu no capítulo de terminal — só que aqui quem recebe é o método main do seu programa Java, não um comando de shell."},{"id":"primeiro-programa-exercise-19","type":"exercise","authorship":"legacy-preserved","title":"Prática obrigatória","prompt":"Crie o arquivo manualmente, compile pelo terminal e execute. Depois remova um ponto e vírgula, observe o diagnóstico e explique por que o problema pertence à compilação. Em seguida, escreva um programa que divide dois números lidos como argumentos (args[0], args[1]) e rode sem argumentos, com um argumento e com o segundo argumento igual a \"0\" — classifique o que acontece em cada caso.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Prática obrigatóriaFundamentoCrie o arquivo manualmente, compile pelo terminal e execute. Depois remova um ponto e vírgula, observe o diagnóstico e explique por que o problema pertence à compilação. Em seguida, escreva um programa que divide dois números lidos como argumentos (args[0], args[1]) e rode sem argumentos, com um argumento e com o segundo argumento igual a \"0\" — classifique o que acontece em cada caso.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Prática obrigatória</h2><span class=\"exercise-tag f\">Fundamento</span></div><p>Crie o arquivo manualmente, compile pelo terminal e execute. Depois remova um ponto e vírgula, observe o diagnóstico e explique por que o problema pertence à compilação. Em seguida, escreva um programa que divide dois números lidos como argumentos (<code>args[0]</code>, <code>args[1]</code>) e rode sem argumentos, com um argumento e com o segundo argumento igual a \"0\" — classifique o que acontece em cada caso.</p></div>"},{"id":"primeiro-programa-content-20","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Critério de domínio</h2><ul><li>Você explica a diferença entre JDK, compilador, bytecode e JVM.</li><li>Consegue compilar sem depender do botão da IDE.</li><li>Distingue erro de compilação, exceção e erro lógico com um exemplo de cada.</li><li>Sabe por que o nome do arquivo precisa coincidir com o nome da classe pública.</li></ul></div>","fidelityText":"Critério de domínioVocê explica a diferença entre JDK, compilador, bytecode e JVM.Consegue compilar sem depender do botão da IDE.Distingue erro de compilação, exceção e erro lógico com um exemplo de cada.Sabe por que o nome do arquivo precisa coincidir com o nome da classe pública."},{"id":"primeiro-errors","type":"comparison","authorship":"authored","title":"Onde a falha acontece","criteria":["momento","evidência","primeira ação"],"alternatives":[{"name":"Compilação","values":["antes de Main.class","mensagem do javac","ler arquivo, linha e símbolo"],"useWhen":"a linguagem ou os tipos estão inválidos","avoidWhen":"o programa já iniciou"},{"name":"Execução","values":["depois de iniciar","exceção ou falha do launcher","ler tipo e primeira linha relevante"],"useWhen":"o bytecode iniciou e algo falhou","avoidWhen":"javac não gerou a classe"},{"name":"Lógica","values":["programa termina","saída incorreta","comparar esperado e observado"],"useWhen":"o código é válido, mas a regra está errada","avoidWhen":"há diagnóstico do compilador"}]},{"id":"primeiro-quiz","type":"quiz","authorship":"authored","conceptId":"categorias-de-falha","prompt":"Após remover o ponto e vírgula, javac mostra vários diagnósticos. O que fazer primeiro?","options":[{"id":"primeiro-q-a","label":"Corrigir o primeiro diagnóstico e compilar novamente.","correct":true,"explanation":"Um erro inicial pode desorganizar a análise das linhas seguintes e causar mensagens em cascata."},{"id":"primeiro-q-b","label":"Tentar executar Main para descobrir o resultado.","correct":false,"explanation":"Sem nova compilação, você executaria bytecode antigo ou não teria classe alguma."},{"id":"primeiro-q-c","label":"Corrigir todas as linhas apontadas ao mesmo tempo, por tentativa.","correct":false,"explanation":"Isso mistura causas e efeitos e reduz a qualidade do diagnóstico."}]}],"resources":[{"id":"primeiro-source-file-mode","type":"official-docs","title":"Java Source-File Mode","url":"https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html#using-source-file-mode-to-launch-single-file-source-code-programs","reinforces":"Explica a diferença entre java Main e java Main.java.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"primeiro-jls-execution","type":"reference","title":"JLS: execução do programa","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-12.html#jls-12.1","reinforces":"Formaliza a inicialização e a busca do método main.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The first program example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public class Main {","instruction":"The first program example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["Contrato main apresentado sem antecipar modificadores."]}},{"id":"variaveis-tipos","moduleId":"programming-fundamentals","order":3,"title":"Variáveis, tipos, memória e conversões","summary":"Uma variável associa um nome a um valor cujo tipo é conhecido pelo compilador. O tipo limita operações válidas, representação e faixa. Java é estaticamente tipada: incompatibilidades são detectadas antes da execução sempre que possível.","objectives":["Declarar e inicializar variáveis locais","Distinguir valor primitivo de referência","Prever promoção, truncamento e divisão inteira"],"whyItExists":"Programas precisam nomear dados e impedir operações incoerentes; o sistema de tipos permite ao compilador rejeitar parte desses erros antes da execução.","prerequisiteChapterIds":["primeiro-programa"],"conceptIds":["primitivos","valor-primitivo-versus-referencia","literais-e-sufixos","overflow-quando-o-tipo-nao-cabe-o-valor","divisao-inteira-e-promocao-numerica","var-nao-e-tipagem-dinamica","conversao-e-perda"],"introducedConceptIds":["variavel-tipo-estatico","primitivo-referencia","conversao-numerica"],"usedConceptIds":["classe-main","stdout-println","categorias-de-falha"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"variaveis-intuition","type":"intuition","authorship":"authored","title":"Nome, tipo e valor respondem perguntas diferentes","body":"O nome expressa a função do dado; o tipo define valores e operações permitidos; o valor é o estado naquele instante. Uma atribuição pode trocar o valor sem trocar o tipo da variável.","analogyLimit":"Uma variável não é uma caixa física universal: referências e primitivos têm comportamentos de cópia diferentes."},{"id":"variaveis-tipos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#primeiro-programa\">Primeiro programa</a></div></div>","fidelityText":"Dificuldade: IniciantePré-requisito: Primeiro programa"},{"id":"variaveis-tipos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Uma variável associa um nome a um valor cujo tipo é conhecido pelo compilador. O tipo limita operações válidas, representação e faixa. Java é estaticamente tipada: incompatibilidades são detectadas antes da execução sempre que possível.</p>","fidelityText":"Uma variável associa um nome a um valor cujo tipo é conhecido pelo compilador. O tipo limita operações válidas, representação e faixa. Java é estaticamente tipada: incompatibilidades são detectadas antes da execução sempre que possível."},{"id":"variaveis-tipos-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Primitivos</h2>","fidelityText":"Primitivos"},{"id":"variaveis-tipos-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Grupo</th><th>Tipos</th><th>Uso</th></tr><tr><td>Inteiros</td><td><code>byte</code>, <code>short</code>, <code>int</code>, <code>long</code></td><td><code>int</code> é a escolha normal; <code>long</code> para faixas maiores</td></tr><tr><td>Decimais</td><td><code>float</code>, <code>double</code></td><td><code>double</code> é o padrão; nenhum deles representa dinheiro com exatidão decimal</td></tr><tr><td>Caractere</td><td><code>char</code></td><td>Uma unidade UTF-16, não necessariamente um caractere humano completo</td></tr><tr><td>Lógico</td><td><code>boolean</code></td><td><code>true</code> ou <code>false</code></td></tr></tbody></table>","fidelityText":"GrupoTiposUsoInteirosbyte, short, int, longint é a escolha normal; long para faixas maioresDecimaisfloat, doubledouble é o padrão; nenhum deles representa dinheiro com exatidão decimalCaracterecharUma unidade UTF-16, não necessariamente um caractere humano completoLógicobooleantrue ou false"},{"id":"variaveis-tipos-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"int age = 17;\nlong residents = 8_100_000_000L;\ndouble height = 1.78;\nboolean active = true;\nString name = \"Felipy\";","fidelityText":"int idade = 17; long habitantes = 8_100_000_000L; double altura = 1.78; boolean ativo = true; String nome = \"Felipy\";","highlightedHtml":"<span class=\"kw\">int</span> age = <span class=\"num\">17</span>;\n<span class=\"kw\">long</span> residents = <span class=\"num\">8_100_000_000L</span>;\n<span class=\"kw\">double</span> height = <span class=\"num\">1.78</span>;\n<span class=\"kw\">boolean</span> active = <span class=\"kw\">true</span>;\nString name = <span class=\"str\">\"Felipy\"</span>;","caption":"Exemplo executável de variaveis-tipos.","explanation":["Cada primitivo tem faixa e uso típico distintos -- int para contagens comuns, long para faixas maiores, double para decimais."]},{"id":"variaveis-tipos-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Valor primitivo versus referência</h2>","fidelityText":"Valor primitivo versus referência"},{"id":"variaveis-tipos-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Uma variável primitiva contém o próprio valor. Uma variável de referência aponta para um objeto ou contém <code>null</code>. Copiar um primitivo copia o valor; copiar uma referência faz duas variáveis apontarem inicialmente para o mesmo objeto.</p>","fidelityText":"Uma variável primitiva contém o próprio valor. Uma variável de referência aponta para um objeto ou contém null. Copiar um primitivo copia o valor; copiar uma referência faz duas variáveis apontarem inicialmente para o mesmo objeto."},{"id":"variaveis-tipos-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>null</code> não significa vazio nem zero.</b> Significa ausência de referência. Tentar acessar um membro por uma referência nula causa <code>NullPointerException</code>.</div>","fidelityText":"null não significa vazio nem zero. Significa ausência de referência. Tentar acessar um membro por uma referência nula causa NullPointerException."},{"id":"variaveis-tipos-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Literais e sufixos</h2>","fidelityText":"Literais e sufixos"},{"id":"variaveis-tipos-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"long populacao = 8_100_000_000L;  // sufixo L -- sem ele, o literal seria interpretado como int e estouraria\nfloat aproximado = 3.14f;      // sufixo f -- literais decimais são double por padrão\ndouble preciso = 3.14;          // sem sufixo: double\nchar initial = 'A';             // aspas simples -- um único caractere\nint hexadecimal = 0xFF;        // 255 em hexadecimal","fidelityText":"long populacao = 8_100_000_000L; // sufixo L -- sem ele, o literal seria interpretado como int e estouraria float aproximado = 3.14f; // sufixo f -- literais decimais são double por padrão double preciso = 3.14; // sem sufixo: double char inicial = 'A'; // aspas simples -- um único caractere int hexadecimal = 0xFF; // 255 em hexadecimal","highlightedHtml":"<span class=\"kw\">long</span> populacao = <span class=\"num\">8_100_000_000L</span>;  <span class=\"com\">// sufixo L -- sem ele, o literal seria interpretado como int e estouraria</span>\n<span class=\"kw\">float</span> aproximado = <span class=\"num\">3.14f</span>;      <span class=\"com\">// sufixo f -- literais decimais são double por padrão</span>\n<span class=\"kw\">double</span> preciso = <span class=\"num\">3.14</span>;          <span class=\"com\">// sem sufixo: double</span>\n<span class=\"kw\">char</span> initial = <span class=\"str\">'A'</span>;             <span class=\"com\">// aspas simples -- um único caractere</span>\n<span class=\"kw\">int</span> hexadecimal = <span class=\"num\">0xFF</span>;        <span class=\"com\">// 255 em hexadecimal</span>","caption":"Exemplo executável de variaveis-tipos.","explanation":["O sufixo L evita que um literal grande seja interpretado como int (32 bits) e estoure antes de virar long."],"commonMistakes":["Omitir o sufixo L em literais long grandes"]},{"id":"variaveis-tipos-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Sublinhados em literais numéricos (<code>8_100_000_000L</code>) existem só para legibilidade humana — o compilador os ignora. Sem o sufixo <code>L</code>, um literal inteiro grande demais para <code>int</code> nem compila: o compilador primeiro tenta encaixá-lo como <code>int</code> (32 bits) antes de saber que você queria um <code>long</code>.</p>","fidelityText":"Sublinhados em literais numéricos (8_100_000_000L) existem só para legibilidade humana — o compilador os ignora. Sem o sufixo L, um literal inteiro grande demais para int nem compila: o compilador primeiro tenta encaixá-lo como int (32 bits) antes de saber que você queria um long."},{"id":"variaveis-tipos-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Overflow: quando o tipo não cabe o valor</h2>","fidelityText":"Overflow: quando o tipo não cabe o valor"},{"id":"variaveis-tipos-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"int maximum = Integer.MAX_VALUE;  // 2147483647\nint estourado = maximum + 1;    // vira -2147483648 -- NÃO lança exceção, apenas \"roda\" o valor","fidelityText":"int maximo = Integer.MAX_VALUE; // 2147483647 int estourado = maximo + 1; // vira -2147483648 -- NÃO lança exceção, apenas \"roda\" o valor","highlightedHtml":"<span class=\"kw\">int</span> maximum = Integer.MAX_VALUE;  <span class=\"com\">// 2147483647</span>\n<span class=\"kw\">int</span> estourado = maximum + <span class=\"num\">1</span>;    <span class=\"com\">// vira -2147483648 -- NÃO lança exceção, apenas \"roda\" o valor</span>","caption":"Exemplo executável de variaveis-tipos.","explanation":["Overflow em int não lança exceção -- o valor dá a volta silenciosamente para o outro extremo da faixa."],"commonMistakes":["Assumir que ultrapassar Integer.MAX_VALUE lança erro"]},{"id":"variaveis-tipos-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Aritmética com tipos inteiros primitivos <strong>não</strong> lança exceção em overflow — o valor simplesmente dá a volta (wraps around) para o outro extremo da faixa representável. Isso é uma fonte real de bugs silenciosos em cálculos com <code>int</code> quando o resultado pode ultrapassar ~2,1 bilhões; nesses casos, use <code>long</code> desde o início do cálculo, não só no resultado.</p>","fidelityText":"Aritmética com tipos inteiros primitivos não lança exceção em overflow — o valor simplesmente dá a volta (wraps around) para o outro extremo da faixa representável. Isso é uma fonte real de bugs silenciosos em cálculos com int quando o resultado pode ultrapassar ~2,1 bilhões; nesses casos, use long desde o início do cálculo, não só no resultado."},{"id":"variaveis-tipos-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Divisão inteira e promoção numérica</h2>","fidelityText":"Divisão inteira e promoção numérica"},{"id":"variaveis-tipos-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"int a = 5;\nint b = 2;\nSystem.out.println(a / b);         // 2 -- divisão inteira descarta o resto, não arredonda\nSystem.out.println(a / 2.0);      // 2.5 -- um operando double \"promove\" o outro para double\nSystem.out.println((double) a / b); // 2.5 -- cast antes da divisão, não depois","fidelityText":"int a = 5; int b = 2; System.out.println(a / b); // 2 -- divisão inteira descarta o resto, não arredonda System.out.println(a / 2.0); // 2.5 -- um operando double \"promove\" o outro para double System.out.println((double) a / b); // 2.5 -- cast antes da divisão, não depois","highlightedHtml":"<span class=\"kw\">int</span> a = <span class=\"num\">5</span>;\n<span class=\"kw\">int</span> b = <span class=\"num\">2</span>;\n<span class=\"kw\">System.out.println</span>(a / b);         <span class=\"com\">// 2 -- divisão inteira descarta o resto, não arredonda</span>\n<span class=\"kw\">System.out.println</span>(a / <span class=\"num\">2.0</span>);      <span class=\"com\">// 2.5 -- um operando double \"promove\" o outro para double</span>\n<span class=\"kw\">System.out.println</span>((<span class=\"kw\">double</span>) a / b); <span class=\"com\">// 2.5 -- cast antes da divisão, não depois</span>","caption":"Exemplo executável de variaveis-tipos.","explanation":["Divisão entre dois int trunca a parte decimal; um operando double promove a expressão inteira para double."],"commonMistakes":["Esperar arredondamento em vez de truncamento na divisão inteira"]},{"id":"variaveis-tipos-content-17","type":"html","authorship":"legacy-preserved","html":"<p>Quando os dois operandos de uma divisão são inteiros, o resultado é inteiro — a parte fracionária é descartada (truncamento em direção a zero, não arredondamento). Essa é a causa mais comum do bug \"por que minha média deu 2 em vez de 2.5\": basta um dos dois operandos ser <code>double</code> para a expressão inteira ser <strong>promovida</strong> a <code>double</code> antes da divisão acontecer.</p>","fidelityText":"Quando os dois operandos de uma divisão são inteiros, o resultado é inteiro — a parte fracionária é descartada (truncamento em direção a zero, não arredondamento). Essa é a causa mais comum do bug \"por que minha média deu 2 em vez de 2.5\": basta um dos dois operandos ser double para a expressão inteira ser promovida a double antes da divisão acontecer."},{"id":"variaveis-tipos-content-18","type":"html","authorship":"legacy-preserved","html":"<h2><code>var</code> não é tipagem dinâmica</h2>","fidelityText":"var não é tipagem dinâmica"},{"id":"variaveis-tipos-code-19","type":"code","authorship":"legacy-preserved","language":"java","source":"var quantity = 10;      // o compilador INFERE int a partir do literal -- e trava nesse tipo\n// quantidade = \"dez\";     // erro de compilação: quantidade continua sendo int","fidelityText":"var quantidade = 10; // o compilador INFERE int a partir do literal -- e trava nesse tipo // quantidade = \"dez\"; // erro de compilação: quantidade continua sendo int","highlightedHtml":"<span class=\"kw\">var</span> quantity = <span class=\"num\">10</span>;      <span class=\"com\">// o compilador INFERE int a partir do literal -- e trava nesse tipo</span>\n<span class=\"com\">// quantidade = \"dez\";     // erro de compilação: quantidade continua sendo int</span>","caption":"Exemplo executável de variaveis-tipos.","explanation":["var pede inferência de tipo ao compilador -- a variável continua estaticamente tipada, só o tipo por extenso foi omitido."],"commonMistakes":["Achar que var permite trocar o tipo da variável depois"]},{"id":"variaveis-tipos-content-20","type":"html","authorship":"legacy-preserved","html":"<p><code>var</code> pede ao compilador para inferir o tipo a partir do valor inicial — a variável continua estaticamente tipada exatamente como se você tivesse escrito o tipo por extenso. A IDE mostrar o tipo inferido ao lado não é \"mágica de tipagem dinâmica\": é apenas o compilador revelando o que já decidiu na primeira linha. Depois de inicializada, o tipo não muda.</p>","fidelityText":"var pede ao compilador para inferir o tipo a partir do valor inicial — a variável continua estaticamente tipada exatamente como se você tivesse escrito o tipo por extenso. A IDE mostrar o tipo inferido ao lado não é \"mágica de tipagem dinâmica\": é apenas o compilador revelando o que já decidiu na primeira linha. Depois de inicializada, o tipo não muda."},{"id":"variaveis-tipos-content-21","type":"html","authorship":"legacy-preserved","html":"<h2>Conversão e perda</h2>","fidelityText":"Conversão e perda"},{"id":"variaveis-tipos-code-22","type":"code","authorship":"legacy-preserved","language":"java","source":"int small = 10;\nlong widened = small;       // widening: seguro, sem perda de informação\ndouble average = 7.9;\nint truncated = (int) average; // narrowing: vira 7, não arredonda -- descarta a parte decimal","fidelityText":"int pequeno = 10; long ampliado = pequeno; // widening: seguro, sem perda de informação double media = 7.9; int truncado = (int) media; // narrowing: vira 7, não arredonda -- descarta a parte decimal","highlightedHtml":"<span class=\"kw\">int</span> small = <span class=\"num\">10</span>;\n<span class=\"kw\">long</span> widened = small;       <span class=\"com\">// widening: seguro, sem perda de informação</span>\n<span class=\"kw\">double</span> average = <span class=\"num\">7.9</span>;\n<span class=\"kw\">int</span> truncated = (<span class=\"kw\">int</span>) average; <span class=\"com\">// narrowing: vira 7, não arredonda -- descarta a parte decimal</span>","caption":"Exemplo executável de variaveis-tipos.","explanation":["Widening (int -> long) é automático e seguro; narrowing (double -> int) exige cast explícito e trunca a parte decimal."],"commonMistakes":["Esperar que o cast (int) arredonde em vez de truncar"]},{"id":"variaveis-tipos-content-23","type":"html","authorship":"legacy-preserved","html":"<p><strong>Widening</strong> (de um tipo menor para um maior compatível, como <code>int</code> → <code>long</code>) acontece automaticamente e nunca perde informação. <strong>Narrowing</strong> (o caminho inverso) exige um cast explícito porque pode perder dados — o compilador força você a reconhecer esse risco escrevendo <code>(int)</code> na frente.</p>","fidelityText":"Widening (de um tipo menor para um maior compatível, como int → long) acontece automaticamente e nunca perde informação. Narrowing (o caminho inverso) exige um cast explícito porque pode perder dados — o compilador força você a reconhecer esse risco escrevendo (int) na frente."},{"id":"variaveis-tipos-content-24","type":"html","authorship":"legacy-preserved","html":"<p>Variáveis locais precisam ser inicializadas antes do uso — o compilador recusa compilar uma leitura de variável local sem valor atribuído. Campos de objetos recebem valores padrão automaticamente (0, false, null conforme o tipo), mas depender disso sem inicialização explícita reduz a clareza de quem lê o código depois.</p>","fidelityText":"Variáveis locais precisam ser inicializadas antes do uso — o compilador recusa compilar uma leitura de variável local sem valor atribuído. Campos de objetos recebem valores padrão automaticamente (0, false, null conforme o tipo), mas depender disso sem inicialização explícita reduz a clareza de quem lê o código depois."},{"id":"variaveis-tipos-exercise-25","type":"exercise","authorship":"legacy-preserved","title":"Preveja antes de executar","prompt":"Explique o valor e o tipo de cada expressão: 5 / 2, 5 / 2.0, (double) 5 / 2 e (int) 5.9. Depois preveja o resultado de Integer.MAX_VALUE + 1 e explique por que não é uma exceção.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Preveja antes de executarFundamentoExplique o valor e o tipo de cada expressão: 5 / 2, 5 / 2.0, (double) 5 / 2 e (int) 5.9. Depois preveja o resultado de Integer.MAX_VALUE + 1 e explique por que não é uma exceção.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Preveja antes de executar</h2><span class=\"exercise-tag f\">Fundamento</span></div><p>Explique o valor e o tipo de cada expressão: <code>5 / 2</code>, <code>5 / 2.0</code>, <code>(double) 5 / 2</code> e <code>(int) 5.9</code>. Depois preveja o resultado de <code>Integer.MAX_VALUE + 1</code> e explique por que não é uma exceção.</p></div>"},{"id":"variaveis-model","type":"mental-model","authorship":"authored","title":"O que uma atribuição copia","body":"Java sempre copia o valor armazenado na variável. Para um int, esse valor é o número; para uma variável de objeto, é uma referência. Por isso duas referências copiadas podem alcançar o mesmo objeto.","flow":["Avaliar a expressão à direita","Verificar compatibilidade com o tipo à esquerda","Copiar o valor calculado para a variável"],"ownership":["A variável guarda seu próprio valor","O objeto referenciado não fica dentro da variável"]},{"id":"variaveis-quiz","type":"quiz","authorship":"authored","conceptId":"conversao-numerica","prompt":"Qual resultado e tipo produz a expressão 5 / 2 antes de qualquer atribuição?","options":[{"id":"variaveis-q-a","label":"O int 2.","correct":true,"explanation":"Os dois operandos são int; a divisão inteira descarta a parte fracionária."},{"id":"variaveis-q-b","label":"O double 2.5.","correct":false,"explanation":"Nenhum operando promove a operação para ponto flutuante."},{"id":"variaveis-q-c","label":"Erro de compilação porque a divisão não é exata.","correct":false,"explanation":"Divisão inteira é válida e trunca o resultado."}]}],"resources":[{"id":"variaveis-jls-types","type":"reference","title":"JLS 4: tipos, valores e variáveis","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html","reinforces":"Define tipos primitivos, referências e variáveis.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"variaveis-jls-conversions","type":"reference","title":"JLS 5: conversões e contextos","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-5.html","reinforces":"Confirma promoção, widening e narrowing.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The variables types example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"int age = 17;","instruction":"The variables types example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"operadores-expressoes","moduleId":"programming-fundamentals","order":4,"title":"Operadores, expressões e precedência","summary":"Expressão é uma combinação que produz um valor. Operadores determinam como os operandos são combinados. Decorar uma tabela de precedência é menos importante que escrever expressões claras com parênteses.","objectives":["Prever valor e tipo de expressões","Usar parênteses para tornar intenção explícita","Distinguir curto-circuito, identidade e conteúdo"],"whyItExists":"Dados só se tornam decisões e cálculos quando são combinados; operadores definem a ordem, o tipo do resultado e até se uma parte do código será executada.","prerequisiteChapterIds":["variaveis-tipos"],"conceptIds":["curto-circuito","nao-e-igualdade-de-conteudo-para-objetos","incremento-decremento-e-atribuicao-composta","precedencia-e-associatividade"],"introducedConceptIds":["expressao-operador","curto-circuito","identidade-conteudo"],"usedConceptIds":["variavel-tipo-estatico","primitivo-referencia","conversao-numerica"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"operadores-intuition","type":"intuition","authorship":"authored","title":"Uma expressão produz um valor","body":"Leia uma expressão de dentro para fora: identifique operandos, aplique precedência, descubra o valor e então seu tipo. Parênteses registram a intenção e evitam depender da memória do leitor.","analogyLimit":"Não é apenas matemática: curto-circuito pode impedir chamadas e atribuições têm efeito sobre estado."},{"id":"operadores-expressoes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#variaveis-tipos\">Variáveis e tipos</a></div></div>","fidelityText":"Dificuldade: IniciantePré-requisito: Variáveis e tipos"},{"id":"operadores-expressoes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Expressão é uma combinação que produz um valor. Operadores determinam como os operandos são combinados. Decorar uma tabela de precedência é menos importante que escrever expressões claras com parênteses.</p>","fidelityText":"Expressão é uma combinação que produz um valor. Operadores determinam como os operandos são combinados. Decorar uma tabela de precedência é menos importante que escrever expressões claras com parênteses."},{"id":"operadores-expressoes-content-3","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Operadores principais</th><th>Resultado</th></tr><tr><td>Aritméticos</td><td><code>+ - * / %</code></td><td>Número</td></tr><tr><td>Comparação</td><td><code>== != &gt; &gt;= &lt; &lt;=</code></td><td><code>boolean</code></td></tr><tr><td>Lógicos</td><td><code>&amp;&amp; || !</code></td><td><code>boolean</code></td></tr><tr><td>Atribuição</td><td><code>= += -= *= /=</code></td><td>Atualiza uma variável</td></tr></tbody></table>","fidelityText":"CategoriaOperadores principaisResultadoAritméticos+ - * / %NúmeroComparação== != > >= < <=booleanLógicos&& || !booleanAtribuição= += -= *= /=Atualiza uma variável"},{"id":"operadores-expressoes-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Curto-circuito</h2>","fidelityText":"Curto-circuito"},{"id":"operadores-expressoes-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"if (user != null && user.estaActive()) {\n    releaseAccess();\n}","fidelityText":"if (usuario != null && usuario.estaAtivo()) { liberarAcesso(); }","highlightedHtml":"<span class=\"kw\">if</span> (user != <span class=\"kw\">null</span> &amp;&amp; user.estaActive()) {\n    releaseAccess();\n}","caption":"Exemplo executável de operadores-expressoes.","explanation":["Com &&, a segunda condição só é avaliada se a primeira for verdadeira -- protege contra NullPointerException nesta ordem."],"commonMistakes":["Inverter a ordem e acessar o objeto antes de checar null"]},{"id":"operadores-expressoes-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Com <code>&amp;&amp;</code>, a segunda condição só é avaliada se a primeira for verdadeira. Com <code>||</code>, ela só é avaliada se a primeira for falsa. Isso afeta segurança contra <code>null</code> e também efeitos colaterais.</p>","fidelityText":"Com &&, a segunda condição só é avaliada se a primeira for verdadeira. Com ||, ela só é avaliada se a primeira for falsa. Isso afeta segurança contra null e também efeitos colaterais."},{"id":"operadores-expressoes-content-7","type":"html","authorship":"legacy-preserved","html":"<h2><code>==</code> não é igualdade de conteúdo para objetos</h2>","fidelityText":"== não é igualdade de conteúdo para objetos"},{"id":"operadores-expressoes-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Em primitivos, <code>==</code> compara valores. Em referências, compara se ambos os lados apontam para o mesmo objeto. Para conteúdo de <code>String</code> e outros value objects, use <code>equals</code> conforme o contrato do tipo.</p>","fidelityText":"Em primitivos, == compara valores. Em referências, compara se ambos os lados apontam para o mesmo objeto. Para conteúdo de String e outros value objects, use equals conforme o contrato do tipo."},{"id":"operadores-expressoes-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"String a = new String(\"Java\");\nString b = new String(\"Java\");\nSystem.out.println(a == b);      // false: referências distintas\nSystem.out.println(a.equals(b)); // true: mesmo conteúdo","fidelityText":"String a = new String(\"Java\"); String b = new String(\"Java\"); System.out.println(a == b); // false: referências distintas System.out.println(a.equals(b)); // true: mesmo conteúdo","highlightedHtml":"String a = <span class=\"kw\">new</span> String(<span class=\"str\">\"Java\"</span>);\nString b = <span class=\"kw\">new</span> String(<span class=\"str\">\"Java\"</span>);\nSystem.out.println(a == b);      <span class=\"com\">// false: referências distintas</span>\nSystem.out.println(a.equals(b)); <span class=\"com\">// true: mesmo conteúdo</span>","caption":"Exemplo executável de operadores-expressoes.","explanation":["== em referências compara identidade de objeto, não conteúdo -- equals() compara o conteúdo conforme o contrato do tipo."],"commonMistakes":["Comparar Strings com == esperando comparação de conteúdo"]},{"id":"operadores-expressoes-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Incremento, decremento e atribuição composta</h2>","fidelityText":"Incremento, decremento e atribuição composta"},{"id":"operadores-expressoes-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"int x = 5;\nint a = x++;  // pós-incremento: a recebe 5, DEPOIS x vira 6\nint y = 5;\nint b = ++y;  // pré-incremento: y vira 6 PRIMEIRO, b recebe 6\n\nint total = 10;\ntotal += 5;  // equivalente a total = total + 5, mas o tipo de \"total\" é preservado no cast implícito","fidelityText":"int x = 5; int a = x++; // pós-incremento: a recebe 5, DEPOIS x vira 6 int y = 5; int b = ++y; // pré-incremento: y vira 6 PRIMEIRO, b recebe 6 int total = 10; total += 5; // equivalente a total = total + 5, mas o tipo de \"total\" é preservado no cast implícito","highlightedHtml":"<span class=\"kw\">int</span> x = <span class=\"num\">5</span>;\n<span class=\"kw\">int</span> a = x++;  <span class=\"com\">// pós-incremento: a recebe 5, DEPOIS x vira 6</span>\n<span class=\"kw\">int</span> y = <span class=\"num\">5</span>;\n<span class=\"kw\">int</span> b = ++y;  <span class=\"com\">// pré-incremento: y vira 6 PRIMEIRO, b recebe 6</span>\n\n<span class=\"kw\">int</span> total = <span class=\"num\">10</span>;\ntotal += <span class=\"num\">5</span>;  <span class=\"com\">// equivalente a total = total + 5, mas o tipo de \"total\" é preservado no cast implícito</span>","caption":"Exemplo executável de operadores-expressoes.","explanation":["Pós-incremento usa o valor antes de incrementar; pré-incremento incrementa antes de usar o valor."],"commonMistakes":["Confundir x++ com ++x quando o valor da expressão é usado imediatamente"]},{"id":"operadores-expressoes-content-12","type":"html","authorship":"legacy-preserved","html":"<p>A diferença entre <code>x++</code> e <code>++x</code> só importa quando o valor da expressão é usado imediatamente (atribuído, impresso, passado como argumento) — como efeito isolado numa linha própria, os dois produzem o mesmo resultado final em <code>x</code>. Evite misturar incremento com uso da mesma variável na mesma expressão (como <code>arr[i++] = i</code>): a ordem de avaliação existe, mas confiar nela em código de produção prejudica a leitura.</p>","fidelityText":"A diferença entre x++ e ++x só importa quando o valor da expressão é usado imediatamente (atribuído, impresso, passado como argumento) — como efeito isolado numa linha própria, os dois produzem o mesmo resultado final em x. Evite misturar incremento com uso da mesma variável na mesma expressão (como arr[i++] = i): a ordem de avaliação existe, mas confiar nela em código de produção prejudica a leitura."},{"id":"operadores-expressoes-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Precedência e associatividade</h2>","fidelityText":"Precedência e associatividade"},{"id":"operadores-expressoes-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"int result = 2 + 3 * 4;      // 14, não 20 -- * tem precedência maior que +\nint withParentheses = (2 + 3) * 4; // 20 -- parênteses sobrepõem qualquer precedência\nboolean valid = age >= 18 && age <= 65 || temAutorizacao;\n// && tem precedência maior que || -- lido como (idade>=18 && idade<=65) || temAutorizacao","fidelityText":"int resultado = 2 + 3 * 4; // 14, não 20 -- * tem precedência maior que + int comParenteses = (2 + 3) * 4; // 20 -- parênteses sobrepõem qualquer precedência boolean valido = idade >= 18 && idade <= 65 || temAutorizacao; // && tem precedência maior que || -- lido como (idade>=18 && idade<=65) || temAutorizacao","highlightedHtml":"<span class=\"kw\">int</span> result = <span class=\"num\">2</span> + <span class=\"num\">3</span> * <span class=\"num\">4</span>;      <span class=\"com\">// 14, não 20 -- * tem precedência maior que +</span>\n<span class=\"kw\">int</span> withParentheses = (<span class=\"num\">2</span> + <span class=\"num\">3</span>) * <span class=\"num\">4</span>; <span class=\"com\">// 20 -- parênteses sobrepõem qualquer precedência</span>\n<span class=\"kw\">boolean</span> valid = age &gt;= <span class=\"num\">18</span> &amp;&amp; age &lt;= <span class=\"num\">65</span> || temAutorizacao;\n<span class=\"com\">// &amp;&amp; tem precedência maior que || -- lido como (idade&gt;=18 &amp;&amp; idade&lt;=65) || temAutorizacao</span>","caption":"Exemplo executável de operadores-expressoes.","explanation":["Multiplicação tem precedência maior que soma; parênteses sobrepõem qualquer precedência padrão."],"commonMistakes":["Assumir avaliação estritamente da esquerda para a direita ignorando precedência"]},{"id":"operadores-expressoes-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Memorizar a tabela completa de precedência é menos importante do que o hábito de usar parênteses sempre que a expressão misturar mais de um tipo de operador lógico ou aritmético numa mesma linha — o parêntese custa nada e remove qualquer ambiguidade para quem lê depois, inclusive você mesmo em seis meses.</p>","fidelityText":"Memorizar a tabela completa de precedência é menos importante do que o hábito de usar parênteses sempre que a expressão misturar mais de um tipo de operador lógico ou aritmético numa mesma linha — o parêntese custa nada e remove qualquer ambiguidade para quem lê depois, inclusive você mesmo em seis meses."},{"id":"operadores-expressoes-exercise-16","type":"exercise","authorship":"legacy-preserved","title":"Validador de intervalo","prompt":"Escreva uma expressão que aceite idade entre 18 e 65, inclusive. Depois negue a condição inteira sem repetir a lógica. Por fim, preveja o valor de int r = 2 + 3 * 4 - 1; antes de rodar, e reescreva com parênteses tornando a ordem de avaliação explícita.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Validador de intervaloFundamentoEscreva uma expressão que aceite idade entre 18 e 65, inclusive. Depois negue a condição inteira sem repetir a lógica. Por fim, preveja o valor de int r = 2 + 3 * 4 - 1; antes de rodar, e reescreva com parênteses tornando a ordem de avaliação explícita.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Validador de intervalo</h2><span class=\"exercise-tag f\">Fundamento</span></div><p>Escreva uma expressão que aceite idade entre 18 e 65, inclusive. Depois negue a condição inteira sem repetir a lógica. Por fim, preveja o valor de <code>int r = 2 + 3 * 4 - 1;</code> antes de rodar, e reescreva com parênteses tornando a ordem de avaliação explícita.</p></div>"},{"id":"operadores-error","type":"error-case","authorship":"authored","title":"Proteção contra null na ordem errada","scenario":"usuario.estaAtivo() && usuario != null","symptom":"NullPointerException quando usuario é null.","cause":"O operando esquerdo é avaliado antes e já acessa a referência ausente.","diagnosis":["Marque a ordem de avaliação","Descubra qual operando pode ser falso sem avaliar o próximo"],"correction":"Use usuario != null && usuario.estaAtivo().","prevention":"Coloque a verificação segura antes do acesso protegido."},{"id":"operadores-quiz","type":"quiz","authorship":"authored","conceptId":"curto-circuito","prompt":"Com usuario igual a null, o que ocorre em usuario != null && usuario.estaAtivo()?","options":[{"id":"operadores-q-a","label":"A segunda expressão não é executada e o resultado é false.","correct":true,"explanation":"&& encerra a avaliação assim que encontra um operando falso."},{"id":"operadores-q-b","label":"estaAtivo é chamado e ocorre NullPointerException.","correct":false,"explanation":"Isso ocorreria se o acesso viesse primeiro ou se fosse usado um operador sem curto-circuito."},{"id":"operadores-q-c","label":"O compilador transforma null em false.","correct":false,"explanation":"null não é boolean; a comparação produz false e controla o curto-circuito."}]}],"resources":[{"id":"operadores-jls","type":"reference","title":"JLS 15: expressões","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html","reinforces":"Define avaliação, tipos e operadores de expressões.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"operadores-equality","type":"reference","title":"JLS: operadores de igualdade","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.21","reinforces":"Diferencia igualdade numérica, booleana e de referência.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The operators expressions example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"if (user != null && user.estaActive()) {","instruction":"The operators expressions example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"controle-fluxo","moduleId":"programming-fundamentals","order":5,"title":"Decisões: if, else e switch","summary":"Controle de fluxo decide qual caminho será executado. A qualidade não está em usar o maior número de recursos, mas em tornar as regras mutuamente compreensíveis e testáveis.","objectives":["Cobrir regras com if, else if e else","Ordenar condições específicas antes das amplas","Usar switch expression para mapear valores discretos"],"whyItExists":"Sem ramificação, um programa repete sempre o mesmo caminho; decisões transformam regras escritas em comportamentos diferentes para entradas diferentes.","prerequisiteChapterIds":["operadores-expressoes"],"conceptIds":["switch-como-expressao","switch-statement-classico-e-o-perigo-do-fall-through","guard-clauses-reduzindo-piramides-de-if"],"introducedConceptIds":["ramificacao-condicional","ordem-de-condicoes","switch-expression"],"usedConceptIds":["expressao-operador","curto-circuito"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"controle-intuition","type":"intuition","authorship":"authored","title":"Decidir é selecionar um caminho","body":"Cada condição é uma pergunta booleana. Em uma cadeia if/else if/else, a primeira resposta verdadeira vence e as demais deixam de ser avaliadas.","analogyLimit":"Um fluxograma ajuda a visualizar caminhos, mas o código também pode executar efeitos dentro de cada ramo."},{"id":"controle-fluxo-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#operadores-expressoes\">Operadores</a></div></div>","fidelityText":"Dificuldade: IniciantePré-requisito: Operadores"},{"id":"controle-fluxo-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Controle de fluxo decide qual caminho será executado. A qualidade não está em usar o maior número de recursos, mas em tornar as regras mutuamente compreensíveis e testáveis.</p>","fidelityText":"Controle de fluxo decide qual caminho será executado. A qualidade não está em usar o maior número de recursos, mas em tornar as regras mutuamente compreensíveis e testáveis."},{"id":"controle-fluxo-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"if (grade >= 7) {\n    status = \"Approved\";\n} else if (grade >= 5) {\n    status = \"Recuperacao\";\n} else {\n    status = \"Reprovado\";\n}","fidelityText":"if (nota >= 7) { situacao = \"Aprovado\"; } else if (nota >= 5) { situacao = \"Recuperação\"; } else { situacao = \"Reprovado\"; }","highlightedHtml":"<span class=\"kw\">if</span> (grade &gt;= <span class=\"num\">7</span>) {\n    status = <span class=\"str\">\"Approved\"</span>;\n} <span class=\"kw\">else if</span> (grade &gt;= <span class=\"num\">5</span>) {\n    status = <span class=\"str\">\"Recuperacao\"</span>;\n} <span class=\"kw\">else</span> {\n    status = <span class=\"str\">\"Reprovado\"</span>;\n}","caption":"Exemplo executável de controle-fluxo.","explanation":["Em uma cadeia de else-if, a ordem importa -- condições mais específicas devem vir antes das mais amplas."],"commonMistakes":["Colocar a condição mais ampla primeiro, tornando as seguintes inalcançáveis"]},{"id":"controle-fluxo-content-4","type":"html","authorship":"legacy-preserved","html":"<p>A ordem importa. Em uma cadeia, coloque condições mais específicas antes das mais amplas. Evite pirâmides de <code>if</code>; retornos antecipados costumam separar casos inválidos do caminho principal.</p>","fidelityText":"A ordem importa. Em uma cadeia, coloque condições mais específicas antes das mais amplas. Evite pirâmides de if; retornos antecipados costumam separar casos inválidos do caminho principal."},{"id":"controle-fluxo-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Switch como expressão</h2>","fidelityText":"Switch como expressão"},{"id":"controle-fluxo-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"String type = switch (code) {\n    case 200, 201 -> \"success\";\n    case 400, 404 -> \"error of the customer\";\n    default -> \"other\";\n};","fidelityText":"String tipo = switch (codigo) { case 200, 201 -> \"sucesso\"; case 400, 404 -> \"erro do cliente\"; default -> \"outro\"; };","highlightedHtml":"String type = <span class=\"kw\">switch</span> (code) {\n    <span class=\"kw\">case</span> <span class=\"num\">200</span>, <span class=\"num\">201</span> -&gt; <span class=\"str\">\"success\"</span>;\n    <span class=\"kw\">case</span> <span class=\"num\">400</span>, <span class=\"num\">404</span> -&gt; <span class=\"str\">\"error of the customer\"</span>;\n    <span class=\"kw\">default</span> -&gt; <span class=\"str\">\"other\"</span>;\n};","caption":"Exemplo executável de controle-fluxo.","explanation":["Switch como expressão produz um valor diretamente e é exhaustive -- o compilador exige cobertura de todos os casos possíveis."],"commonMistakes":["Esquecer o default quando o tipo não garante cobertura total"]},{"id":"controle-fluxo-content-7","type":"html","authorship":"legacy-preserved","html":"<p>O <code>switch</code> em forma de <strong>expressão</strong> (com <code>-&gt;</code>) produz um valor diretamente e é <em>exhaustive</em>: o compilador exige que todo caso possível seja coberto — se você omitir o <code>default</code> e o tipo não garantir cobertura total (como um <code>enum</code> com todos os valores listados), o código não compila. Isso elimina uma classe inteira de bugs de \"esqueci um caso\".</p>","fidelityText":"O switch em forma de expressão (com ->) produz um valor diretamente e é exhaustive: o compilador exige que todo caso possível seja coberto — se você omitir o default e o tipo não garantir cobertura total (como um enum com todos os valores listados), o código não compila. Isso elimina uma classe inteira de bugs de \"esqueci um caso\"."},{"id":"controle-fluxo-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Switch statement clássico e o perigo do fall-through</h2>","fidelityText":"Switch statement clássico e o perigo do fall-through"},{"id":"controle-fluxo-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"switch (dayOfWeek) {\n    case 6:\n    case 7:\n        System.out.println(\"End of week\");\n        break; // sem o break, a execução \"cai\" para o próximo case\n    default:\n        System.out.println(\"Day useful\");\n}","fidelityText":"switch (diaDaSemana) { case 6: case 7: System.out.println(\"Fim de semana\"); break; // sem o break, a execução \"cai\" para o próximo case default: System.out.println(\"Dia útil\"); }","highlightedHtml":"<span class=\"kw\">switch</span> (dayOfWeek) {\n    <span class=\"kw\">case</span> <span class=\"num\">6</span>:\n    <span class=\"kw\">case</span> <span class=\"num\">7</span>:\n        System.out.println(<span class=\"str\">\"End of week\"</span>);\n        <span class=\"kw\">break</span>; <span class=\"com\">// sem o break, a execução \"cai\" para o próximo case</span>\n    <span class=\"kw\">default</span>:\n        System.out.println(<span class=\"str\">\"Day useful\"</span>);\n}","caption":"Exemplo executável de controle-fluxo.","explanation":["Sem break, a execução cai (fall-through) para o próximo case silenciosamente -- um dos bugs clássicos do switch statement legado."],"commonMistakes":["Esquecer o break e executar múltiplos casos sem querer"]},{"id":"controle-fluxo-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Fall-through é legado, não um recurso a explorar.</b> No <code>switch</code> statement clássico (com <code>:</code> e não <code>-&gt;</code>), esquecer um <code>break</code> faz a execução continuar para o próximo <code>case</code> silenciosamente — um dos bugs mais clássicos e difíceis de enxergar em revisão de código de Java antigo. O <code>switch</code> em forma de expressão com <code>-&gt;</code> não tem esse problema: cada braço executa isoladamente, sem cair para o próximo. Prefira a forma de expressão sempre que o objetivo for produzir um valor; use <code>yield</code> quando um braço precisar de um bloco com lógica antes de produzir o valor.</div>","fidelityText":"Fall-through é legado, não um recurso a explorar. No switch statement clássico (com : e não ->), esquecer um break faz a execução continuar para o próximo case silenciosamente — um dos bugs mais clássicos e difíceis de enxergar em revisão de código de Java antigo. O switch em forma de expressão com -> não tem esse problema: cada braço executa isoladamente, sem cair para o próximo. Prefira a forma de expressão sempre que o objetivo for produzir um valor; use yield quando um braço precisar de um bloco com lógica antes de produzir o valor."},{"id":"controle-fluxo-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Guard clauses: reduzindo pirâmides de if</h2>","fidelityText":"Guard clauses: reduzindo pirâmides de if"},{"id":"controle-fluxo-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"// difícil de ler: aninhamento profundo\nif (order != null) {\n    if (order.getItems().size() > 0) {\n        if (order.getTotal() > 0) {\n            process(order);\n        }\n    }\n}\n\n// guard clauses: retorno antecipado separa casos inválidos do caminho principal\nif (order == null) return;\nif (order.getItems().isEmpty()) return;\nif (order.getTotal() <= 0) return;\nprocess(order);","fidelityText":"// difícil de ler: aninhamento profundo if (pedido != null) { if (pedido.getItens().size() > 0) { if (pedido.getTotal() > 0) { processar(pedido); } } } // guard clauses: retorno antecipado separa casos inválidos do caminho principal if (pedido == null) return; if (pedido.getItens().isEmpty()) return; if (pedido.getTotal() <= 0) return; processar(pedido);","highlightedHtml":"<span class=\"com\">// difícil de ler: aninhamento profundo</span>\n<span class=\"kw\">if</span> (order != <span class=\"kw\">null</span>) {\n    <span class=\"kw\">if</span> (order.getItems().size() &gt; <span class=\"num\">0</span>) {\n        <span class=\"kw\">if</span> (order.getTotal() &gt; <span class=\"num\">0</span>) {\n            process(order);\n        }\n    }\n}\n\n<span class=\"com\">// guard clauses: retorno antecipado separa casos inválidos do caminho principal</span>\n<span class=\"kw\">if</span> (order == <span class=\"kw\">null</span>) <span class=\"kw\">return</span>;\n<span class=\"kw\">if</span> (order.getItems().isEmpty()) <span class=\"kw\">return</span>;\n<span class=\"kw\">if</span> (order.getTotal() &lt;= <span class=\"num\">0</span>) <span class=\"kw\">return</span>;\nprocess(order);","caption":"Exemplo executável de controle-fluxo.","explanation":["Guard clauses com retorno antecipado eliminam aninhamento profundo, separando casos inválidos do caminho principal."],"commonMistakes":["Aninhar if em vários níveis em vez de usar retorno antecipado"]},{"id":"controle-fluxo-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Guard clauses tratam casos inválidos/de saída antecipada primeiro, deixando o caminho principal do método sem aninhamento — o leitor não precisa manter três níveis de contexto na cabeça para entender o que acontece no caso normal.</p>","fidelityText":"Guard clauses tratam casos inválidos/de saída antecipada primeiro, deixando o caminho principal do método sem aninhamento — o leitor não precisa manter três níveis de contexto na cabeça para entender o que acontece no caso normal."},{"id":"controle-fluxo-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>Modele regras, não acidentes.</b> Se uma decisão cresce continuamente, talvez o problema peça enum, polimorfismo, tabela de decisão ou objeto de regra — não mais um <code>else if</code>.</div>","fidelityText":"Modele regras, não acidentes. Se uma decisão cresce continuamente, talvez o problema peça enum, polimorfismo, tabela de decisão ou objeto de regra — não mais um else if."},{"id":"controle-fluxo-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Frete por regra","prompt":"Calcule frete considerando região, peso e gratuidade acima de um valor. Liste primeiro casos-limite e só então escreva o código. Reescreva a mesma lógica duas vezes: uma com if/else aninhado e outra com guard clauses — compare qual comunica melhor a intenção.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Frete por regraPráticaCalcule frete considerando região, peso e gratuidade acima de um valor. Liste primeiro casos-limite e só então escreva o código. Reescreva a mesma lógica duas vezes: uma com if/else aninhado e outra com guard clauses — compare qual comunica melhor a intenção.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Frete por regra</h2><span class=\"exercise-tag m\">Prática</span></div><p>Calcule frete considerando região, peso e gratuidade acima de um valor. Liste primeiro casos-limite e só então escreva o código. Reescreva a mesma lógica duas vezes: uma com <code>if</code>/<code>else</code> aninhado e outra com guard clauses — compare qual comunica melhor a intenção.</p></div>"},{"id":"controle-table","type":"table","authorship":"authored","title":"Tabela de decisão antes do código","headers":["Nota","Resultado","Ramo"],"rows":[["7 ou mais","Aprovado","if"],["5 até menos de 7","Recuperação","else if"],["menos de 5","Reprovado","else"]],"caption":"As faixas não se sobrepõem porque a cadeia já excluiu as condições anteriores."},{"id":"controle-quiz","type":"quiz","authorship":"authored","conceptId":"ordem-de-condicoes","prompt":"Por que testar nota >= 5 antes de nota >= 7 classificaria nota 9 como recuperação?","options":[{"id":"controle-q-a","label":"A primeira condição já é verdadeira e encerra a cadeia.","correct":true,"explanation":"Uma cadeia escolhe o primeiro ramo verdadeiro; o caso amplo esconderia o específico."},{"id":"controle-q-b","label":"Java sempre escolhe o último else if verdadeiro.","correct":false,"explanation":"Os ramos posteriores nem são avaliados depois da primeira correspondência."},{"id":"controle-q-c","label":"O operador >= arredonda a nota.","correct":false,"explanation":">= apenas compara valores; não altera nenhum deles."}]}],"resources":[{"id":"controle-jls-if","type":"reference","title":"JLS: if e if-then-else","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.9","reinforces":"Define seleção e associação do else.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"controle-jls-switch","type":"reference","title":"JLS: switch expressions","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.28","reinforces":"Formaliza resultado, regras e avaliação de switch expression.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The control flow example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"if (grade >= 7) {","instruction":"The control flow example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"lacos-repeticao","moduleId":"programming-fundamentals","order":6,"title":"Repetição: while, for e controle de laços","summary":"Um laço repete enquanto uma condição permite. Todo laço precisa de estado inicial, condição de continuidade e progresso em direção ao término. Se um desses elementos estiver errado, surge um laço infinito ou uma iteração incompleta.","objectives":["Identificar estado inicial, condição e progresso","Escolher while, do-while, for ou for-each pelo problema","Demonstrar término e testar fronteiras"],"whyItExists":"Repetir código manualmente não escala e favorece inconsistência; laços descrevem uma regra de repetição com início, continuidade e término verificáveis.","prerequisiteChapterIds":["controle-fluxo"],"conceptIds":["erros-de-fronteira-off-by-one","break-e-continue-nao-sao-a-mesma-coisa","lacos-aninhados"],"introducedConceptIds":["contrato-do-laco","while-for-foreach","fronteira-off-by-one"],"usedConceptIds":["ramificacao-condicional","ordem-de-condicoes","expressao-operador"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"lacos-intuition","type":"intuition","authorship":"authored","title":"Todo laço deve conseguir terminar","body":"Antes do corpo, escreva três respostas: qual é o estado inicial, enquanto qual condição repito e qual mudança aproxima essa condição de false. Isso é o contrato mínimo do laço.","analogyLimit":"Contagem é apenas um tipo de progresso; leitura até EOF e busca até encontrar algo também podem terminar."},{"id":"lacos-repeticao-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#controle-fluxo\">Decisões</a></div></div>","fidelityText":"Dificuldade: IniciantePré-requisito: Decisões"},{"id":"lacos-repeticao-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Um laço repete enquanto uma condição permite. Todo laço precisa de estado inicial, condição de continuidade e progresso em direção ao término. Se um desses elementos estiver errado, surge um laço infinito ou uma iteração incompleta.</p>","fidelityText":"Um laço repete enquanto uma condição permite. Todo laço precisa de estado inicial, condição de continuidade e progresso em direção ao término. Se um desses elementos estiver errado, surge um laço infinito ou uma iteração incompleta."},{"id":"lacos-repeticao-content-3","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Estrutura</th><th>Use quando</th></tr><tr><td><code>while</code></td><td>A quantidade de repetições depende de uma condição externa</td></tr><tr><td><code>do-while</code></td><td>O corpo precisa executar pelo menos uma vez</td></tr><tr><td><code>for</code></td><td>Inicialização, condição e incremento formam uma contagem clara</td></tr><tr><td><code>for-each</code></td><td>Você percorre todos os elementos e não precisa do índice</td></tr></tbody></table>","fidelityText":"EstruturaUse quandowhileA quantidade de repetições depende de uma condição externado-whileO corpo precisa executar pelo menos uma vezforInicialização, condição e incremento formam uma contagem clarafor-eachVocê percorre todos os elementos e não precisa do índice"},{"id":"lacos-repeticao-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"int sum = 0;\nfor (int i = 1; i <= 100; i++) {\n    if (i % 2 != 0) continue;\n    sum += i;\n}","fidelityText":"int soma = 0; for (int i = 1; i <= 100; i++) { if (i % 2 != 0) continue; soma += i; }","highlightedHtml":"<span class=\"kw\">int</span> sum = <span class=\"num\">0</span>;\n<span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = <span class=\"num\">1</span>; i &lt;= <span class=\"num\">100</span>; i++) {\n    <span class=\"kw\">if</span> (i % <span class=\"num\">2</span> != <span class=\"num\">0</span>) <span class=\"kw\">continue</span>;\n    sum += i;\n}","caption":"Exemplo executável de lacos-repeticao.","explanation":["continue pula o restante do corpo e volta para a condição -- aqui pula os números pares antes de somar."]},{"id":"lacos-repeticao-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Erros de fronteira (off-by-one)</h2>","fidelityText":"Erros de fronteira (off-by-one)"},{"id":"lacos-repeticao-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Os bugs mais comuns são começar uma posição tarde, terminar uma posição cedo ou usar <code>&lt;=</code> quando o limite é exclusivo. Simule à mão a primeira e a última iteração. Para coleções indexadas de tamanho <code>n</code>, índices válidos normalmente vão de <code>0</code> a <code>n - 1</code>:</p>","fidelityText":"Os bugs mais comuns são começar uma posição tarde, terminar uma posição cedo ou usar <= quando o limite é exclusivo. Simule à mão a primeira e a última iteração. Para coleções indexadas de tamanho n, índices válidos normalmente vão de 0 a n - 1:"},{"id":"lacos-repeticao-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"int[] values = {10, 20, 30};\nfor (int i = 0; i <= values.length; i++) { // erro: <= permite i = 3, índice inexistente\n    System.out.println(values[i]);    // ArrayIndexOutOfBoundsException na última iteração\n}","fidelityText":"int[] valores = {10, 20, 30}; for (int i = 0; i <= valores.length; i++) { // erro: <= permite i = 3, índice inexistente System.out.println(valores[i]); // ArrayIndexOutOfBoundsException na última iteração }","highlightedHtml":"<span class=\"kw\">int</span>[] values = {<span class=\"num\">10</span>, <span class=\"num\">20</span>, <span class=\"num\">30</span>};\n<span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = <span class=\"num\">0</span>; i &lt;= values.length; i++) { <span class=\"com\">// erro: &lt;= permite i = 3, índice inexistente</span>\n    System.out.println(values[i]);    <span class=\"com\">// ArrayIndexOutOfBoundsException na última iteração</span>\n}","caption":"Exemplo executável de lacos-repeticao.","explanation":["Usar <= no limite quando o índice deveria ser exclusivo causa acesso fora dos limites do array na última iteração."],"commonMistakes":["Usar <= em vez de < ao comparar com length"]},{"id":"lacos-repeticao-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>break e continue não são a mesma coisa</h2>","fidelityText":"break e continue não são a mesma coisa"},{"id":"lacos-repeticao-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"for (int i = 1; i <= 5; i++) {\n    if (i == 3) continue; // pula só esta iteração, o laço continua\n    if (i == 5) break;    // encerra o laço inteiro, imediatamente\n    System.out.println(i); // imprime 1, 2, 4\n}","fidelityText":"for (int i = 1; i <= 5; i++) { if (i == 3) continue; // pula só esta iteração, o laço continua if (i == 5) break; // encerra o laço inteiro, imediatamente System.out.println(i); // imprime 1, 2, 4 }","highlightedHtml":"<span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = <span class=\"num\">1</span>; i &lt;= <span class=\"num\">5</span>; i++) {\n    <span class=\"kw\">if</span> (i == <span class=\"num\">3</span>) <span class=\"kw\">continue</span>; <span class=\"com\">// pula só esta iteração, o laço continua</span>\n    <span class=\"kw\">if</span> (i == <span class=\"num\">5</span>) <span class=\"kw\">break</span>;    <span class=\"com\">// encerra o laço inteiro, imediatamente</span>\n    System.out.println(i); <span class=\"com\">// imprime 1, 2, 4</span>\n}","caption":"Exemplo executável de lacos-repeticao.","explanation":["continue pula só a iteração atual; break encerra o laço inteiro imediatamente -- efeitos bem diferentes."],"commonMistakes":["Usar break esperando pular só uma iteração"]},{"id":"lacos-repeticao-content-10","type":"html","authorship":"legacy-preserved","html":"<p><code>continue</code> pula o restante do corpo e volta para a condição do laço (avançando para a próxima iteração); <code>break</code> sai do laço por completo, como se a condição tivesse se tornado falsa. Confundir os dois é um erro lógico silencioso: o laço roda, só que menos vezes (ou mais) do que o pretendido.</p>","fidelityText":"continue pula o restante do corpo e volta para a condição do laço (avançando para a próxima iteração); break sai do laço por completo, como se a condição tivesse se tornado falsa. Confundir os dois é um erro lógico silencioso: o laço roda, só que menos vezes (ou mais) do que o pretendido."},{"id":"lacos-repeticao-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Laços aninhados</h2>","fidelityText":"Laços aninhados"},{"id":"lacos-repeticao-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"for (int line = 0; line < 3; line++) {\n    for (int column = 0; column < 3; column++) {\n        System.out.print(line == column ? \"X\" : \".\");\n    }\n    System.out.println();\n}","fidelityText":"for (int linha = 0; linha < 3; linha++) { for (int coluna = 0; coluna < 3; coluna++) { System.out.print(linha == coluna ? \"X\" : \".\"); } System.out.println(); }","highlightedHtml":"<span class=\"kw\">for</span> (<span class=\"kw\">int</span> line = <span class=\"num\">0</span>; line &lt; <span class=\"num\">3</span>; line++) {\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> column = <span class=\"num\">0</span>; column &lt; <span class=\"num\">3</span>; column++) {\n        System.out.print(line == column ? <span class=\"str\">\"X\"</span> : <span class=\"str\">\".\"</span>);\n    }\n    System.out.println();\n}","caption":"Exemplo executável de lacos-repeticao.","explanation":["break/continue no laço interno não afetam o laço externo -- cada um controla apenas o laço mais próximo onde aparece."]},{"id":"lacos-repeticao-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Em laços aninhados, <code>break</code>/<code>continue</code> só afetam o laço mais interno onde aparecem — sair do laço externo a partir do interno exige uma variável de controle (uma flag booleana) ou reestruturar a lógica em um método com <code>return</code>. Não existe \"break duplo\" nativo em Java sem rótulo (<em>labeled break</em>), e mesmo esse recurso deve ser usado com parcimônia por reduzir a legibilidade.</p>","fidelityText":"Em laços aninhados, break/continue só afetam o laço mais interno onde aparecem — sair do laço externo a partir do interno exige uma variável de controle (uma flag booleana) ou reestruturar a lógica em um método com return. Não existe \"break duplo\" nativo em Java sem rótulo (labeled break), e mesmo esse recurso deve ser usado com parcimônia por reduzir a legibilidade."},{"id":"lacos-repeticao-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Sentinela e validação","prompt":"Leia números até receber zero. Ignore negativos, some positivos e conte quantos foram aceitos. Teste: zero como primeiro valor, apenas negativos e uma sequência mista. Depois, escreva um laço que imprime os índices válidos de um array de tamanho 5 e explique por que usar <= no limite seria um erro.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Sentinela e validaçãoFundamentoLeia números até receber zero. Ignore negativos, some positivos e conte quantos foram aceitos. Teste: zero como primeiro valor, apenas negativos e uma sequência mista. Depois, escreva um laço que imprime os índices válidos de um array de tamanho 5 e explique por que usar <= no limite seria um erro.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Sentinela e validação</h2><span class=\"exercise-tag f\">Fundamento</span></div><p>Leia números até receber zero. Ignore negativos, some positivos e conte quantos foram aceitos. Teste: zero como primeiro valor, apenas negativos e uma sequência mista. Depois, escreva um laço que imprime os índices válidos de um array de tamanho 5 e explique por que usar <code>&lt;=</code> no limite seria um erro.</p></div>"},{"id":"lacos-model","type":"mental-model","authorship":"authored","title":"Uma volta do for","body":"O for executa inicialização uma vez; testa a condição antes de cada volta; executa o corpo; aplica a atualização; e volta ao teste.","flow":["int i = 1","testar i <= 100","executar o corpo","executar i++","voltar ao teste"],"lifecycle":["i existe no escopo do for","ao falhar a condição, o laço termina"]},{"id":"lacos-quiz","type":"quiz","authorship":"authored","conceptId":"fronteira-off-by-one","prompt":"Quantas vezes executa for (int i = 0; i < 3; i++)?","options":[{"id":"lacos-q-a","label":"3 vezes, com i igual a 0, 1 e 2.","correct":true,"explanation":"O limite 3 é exclusivo; após i++ de 2 para 3, a condição falha."},{"id":"lacos-q-b","label":"4 vezes, incluindo i igual a 3.","correct":false,"explanation":"i < 3 é falso quando i vale 3."},{"id":"lacos-q-c","label":"Infinitas vezes porque i é recriado.","correct":false,"explanation":"A inicialização ocorre uma vez e i++ atualiza a mesma variável."}]}],"resources":[{"id":"lacos-jls-while","type":"reference","title":"JLS: while e do","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.12","reinforces":"Define quando condição e corpo são avaliados.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"lacos-jls-for","type":"reference","title":"JLS: for básico e enhanced for","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.14","reinforces":"Confirma ordem de inicialização, teste, atualização e iteração.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The loops iteration example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"int sum = 0;","instruction":"The loops iteration example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"arrays-matrizes","moduleId":"programming-fundamentals","order":7,"title":"Arrays, matrizes e raciocínio por índice","summary":"Array é um objeto de tamanho fixo que armazena elementos do mesmo tipo. A variável guarda uma referência; o objeto array conhece seu tamanho por length. Fixar isso evita a falsa ideia de que int[] é apenas uma lista de variáveis soltas.","objectives":["Criar, acessar e percorrer arrays por limites válidos","Explicar aliasing e cópia","Percorrer arrays de arrays inclusive irregulares"],"whyItExists":"Quando vários valores têm o mesmo papel, nomes separados impedem tratamento uniforme; um array agrupa uma quantidade fixa e permite processá-la por índice.","prerequisiteChapterIds":["lacos-repeticao"],"conceptIds":["copia-rasa-e-mutabilidade","valores-default-e-arrays-de-referencias","passagem-para-metodo-e-retorno","arrays-utility-class-pare-de-reinventar","matrizes-sao-arrays-de-arrays"],"introducedConceptIds":["array-indice-length","aliasing-array","array-multidimensional"],"usedConceptIds":["contrato-do-laco","while-for-foreach","fronteira-off-by-one","primitivo-referencia"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"arrays-intuition","type":"intuition","authorship":"authored","title":"Posições numeradas, tamanho fixo","body":"O array é um objeto criado com tamanho definido. length informa quantos elementos existem; como o primeiro índice é zero, o último é length - 1.","analogyLimit":"Uma fileira de posições ajuda com índices, mas a variável guarda uma referência ao array, não a fileira inteira."},{"id":"arrays-matrizes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Iniciante+</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#lacos-repeticao\">Laços</a></div></div>","fidelityText":"Dificuldade: Iniciante+Pré-requisito: Laços"},{"id":"arrays-matrizes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Array é um objeto de tamanho fixo que armazena elementos do mesmo tipo. A variável guarda uma referência; o objeto array conhece seu tamanho por <code>length</code>. Fixar isso evita a falsa ideia de que <code>int[]</code> é apenas uma lista de variáveis soltas.</p>","fidelityText":"Array é um objeto de tamanho fixo que armazena elementos do mesmo tipo. A variável guarda uma referência; o objeto array conhece seu tamanho por length. Fixar isso evita a falsa ideia de que int[] é apenas uma lista de variáveis soltas."},{"id":"arrays-matrizes-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"int[] grades = {7, 9, 6};\ngrades[1] = 10;\n\nfor (int i = 0; i < grades.length; i++) {\n    System.out.printf(\"grades[%d] = %d%n\", i, grades[i]);\n}","fidelityText":"int[] notas = {7, 9, 6}; notas[1] = 10; for (int i = 0; i < notas.length; i++) { System.out.printf(\"notas[%d] = %d%n\", i, notas[i]); }","highlightedHtml":"<span class=\"kw\">int</span>[] grades = {<span class=\"num\">7</span>, <span class=\"num\">9</span>, <span class=\"num\">6</span>};\ngrades[<span class=\"num\">1</span>] = <span class=\"num\">10</span>;\n\n<span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = <span class=\"num\">0</span>; i &lt; grades.length; i++) {\n    System.out.printf(<span class=\"str\">\"grades[%d] = %d%n\"</span>, i, grades[i]);\n}","caption":"Exemplo executável de arrays-matrizes.","explanation":["O array guarda uma referência; o objeto conhece o próprio tamanho por length."]},{"id":"arrays-matrizes-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Cópia rasa e mutabilidade</h2>","fidelityText":"Cópia rasa e mutabilidade"},{"id":"arrays-matrizes-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"int[] a = {1, 2};\nint[] b = a;                 // mesmo array\nint[] c = a.clone();         // novo array de primitivos\nb[0] = 99;                    // a[0] também vira 99","fidelityText":"int[] a = {1, 2}; int[] b = a; // mesmo array int[] c = a.clone(); // novo array de primitivos b[0] = 99; // a[0] também vira 99","highlightedHtml":"<span class=\"kw\">int</span>[] a = {<span class=\"num\">1</span>, <span class=\"num\">2</span>};\n<span class=\"kw\">int</span>[] b = a;                 <span class=\"com\">// mesmo array</span>\n<span class=\"kw\">int</span>[] c = a.clone();         <span class=\"com\">// novo array de primitivos</span>\nb[<span class=\"num\">0</span>] = <span class=\"num\">99</span>;                    <span class=\"com\">// a[0] também vira 99</span>","caption":"Exemplo executável de arrays-matrizes.","explanation":["Atribuir um array a outra variável copia a referência, não o conteúdo -- as duas variáveis passam a apontar para o mesmo array."],"commonMistakes":["Achar que int[] b = a; cria uma cópia independente"]},{"id":"arrays-matrizes-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Em arrays de objetos, <code>clone</code> cria outro array, mas copia referências dos elementos. Os objetos internos continuam compartilhados. Isso é uma cópia rasa.</p>","fidelityText":"Em arrays de objetos, clone cria outro array, mas copia referências dos elementos. Os objetos internos continuam compartilhados. Isso é uma cópia rasa."},{"id":"arrays-matrizes-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Valores default e arrays de referências</h2>","fidelityText":"Valores default e arrays de referências"},{"id":"arrays-matrizes-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"int[] integers = new int[3];      // {0, 0, 0} -- primitivos numéricos começam em zero\nboolean[] flags = new boolean[2]; // {false, false}\nString[] names = new String[3];    // {null, null, null} -- array de REFERÊNCIAS, não de objetos prontos\n\nSystem.out.println(names[0].length()); // NullPointerException -- a posição existe, mas está vazia (null)","fidelityText":"int[] inteiros = new int[3]; // {0, 0, 0} -- primitivos numéricos começam em zero boolean[] flags = new boolean[2]; // {false, false} String[] nomes = new String[3]; // {null, null, null} -- array de REFERÊNCIAS, não de objetos prontos System.out.println(nomes[0].length()); // NullPointerException -- a posição existe, mas está vazia (null)","highlightedHtml":"<span class=\"kw\">int</span>[] integers = <span class=\"kw\">new</span> <span class=\"kw\">int</span>[<span class=\"num\">3</span>];      <span class=\"com\">// {0, 0, 0} -- primitivos numéricos começam em zero</span>\n<span class=\"kw\">boolean</span>[] flags = <span class=\"kw\">new</span> <span class=\"kw\">boolean</span>[<span class=\"num\">2</span>]; <span class=\"com\">// {false, false}</span>\nString[] names = <span class=\"kw\">new</span> String[<span class=\"num\">3</span>];    <span class=\"com\">// {null, null, null} -- array de REFERÊNCIAS, não de objetos prontos</span>\n\nSystem.out.println(names[<span class=\"num\">0</span>].length()); <span class=\"com\">// NullPointerException -- a posição existe, mas está vazia (null)</span>","caption":"Exemplo executável de arrays-matrizes.","explanation":["Arrays de referência começam com null em cada posição -- acessar um membro de uma posição null lança NullPointerException, diferente de acessar um índice inexistente."],"commonMistakes":["Confundir ArrayIndexOutOfBoundsException com NullPointerException"]},{"id":"arrays-matrizes-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Um array de tipo referência (como <code>String[]</code>) não cria os objetos automaticamente — cada posição começa como <code>null</code> até você atribuir algo explicitamente. Acessar uma posição fora dos limites do array (índice negativo ou <code>&gt;= length</code>) lança <code>ArrayIndexOutOfBoundsException</code>; acessar um membro de uma posição que existe mas está <code>null</code> lança <code>NullPointerException</code> — são erros diferentes, com causas diferentes, e a mensagem da exceção diz qual dos dois aconteceu.</p>","fidelityText":"Um array de tipo referência (como String[]) não cria os objetos automaticamente — cada posição começa como null até você atribuir algo explicitamente. Acessar uma posição fora dos limites do array (índice negativo ou >= length) lança ArrayIndexOutOfBoundsException; acessar um membro de uma posição que existe mas está null lança NullPointerException — são erros diferentes, com causas diferentes, e a mensagem da exceção diz qual dos dois aconteceu."},{"id":"arrays-matrizes-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Passagem para método e retorno</h2>","fidelityText":"Passagem para método e retorno"},{"id":"arrays-matrizes-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"static void duplicarValues(int[] values) {\n    for (int i = 0; i < values.length; i++) values[i] *= 2;\n}\n\nstatic int[] createSequencia(int size) {\n    int[] result = new int[size];\n    for (int i = 0; i < size; i++) result[i] = i;\n    return result; // devolve a REFERÊNCIA -- o array não é copiado ao retornar\n}","fidelityText":"static void duplicarValores(int[] valores) { for (int i = 0; i < valores.length; i++) valores[i] *= 2; } static int[] criarSequencia(int tamanho) { int[] resultado = new int[tamanho]; for (int i = 0; i < tamanho; i++) resultado[i] = i; return resultado; // devolve a REFERÊNCIA -- o array não é copiado ao retornar }","highlightedHtml":"<span class=\"kw\">static void</span> <span class=\"fn\">duplicarValues</span>(<span class=\"kw\">int</span>[] values) {\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = <span class=\"num\">0</span>; i &lt; values.length; i++) values[i] *= <span class=\"num\">2</span>;\n}\n\n<span class=\"kw\">static int</span>[] <span class=\"fn\">createSequencia</span>(<span class=\"kw\">int</span> size) {\n    <span class=\"kw\">int</span>[] result = <span class=\"kw\">new</span> <span class=\"kw\">int</span>[size];\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = <span class=\"num\">0</span>; i &lt; size; i++) result[i] = i;\n    <span class=\"kw\">return</span> result; <span class=\"com\">// devolve a REFERÊNCIA -- o array não é copiado ao retornar</span>\n}","caption":"Exemplo executável de arrays-matrizes.","explanation":["Passar um array para um método passa a referência -- alterações nas posições são visíveis para quem chamou; retornar um array devolve a mesma referência, sem copiar."]},{"id":"arrays-matrizes-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Como array é um objeto, passá-lo para um método passa a referência (uma cópia da referência, não do conteúdo) — o método recebe acesso ao mesmo array do chamador, e alterações nas posições são visíveis para quem chamou. Isso é diferente de reatribuir o parâmetro (<code>valores = new int[0]</code> dentro do método não afeta a variável do chamador).</p>","fidelityText":"Como array é um objeto, passá-lo para um método passa a referência (uma cópia da referência, não do conteúdo) — o método recebe acesso ao mesmo array do chamador, e alterações nas posições são visíveis para quem chamou. Isso é diferente de reatribuir o parâmetro (valores = new int[0] dentro do método não afeta a variável do chamador)."},{"id":"arrays-matrizes-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Arrays utility class: pare de reinventar</h2>","fidelityText":"Arrays utility class: pare de reinventar"},{"id":"arrays-matrizes-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"import java.util.Arrays;\n\nint[] original = {5, 3, 1, 4};\nint[] copy = Arrays.copyOf(original, original.length); // cópia independente, mesmo tamanho\nint[] greater = Arrays.copyOf(original, 6);           // cópia com 2 posições extras, preenchidas com 0\nint[] fatia = Arrays.copyOfRange(original, 1, 3);  // {3, 1} -- início inclusivo, fim exclusivo\n\nArrays.fill(greater, 4, greater.length, -1); // preenche as posições 4 e 5 com -1\nArrays.sort(original);                       // ordena IN PLACE -- muda o array original, não retorna outro\nSystem.out.println(Arrays.toString(original)); // [1, 3, 4, 5] -- toString normal mostraria só o hash do objeto array\nSystem.out.println(Arrays.equals(original, copy)); // false -- copia não foi ordenada, o conteúdo diverge agora","fidelityText":"import java.util.Arrays; int[] original = {5, 3, 1, 4}; int[] copia = Arrays.copyOf(original, original.length); // cópia independente, mesmo tamanho int[] maior = Arrays.copyOf(original, 6); // cópia com 2 posições extras, preenchidas com 0 int[] fatia = Arrays.copyOfRange(original, 1, 3); // {3, 1} -- início inclusivo, fim exclusivo Arrays.fill(maior, 4, maior.length, -1); // preenche as posições 4 e 5 com -1 Arrays.sort(original); // ordena IN PLACE -- muda o array original, não retorna outro System.out.println(Arrays.toString(original)); // [1, 3, 4, 5] -- toString normal mostraria só o hash do objeto array System.out.println(Arrays.equals(original, copia)); // false -- copia não foi ordenada, o conteúdo diverge agora","highlightedHtml":"<span class=\"kw\">import</span> java.util.Arrays;\n\n<span class=\"kw\">int</span>[] original = {<span class=\"num\">5</span>, <span class=\"num\">3</span>, <span class=\"num\">1</span>, <span class=\"num\">4</span>};\n<span class=\"kw\">int</span>[] copy = Arrays.copyOf(original, original.length); <span class=\"com\">// cópia independente, mesmo tamanho</span>\n<span class=\"kw\">int</span>[] greater = Arrays.copyOf(original, <span class=\"num\">6</span>);           <span class=\"com\">// cópia com 2 posições extras, preenchidas com 0</span>\n<span class=\"kw\">int</span>[] fatia = Arrays.copyOfRange(original, <span class=\"num\">1</span>, <span class=\"num\">3</span>);  <span class=\"com\">// {3, 1} -- início inclusivo, fim exclusivo</span>\n\nArrays.fill(greater, <span class=\"num\">4</span>, greater.length, -<span class=\"num\">1</span>); <span class=\"com\">// preenche as posições 4 e 5 com -1</span>\nArrays.sort(original);                       <span class=\"com\">// ordena IN PLACE -- muda o array original, não retorna outro</span>\nSystem.out.println(Arrays.toString(original)); <span class=\"com\">// [1, 3, 4, 5] -- toString normal mostraria só o hash do objeto array</span>\nSystem.out.println(Arrays.equals(original, copy)); <span class=\"com\">// false -- copia não foi ordenada, o conteúdo diverge agora</span>","caption":"Exemplo executável de arrays-matrizes.","explanation":["Arrays.sort ordena in place (muda o array original); Arrays.copyOf/copyOfRange criam cópias independentes; Arrays.toString/equals substituem laços manuais."],"commonMistakes":["Usar System.out.println(array) esperando ver o conteúdo em vez do hash"]},{"id":"arrays-matrizes-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>binarySearch</code> exige array ordenado.</b> <code>Arrays.binarySearch(array, alvo)</code> só funciona corretamente se o array já estiver ordenado (normalmente com <code>Arrays.sort</code> antes) — em um array fora de ordem, o resultado é indefinido, não necessariamente \"não encontrado\". A precondição é responsabilidade de quem chama, o método não verifica.</div>","fidelityText":"binarySearch exige array ordenado. Arrays.binarySearch(array, alvo) só funciona corretamente se o array já estiver ordenado (normalmente com Arrays.sort antes) — em um array fora de ordem, o resultado é indefinido, não necessariamente \"não encontrado\". A precondição é responsabilidade de quem chama, o método não verifica."},{"id":"arrays-matrizes-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Nunca escreva um laço manual para copiar, preencher, imprimir ou comparar arrays de valores primitivos: <code>Arrays.copyOf</code>, <code>Arrays.fill</code>, <code>Arrays.toString</code> e <code>Arrays.equals</code> já existem, são testados e comunicam a intenção imediatamente a quem lê. <code>System.out.println(array)</code> direto imprime algo como <code>[I@1b6d3586</code> (o hash do objeto) — não o conteúdo; use sempre <code>Arrays.toString</code> para depurar.</p>","fidelityText":"Nunca escreva um laço manual para copiar, preencher, imprimir ou comparar arrays de valores primitivos: Arrays.copyOf, Arrays.fill, Arrays.toString e Arrays.equals já existem, são testados e comunicam a intenção imediatamente a quem lê. System.out.println(array) direto imprime algo como [I@1b6d3586 (o hash do objeto) — não o conteúdo; use sempre Arrays.toString para depurar."},{"id":"arrays-matrizes-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Matrizes são arrays de arrays</h2>","fidelityText":"Matrizes são arrays de arrays"},{"id":"arrays-matrizes-content-18","type":"html","authorship":"legacy-preserved","html":"<p><code>int[][]</code> não exige linhas do mesmo tamanho. Cada linha é outro array e pode até ser <code>null</code>. Percorra usando <code>matriz.length</code> para linhas e <code>matriz[i].length</code> para colunas.</p>","fidelityText":"int[][] não exige linhas do mesmo tamanho. Cada linha é outro array e pode até ser null. Percorra usando matriz.length para linhas e matriz[i].length para colunas."},{"id":"arrays-matrizes-code-19","type":"code","authorship":"legacy-preserved","language":"java","source":"int[][] triangular = {\n    {1},\n    {1, 2},\n    {1, 2, 3}\n}; // jagged array -- linhas de tamanhos diferentes, perfeitamente válido em Java\n\nfor (int line = 0; line < triangular.length; line++) {\n    for (int column = 0; column < triangular[line].length; column++) {\n        System.out.print(triangular[line][column] + \" \");\n    }\n    System.out.println();\n}","fidelityText":"int[][] triangular = { {1}, {1, 2}, {1, 2, 3} }; // jagged array -- linhas de tamanhos diferentes, perfeitamente válido em Java for (int linha = 0; linha < triangular.length; linha++) { for (int coluna = 0; coluna < triangular[linha].length; coluna++) { System.out.print(triangular[linha][coluna] + \" \"); } System.out.println(); }","highlightedHtml":"<span class=\"kw\">int</span>[][] triangular = {\n    {<span class=\"num\">1</span>},\n    {<span class=\"num\">1</span>, <span class=\"num\">2</span>},\n    {<span class=\"num\">1</span>, <span class=\"num\">2</span>, <span class=\"num\">3</span>}\n}; <span class=\"com\">// jagged array -- linhas de tamanhos diferentes, perfeitamente válido em Java</span>\n\n<span class=\"kw\">for</span> (<span class=\"kw\">int</span> line = <span class=\"num\">0</span>; line &lt; triangular.length; line++) {\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> column = <span class=\"num\">0</span>; column &lt; triangular[line].length; column++) {\n        System.out.print(triangular[line][column] + <span class=\"str\">\" \"</span>);\n    }\n    System.out.println();\n}","caption":"Exemplo executável de arrays-matrizes.","explanation":["Uma matriz Java é um array de arrays -- linhas podem ter tamanhos diferentes (jagged array) e até ser null."],"commonMistakes":["Assumir que toda matriz é retangular"]},{"id":"arrays-matrizes-content-20","type":"html","authorship":"legacy-preserved","html":"<p>Diferente de outras linguagens que exigem matrizes retangulares, Java literalmente representa <code>int[][]</code> como um array de arrays independentes — <strong>jagged arrays</strong> (linhas de tamanhos diferentes) não são um caso especial, são a estrutura de dados de verdade por trás de toda matriz Java. Uma linha específica pode até ser <code>null</code> (nunca inicializada), então percorrer sem checar <code>triangular[linha] != null</code> antes pode lançar <code>NullPointerException</code> se alguma linha ficou sem atribuição.</p>","fidelityText":"Diferente de outras linguagens que exigem matrizes retangulares, Java literalmente representa int[][] como um array de arrays independentes — jagged arrays (linhas de tamanhos diferentes) não são um caso especial, são a estrutura de dados de verdade por trás de toda matriz Java. Uma linha específica pode até ser null (nunca inicializada), então percorrer sem checar triangular[linha] != null antes pode lançar NullPointerException se alguma linha ficou sem atribuição."},{"id":"arrays-matrizes-content-21","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Array não cresce.</b> Se o tamanho varia durante a execução, a coleção adequada costuma ser <code>ArrayList</code>. Não reimplemente crescimento manual sem uma razão técnica.</div>","fidelityText":"Array não cresce. Se o tamanho varia durante a execução, a coleção adequada costuma ser ArrayList. Não reimplemente crescimento manual sem uma razão técnica."},{"id":"arrays-matrizes-exercise-22","type":"exercise","authorship":"legacy-preserved","title":"Estatísticas sem atalhos","prompt":"Receba um array e encontre mínimo, máximo, média e posição do primeiro máximo. Trate array vazio explicitamente.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Estatísticas sem atalhosPráticaReceba um array e encontre mínimo, máximo, média e posição do primeiro máximo. Trate array vazio explicitamente.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Estatísticas sem atalhos</h2><span class=\"exercise-tag m\">Prática</span></div><p>Receba um array e encontre mínimo, máximo, média e posição do primeiro máximo. Trate array vazio explicitamente.</p></div>"},{"id":"arrays-matrizes-exercise-23","type":"exercise","authorship":"legacy-preserved","title":"Aliasing e cópia","prompt":"Escreva um método que recebe um array de int, um método que o modifica in-place, e demonstre com Arrays.toString a diferença entre passar o array direto (o chamador vê a mudança) e passar Arrays.copyOf(array, array.length) (o chamador não vê). Depois monte uma matriz triangular (jagged) de 4 linhas e imprima usando dois laços aninhados.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Aliasing e cópiaDiagnósticoEscreva um método que recebe um array de int, um método que o modifica in-place, e demonstre com Arrays.toString a diferença entre passar o array direto (o chamador vê a mudança) e passar Arrays.copyOf(array, array.length) (o chamador não vê). Depois monte uma matriz triangular (jagged) de 4 linhas e imprima usando dois laços aninhados.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Aliasing e cópia</h2><span class=\"exercise-tag m\">Diagnóstico</span></div><p>Escreva um método que recebe um array de <code>int</code>, um método que o modifica in-place, e demonstre com <code>Arrays.toString</code> a diferença entre passar o array direto (o chamador vê a mudança) e passar <code>Arrays.copyOf(array, array.length)</code> (o chamador não vê). Depois monte uma matriz triangular (jagged) de 4 linhas e imprima usando dois laços aninhados.</p></div>"},{"id":"arrays-error","type":"error-case","authorship":"authored","title":"length não é um índice válido","scenario":"Acessar numeros[numeros.length].","symptom":"ArrayIndexOutOfBoundsException durante a execução.","cause":"Com n elementos, os índices válidos são 0 até n - 1.","diagnosis":["Anote length","Liste primeiro e último índice","Faça dry run da última iteração"],"correction":"Use i < numeros.length ao percorrer desde zero.","prevention":"Trate o limite superior como exclusivo."},{"id":"arrays-quiz","type":"quiz","authorship":"authored","conceptId":"aliasing-array","prompt":"Após int[] b = a; e b[0] = 9;, o que ocorre com a[0]?","options":[{"id":"arrays-q-a","label":"Também passa a valer 9.","correct":true,"explanation":"A atribuição copiou a referência; a e b alcançam o mesmo array."},{"id":"arrays-q-b","label":"Mantém o valor anterior porque o array foi copiado.","correct":false,"explanation":"Atribuição não cria um novo array nem copia seus elementos."},{"id":"arrays-q-c","label":"O código não compila porque arrays não podem ser atribuídos.","correct":false,"explanation":"Referências de arrays compatíveis podem ser atribuídas normalmente."}]}],"resources":[{"id":"arrays-jls","type":"reference","title":"JLS 10: arrays","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-10.html","reinforces":"Define criação, membros, índices e arrays de arrays.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"arrays-dev-java","type":"guide","title":"dev.java: arrays","url":"https://dev.java/learn/language-basics/arrays/","reinforces":"Mostra criação, inicialização e acesso em exemplos curtos.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The arrays matrices example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"int[] grades = {7, 9, 6};","instruction":"The arrays matrices example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"strings-wrapper","moduleId":"programming-fundamentals","order":8,"title":"String, imutabilidade e classes wrapper","summary":"String é objeto imutável. Operações como toUpperCase, replace e concatenação não modificam a instância; produzem outra. Ignorar o retorno é um erro lógico frequente.","objectives":["Explicar por que operações de String produzem novos valores","Comparar texto por conteúdo","Usar StringBuilder e converter texto com wrappers conscientemente"],"whyItExists":"Quase toda aplicação recebe, produz ou valida texto; compreender imutabilidade e conversão evita resultados descartados, comparações erradas e falhas com null.","prerequisiteChapterIds":["arrays-matrizes"],"conceptIds":["metodos-de-consulta-mais-usados","concatenacao-repetida","text-blocks-java-15","wrappers-e-autoboxing"],"introducedConceptIds":["string-imutavel","stringbuilder","wrapper-boxing-parsing"],"usedConceptIds":["identidade-conteudo","array-indice-length","primitivo-referencia","contrato-do-laco"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"strings-intuition","type":"intuition","authorship":"authored","title":"Transformar texto não altera o original","body":"Uma String não muda depois de criada. strip, toUpperCase e replace devolvem outra referência; se o resultado importa, ele precisa ser usado ou atribuído.","analogyLimit":"Falar em cópia ajuda, mas a JVM pode compartilhar representações internas; o contrato relevante é a imutabilidade observável."},{"id":"strings-wrapper-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Iniciante+</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#arrays-matrizes\">Arrays</a></div></div>","fidelityText":"Dificuldade: Iniciante+Pré-requisito: Arrays"},{"id":"strings-wrapper-content-2","type":"html","authorship":"legacy-preserved","html":"<p><code>String</code> é objeto imutável. Operações como <code>toUpperCase</code>, <code>replace</code> e concatenação não modificam a instância; produzem outra. Ignorar o retorno é um erro lógico frequente.</p>","fidelityText":"String é objeto imutável. Operações como toUpperCase, replace e concatenação não modificam a instância; produzem outra. Ignorar o retorno é um erro lógico frequente."},{"id":"strings-wrapper-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"String name = \"  Ada Lovelace  \";\nString normalizado = name.trim().toLowerCase();\nboolean contemAda = normalizado.contains(\"ada\");","fidelityText":"String nome = \" Ada Lovelace \"; String normalizado = nome.trim().toLowerCase(); boolean contemAda = normalizado.contains(\"ada\");","highlightedHtml":"String name = <span class=\"str\">\"  Ada Lovelace  \"</span>;\nString normalizado = name.trim().toLowerCase();\n<span class=\"kw\">boolean</span> contemAda = normalizado.contains(<span class=\"str\">\"ada\"</span>);","caption":"Exemplo executável de strings-wrapper.","explanation":["String é imutável -- trim()/toLowerCase() retornam uma nova String, não alteram a original."],"commonMistakes":["Ignorar o retorno de um método de String esperando mutação in-place"]},{"id":"strings-wrapper-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Métodos de consulta mais usados</h2>","fidelityText":"Métodos de consulta mais usados"},{"id":"strings-wrapper-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"String text = \"Java, the language\";\ntext.length();                 // 17 -- quantidade de caracteres UTF-16\ntext.isEmpty();                 // false -- length() == 0?\ntext.isBlank();                 // false -- só espaços em branco (ou vazio)?\ntext.charAt(0);                  // 'J'\ntext.indexOf(\"language\");       // 8 -- posição onde começa, ou -1 se não encontrar\ntext.contains(\"Java\");            // true\ntext.startsWith(\"Java\");          // true\ntext.endsWith(\"act\");            // true\ntext.substring(6);                // \"a linguagem\" -- a partir do índice 6, até o fim","fidelityText":"String texto = \"Java, a linguagem\"; texto.length(); // 17 -- quantidade de caracteres UTF-16 texto.isEmpty(); // false -- length() == 0? texto.isBlank(); // false -- só espaços em branco (ou vazio)? texto.charAt(0); // 'J' texto.indexOf(\"linguagem\"); // 8 -- posição onde começa, ou -1 se não encontrar texto.contains(\"Java\"); // true texto.startsWith(\"Java\"); // true texto.endsWith(\"agem\"); // true texto.substring(6); // \"a linguagem\" -- a partir do índice 6, até o fim","highlightedHtml":"String text = <span class=\"str\">\"Java, the language\"</span>;\ntext.length();                 <span class=\"com\">// 17 -- quantidade de caracteres UTF-16</span>\ntext.isEmpty();                 <span class=\"com\">// false -- length() == 0?</span>\ntext.isBlank();                 <span class=\"com\">// false -- só espaços em branco (ou vazio)?</span>\ntext.charAt(<span class=\"num\">0</span>);                  <span class=\"com\">// 'J'</span>\ntext.indexOf(<span class=\"str\">\"language\"</span>);       <span class=\"com\">// 8 -- posição onde começa, ou -1 se não encontrar</span>\ntext.contains(<span class=\"str\">\"Java\"</span>);            <span class=\"com\">// true</span>\ntext.startsWith(<span class=\"str\">\"Java\"</span>);          <span class=\"com\">// true</span>\ntext.endsWith(<span class=\"str\">\"act\"</span>);            <span class=\"com\">// true</span>\ntext.substring(<span class=\"num\">6</span>);                <span class=\"com\">// \"a linguagem\" -- a partir do índice 6, até o fim</span>","caption":"Exemplo executável de strings-wrapper.","explanation":["Métodos de consulta comuns de String -- length, isEmpty/isBlank, charAt, indexOf, contains, startsWith/endsWith, substring."]},{"id":"strings-wrapper-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>split</code> recebe uma expressão regular, não um texto literal.</b> <code>\"1.2.3\".split(\".\")</code> devolve um array <strong>vazio</strong>, porque <code>.</code> em regex significa \"qualquer caractere\" e casa com tudo — o resultado esperado exige <code>split(\"\\\\.\")</code> (escapando o ponto) ou <code>Pattern.quote(\".\")</code>. Isso surpreende iniciantes justamente porque para textos comuns (sem caracteres especiais de regex) <code>split</code> se comporta como esperado, escondendo o problema até o primeiro delimitador especial.</div>","fidelityText":"split recebe uma expressão regular, não um texto literal. \"1.2.3\".split(\".\") devolve um array vazio, porque . em regex significa \"qualquer caractere\" e casa com tudo — o resultado esperado exige split(\"\\\\.\") (escapando o ponto) ou Pattern.quote(\".\"). Isso surpreende iniciantes justamente porque para textos comuns (sem caracteres especiais de regex) split se comporta como esperado, escondendo o problema até o primeiro delimitador especial."},{"id":"strings-wrapper-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Concatenação repetida</h2>","fidelityText":"Concatenação repetida"},{"id":"strings-wrapper-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Em um laço grande, concatenar repetidamente pode criar muitos objetos temporários. Use <code>StringBuilder</code> quando estiver construindo texto incrementalmente.</p>","fidelityText":"Em um laço grande, concatenar repetidamente pode criar muitos objetos temporários. Use StringBuilder quando estiver construindo texto incrementalmente."},{"id":"strings-wrapper-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"StringBuilder builder = new StringBuilder();\nfor (String item : items) {\n    builder.append(item).append(System.lineSeparator());\n}\nString result = builder.toString();","fidelityText":"StringBuilder builder = new StringBuilder(); for (String item : itens) { builder.append(item).append(System.lineSeparator()); } String resultado = builder.toString();","highlightedHtml":"StringBuilder builder = <span class=\"kw\">new</span> StringBuilder();\n<span class=\"kw\">for</span> (String item : items) {\n    builder.append(item).append(System.lineSeparator());\n}\nString result = builder.toString();","caption":"Exemplo executável de strings-wrapper.","explanation":["StringBuilder mantém um buffer mutável -- evita criar um novo objeto String a cada concatenação dentro do laço."],"commonMistakes":["Concatenar Strings com + dentro de um laço grande"]},{"id":"strings-wrapper-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Cada <code>+</code> entre <code>String</code>s dentro de um laço cria um novo objeto <code>String</code> a cada iteração, descartando o anterior — para poucas concatenações isso não importa, mas em um laço com centenas ou milhares de iterações o custo cresce de forma perceptível. <code>StringBuilder</code> mantém um buffer mutável interno e só produz o <code>String</code> final uma vez, no <code>toString()</code>.</p>","fidelityText":"Cada + entre Strings dentro de um laço cria um novo objeto String a cada iteração, descartando o anterior — para poucas concatenações isso não importa, mas em um laço com centenas ou milhares de iterações o custo cresce de forma perceptível. StringBuilder mantém um buffer mutável interno e só produz o String final uma vez, no toString()."},{"id":"strings-wrapper-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Text blocks (Java 15+)</h2>","fidelityText":"Text blocks (Java 15+)"},{"id":"strings-wrapper-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"String json = \"\"\"\n    {\n      \"name\": \"Ada\",\n      \"year\": 1815\n    }\"\"\";\n// preserva quebras de linha e indentação relativa -- sem concatenar \\n manualmente","fidelityText":"String json = \"\"\" { \"nome\": \"Ada\", \"ano\": 1815 }\"\"\"; // preserva quebras de linha e indentação relativa -- sem concatenar \\n manualmente","highlightedHtml":"String json = <span class=\"str\">\"\"\"\n    {\n      \"name\": \"Ada\",\n      \"year\": 1815\n    }\"\"\"</span>;\n<span class=\"com\">// preserva quebras de linha e indentação relativa -- sem concatenar \\n manualmente</span>","caption":"Exemplo executável de strings-wrapper.","explanation":["Text blocks preservam quebras de linha e indentação relativa sem concatenar \\n manualmente."]},{"id":"strings-wrapper-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Wrappers e autoboxing</h2>","fidelityText":"Wrappers e autoboxing"},{"id":"strings-wrapper-content-14","type":"html","authorship":"legacy-preserved","html":"<p><code>Integer</code>, <code>Long</code>, <code>Double</code> e <code>Boolean</code> representam primitivos como objetos. São necessários em generics (que não aceitam tipos primitivos diretamente) e podem ser <code>null</code> — algo que um <code>int</code> nunca pode. A conversão automática entre <code>int</code> e <code>Integer</code> chama-se <strong>autoboxing</strong> (empacotar) e <strong>unboxing</strong> (desempacotar).</p>","fidelityText":"Integer, Long, Double e Boolean representam primitivos como objetos. São necessários em generics (que não aceitam tipos primitivos diretamente) e podem ser null — algo que um int nunca pode. A conversão automática entre int e Integer chama-se autoboxing (empacotar) e unboxing (desempacotar)."},{"id":"strings-wrapper-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"Integer counter = 0;      // autoboxing: int literal empacotado em Integer\ncounter++;                // unboxing, incrementa, autoboxing de volta -- funciona, mas com custo\n\nInteger valueNulo = null;\nint primitive = valueNulo;  // NullPointerException no unboxing -- null não vira 0 magicamente","fidelityText":"Integer contador = 0; // autoboxing: int literal empacotado em Integer contador++; // unboxing, incrementa, autoboxing de volta -- funciona, mas com custo Integer valorNulo = null; int primitivo = valorNulo; // NullPointerException no unboxing -- null não vira 0 magicamente","highlightedHtml":"Integer counter = <span class=\"num\">0</span>;      <span class=\"com\">// autoboxing: int literal empacotado em Integer</span>\ncounter++;                <span class=\"com\">// unboxing, incrementa, autoboxing de volta -- funciona, mas com custo</span>\n\nInteger valueNulo = <span class=\"kw\">null</span>;\n<span class=\"kw\">int</span> primitive = valueNulo;  <span class=\"com\">// NullPointerException no unboxing -- null não vira 0 magicamente</span>","caption":"Exemplo executável de strings-wrapper.","explanation":["Autoboxing/unboxing convertem automaticamente entre int e Integer -- mas desempacotar um Integer null lança NullPointerException, não produz zero."],"commonMistakes":["Usar Integer em vez de int em contexto que pode ser null sem tratar o caso"]},{"id":"strings-wrapper-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Desempacotar <code>null</code> lança <code>NullPointerException</code>, não zero.</b> É um dos bugs mais comuns ao migrar de <code>int</code> para <code>Integer</code> em campos opcionais (comum em entidades de banco, onde uma coluna pode ser <code>NULL</code>): o unboxing implícito (<code>Integer</code> usado onde um <code>int</code> é esperado, como numa comparação aritmética) falha silenciosamente até rodar com um valor ausente.</div>","fidelityText":"Desempacotar null lança NullPointerException, não zero. É um dos bugs mais comuns ao migrar de int para Integer em campos opcionais (comum em entidades de banco, onde uma coluna pode ser NULL): o unboxing implícito (Integer usado onde um int é esperado, como numa comparação aritmética) falha silenciosamente até rodar com um valor ausente."},{"id":"strings-wrapper-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"int value = Integer.parseInt(\"42\");   // texto -> int primitivo\nInteger object = Integer.valueOf(\"42\"); // texto -> Integer (objeto)\nInteger a = 100, b = 100;\nSystem.out.println(a == b); // true -- cache de Integer para valores entre -128 e 127\nInteger c = 200, d = 200;\nSystem.out.println(c == d); // false -- fora do cache, são objetos distintos; use equals()","fidelityText":"int valor = Integer.parseInt(\"42\"); // texto -> int primitivo Integer objeto = Integer.valueOf(\"42\"); // texto -> Integer (objeto) Integer a = 100, b = 100; System.out.println(a == b); // true -- cache de Integer para valores entre -128 e 127 Integer c = 200, d = 200; System.out.println(c == d); // false -- fora do cache, são objetos distintos; use equals()","highlightedHtml":"<span class=\"kw\">int</span> value = Integer.parseInt(<span class=\"str\">\"42\"</span>);   <span class=\"com\">// texto -&gt; int primitivo</span>\nInteger object = Integer.valueOf(<span class=\"str\">\"42\"</span>); <span class=\"com\">// texto -&gt; Integer (objeto)</span>\nInteger a = <span class=\"num\">100</span>, b = <span class=\"num\">100</span>;\nSystem.out.println(a == b); <span class=\"com\">// true -- cache de Integer para valores entre -128 e 127</span>\nInteger c = <span class=\"num\">200</span>, d = <span class=\"num\">200</span>;\nSystem.out.println(c == d); <span class=\"com\">// false -- fora do cache, são objetos distintos; use equals()</span>","caption":"Exemplo executável de strings-wrapper.","explanation":["Integer.parseInt devolve int primitivo; Integer.valueOf devolve o objeto -- o cache de Integer entre -128 e 127 faz == parecer funcionar só para valores pequenos."],"commonMistakes":["Comparar wrappers com == em vez de equals()"]},{"id":"strings-wrapper-content-18","type":"html","authorship":"legacy-preserved","html":"<p>A JVM mantém um cache de instâncias <code>Integer</code> para valores pequenos (-128 a 127) por otimização — isso faz <code>==</code> \"parecer funcionar\" para números pequenos e falhar de forma inconsistente para números maiores. A lição prática: nunca compare wrappers com <code>==</code>, sempre use <code>equals()</code> (ou <code>Objects.equals</code> se um dos lados pode ser <code>null</code>).</p>","fidelityText":"A JVM mantém um cache de instâncias Integer para valores pequenos (-128 a 127) por otimização — isso faz == \"parecer funcionar\" para números pequenos e falhar de forma inconsistente para números maiores. A lição prática: nunca compare wrappers com ==, sempre use equals() (ou Objects.equals se um dos lados pode ser null)."},{"id":"strings-wrapper-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Dinheiro não é <code>double</code>.</b> Para valores monetários decimais exatos, estude <code>BigDecimal</code>, escala e modo de arredondamento.</div>","fidelityText":"Dinheiro não é double. Para valores monetários decimais exatos, estude BigDecimal, escala e modo de arredondamento."},{"id":"strings-wrapper-exercise-20","type":"exercise","authorship":"legacy-preserved","title":"Normalizador","prompt":"Normalize nomes removendo espaços externos, recusando texto vazio e capitalizando cada palavra sem destruir acentos.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"NormalizadorFundamentoNormalize nomes removendo espaços externos, recusando texto vazio e capitalizando cada palavra sem destruir acentos.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Normalizador</h2><span class=\"exercise-tag f\">Fundamento</span></div><p>Normalize nomes removendo espaços externos, recusando texto vazio e capitalizando cada palavra sem destruir acentos.</p></div>"},{"id":"strings-wrapper-exercise-21","type":"exercise","authorship":"legacy-preserved","title":"split traiçoeiro","prompt":"Rode \"1.2.3\".split(\".\") e imprima o tamanho do array resultante. Explique o resultado, depois corrija usando o escape correto para dividir por ponto literal.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"split traiçoeiroDiagnósticoRode \"1.2.3\".split(\".\") e imprima o tamanho do array resultante. Explique o resultado, depois corrija usando o escape correto para dividir por ponto literal.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>split traiçoeiro</h2><span class=\"exercise-tag m\">Diagnóstico</span></div><p>Rode <code>\"1.2.3\".split(\".\")</code> e imprima o tamanho do array resultante. Explique o resultado, depois corrija usando o escape correto para dividir por ponto literal.</p></div>"},{"id":"strings-comparison","type":"comparison","authorship":"authored","title":"String, StringBuilder e wrapper","criteria":["responsabilidade","mutabilidade","uso"],"alternatives":[{"name":"String","values":["texto","imutável","equals compara conteúdo"],"useWhen":"representar um valor textual","avoidWhen":"acumular milhares de trechos em laço"},{"name":"StringBuilder","values":["montagem de texto","mutável","append altera o builder"],"useWhen":"construir texto em várias etapas locais","avoidWhen":"expor como valor de domínio"},{"name":"Integer","values":["objeto para int","pode ser null","parseInt converte texto"],"useWhen":"API exige objeto ou texto precisa virar int","avoidWhen":"null não tem significado e int basta"}]},{"id":"strings-quiz","type":"quiz","authorship":"authored","conceptId":"string-imutavel","prompt":"Depois de String nome = \" Ana \"; nome.strip();, qual é o valor de nome?","options":[{"id":"strings-q-a","label":"Continua sendo \" Ana \".","correct":true,"explanation":"strip devolve uma nova String; o retorno foi descartado."},{"id":"strings-q-b","label":"Passa a ser \"Ana\".","correct":false,"explanation":"Isso exigiria nome = nome.strip()."},{"id":"strings-q-c","label":"O código falha porque String não possui strip.","correct":false,"explanation":"strip existe; imutabilidade muda seu retorno, não a disponibilidade do método."}]}],"resources":[{"id":"strings-api","type":"official-docs","title":"API String Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html","reinforces":"Confirma imutabilidade, retornos e igualdade de String.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"strings-builder-api","type":"official-docs","title":"API StringBuilder Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/StringBuilder.html","reinforces":"Documenta buffer mutável, append e conversão final.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The strings wrapper example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"String name = \"  Ada Lovelace  \";","instruction":"The strings wrapper example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"metodos-escopo","moduleId":"programming-fundamentals","order":9,"title":"Métodos, parâmetros, retorno e escopo","summary":"Método dá nome a uma operação e cria um limite de responsabilidade. Um bom método recebe apenas o necessário, devolve um resultado claro e evita alterar estado que o nome não anuncia.","objectives":["Definir contratos por parâmetros, retorno e efeitos","Prever passagem por valor com primitivos e referências","Distinguir escopo, sobrecarga e varargs"],"whyItExists":"Um programa em um único bloco repete regras e mistura responsabilidades; métodos dão nome a uma operação e tornam entradas, resultado e limites explícitos.","prerequisiteChapterIds":["strings-wrapper"],"conceptIds":["java-passa-tudo-por-valor","escopo-e-tempo-de-vida","sobrecarga-overload","varargs"],"introducedConceptIds":["metodo-contrato","passagem-por-valor","escopo-sobrecarga-varargs"],"usedConceptIds":["variavel-tipo-estatico","primitivo-referencia","array-indice-length","string-imutavel"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"metodos-intuition","type":"intuition","authorship":"authored","title":"Um método promete uma operação","body":"Leia uma assinatura como contrato: o nome comunica intenção, parâmetros são dados de entrada e o tipo de retorno descreve o resultado. Impressão e mutação são efeitos adicionais e precisam ser deliberados.","analogyLimit":"Método não é uma caixa isolada: pode receber referências para objetos mutáveis e produzir efeitos observáveis."},{"id":"metodos-escopo-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Iniciante+</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#strings-wrapper\">String e wrappers</a></div></div>","fidelityText":"Dificuldade: Iniciante+Pré-requisito: String e wrappers"},{"id":"metodos-escopo-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Método dá nome a uma operação e cria um limite de responsabilidade. Um bom método recebe apenas o necessário, devolve um resultado claro e evita alterar estado que o nome não anuncia.</p>","fidelityText":"Método dá nome a uma operação e cria um limite de responsabilidade. Um bom método recebe apenas o necessário, devolve um resultado claro e evita alterar estado que o nome não anuncia."},{"id":"metodos-escopo-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"static double calculateAverage(double[] values) {\n    if (values.length == 0) {\n        throw new IllegalArgumentException(\"Array empty\");\n    }\n    double sum = 0;\n    for (double value : values) sum += value;\n    return sum / values.length;\n}","fidelityText":"static double calcularMedia(double[] valores) { if (valores.length == 0) { throw new IllegalArgumentException(\"Array vazio\"); } double soma = 0; for (double valor : valores) soma += valor; return soma / valores.length; }","highlightedHtml":"<span class=\"kw\">static double</span> <span class=\"fn\">calculateAverage</span>(<span class=\"kw\">double</span>[] values) {\n    <span class=\"kw\">if</span> (values.length == <span class=\"num\">0</span>) {\n        <span class=\"kw\">throw new</span> IllegalArgumentException(<span class=\"str\">\"Array empty\"</span>);\n    }\n    <span class=\"kw\">double</span> sum = <span class=\"num\">0</span>;\n    <span class=\"kw\">for</span> (<span class=\"kw\">double</span> value : values) sum += value;\n    <span class=\"kw\">return</span> sum / values.length;\n}","caption":"Exemplo executável de metodos-escopo.","explanation":["Validar a precondição (array vazio) com uma exceção explícita evita dividir por zero silenciosamente."],"commonMistakes":["Deixar o método dividir por zero sem validar o array vazio"]},{"id":"metodos-escopo-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Java passa tudo por valor</h2>","fidelityText":"Java passa tudo por valor"},{"id":"metodos-escopo-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Para primitivos, o método recebe uma cópia do valor. Para objetos, recebe uma cópia da referência. O método pode alterar o objeto apontado, mas reatribuir o parâmetro não muda a variável do chamador.</p>","fidelityText":"Para primitivos, o método recebe uma cópia do valor. Para objetos, recebe uma cópia da referência. O método pode alterar o objeto apontado, mas reatribuir o parâmetro não muda a variável do chamador."},{"id":"metodos-escopo-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Escopo e tempo de vida</h2>","fidelityText":"Escopo e tempo de vida"},{"id":"metodos-escopo-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Uma variável local existe apenas no bloco em que foi declarada. Prefira o menor escopo possível: isso reduz estados simultâneos e impede uso acidental antes ou depois do ponto correto.</p>","fidelityText":"Uma variável local existe apenas no bloco em que foi declarada. Prefira o menor escopo possível: isso reduz estados simultâneos e impede uso acidental antes ou depois do ponto correto."},{"id":"metodos-escopo-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Sobrecarga (overload)</h2>","fidelityText":"Sobrecarga (overload)"},{"id":"metodos-escopo-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"static int sum(int a, int b) { return a + b; }\nstatic double sum(double a, double b) { return a + b; }\n// static double somar(int a, int b) { ... } // ERRO: não compila -- retorno sozinho não distingue sobrecarga","fidelityText":"static int somar(int a, int b) { return a + b; } static double somar(double a, double b) { return a + b; } // static double somar(int a, int b) { ... } // ERRO: não compila -- retorno sozinho não distingue sobrecarga","highlightedHtml":"<span class=\"kw\">static int</span> <span class=\"fn\">sum</span>(<span class=\"kw\">int</span> a, <span class=\"kw\">int</span> b) { <span class=\"kw\">return</span> a + b; }\n<span class=\"kw\">static double</span> <span class=\"fn\">sum</span>(<span class=\"kw\">double</span> a, <span class=\"kw\">double</span> b) { <span class=\"kw\">return</span> a + b; }\n<span class=\"com\">// static double somar(int a, int b) { ... } // ERRO: não compila -- retorno sozinho não distingue sobrecarga</span>","caption":"Exemplo executável de metodos-escopo.","explanation":["Sobrecarga distingue métodos pela lista de parâmetros -- o tipo de retorno sozinho não é suficiente para o compilador escolher a versão certa."],"commonMistakes":["Tentar sobrecarregar dois métodos que diferem só no tipo de retorno"]},{"id":"metodos-escopo-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Métodos podem compartilhar o nome se diferirem na lista de parâmetros (quantidade ou tipo). O tipo de retorno sozinho não distingue sobrecargas — o compilador escolhe qual versão chamar olhando os <strong>argumentos</strong> na chamada, nunca o que você faz com o retorno. Use sobrecarga para variações coerentes da mesma operação conceitual, não para esconder comportamentos diferentes atrás do mesmo nome.</p>","fidelityText":"Métodos podem compartilhar o nome se diferirem na lista de parâmetros (quantidade ou tipo). O tipo de retorno sozinho não distingue sobrecargas — o compilador escolhe qual versão chamar olhando os argumentos na chamada, nunca o que você faz com o retorno. Use sobrecarga para variações coerentes da mesma operação conceitual, não para esconder comportamentos diferentes atrás do mesmo nome."},{"id":"metodos-escopo-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Varargs</h2>","fidelityText":"Varargs"},{"id":"metodos-escopo-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"static int sumAll(int... values) { // varargs -- por baixo, é simplesmente um int[]\n    int total = 0;\n    for (int value : values) total += value;\n    return total;\n}\n\nsumAll();           // 0 -- varargs aceita zero argumentos, vira um array vazio\nsumAll(1, 2, 3);      // 6\nsumAll(new int[]{1, 2}); // também aceita um array explícito diretamente","fidelityText":"static int somarTodos(int... valores) { // varargs -- por baixo, é simplesmente um int[] int total = 0; for (int valor : valores) total += valor; return total; } somarTodos(); // 0 -- varargs aceita zero argumentos, vira um array vazio somarTodos(1, 2, 3); // 6 somarTodos(new int[]{1, 2}); // também aceita um array explícito diretamente","highlightedHtml":"<span class=\"kw\">static int</span> <span class=\"fn\">sumAll</span>(<span class=\"kw\">int</span>... values) { <span class=\"com\">// varargs -- por baixo, é simplesmente um int[]</span>\n    <span class=\"kw\">int</span> total = <span class=\"num\">0</span>;\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> value : values) total += value;\n    <span class=\"kw\">return</span> total;\n}\n\nsumAll();           <span class=\"com\">// 0 -- varargs aceita zero argumentos, vira um array vazio</span>\nsumAll(<span class=\"num\">1</span>, <span class=\"num\">2</span>, <span class=\"num\">3</span>);      <span class=\"com\">// 6</span>\nsumAll(<span class=\"kw\">new int</span>[]{<span class=\"num\">1</span>, <span class=\"num\">2</span>}); <span class=\"com\">// também aceita um array explícito diretamente</span>","caption":"Exemplo executável de metodos-escopo.","explanation":["Varargs (int...) é açúcar sintático para um array -- só pode ser o último parâmetro da lista."],"commonMistakes":["Declarar mais de um parâmetro varargs no mesmo método"]},{"id":"metodos-escopo-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Um parâmetro varargs (<code>int...</code>) é açúcar sintático para receber um array — dentro do método, <code>valores</code> é literalmente um <code>int[]</code>. Varargs só pode ser o <strong>último</strong> parâmetro da lista, e um método não pode ter dois parâmetros varargs (ambiguidade óbvia para o compilador resolver).</p>","fidelityText":"Um parâmetro varargs (int...) é açúcar sintático para receber um array — dentro do método, valores é literalmente um int[]. Varargs só pode ser o último parâmetro da lista, e um método não pode ter dois parâmetros varargs (ambiguidade óbvia para o compilador resolver)."},{"id":"metodos-escopo-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>O tamanho não é o único sinal.</b> Extraia um método quando um trecho tem um nome conceitual melhor do que o comentário que seria necessário para explicá-lo.</div>","fidelityText":"O tamanho não é o único sinal. Extraia um método quando um trecho tem um nome conceitual melhor do que o comentário que seria necessário para explicá-lo."},{"id":"metodos-escopo-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Decomposição","prompt":"Crie um programa de relatório de notas separando leitura, validação, cálculo e apresentação. Nenhum método deve misturar as quatro responsabilidades.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"DecomposiçãoPráticaCrie um programa de relatório de notas separando leitura, validação, cálculo e apresentação. Nenhum método deve misturar as quatro responsabilidades.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Decomposição</h2><span class=\"exercise-tag m\">Prática</span></div><p>Crie um programa de relatório de notas separando leitura, validação, cálculo e apresentação. Nenhum método deve misturar as quatro responsabilidades.</p></div>"},{"id":"metodos-escopo-exercise-16","type":"exercise","authorship":"legacy-preserved","title":"Sobrecarga vs. varargs","prompt":"Escreva duas versões sobrecarregadas de um método formatar (uma para int, outra para double). Depois reescreva a soma de uma lista de números usando varargs em vez de receber um array explícito, e explique quando cada abordagem comunica melhor a intenção.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Sobrecarga vs. varargsDiagnósticoEscreva duas versões sobrecarregadas de um método formatar (uma para int, outra para double). Depois reescreva a soma de uma lista de números usando varargs em vez de receber um array explícito, e explique quando cada abordagem comunica melhor a intenção.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Sobrecarga vs. varargs</h2><span class=\"exercise-tag m\">Diagnóstico</span></div><p>Escreva duas versões sobrecarregadas de um método <code>formatar</code> (uma para <code>int</code>, outra para <code>double</code>). Depois reescreva a soma de uma lista de números usando varargs em vez de receber um array explícito, e explique quando cada abordagem comunica melhor a intenção.</p></div>"},{"id":"metodos-escopo-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Pronto para POO quando</h2><ul><li>Você escreve e chama métodos sem copiar exemplos.</li><li>Explica passagem por valor em primitivos e referências.</li><li>Usa retorno em vez de imprimir dentro de toda função.</li><li>Consegue dividir um problema em operações pequenas e nomeadas.</li></ul></div>","fidelityText":"Pronto para POO quandoVocê escreve e chama métodos sem copiar exemplos.Explica passagem por valor em primitivos e referências.Usa retorno em vez de imprimir dentro de toda função.Consegue dividir um problema em operações pequenas e nomeadas."},{"id":"metodos-varargs","type":"concept","authorship":"authored","title":"Varargs é um array na entrada do método","body":"Em static int somar(int... valores), quem chama pode passar zero ou vários int. Dentro do método, valores é int[]. Só pode existir um varargs, ele deve ser o último parâmetro e chamadas com sobrecargas parecidas podem ficar ambíguas."},{"id":"metodos-model","type":"mental-model","authorship":"authored","title":"Java copia todo argumento","body":"Ao chamar um método, cada parâmetro recebe uma cópia do valor do argumento. Copiar int cria outro número independente; copiar uma referência permite alcançar o mesmo objeto, mas reatribuir o parâmetro não reatribui a variável de quem chamou.","flow":["Avaliar argumentos","Copiar valores para parâmetros","Executar corpo em novo escopo","produzir retorno ou efeito"],"ownership":["Parâmetros pertencem à chamada","Objetos mutáveis podem ser compartilhados por referências copiadas"]},{"id":"metodos-quiz","type":"quiz","authorship":"authored","conceptId":"passagem-por-valor","prompt":"Um método recebe int[] dados e executa dados[0] = 7. Por que o chamador observa a mudança?","options":[{"id":"metodos-q-a","label":"A referência foi copiada, e ambas alcançam o mesmo array.","correct":true,"explanation":"Java passou por valor; o valor copiado era uma referência ao mesmo objeto mutável."},{"id":"metodos-q-b","label":"Arrays são passados por referência, ao contrário de todos os objetos.","correct":false,"explanation":"Arrays também seguem passagem por valor de referência."},{"id":"metodos-q-c","label":"O parâmetro substituiu automaticamente a variável do chamador.","correct":false,"explanation":"Reatribuir o parâmetro não altera a variável externa; aqui houve mutação do objeto."}]}],"resources":[{"id":"metodos-jls-declare","type":"reference","title":"JLS: declaração de métodos","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4","reinforces":"Define assinatura, parâmetros, varargs, retorno e corpo.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"metodos-jls-call","type":"reference","title":"JLS: invocação de métodos","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.12","reinforces":"Explica seleção de sobrecarga e avaliação de argumentos.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The methods scope example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"static double calculateAverage(double[] values) {","instruction":"The methods scope example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"entrada-console","moduleId":"programming-fundamentals","order":10,"title":"Entrada pelo console, parsing e validação","summary":"Neste capítulo você começa com o menor programa interativo possível e o evolui em camadas. Primeiro leremos texto; depois entenderemos o caminho percorrido pelos caracteres; somente então entraremos em conversão, validação, término da entrada e valores monetários.","objectives":["Explicar CLI, stdin, stdout e o papel do Scanner","Ler linhas de forma previsível e converter explicitamente","Separar parsing, validação, regra e tratamento de EOF"],"whyItExists":"Programas úteis recebem dados externos; o console é a primeira fronteira em que texto incerto precisa virar valores confiáveis sem misturar interação com regra de negócio.","prerequisiteChapterIds":["metodos-escopo"],"conceptIds":["antes-do-codigo-o-que-e-um-programa-de-console","camada-1-seu-primeiro-uso-de-scanner","camada-2-linha-token-e-buffer","camada-3-ler-normalizar-converter-e-validar","camada-4-quando-a-entrada-termina","aprofundamento-dinheiro-e-formato-regional","depois-do-scanner-arquivos-sockets-e-protocolos","uma-previa-nao-um-pre-requisito"],"introducedConceptIds":["cli-stdin-stdout","scanner-token-linha","parse-validacao-eof"],"usedConceptIds":["metodo-contrato","escopo-sobrecarga-varargs","string-imutavel","wrapper-boxing-parsing","contrato-do-laco","ramificacao-condicional"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"console-intuition","type":"intuition","authorship":"authored","title":"Primeiro texto; depois significado","body":"O teclado entrega caracteres ao fluxo de entrada padrão. Scanner organiza essa leitura; seu programa decide como converter o texto e se o valor respeita a regra. São responsabilidades diferentes.","analogyLimit":"Pensar em um cano ajuda no fluxo, mas stdin não é necessariamente teclado: pode receber conteúdo redirecionado de um arquivo."},{"id":"entrada-console-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-java\">Java</span><div class=\"meta-item\">Dificuldade: <b>Iniciante</b></div><div class=\"time-est\">⏱ <b>~3h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#metodos-escopo\">Métodos e escopo</a></div></div>","fidelityText":"JavaDificuldade: Iniciante⏱ ~3h de estudo e práticaPré-requisitos: Métodos e escopo"},{"id":"entrada-console-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Neste capítulo você começa com o menor programa interativo possível e o evolui em camadas. Primeiro leremos texto; depois entenderemos o caminho percorrido pelos caracteres; somente então entraremos em conversão, validação, término da entrada e valores monetários.</p>","fidelityText":"Neste capítulo você começa com o menor programa interativo possível e o evolui em camadas. Primeiro leremos texto; depois entenderemos o caminho percorrido pelos caracteres; somente então entraremos em conversão, validação, término da entrada e valores monetários."},{"id":"entrada-console-content-3","type":"html","authorship":"legacy-preserved","html":"<ol class=\"learning-layers\"><li><span>1</span><div><strong>Fazer funcionar</strong><p>Ler nome e idade como texto e imprimir uma resposta.</p></div></li><li><span>2</span><div><strong>Entender</strong><p>Distinguir terminal, CLI, entrada, saída, linha e buffer.</p></div></li><li><span>3</span><div><strong>Tornar resistente</strong><p>Converter, validar e repetir a pergunta sem encerrar o programa.</p></div></li><li><span>4</span><div><strong>Aprofundar</strong><p>Tratar EOF, dinheiro, formatos regionais e propriedade de recursos.</p></div></li></ol>","fidelityText":"1Fazer funcionarLer nome e idade como texto e imprimir uma resposta.2EntenderDistinguir terminal, CLI, entrada, saída, linha e buffer.3Tornar resistenteConverter, validar e repetir a pergunta sem encerrar o programa.4AprofundarTratar EOF, dinheiro, formatos regionais e propriedade de recursos."},{"id":"entrada-console-content-4","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Antes do código: o que é um programa de console?</h2></div>\n    <p>Estes nomes descrevem o ambiente no qual o programa conversa com a pessoa. Nenhum conhecimento de rede é necessário para começar.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Terminal</dt><dd>A janela textual que mostra a saída do processo e encaminha a ele o que foi digitado.</dd></div><div class=\"concept-card\"><dt>CLI</dt><dd><em>Command-line interface</em>, ou interface de linha de comando: uma interface controlada por texto, argumentos e comandos, em vez de botões. Um menu textual simples já é uma CLI.</dd></div><div class=\"concept-card\"><dt>Entrada padrão</dt><dd>Canal de entrada do processo, representado em Java por <code>System.in</code>. No uso comum, recebe o que é digitado no terminal.</dd></div><div class=\"concept-card\"><dt>Saída padrão</dt><dd>Canal normal de saída, representado por <code>System.out</code>. <code>System.err</code> é um canal separado para erros e diagnóstico.</dd></div><div class=\"concept-card\"><dt>Fluxo de dados</dt><dd>Uma sequência de dados que chega ou sai ao longo do tempo. Aqui “fluxo” não é a API <code>Stream</code> de coleções.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoAntes do código: o que é um programa de console? Estes nomes descrevem o ambiente no qual o programa conversa com a pessoa. Nenhum conhecimento de rede é necessário para começar. TerminalA janela textual que mostra a saída do processo e encaminha a ele o que foi digitado.CLICommand-line interface, ou interface de linha de comando: uma interface controlada por texto, argumentos e comandos, em vez de botões. Um menu textual simples já é uma CLI.Entrada padrãoCanal de entrada do processo, representado em Java por System.in. No uso comum, recebe o que é digitado no terminal.Saída padrãoCanal normal de saída, representado por System.out. System.err é um canal separado para erros e diagnóstico.Fluxo de dadosUma sequência de dados que chega ou sai ao longo do tempo. Aqui “fluxo” não é a API Stream de coleções."},{"id":"entrada-console-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Camada 1 — seu primeiro uso de Scanner</h2>","fidelityText":"Camada 1 — seu primeiro uso de Scanner"},{"id":"entrada-console-content-6","type":"html","authorship":"legacy-preserved","html":"<p><code>Scanner</code> é uma classe da biblioteca padrão que ajuda a separar a entrada em linhas ou partes menores. O <code>import</code> informa ao compilador onde a classe está. <code>new Scanner(System.in)</code> cria um leitor ligado à entrada padrão, e <code>nextLine()</code> espera até receber uma linha terminada por Enter.</p>","fidelityText":"Scanner é uma classe da biblioteca padrão que ajuda a separar a entrada em linhas ou partes menores. O import informa ao compilador onde a classe está. new Scanner(System.in) cria um leitor ligado à entrada padrão, e nextLine() espera até receber uma linha terminada por Enter."},{"id":"entrada-console-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"import java.util.Scanner;\n\npublic class Greeting {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n\n        System.out.print(\"Which and the its name? \");\n        String name = scanner.nextLine();\n\n        System.out.println(\"Hello, \" + name + \"!\");\n    }\n}","fidelityText":"import java.util.Scanner; public class Saudacao { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.print(\"Qual é o seu nome? \"); String nome = scanner.nextLine(); System.out.println(\"Olá, \" + nome + \"!\"); } }","highlightedHtml":"<span class=\"kw\">import</span> java.util.Scanner;\n\n<span class=\"kw\">public class</span> Greeting {\n    <span class=\"kw\">public static void</span> main(String[] args) {\n        Scanner scanner = <span class=\"kw\">new</span> Scanner(System.in);\n\n        System.out.print(<span class=\"str\">\"Which and the its name? \"</span>);\n        String name = scanner.nextLine();\n\n        System.out.println(<span class=\"str\">\"Hello, \"</span> + name + <span class=\"str\">\"!\"</span>);\n    }\n}","caption":"Exemplo executável de entrada-console.","explanation":["System.in é o fluxo de entrada do processo e Scanner fornece operações de leitura sobre ele.","try-with-resources fecharia o Scanner ao fim; em uma aplicação simples isso coincide com o fim do processo."],"commonMistakes":["Usar Scanner sem import","Fechar System.in em componente reutilizável"]},{"id":"entrada-console-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Leia o programa de cima para baixo:</b> importar a classe → iniciar o programa → criar o leitor → mostrar a pergunta → aguardar uma linha → guardar a linha em <code>nome</code> → imprimir a resposta. Copie, compile e execute esta versão antes de continuar.</div>","fidelityText":"Leia o programa de cima para baixo: importar a classe → iniciar o programa → criar o leitor → mostrar a pergunta → aguardar uma linha → guardar a linha em nome → imprimir a resposta. Copie, compile e execute esta versão antes de continuar."},{"id":"entrada-console-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Camada 2 — linha, token e buffer</h2>","fidelityText":"Camada 2 — linha, token e buffer"},{"id":"entrada-console-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Ao pressionar Enter, os caracteres digitados e uma marca de fim de linha entram no canal. O <strong>buffer</strong> é uma pequena área temporária que guarda dados já recebidos, mas ainda não consumidos pelo programa. Uma <strong>linha</strong> termina nessa marca; um <strong>token</strong> é apenas uma parte separada normalmente por espaço.</p>","fidelityText":"Ao pressionar Enter, os caracteres digitados e uma marca de fim de linha entram no canal. O buffer é uma pequena área temporária que guarda dados já recebidos, mas ainda não consumidos pelo programa. Uma linha termina nessa marca; um token é apenas uma parte separada normalmente por espaço."},{"id":"entrada-console-content-11","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Método</th><th>O que consome</th><th>Exemplo</th></tr><tr><td><code>nextLine()</code></td><td>o restante da linha, incluindo sua terminação</td><td><code>\"Ana Maria\"</code></td></tr><tr><td><code>next()</code></td><td>o próximo token</td><td><code>\"Ana\"</code></td></tr><tr><td><code>nextInt()</code></td><td>o próximo token interpretado como inteiro</td><td><code>21</code></td></tr></tbody></table>","fidelityText":"MétodoO que consomeExemplonextLine()o restante da linha, incluindo sua terminação\"Ana Maria\"next()o próximo token\"Ana\"nextInt()o próximo token interpretado como inteiro21"},{"id":"entrada-console-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Se <code>nextInt()</code> lê o número de <code>21⏎</code>, a marca de fim de linha pode continuar no buffer. O <code>nextLine()</code> seguinte encontra essa marca e devolve uma linha vazia. Para manter um único modelo mental, o curso adotará esta regra: <strong>leia sempre a linha inteira e depois converta o texto</strong>.</p>","fidelityText":"Se nextInt() lê o número de 21⏎, a marca de fim de linha pode continuar no buffer. O nextLine() seguinte encontra essa marca e devolve uma linha vazia. Para manter um único modelo mental, o curso adotará esta regra: leia sempre a linha inteira e depois converta o texto."},{"id":"entrada-console-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Camada 3 — ler, normalizar, converter e validar</h2>","fidelityText":"Camada 3 — ler, normalizar, converter e validar"},{"id":"entrada-console-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Essas são quatro operações diferentes. <strong>Ler</strong> obtém texto. <strong>Normalizar</strong> remove apenas variações permitidas, como espaços externos com <code>strip()</code>. <strong>Parsing</strong> converte a representação textual para um tipo, como <code>int</code>. <strong>Validar</strong> verifica a regra do domínio: <code>\"200\"</code> é um inteiro válido, mas não uma idade humana aceita pelo programa.</p>","fidelityText":"Essas são quatro operações diferentes. Ler obtém texto. Normalizar remove apenas variações permitidas, como espaços externos com strip(). Parsing converte a representação textual para um tipo, como int. Validar verifica a regra do domínio: \"200\" é um inteiro válido, mas não uma idade humana aceita pelo programa."},{"id":"entrada-console-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"System.out.print(\"Age: \");\nString text = scanner.nextLine().strip();\nint age = Integer.parseInt(text);\n\nif (age < 0 || age > 130) {\n    System.out.println(\"The age must be between 0 and 130.\");\n}","fidelityText":"System.out.print(\"Idade: \"); String texto = scanner.nextLine().strip(); int idade = Integer.parseInt(texto); if (idade < 0 || idade > 130) { System.out.println(\"A idade deve estar entre 0 e 130.\"); }","highlightedHtml":"System.out.print(<span class=\"str\">\"Age: \"</span>);\nString text = scanner.nextLine().strip();\n<span class=\"kw\">int</span> age = Integer.parseInt(text);\n\n<span class=\"kw\">if</span> (age &lt; 0 || age &gt; 130) {\n    System.out.println(<span class=\"str\">\"The age must be between 0 and 130.\"</span>);\n}","caption":"Exemplo executável de entrada-console.","explanation":["nextLine lê a linha inteira e strip remove espaços das bordas.","parseInt converte o texto; a condição seguinte valida a faixa da idade."],"commonMistakes":["Confundir parsing com validação","Não decidir o que fazer com entrada vazia"]},{"id":"entrada-console-content-16","type":"html","authorship":"legacy-preserved","html":"<p><code>Integer.parseInt</code> não consegue converter <code>\"vinte\"</code>. Nesse caso ele sinaliza uma <code>NumberFormatException</code>. Uma <strong>exceção</strong> é um objeto que representa uma interrupção do fluxo normal; <code>try</code> delimita a tentativa e <code>catch</code> escolhe como reagir àquele tipo de falha. O capítulo <a href=\"#excecoes\">Exceções, try/catch e recursos</a> aprofundará propagação, hierarquia e criação de exceções.</p>","fidelityText":"Integer.parseInt não consegue converter \"vinte\". Nesse caso ele sinaliza uma NumberFormatException. Uma exceção é um objeto que representa uma interrupção do fluxo normal; try delimita a tentativa e catch escolhe como reagir àquele tipo de falha. O capítulo Exceções, try/catch e recursos aprofundará propagação, hierarquia e criação de exceções."},{"id":"entrada-console-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"static int readInteger(Scanner scanner, String asks, int minimum, int maximum) {\n    while (true) {\n        System.out.print(asks);\n        String text = scanner.nextLine().strip();\n\n        try {\n            int value = Integer.parseInt(text);\n            if (value >= minimum && value <= maximum) {\n                return value;\n            }\n            System.out.printf(\"Use a value between %d and %d.%n\", minimum, maximum);\n        } catch (NumberFormatException error) {\n            System.out.println(\"Digite a number entire, as 18.\");\n        }\n    }\n}","fidelityText":"static int lerInteiro(Scanner scanner, String pergunta, int minimo, int maximo) { while (true) { System.out.print(pergunta); String texto = scanner.nextLine().strip(); try { int valor = Integer.parseInt(texto); if (valor >= minimo && valor <= maximo) { return valor; } System.out.printf(\"Use um valor entre %d e %d.%n\", minimo, maximo); } catch (NumberFormatException erro) { System.out.println(\"Digite um número inteiro, como 18.\"); } } }","highlightedHtml":"<span class=\"kw\">static int</span> readInteger(Scanner scanner, String asks, <span class=\"kw\">int</span> minimum, <span class=\"kw\">int</span> maximum) {\n    <span class=\"kw\">while</span> (<span class=\"kw\">true</span>) {\n        System.out.print(asks);\n        String text = scanner.nextLine().strip();\n\n        <span class=\"kw\">try</span> {\n            <span class=\"kw\">int</span> value = Integer.parseInt(text);\n            <span class=\"kw\">if</span> (value &gt;= minimum &amp;&amp; value &lt;= maximum) {\n                <span class=\"kw\">return</span> value;\n            }\n            System.out.printf(<span class=\"str\">\"Use a value between %d and %d.%n\"</span>, minimum, maximum);\n        } <span class=\"kw\">catch</span> (NumberFormatException error) {\n            System.out.println(<span class=\"str\">\"Digite a number entire, as 18.\"</span>);\n        }\n    }\n}","caption":"Exemplo executável de entrada-console.","explanation":["O laço repete a coleta até existir um inteiro dentro da faixa.","catch trata somente o formato inválido; o retorno ocorre apenas após validação."],"commonMistakes":["Capturar Exception genérica","Colocar regra de negócio dentro do catch"]},{"id":"entrada-console-content-18","type":"html","authorship":"legacy-preserved","html":"<p>No <code>printf</code>, <code>%d</code> é substituído por um inteiro e <code>%n</code> produz a quebra de linha adequada ao sistema operacional. O laço repete somente a coleta; a regra de negócio pode continuar em outro método, sem conhecer teclado ou mensagens.</p>","fidelityText":"No printf, %d é substituído por um inteiro e %n produz a quebra de linha adequada ao sistema operacional. O laço repete somente a coleta; a regra de negócio pode continuar em outro método, sem conhecer teclado ou mensagens."},{"id":"entrada-console-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Camada 4 — quando a entrada termina</h2>","fidelityText":"Camada 4 — quando a entrada termina"},{"id":"entrada-console-content-20","type":"html","authorship":"legacy-preserved","html":"<p><strong>EOF</strong> significa <em>end of file</em>, ou fim da entrada: não existe outra linha para ler. Isso pode acontecer ao redirecionar um arquivo ou quando a pessoa sinaliza o fim com <kbd>Ctrl</kbd>+<kbd>D</kbd> em Linux/macOS ou <kbd>Ctrl</kbd>+<kbd>Z</kbd> e Enter no Windows. <code>hasNextLine()</code> permite verificar antes de chamar <code>nextLine()</code>.</p>","fidelityText":"EOF significa end of file, ou fim da entrada: não existe outra linha para ler. Isso pode acontecer ao redirecionar um arquivo ou quando a pessoa sinaliza o fim com Ctrl+D em Linux/macOS ou Ctrl+Z e Enter no Windows. hasNextLine() permite verificar antes de chamar nextLine()."},{"id":"entrada-console-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"while (scanner.hasNextLine()) {\n    String command = scanner.nextLine().strip();\n    if (command.equalsIgnoreCase(\"exit\")) break;\n    System.out.println(\"Recebido: \" + command);\n}\nSystem.out.println(\"Input closed.\");","fidelityText":"while (scanner.hasNextLine()) { String comando = scanner.nextLine().strip(); if (comando.equalsIgnoreCase(\"sair\")) break; System.out.println(\"Recebido: \" + comando); } System.out.println(\"Entrada encerrada.\");","highlightedHtml":"<span class=\"kw\">while</span> (scanner.hasNextLine()) {\n    String command = scanner.nextLine().strip();\n    <span class=\"kw\">if</span> (command.equalsIgnoreCase(<span class=\"str\">\"exit\"</span>)) <span class=\"kw\">break</span>;\n    System.out.println(<span class=\"str\">\"Recebido: \"</span> + command);\n}\nSystem.out.println(<span class=\"str\">\"Input closed.\"</span>);","caption":"Exemplo executável de entrada-console.","explanation":["hasNextLine distingue uma próxima linha de EOF.","break trata o comando sair; EOF também encerra o laço conscientemente."],"commonMistakes":["Chamar nextLine após EOF","Tratar EOF necessariamente como falha"]},{"id":"entrada-console-content-22","type":"html","authorship":"legacy-preserved","html":"<p>EOF não é automaticamente um erro: em um programa que lê arquivo pela entrada padrão, é o encerramento esperado. Em um formulário interativo, pode significar que a operação ficou incompleta. O contrato do programa decide como reagir.</p>","fidelityText":"EOF não é automaticamente um erro: em um programa que lê arquivo pela entrada padrão, é o encerramento esperado. Em um formulário interativo, pode significar que a operação ficou incompleta. O contrato do programa decide como reagir."},{"id":"entrada-console-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>Aprofundamento — dinheiro e formato regional</h2>","fidelityText":"Aprofundamento — dinheiro e formato regional"},{"id":"entrada-console-content-24","type":"html","authorship":"legacy-preserved","html":"<p><code>double</code> representa números em formato binário e vários decimais comuns, como 0,1, não possuem representação exata. <code>BigDecimal</code> armazena um decimal com precisão controlada, por isso é apropriado quando centavos precisam ser exatos. Construa-o a partir do texto, nunca a partir de um <code>double</code> já aproximado.</p>","fidelityText":"double representa números em formato binário e vários decimais comuns, como 0,1, não possuem representação exata. BigDecimal armazena um decimal com precisão controlada, por isso é apropriado quando centavos precisam ser exatos. Construa-o a partir do texto, nunca a partir de um double já aproximado."},{"id":"entrada-console-content-25","type":"html","authorship":"legacy-preserved","html":"<p><strong>Locale</strong> é o conjunto de convenções regionais de idioma e formatação. No Brasil, <code>123,45</code> é comum; em formatos técnicos, <code>123.45</code> é comum. O <strong>contrato de entrada</strong> deve declarar qual aceita. Não troque vírgulas e pontos indiscriminadamente, pois <code>1.234</code> pode significar um inteiro com milhar ou um decimal.</p>","fidelityText":"Locale é o conjunto de convenções regionais de idioma e formatação. No Brasil, 123,45 é comum; em formatos técnicos, 123.45 é comum. O contrato de entrada deve declarar qual aceita. Não troque vírgulas e pontos indiscriminadamente, pois 1.234 pode significar um inteiro com milhar ou um decimal."},{"id":"entrada-console-code-26","type":"code","authorship":"legacy-preserved","language":"java","source":"import java.math.BigDecimal;\nimport java.math.RoundingMode;\n\nstatic BigDecimal convertReais(String text) {\n    BigDecimal value = new BigDecimal(text.strip()); // contrato: 123.45\n    if (value.signum() <= 0) {\n        throw new IllegalArgumentException(\"The value must be positive.\");\n    }\n    return value.setScale(2, RoundingMode.HALF_EVEN);\n}","fidelityText":"import java.math.BigDecimal; import java.math.RoundingMode; static BigDecimal converterReais(String texto) { BigDecimal valor = new BigDecimal(texto.strip()); // contrato: 123.45 if (valor.signum() <= 0) { throw new IllegalArgumentException(\"O valor deve ser positivo.\"); } return valor.setScale(2, RoundingMode.HALF_EVEN); }","highlightedHtml":"<span class=\"kw\">import</span> java.math.BigDecimal;\n<span class=\"kw\">import</span> java.math.RoundingMode;\n\n<span class=\"kw\">static</span> BigDecimal convertReais(String text) {\n    BigDecimal value = <span class=\"kw\">new</span> BigDecimal(text.strip()); <span class=\"com\">// contrato: 123.45</span>\n    <span class=\"kw\">if</span> (value.signum() &lt;= 0) {\n        <span class=\"kw\">throw new</span> IllegalArgumentException(<span class=\"str\">\"The value must be positive.\"</span>);\n    }\n    <span class=\"kw\">return</span> value.setScale(2, RoundingMode.HALF_EVEN);\n}","caption":"Exemplo executável de entrada-console.","explanation":["BigDecimal é criado do texto para não herdar aproximação de double.","signum valida positividade e setScale aplica a regra explícita de casas e arredondamento."],"commonMistakes":["Usar new BigDecimal(0.1)","Aceitar vírgula e ponto sem contrato"]},{"id":"entrada-console-content-27","type":"html","authorship":"legacy-preserved","html":"<p><code>setScale(2, ...)</code> fixa duas casas decimais. <code>HALF_EVEN</code> é uma regra de arredondamento: no empate exato, escolhe o último dígito par, reduzindo viés em muitas operações. Em um sistema real, a regra deve vir do domínio, e erros de formato devem ser convertidos em mensagens úteis na fronteira da CLI.</p>","fidelityText":"setScale(2, ...) fixa duas casas decimais. HALF_EVEN é uma regra de arredondamento: no empate exato, escolhe o último dígito par, reduzindo viés em muitas operações. Em um sistema real, a regra deve vir do domínio, e erros de formato devem ser convertidos em mensagens úteis na fronteira da CLI."},{"id":"entrada-console-content-28","type":"html","authorship":"legacy-preserved","html":"<h2>Depois do Scanner: arquivos, sockets e protocolos</h2>","fidelityText":"Depois do Scanner: arquivos, sockets e protocolos"},{"id":"entrada-console-content-29","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Uma prévia, não um pré-requisito</h2></div>\n    <p>Os conceitos abaixo não são usados no exercício. Eles explicam apenas por que uma API conveniente para teclado não é a resposta para toda fonte de dados.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Arquivo</dt><dd>Uma sequência persistida de bytes. Arquivos grandes costumam ser processados aos poucos para não ocupar toda a memória.</dd></div><div class=\"concept-card\"><dt>Socket</dt><dd>Uma extremidade de comunicação entre processos, normalmente através da rede. Ele entrega bytes; sozinho não diz onde termina uma mensagem.</dd></div><div class=\"concept-card\"><dt>Protocolo</dt><dd>O acordo que dá significado aos bytes: formato, ordem das mensagens, limites, erros e encerramento. HTTP é um protocolo.</dd></div><div class=\"concept-card\"><dt>API bufferizada</dt><dd>Leitor que agrupa acessos ao recurso em uma memória temporária, reduzindo operações pequenas e permitindo leitura eficiente.</dd></div><div class=\"concept-card\"><dt>Recurso</dt><dd>Algo externo ao objeto Java, como arquivo, conexão ou socket, que possui ciclo de vida e geralmente precisa ser fechado.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoUma prévia, não um pré-requisito Os conceitos abaixo não são usados no exercício. Eles explicam apenas por que uma API conveniente para teclado não é a resposta para toda fonte de dados. ArquivoUma sequência persistida de bytes. Arquivos grandes costumam ser processados aos poucos para não ocupar toda a memória.SocketUma extremidade de comunicação entre processos, normalmente através da rede. Ele entrega bytes; sozinho não diz onde termina uma mensagem.ProtocoloO acordo que dá significado aos bytes: formato, ordem das mensagens, limites, erros e encerramento. HTTP é um protocolo.API bufferizadaLeitor que agrupa acessos ao recurso em uma memória temporária, reduzindo operações pequenas e permitindo leitura eficiente.RecursoAlgo externo ao objeto Java, como arquivo, conexão ou socket, que possui ciclo de vida e geralmente precisa ser fechado."},{"id":"entrada-console-content-30","type":"html","authorship":"legacy-preserved","html":"<p>Um <code>Scanner</code> é ótimo para exercícios e CLIs pequenas. Para texto de arquivo, você conhecerá <code>BufferedReader</code> em <a href=\"#java-io\">Arquivos e Java I/O</a>. Para rede, HTTP e formato das mensagens, avance até <a href=\"#http\">HTTP</a>. A regra de propriedade é: quem cria ou recebe responsabilidade explícita por um recurso decide quando fechá-lo. <code>System.in</code> pertence ao processo; código reutilizável de biblioteca não deve fechá-lo e surpreender o restante da aplicação.</p>","fidelityText":"Um Scanner é ótimo para exercícios e CLIs pequenas. Para texto de arquivo, você conhecerá BufferedReader em Arquivos e Java I/O. Para rede, HTTP e formato das mensagens, avance até HTTP. A regra de propriedade é: quem cria ou recebe responsabilidade explícita por um recurso decide quando fechá-lo. System.in pertence ao processo; código reutilizável de biblioteca não deve fechá-lo e surpreender o restante da aplicação."},{"id":"entrada-console-exercise-31","type":"exercise","authorship":"legacy-preserved","title":"Laboratório em quatro etapas — menu resistente a erros","prompt":"1) leia e repita um nome; 2) adicione um menu textual; 3) converta a opção e repita a pergunta quando ela for inválida; 4) acrescente depósito, saque, extrato, saída e término por EOF. Só depois troque os valores monetários por BigDecimal. Separe leitura, regra de negócio e impressão.","difficulty":"intermediate","criteria":["Cada etapa compila e funciona antes da próxima.","Entrada inválida produz orientação e permite nova tentativa, sem stack trace.","EOF encerra o programa conscientemente.","Dinheiro usa BigDecimal com formato de entrada documentado.","A opção de saída termina o laço explicitamente.","A classe da conta não conhece Scanner nem imprime mensagens."],"fidelityText":"Laboratório em quatro etapas — menu resistente a errosmédio1) leia e repita um nome; 2) adicione um menu textual; 3) converta a opção e repita a pergunta quando ela for inválida; 4) acrescente depósito, saque, extrato, saída e término por EOF. Só depois troque os valores monetários por BigDecimal. Separe leitura, regra de negócio e impressão.Ver critériosCada etapa compila e funciona antes da próxima.Entrada inválida produz orientação e permite nova tentativa, sem stack trace.EOF encerra o programa conscientemente.Dinheiro usa BigDecimal com formato de entrada documentado.A opção de saída termina o laço explicitamente.A classe da conta não conhece Scanner nem imprime mensagens.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório em quatro etapas — menu resistente a erros</h2><span class=\"exercise-tag m\">médio</span></div><p>1) leia e repita um nome; 2) adicione um menu textual; 3) converta a opção e repita a pergunta quando ela for inválida; 4) acrescente depósito, saque, extrato, saída e término por EOF. Só depois troque os valores monetários por <code>BigDecimal</code>. Separe leitura, regra de negócio e impressão.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><ul><li>Cada etapa compila e funciona antes da próxima.</li><li>Entrada inválida produz orientação e permite nova tentativa, sem stack trace.</li><li>EOF encerra o programa conscientemente.</li><li>Dinheiro usa <code>BigDecimal</code> com formato de entrada documentado.</li><li>A opção de saída termina o laço explicitamente.</li><li>A classe da conta não conhece <code>Scanner</code> nem imprime mensagens.</li></ul></div></div>"},{"id":"console-flow","type":"diagram","authorship":"authored","title":"Da pessoa à regra","description":"A fronteira deve transformar entrada incerta em um valor que a regra possa confiar.","steps":["Prompt em stdout","Linha chega por stdin","Scanner entrega String","parseInt tenta converter formato","validação verifica faixa","método de negócio recebe valor válido","mensagem apresenta o resultado"]},{"id":"console-comparison","type":"comparison","authorship":"authored","title":"Converter não é validar","criteria":["pergunta","exemplo aceito","falha"],"alternatives":[{"name":"Parsing","values":["tem formato de int?","\"200\"","NumberFormatException para \"vinte\""],"useWhen":"transformar representação textual em tipo","avoidWhen":"decidir se o valor serve ao domínio"},{"name":"Validação","values":["está entre limites aceitos?","18 como idade","mensagem de regra para 200"],"useWhen":"aplicar contrato do programa","avoidWhen":"interpretar caracteres"}]},{"id":"console-quiz","type":"quiz","authorship":"authored","conceptId":"scanner-token-linha","prompt":"Por que ler sempre com nextLine e depois usar parseInt costuma ser previsível em uma CLI pequena?","options":[{"id":"console-q-a","label":"Porque há uma única unidade de leitura — a linha — e a conversão fica explícita.","correct":true,"explanation":"Isso evita deixar a quebra de linha pendente ao alternar leitores de token e de linha."},{"id":"console-q-b","label":"Porque parseInt aceita qualquer texto e nunca falha.","correct":false,"explanation":"Texto fora do formato inteiro causa NumberFormatException e precisa de reação planejada."},{"id":"console-q-c","label":"Porque nextLine já valida regras como idade máxima.","correct":false,"explanation":"nextLine apenas lê texto; validação de domínio pertence ao programa."}]}],"resources":[{"id":"console-scanner-api","type":"official-docs","title":"API Scanner Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Scanner.html","reinforces":"Documenta tokens, delimitadores, nextLine, hasNextLine e fechamento.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"console-system-api","type":"official-docs","title":"API System: in, out e err","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html","reinforces":"Confirma os fluxos padrão pertencentes ao processo.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The input console example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"import java.util.Scanner;","instruction":"The input console example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["Scanner parte de leitura mínima e avança até contrato de entrada; arquivos e sockets permanecem apenas como prévia não exigida."]}},{"id":"logica-programacao","moduleId":"programming-fundamentals","order":11,"title":"Lógica de Programação & Pensamento Algorítmico","summary":"Este capítulo é diferente dos outros: não ensina uma ferramenta ou biblioteca — treina a habilidade que sustenta tudo mais no curso. Quem programa bem não é quem decorou mais sintaxe, é quem consegue quebrar um problema grande em passos pequenos e certos.","objectives":["Decompor problemas antes da sintaxe","Executar dry run registrando estado","Definir casos-limite e invariantes antes de implementar"],"whyItExists":"Conhecer sintaxe não escolhe uma solução; raciocínio algorítmico transforma uma regra ambígua em passos pequenos que podem ser previstos, implementados e verificados.","prerequisiteChapterIds":["metodos-escopo"],"conceptIds":["o-habito-central-decompor-antes-de-codificar","estruturas-de-controle-como-blocos-de-raciocinio","rastreamento-manual-dry-run-depurando-sem-executar"],"introducedConceptIds":["decomposicao-algoritmica","dry-run","casos-limite-invariantes"],"usedConceptIds":["metodo-contrato","while-for-foreach","ramificacao-condicional","array-indice-length","fronteira-off-by-one"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"logica-intuition","type":"intuition","authorship":"authored","title":"Resolva uma versão que você consegue explicar","body":"Antes de escrever Java, declare entrada, saída, exemplos e passos. Se um passo ainda diz apenas “resolver” ou “processar”, ele precisa ser dividido até virar uma decisão, repetição ou operação conhecida.","analogyLimit":"Receita é uma boa imagem para sequência, mas algoritmos também possuem ramificações, repetição, estado e contratos formais."},{"id":"logica-programacao-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-java\">Fundamentos</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i><i></i><i></i></span> <b>Iniciante</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + muita prática</div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#metodos-escopo\">Métodos, parâmetros e escopo</a></div>\n      </div>","fidelityText":"Fundamentos Dificuldade: Iniciante ⏱ ~2h de estudo + muita prática Pré-requisito: Métodos, parâmetros e escopo"},{"id":"logica-programacao-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Este capítulo é diferente dos outros: não ensina uma ferramenta ou biblioteca — treina a <strong>habilidade</strong> que sustenta tudo mais no curso. Quem programa bem não é quem decorou mais sintaxe, é quem consegue quebrar um problema grande em passos pequenos e certos.</p>","fidelityText":"Este capítulo é diferente dos outros: não ensina uma ferramenta ou biblioteca — treina a habilidade que sustenta tudo mais no curso. Quem programa bem não é quem decorou mais sintaxe, é quem consegue quebrar um problema grande em passos pequenos e certos."},{"id":"logica-programacao-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>O hábito central: decompor antes de codificar</h2>","fidelityText":"O hábito central: decompor antes de codificar"},{"id":"logica-programacao-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em uma receita de bolo complexa. Ninguém memoriza \"faça um bolo\" como uma instrução única — a receita quebra em passos: separar ingredientes, misturar secos, misturar líquidos, combinar, assar. Programar bem é o mesmo hábito aplicado a problemas: antes de escrever a primeira linha de código, escreva os passos em português (ou pseudocódigo), depois traduza cada passo para Java.</div>","fidelityText":"Pense em uma receita de bolo complexa. Ninguém memoriza \"faça um bolo\" como uma instrução única — a receita quebra em passos: separar ingredientes, misturar secos, misturar líquidos, combinar, assar. Programar bem é o mesmo hábito aplicado a problemas: antes de escrever a primeira linha de código, escreva os passos em português (ou pseudocódigo), depois traduza cada passo para Java."},{"id":"logica-programacao-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"// Problema: \"dado um array, contar quantos números são positivos\"\n//\n// Decomposição em português ANTES do código:\n// 1. começar um contador em zero\n// 2. percorrer cada número\n// 3. aumentar o contador quando o número for maior que zero\n// 4. devolver o contador\n//\n// SÓ AGORA traduzir para Java:\nint countPositive(int[] numbers) {\n    int quantity = 0;\n    for (int number : numbers) {\n        if (number > 0) quantity++;\n    }\n    return quantity;\n}","fidelityText":"// Problema: \"dado um array, contar quantos números são positivos\" // // Decomposição em português ANTES do código: // 1. começar um contador em zero // 2. percorrer cada número // 3. aumentar o contador quando o número for maior que zero // 4. devolver o contador // // SÓ AGORA traduzir para Java: int contarPositivos(int[] numeros) { int quantidade = 0; for (int numero : numeros) { if (numero > 0) quantidade++; } return quantidade; }","highlightedHtml":"<span class=\"com\">// Problema: \"dado um array, contar quantos números são positivos\"\n//\n// Decomposição em português ANTES do código:\n// 1. começar um contador em zero\n// 2. percorrer cada número\n// 3. aumentar o contador quando o número for maior que zero\n// 4. devolver o contador\n//\n// SÓ AGORA traduzir para Java:</span>\n<span class=\"kw\">int</span> <span class=\"fn\">countPositive</span>(<span class=\"kw\">int</span>[] numbers) {\n    <span class=\"kw\">int</span> quantity = 0;\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> number : numbers) {\n        <span class=\"kw\">if</span> (number &gt; 0) quantity++;\n    }\n    <span class=\"kw\">return</span> quantity;\n}","caption":"Exemplo executável de logica-programacao.","explanation":["Os comentários definem um algoritmo independente da sintaxe antes da tradução.","O acumulador começa em zero, o for-each visita cada valor e o if incrementa somente para positivos."],"commonMistakes":["Contar zero como positivo","Misturar a decomposição com detalhes ainda não decididos"]},{"id":"logica-programacao-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Sempre que travar num exercício (deste curso ou de qualquer outro lugar), pare de olhar para o teclado e escreva os passos em português, numa folha ou comentário, <strong>antes</strong> de tentar de novo em código. A trava quase nunca é \"não sei a sintaxe\" — é \"não decompus o problema o suficiente ainda\".</div>","fidelityText":"Sempre que travar num exercício (deste curso ou de qualquer outro lugar), pare de olhar para o teclado e escreva os passos em português, numa folha ou comentário, antes de tentar de novo em código. A trava quase nunca é \"não sei a sintaxe\" — é \"não decompus o problema o suficiente ainda\"."},{"id":"logica-programacao-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Estruturas de controle como blocos de raciocínio</h2>","fidelityText":"Estruturas de controle como blocos de raciocínio"},{"id":"logica-programacao-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Você já usa <code>if</code>, <code>for</code>, <code>while</code> desde os primeiros capítulos — mas pensar <em>em quais situações cada um é a ferramenta certa</em> é uma habilidade separada de saber a sintaxe.</p>","fidelityText":"Você já usa if, for, while desde os primeiros capítulos — mas pensar em quais situações cada um é a ferramenta certa é uma habilidade separada de saber a sintaxe."},{"id":"logica-programacao-content-9","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Estrutura</th><th>Pensamento por trás</th></tr>\n        <tr><td><code>if/else</code></td><td>\"Dependendo de uma condição, o caminho muda\" — uma decisão</td></tr>\n        <tr><td><code>for</code></td><td>\"Sei exatamente quantas vezes preciso repetir\" (ou tenho uma coleção para percorrer)</td></tr>\n        <tr><td><code>while</code></td><td>\"Repito até uma condição parar de ser verdadeira, mas não sei de antemão quantas vezes\"</td></tr>\n        <tr><td>Recursão (capítulo 81)</td><td>\"O problema grande é feito de versões menores de si mesmo\"</td></tr>\n      </tbody></table>","fidelityText":"EstruturaPensamento por trás if/else\"Dependendo de uma condição, o caminho muda\" — uma decisão for\"Sei exatamente quantas vezes preciso repetir\" (ou tenho uma coleção para percorrer) while\"Repito até uma condição parar de ser verdadeira, mas não sei de antemão quantas vezes\" Recursão (capítulo 81)\"O problema grande é feito de versões menores de si mesmo\""},{"id":"logica-programacao-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Rastreamento manual (dry run) — depurando sem executar</h2>","fidelityText":"Rastreamento manual (dry run) — depurando sem executar"},{"id":"logica-programacao-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Antes de rodar qualquer código, treine \"ser a máquina\" — percorrer o código linha por linha no papel, anotando o valor de cada variável a cada passo.</p>","fidelityText":"Antes de rodar qualquer código, treine \"ser a máquina\" — percorrer o código linha por linha no papel, anotando o valor de cada variável a cada passo."},{"id":"logica-programacao-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"int factorial(int n) {\n    int result = 1;\n    for (int i = 1; i <= n; i++) {\n        result = result * i;\n    }\n    return result;\n}\n// dry run manual de fatorial(4):\n// i=1: resultado = 1*1 = 1\n// i=2: resultado = 1*2 = 2\n// i=3: resultado = 2*3 = 6\n// i=4: resultado = 6*4 = 24\n// i=5: condição \"i<=4\" falsa, sai do loop\n// retorna 24","fidelityText":"int fatorial(int n) { int resultado = 1; for (int i = 1; i <= n; i++) { resultado = resultado * i; } return resultado; } // dry run manual de fatorial(4): // i=1: resultado = 1*1 = 1 // i=2: resultado = 1*2 = 2 // i=3: resultado = 2*3 = 6 // i=4: resultado = 6*4 = 24 // i=5: condição \"i<=4\" falsa, sai do loop // retorna 24","highlightedHtml":"<span class=\"kw\">int</span> <span class=\"fn\">factorial</span>(<span class=\"kw\">int</span> n) {\n    <span class=\"kw\">int</span> result = 1;\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = 1; i &lt;= n; i++) {\n        result = result * i;\n    }\n    <span class=\"kw\">return</span> result;\n}\n<span class=\"com\">// dry run manual de fatorial(4):\n// i=1: resultado = 1*1 = 1\n// i=2: resultado = 1*2 = 2\n// i=3: resultado = 2*3 = 6\n// i=4: resultado = 6*4 = 24\n// i=5: condição \"i&lt;=4\" falsa, sai do loop\n// retorna 24</span>","caption":"Exemplo executável de logica-programacao.","explanation":["resultado começa no elemento neutro da multiplicação.","O dry run registra estado antes de verificar o término e permite conferir fatorial(4)."],"commonMistakes":["Começar resultado em zero","Usar i < n e omitir n"]},{"id":"logica-programacao-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Esse exercício de \"ser a máquina\" no papel é o que separa quem entende um bug de quem só está tentando adivinhar a correção mexendo aleatoriamente no código até \"funcionar\". Um debugger (ferramenta da IDE que pausa a execução linha por linha) automatiza exatamente esse processo — mas a habilidade mental de rastrear estado passo a passo precisa existir <em>antes</em>, senão você olha os valores no debugger sem saber o que esperar deles.</div>","fidelityText":"Esse exercício de \"ser a máquina\" no papel é o que separa quem entende um bug de quem só está tentando adivinhar a correção mexendo aleatoriamente no código até \"funcionar\". Um debugger (ferramenta da IDE que pausa a execução linha por linha) automatiza exatamente esse processo — mas a habilidade mental de rastrear estado passo a passo precisa existir antes, senão você olha os valores no debugger sem saber o que esperar deles."},{"id":"logica-programacao-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Off-by-one errors — o erro de lógica mais comum de todos:</b> confundir <code>i &lt; n</code> com <code>i &lt;= n</code>, ou começar um índice em 0 quando deveria começar em 1 (ou vice-versa). Não existe atalho mágico para evitar isso além de fazer o dry run mentalmente nos casos-limite (primeira iteração, última iteração, coleção vazia) toda vez que escrever um loop novo.</div>","fidelityText":"Off-by-one errors — o erro de lógica mais comum de todos: confundir i < n com i <= n, ou começar um índice em 0 quando deveria começar em 1 (ou vice-versa). Não existe atalho mágico para evitar isso além de fazer o dry run mentalmente nos casos-limite (primeira iteração, última iteração, coleção vazia) toda vez que escrever um loop novo."},{"id":"logica-programacao-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 79.1 — Decompondo antes de codificar","prompt":"Antes de escrever qualquer código, escreva em português os passos para resolver: \"dado um array de inteiros, encontrar o segundo maior valor, sem ordenar o array inteiro\". Só depois traduza para Java.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 79.1 — Decompondo antes de codificarfácil Antes de escrever qualquer código, escreva em português os passos para resolver: \"dado um array de inteiros, encontrar o segundo maior valor, sem ordenar o array inteiro\". Só depois traduza para Java. Ver solução // Decomposição: // 1. guardar duas variáveis: \"maior\" e \"segundoMaior\" // 2. percorrer o array uma única vez // 3. se o valor atual for maior que \"maior\": o antigo \"maior\" vira \"segundoMaior\", // e o valor atual vira o novo \"maior\" // 4. senão, se o valor atual for maior que \"segundoMaior\" (mas menor que \"maior\"): // só atualiza \"segundoMaior\" int segundoMaior(int[] numeros) { int maior = Integer.MIN_VALUE, segundoMaior = Integer.MIN_VALUE; boolean encontrouMaior = false, encontrouSegundo = false; for (int n : numeros) { if (!encontrouMaior || n > maior) { if (encontrouMaior) { segundoMaior = maior; encontrouSegundo = true; } maior = n; encontrouMaior = true; } else if (n != maior && (!encontrouSegundo || n > segundoMaior)) { segundoMaior = n; encontrouSegundo = true; } } if (!encontrouSegundo) throw new IllegalArgumentException(\"não há dois valores distintos\"); return segundoMaior; }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 79.1 — Decompondo antes de codificar</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Antes de escrever qualquer código, escreva em português os passos para resolver: \"dado um array de inteiros, encontrar o segundo maior valor, sem ordenar o array inteiro\". Só depois traduza para Java.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"com\">// Decomposição:\n// 1. guardar duas variáveis: \"maior\" e \"segundoMaior\"\n// 2. percorrer o array uma única vez\n// 3. se o valor atual for maior que \"maior\": o antigo \"maior\" vira \"segundoMaior\",\n//    e o valor atual vira o novo \"maior\"\n// 4. senão, se o valor atual for maior que \"segundoMaior\" (mas menor que \"maior\"):\n//    só atualiza \"segundoMaior\"</span>\n<span class=\"kw\">int</span> <span class=\"fn\">secondGreater</span>(<span class=\"kw\">int</span>[] numbers) {\n    <span class=\"kw\">int</span> greater = Integer.MIN_VALUE, secondGreater = Integer.MIN_VALUE;\n    <span class=\"kw\">boolean</span> encontrouGreater = <span class=\"kw\">false</span>, encontrouSecond = <span class=\"kw\">false</span>;\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> n : numbers) {\n        <span class=\"kw\">if</span> (!encontrouGreater || n &gt; greater) {\n            <span class=\"kw\">if</span> (encontrouGreater) { secondGreater = greater; encontrouSecond = <span class=\"kw\">true</span>; }\n            greater = n;\n            encontrouGreater = <span class=\"kw\">true</span>;\n        } <span class=\"kw\">else if</span> (n != greater &amp;&amp; (!encontrouSecond || n &gt; secondGreater)) {\n            secondGreater = n;\n            encontrouSecond = <span class=\"kw\">true</span>;\n        }\n    }\n    <span class=\"kw\">if</span> (!encontrouSecond) <span class=\"kw\">throw new</span> IllegalArgumentException(<span class=\"str\">\"not there is two values distinct\"</span>);\n    <span class=\"kw\">return</span> secondGreater;\n}</pre>\n        </div>\n      </div>"},{"id":"logica-table","type":"table","authorship":"authored","title":"Ficha mínima de solução","headers":["Pergunta","Registro"],"rows":[["Qual é a entrada?","tipo, formato e limites"],["Qual é a saída?","tipo e significado"],["O que permanece verdadeiro?","invariante"],["Onde costuma quebrar?","vazio, mínimo, máximo, repetidos"],["Como provar?","dry run e casos com resultado esperado"]],"caption":"Preencha antes da implementação e atualize se descobrir uma regra ausente."},{"id":"logica-quiz","type":"quiz","authorship":"authored","conceptId":"casos-limite-invariantes","prompt":"Um algoritmo encontra o maior valor iniciando maior = 0. Qual caso revela o defeito?","options":[{"id":"logica-q-a","label":"Um array não vazio contendo somente números negativos.","correct":true,"explanation":"Zero não veio da entrada e permaneceria como resultado incorreto."},{"id":"logica-q-b","label":"Um array contendo 0 e números positivos.","correct":false,"explanation":"Esse caso pode passar e esconder a inicialização inválida."},{"id":"logica-q-c","label":"Um array ordenado em ordem crescente.","correct":false,"explanation":"A ordem não ataca o pressuposto incorreto de que zero é um valor inicial válido."}]}],"resources":[{"id":"logica-dev-statements","type":"guide","title":"dev.java: statements","url":"https://dev.java/learn/language-basics/controlling-flow/","reinforces":"Retoma os blocos de controle usados para traduzir algoritmos.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"logica-jls-abrupt","type":"reference","title":"JLS 14: statements e fluxo","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html","reinforces":"Serve para confirmar o comportamento preciso das estruturas usadas no dry run.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The logic programming example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"// Problema: \"dado um array, contar quantos números são positivos\"","instruction":"The logic programming example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["Exceção no desafio é apresentada como sinalização local; hierarquia e propagação permanecem para o capítulo próprio."]}},{"id":"mini-caixa-eletronico","moduleId":"programming-fundamentals","order":12,"title":"Mini-projeto: caixa eletrônico no terminal","summary":"Construa um caixa eletrônico sem POO avançada. O programa mantém saldo durante a execução e oferece depósito, saque, extrato e encerramento.","objectives":["Construir o programa em incrementos compiláveis","Separar leitura, regras e apresentação","Produzir uma matriz reproduzível de testes manuais"],"whyItExists":"O projeto reúne os fundamentos em uma fronteira realista: texto incerto entra pelo console, regras preservam o saldo e uma saída observável permite verificar cada decisão.","prerequisiteChapterIds":["entrada-console","logica-programacao"],"conceptIds":["requisitos","casos-de-teste-manuais","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["incremento-executavel","separacao-io-regra","evidencia-manual"],"usedConceptIds":["parse-validacao-eof","scanner-token-linha","metodo-contrato","array-indice-length","decomposicao-algoritmica","casos-limite-invariantes"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"caixa-intuition","type":"intuition","authorship":"authored","title":"Um projeto é uma sequência de versões pequenas","body":"Implemente menu e saída; depois depósito; depois saque; depois extrato; por fim entradas inválidas. Compile e execute em cada marco para que uma falha tenha poucas causas possíveis.","analogyLimit":"Incrementos não autorizam deixar regras incoerentes: cada versão precisa funcionar dentro do escopo que declara."},{"id":"mini-caixa-eletronico-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>consolidar fundamentos</b></div><div class=\"time-est\">Tempo: <b>4–7 horas</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#logica-programacao\">Lógica de programação</a></div></div>","fidelityText":"Objetivo: consolidar fundamentosTempo: 4–7 horasPré-requisito: Lógica de programação"},{"id":"mini-caixa-eletronico-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Construa um caixa eletrônico sem POO avançada. O programa mantém saldo durante a execução e oferece depósito, saque, extrato e encerramento.</p>","fidelityText":"Construa um caixa eletrônico sem POO avançada. O programa mantém saldo durante a execução e oferece depósito, saque, extrato e encerramento."},{"id":"mini-caixa-eletronico-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Requisitos</h2>","fidelityText":"Requisitos"},{"id":"mini-caixa-eletronico-checklist-4","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-caixa-eletronico-checklist-0","label":"Menu em laço até o usuário escolher sair."},{"id":"mini-caixa-eletronico-checklist-1","label":"Valores monetários validados; zero, negativos e texto inválido não derrubam o programa."},{"id":"mini-caixa-eletronico-checklist-2","label":"Saque recusado quando exceder o saldo."},{"id":"mini-caixa-eletronico-checklist-3","label":"Extrato armazenado em arrays, sem coleções nesta etapa."},{"id":"mini-caixa-eletronico-checklist-4","label":"Cada operação relevante isolada em método."}]},{"id":"mini-caixa-eletronico-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Casos de teste manuais</h2>","fidelityText":"Casos de teste manuais"},{"id":"mini-caixa-eletronico-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Saldo inicial zero; depósito de 100; saque de 30; saque de 100; entrada textual onde se espera número; mais transações que a capacidade do array.</p>","fidelityText":"Saldo inicial zero; depósito de 100; saque de 30; saque de 100; entrada textual onde se espera número; mais transações que a capacidade do array."},{"id":"mini-caixa-eletronico-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Restrição:</b> não use banco, Spring, Lombok ou IA gerando o código. O objetivo é dominar fluxo, arrays, validação e métodos.</div>","fidelityText":"Restrição: não use banco, Spring, Lombok ou IA gerando o código. O objetivo é dominar fluxo, arrays, validação e métodos."},{"id":"mini-caixa-eletronico-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Pronto quando</h2><ul><li>O programa não encerra por entrada inválida.</li><li>Você explica cada variável e cada condição.</li><li>Nenhum método mistura leitura, regra e impressão sem justificativa.</li></ul></div>","fidelityText":"Pronto quandoO programa não encerra por entrada inválida.Você explica cada variável e cada condição.Nenhum método mistura leitura, regra e impressão sem justificativa."},{"id":"mini-caixa-eletronico-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-caixa-eletronico:0","type":"quiz","authorship":"legacy-preserved","conceptId":"qual-separacao-deixa-o-caixa-eletronico-mais-testavel","prompt":"Qual separação deixa o caixa eletrônico mais testável?","options":[{"id":"mini-caixa-eletronico:0:option:0","label":"A regra de saque recebe valores e retorna um resultado, sem ler Scanner nem imprimir.","correct":true,"explanation":"A regra fica determinística: recebe dados confiáveis e pode ser verificada sem interação com terminal."},{"id":"mini-caixa-eletronico:0:option:1","label":"Colocar Scanner, saldo e impressão no mesmo método.","correct":false,"explanation":"Misturar fronteira e regra exige simular teclado e observar texto para conferir um cálculo."},{"id":"mini-caixa-eletronico:0:option:2","label":"Usar campos globais para qualquer operação.","correct":false,"explanation":"Estado global cria dependências ocultas e dificulta prever quem pode alterá-lo."}],"sourceIndex":10},{"id":"mini-caixa-eletronico:1","type":"quiz","authorship":"legacy-preserved","conceptId":"o-programa-le-a-proxima-linha-e-tenta-converte-la-em-numero-o-usuario-fe","prompt":"O programa lê a próxima linha e tenta convertê-la em número. O usuário fecha o terminal (Ctrl+D), e a entrada chega ao fim (EOF) no meio da leitura. O que o programa deveria fazer?","options":[{"id":"mini-caixa-eletronico:1:option:0","label":"Detectar o fim da entrada e encerrar o laço de forma controlada, sem tentar interpretar uma linha inexistente como número.","correct":true,"explanation":"EOF é um sinal de que não há mais entrada -- tentar interpretar uma linha inexistente como número lança exceção e derruba o programa."},{"id":"mini-caixa-eletronico:1:option:1","label":"Continuar chamando o método de leitura, que eventualmente devolve um valor padrão.","correct":false,"explanation":"Não existe 'valor padrão' automático: sem tratar o fim da entrada, a próxima leitura lança exceção."},{"id":"mini-caixa-eletronico:1:option:2","label":"Tratar EOF como uma entrada inválida comum e pedir a linha novamente.","correct":false,"explanation":"EOF não é um formato inválido comum; pedir a linha novamente entra em laço infinito porque nunca mais chegará entrada."}],"sourceIndex":11},{"id":"mini-caixa-eletronico:2","type":"quiz","authorship":"legacy-preserved","conceptId":"o-array-de-operacoes-tem-capacidade-fixa-de-50-posicoes-e-o-contador-de-","prompt":"O array de operações tem capacidade fixa de 50 posições e o contador de operações já é 50. Uma nova operação chega. O que a implementação correta faz?","options":[{"id":"mini-caixa-eletronico:2:option:0","label":"Recusa registrar a nova operação e avisa que o extrato atingiu a capacidade máxima, sem tentar gravar no índice 50 (fora da faixa válida 0–49).","correct":true,"explanation":"O array já está no limite (índices válidos 0 a 49); gravar no índice 50 lança ArrayIndexOutOfBoundsException."},{"id":"mini-caixa-eletronico:2:option:1","label":"Grava na posição 50 mesmo assim, já que arrays em Java crescem automaticamente quando necessário.","correct":false,"explanation":"Arrays em Java têm tamanho fixo desde a criação -- não crescem sozinhos."},{"id":"mini-caixa-eletronico:2:option:2","label":"Sobrescreve a posição 0, reaproveitando o espaço da operação mais antiga.","correct":false,"explanation":"Sobrescrever a posição 0 apaga um registro real do extrato sem que isso seja um requisito do projeto."}],"sourceIndex":12},{"id":"mini-caixa-eletronico:3","type":"quiz","authorship":"legacy-preserved","conceptId":"um-saque-de-valor-0-00-e-solicitado-o-que-o-programa-deveria-fazer","prompt":"Um saque de valor 0.00 é solicitado. O que o programa deveria fazer?","options":[{"id":"mini-caixa-eletronico:3:option:0","label":"Recusar a operação: zero não é um saque válido, o requisito exige valor positivo.","correct":true,"explanation":"O requisito diz explicitamente que valores devem ser positivos; zero fica de fora da faixa aceita."},{"id":"mini-caixa-eletronico:3:option:1","label":"Aceitar e não alterar o saldo, já que zero não muda nada.","correct":false,"explanation":"Mesmo sem alterar o saldo, aceitar a operação contradiz a regra de validação declarada."},{"id":"mini-caixa-eletronico:3:option:2","label":"Aceitar e registrar no extrato como um saque bem-sucedido.","correct":false,"explanation":"Registrar uma operação de valor zero como 'saque bem-sucedido' no extrato é enganoso para quem lê o histórico."}],"sourceIndex":13},{"id":"mini-caixa-eletronico:4","type":"quiz","authorship":"legacy-preserved","conceptId":"depois-de-processar-uma-opcao-valida-como-deposito-o-que-o-laco-do-menu-","prompt":"Depois de processar uma opção válida como depósito, o que o laço do menu deve fazer em seguida?","options":[{"id":"mini-caixa-eletronico:4:option:0","label":"Voltar a mostrar o menu e ler a próxima opção, sem encerrar o programa.","correct":true,"explanation":"O menu só termina quando o usuário escolhe sair (ou a entrada chega ao fim) -- uma operação válida é só mais uma iteração do laço."},{"id":"mini-caixa-eletronico:4:option:1","label":"Encerrar automaticamente, já que uma operação válida foi concluída.","correct":false,"explanation":"Encerrar após qualquer operação contradiz o requisito de 'menu em laço até o usuário escolher sair'."},{"id":"mini-caixa-eletronico:4:option:2","label":"Pedir confirmação para reiniciar o programa do zero.","correct":false,"explanation":"Reiniciar do zero descartaria saldo e extrato sem que isso tenha sido pedido."}],"sourceIndex":14},{"id":"mini-caixa-eletronico-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto usa somente o que já foi ensinado. Construa em incrementos pequenos, compile e execute a cada etapa e registre evidências manuais que outra pessoa consiga repetir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Menu:</strong> mostre opções, leia uma linha e encerre ao escolher sair.</li>\n        <li><strong>Depósito:</strong> converta e valide um valor positivo antes de alterar o saldo.</li>\n        <li><strong>Saque:</strong> recuse zero, negativos e valores acima do saldo.</li>\n        <li><strong>Extrato:</strong> registre operações em um array e trate sua capacidade máxima.</li>\n        <li><strong>Resistência:</strong> repita a pergunta após formato inválido e encerre conscientemente em EOF.</li>\n      </ol>\n      <h2 class=\"sub\">Roteiro mínimo de verificação manual</h2>\n      <table class=\"cmp\"><tbody><tr><th>Caso</th><th>Entrada</th><th>Resultado esperado</th></tr><tr><td>depósito válido</td><td>100.00</td><td>saldo aumenta em 100.00</td></tr><tr><td>saque válido</td><td>30.00 após depósito</td><td>saldo 70.00 e operação no extrato</td></tr><tr><td>saldo insuficiente</td><td>saque 100.00</td><td>mensagem clara e saldo preservado</td></tr><tr><td>formato inválido</td><td>\"dez\"</td><td>nova tentativa, sem stack trace</td></tr><tr><td>array cheio</td><td>uma operação além da capacidade</td><td>aviso, sem acessar índice inválido</td></tr><tr><td>fim da entrada</td><td>EOF</td><td>encerramento consciente</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>O arquivo compila com <code>javac</code> e executa com <code>java</code>.</span></li><li><input type=\"checkbox\"><span>Cada caso registra entrada, resultado esperado e resultado observado.</span></li><li><input type=\"checkbox\"><span>Casos-limite foram executados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Você consegue explicar por que o saldo nunca fica negativo.</span></li></ul>\n      <div class=\"warn\"><b>Limite pedagógico:</b> não use banco de dados, framework, coleção, testes automatizados, rede, retry ou timeout nesta etapa. Essas ferramentas serão ensinadas antes de serem exigidas.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto usa somente o que já foi ensinado. Construa em incrementos pequenos, compile e execute a cada etapa e registre evidências manuais que outra pessoa consiga repetir. Marcos obrigatórios Menu: mostre opções, leia uma linha e encerre ao escolher sair. Depósito: converta e valide um valor positivo antes de alterar o saldo. Saque: recuse zero, negativos e valores acima do saldo. Extrato: registre operações em um array e trate sua capacidade máxima. Resistência: repita a pergunta após formato inválido e encerre conscientemente em EOF. Roteiro mínimo de verificação manual CasoEntradaResultado esperadodepósito válido100.00saldo aumenta em 100.00saque válido30.00 após depósitosaldo 70.00 e operação no extratosaldo insuficientesaque 100.00mensagem clara e saldo preservadoformato inválido\"dez\"nova tentativa, sem stack tracearray cheiouma operação além da capacidadeaviso, sem acessar índice inválidofim da entradaEOFencerramento consciente O arquivo compila com javac e executa com java.Cada caso registra entrada, resultado esperado e resultado observado.Casos-limite foram executados, não apenas descritos.Você consegue explicar por que o saldo nunca fica negativo. Limite pedagógico: não use banco de dados, framework, coleção, testes automatizados, rede, retry ou timeout nesta etapa. Essas ferramentas serão ensinadas antes de serem exigidas."},{"id":"caixa-exercise-decisoes","type":"exercise","authorship":"authored","title":"Antes de codificar: decisões do caixa eletrônico","prompt":"Antes de abrir o editor, responda por escrito: (1) que tipo você vai usar para representar dinheiro e por que double é arriscado aqui; (2) qual vai ser a capacidade do array de operações e onde esse número mora no código; (3) o que acontece quando o array já está cheio e chega uma nova operação; (4) o que o usuário vê na tela quando digita texto onde o programa espera um número.","difficulty":"foundation","criteria":["A escolha de tipo para valores monetários evita o erro de arredondamento binário do double e é justificada, não só declarada.","A capacidade do array aparece como uma constante nomeada, não como um número mágico repetido pelo código.","A resposta sobre array cheio descreve uma recusa explícita, sem tentar escrever num índice fora da faixa válida.","Cada resposta aponta o método ou trecho que a implementa, não apenas a intenção."]},{"id":"caixa-project","type":"project","authorship":"authored","title":"Caixa eletrônico fundamental","brief":"Implemente uma CLI que mantém saldo em memória, registra até uma capacidade declarada de transações e permanece utilizável após entradas inválidas.","requirements":["Menu repete até sair ou chegar EOF","Depósito exige valor positivo","Saque exige valor positivo e saldo suficiente","Extrato registra operações em arrays e informa capacidade esgotada","Leitura, regra e apresentação ficam em métodos separados"],"guidance":"guided","acceptanceCriteria":["Compila com javac e executa com java","Nenhuma entrada textual inválida encerra o programa com stack trace","Saldo nunca fica negativo","Limite do array é tratado sem acesso fora da faixa","Roteiro registra entrada, esperado e observado"],"knowledgeMatrix":[{"requirement":"Menu e encerramento","conceptIds":["contrato-do-laco","switch-expression","parse-validacao-eof"],"chapterIds":["lacos-repeticao","controle-fluxo","entrada-console"],"expectedEvidence":"Sequência com opção inválida, opção válida, sair e EOF."},{"requirement":"Depósito e saque","conceptIds":["ramificacao-condicional","metodo-contrato","casos-limite-invariantes"],"chapterIds":["controle-fluxo","metodos-escopo","logica-programacao"],"expectedEvidence":"Tabela cobrindo zero, negativo, saldo suficiente e insuficiente."},{"requirement":"Extrato em capacidade fixa","conceptIds":["array-indice-length","fronteira-off-by-one"],"chapterIds":["arrays-matrizes","lacos-repeticao"],"expectedEvidence":"Preencher a última posição e tentar registrar uma operação adicional."},{"requirement":"Entrada resistente a formato inválido","conceptIds":["scanner-token-linha","wrapper-boxing-parsing","parse-validacao-eof"],"chapterIds":["strings-wrapper","entrada-console"],"expectedEvidence":"Texto no lugar de opção e valor, seguido de uma operação válida."},{"requirement":"Separação de responsabilidades","conceptIds":["separacao-io-regra","metodo-contrato"],"chapterIds":["metodos-escopo","mini-caixa-eletronico"],"expectedEvidence":"Método de regra recebe valores e retorna resultado sem Scanner nem println."}]}],"resources":[{"id":"caixa-bigdecimal-api","type":"official-docs","title":"API BigDecimal Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/math/BigDecimal.html","reinforces":"Confirma construção por texto, escala e regras de arredondamento usadas em valores monetários.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"caixa-arrays-copy","type":"official-docs","title":"API Arrays Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Arrays.html","reinforces":"Referência opcional para inspecionar arrays durante o projeto, sem exigir coleções.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The terminal ATM project example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"// Observe how names reveal the terminal ATM project contract.","instruction":"The terminal ATM project example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["Matriz garante que o projeto usa somente fundamentos previamente ensinados."]},"editorialReview":{"requiredTopics":["menu-em-laco-com-encerramento-controlado","validacao-de-valores-monetarios","extrato-em-array-de-capacidade-fixa","separacao-de-leitura-regra-e-apresentacao","evidencia-manual-reproduzivel"],"evidenceBlocks":{"menu-em-laco-com-encerramento-controlado":["mini-caixa-eletronico-checklist-4","mini-caixa-eletronico:1","mini-caixa-eletronico:4","mini-caixa-eletronico-content-15"],"validacao-de-valores-monetarios":["mini-caixa-eletronico-checklist-4","mini-caixa-eletronico:3","mini-caixa-eletronico-content-6"],"extrato-em-array-de-capacidade-fixa":["mini-caixa-eletronico-checklist-4","mini-caixa-eletronico:2","mini-caixa-eletronico-content-15"],"separacao-de-leitura-regra-e-apresentacao":["mini-caixa-eletronico-content-8","mini-caixa-eletronico:0","caixa-exercise-decisoes"],"evidencia-manual-reproduzivel":["mini-caixa-eletronico-content-15","caixa-project"]},"primarySources":["JDK Scanner API -- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Scanner.html","JDK BigDecimal API -- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/math/BigDecimal.html"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"classes","moduleId":"oop-modeling","order":0,"title":"Classes e objetos","summary":"Uma classe é um molde — define quais atributos e métodos os objetos criados a partir dela terão. Um objeto é uma instância concreta desse molde, vivendo na memória com seus próprios valores.","objectives":["Distinguir classe, objeto e referência","Criar instâncias e prever aliasing","Explicar alcançabilidade sem tratar stack/heap como garantia da linguagem"],"whyItExists":"Os fundamentos manipulavam valores soltos; classes permitem definir um novo tipo que reúne o estado e as operações de uma responsabilidade do problema.","prerequisiteChapterIds":["mini-caixa-eletronico"],"conceptIds":["modelo-mental-objeto-referencia-e-memoria"],"introducedConceptIds":["classe-instancia-objeto","identidade-referencia-objeto","ciclo-alcancabilidade"],"usedConceptIds":["variavel-tipo-estatico","primitivo-referencia","identidade-conteudo","metodo-contrato"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"classes-intuition","type":"intuition","authorship":"authored","title":"A classe define; cada objeto existe separadamente","body":"Classe é a declaração de um tipo. new avalia argumentos, reserva uma nova instância e devolve uma referência. Duas chamadas de new produzem identidades distintas mesmo quando os campos têm valores iguais.","analogyLimit":"Molde e produto ajudam no início, mas objetos também possuem comportamento e identidade; a classe não fabrica fisicamente nem guarda todas as instâncias."},{"id":"classes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#mini-caixa-eletronico\">Mini-projeto: caixa eletrônico</a></div>\n      </div>","fidelityText":"Dificuldade: Iniciante Pré-requisito: Mini-projeto: caixa eletrônico"},{"id":"classes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Uma <strong>classe</strong> é um molde — define quais atributos e métodos os objetos criados a partir dela terão. Um <strong>objeto</strong> é uma instância concreta desse molde, vivendo na memória com seus próprios valores.</p>","fidelityText":"Uma classe é um molde — define quais atributos e métodos os objetos criados a partir dela terão. Um objeto é uma instância concreta desse molde, vivendo na memória com seus próprios valores."},{"id":"classes-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Coffee {\n    String type;\n    boolean withAcucar;\n\n    void describe() {\n        System.out.println(\"A \" + type + (withAcucar ? \" sweet\" : \" plain\"));\n    }\n}\n\nCoffee express = new Coffee();\nexpress.type = \"express\";\nexpress.withAcucar = false;\nexpress.describe(); // Um expresso puro","fidelityText":"public class Cafe { String tipo; boolean comAcucar; void descrever() { System.out.println(\"Um \" + tipo + (comAcucar ? \" doce\" : \" puro\")); } } Cafe expresso = new Cafe(); expresso.tipo = \"expresso\"; expresso.comAcucar = false; expresso.descrever(); // Um expresso puro","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Coffee</span> {\n    <span class=\"kw\">String</span> type;\n    <span class=\"kw\">boolean</span> withAcucar;\n\n    <span class=\"kw\">void</span> <span class=\"fn\">describe</span>() {\n        System.out.println(<span class=\"str\">\"A \"</span> + type + (withAcucar ? <span class=\"str\">\" sweet\"</span> : <span class=\"str\">\" plain\"</span>));\n    }\n}\n\n<span class=\"cls\">Coffee</span> express = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>();\nexpress.type = <span class=\"str\">\"express\"</span>;\nexpress.withAcucar = <span class=\"kw\">false</span>;\nexpress.describe(); <span class=\"com\">// Um expresso puro</span>","caption":"Exemplo executável de classes.","explanation":["A declaração Cafe define campos e um método de instância.","new Cafe() cria uma instância e a chamada descrever usa o objeto referenciado por expresso."],"commonMistakes":["Achar que Cafe expresso já cria objeto","Acessar membro quando a referência é null"]},{"id":"classes-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Modelo mental: objeto, referência e memória</h2>","fidelityText":"Modelo mental: objeto, referência e memória"},{"id":"classes-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Para raciocinar sobre o comportamento do programa, separe a instância da variável que permite alcançá-la. É comum desenhar variáveis locais em uma <em>stack</em> e objetos em um <em>heap</em>, mas esse é um modelo didático: a especificação da linguagem não promete a localização física de cada valor, e a JVM pode otimizar a execução.</p>","fidelityText":"Para raciocinar sobre o comportamento do programa, separe a instância da variável que permite alcançá-la. É comum desenhar variáveis locais em uma stack e objetos em um heap, mas esse é um modelo didático: a especificação da linguagem não promete a localização física de cada valor, e a JVM pode otimizar a execução."},{"id":"classes-content-6","type":"html","authorship":"legacy-preserved","html":"<ul style=\"color:var(--ink-dim)\">\n        <li><strong>Variável de referência</strong> — contém <code>null</code> ou um valor que permite alcançar um objeto; não contém uma cópia automática de todos os campos.</li>\n        <li><strong>Objeto</strong> — possui identidade e campos próprios. Cada expressão <code>new Cafe()</code> cria uma nova instância antes de devolver sua referência.</li>\n      </ul>","fidelityText":"Variável de referência — contém null ou um valor que permite alcançar um objeto; não contém uma cópia automática de todos os campos. Objeto — possui identidade e campos próprios. Cada expressão new Cafe() cria uma nova instância antes de devolver sua referência."},{"id":"classes-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"Coffee a = new Coffee();\na.type = \"express\";\n\nCoffee b = a;              // b aponta para o MESMO objeto que a\nb.type = \"latte\";\n\nSystem.out.println(a.type); // \"latte\" -- a e b são a mesma referência!","fidelityText":"Cafe a = new Cafe(); a.tipo = \"expresso\"; Cafe b = a; // b aponta para o MESMO objeto que a b.tipo = \"latte\"; System.out.println(a.tipo); // \"latte\" -- a e b são a mesma referência!","highlightedHtml":"<span class=\"cls\">Coffee</span> a = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>();\na.type = <span class=\"str\">\"express\"</span>;\n\n<span class=\"cls\">Coffee</span> b = a;              <span class=\"com\">// b aponta para o MESMO objeto que a</span>\nb.type = <span class=\"str\">\"latte\"</span>;\n\nSystem.out.println(a.type); <span class=\"com\">// \"latte\" -- a e b são a mesma referência!</span>","caption":"Exemplo executável de classes.","explanation":["b = a copia a referência para o mesmo Cafe.","A atribuição por b altera o único objeto alcançado também por a."],"commonMistakes":["Esperar uma cópia automática","Usar clone sem conhecer seu contrato"]},{"id":"classes-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Em Java, <code>b = a</code> copia o valor da referência e não cria uma instância. Para obter outro objeto independente, execute outro <code>new</code> e defina conscientemente quais valores precisam ser copiados. Estratégias gerais de cópia possuem contratos próprios e não são necessárias neste capítulo.</div>","fidelityText":"Em Java, b = a copia o valor da referência e não cria uma instância. Para obter outro objeto independente, execute outro new e defina conscientemente quais valores precisam ser copiados. Estratégias gerais de cópia possuem contratos próprios e não são necessárias neste capítulo."},{"id":"classes-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Garbage Collector:</b> quando um objeto deixa de ser alcançável a partir das referências vivas do programa, ele fica elegível à coleta. Não existe garantia de quando a coleta ocorrerá. GC administra memória; não substitui o fechamento explícito de arquivos, conexões ou outros recursos externos, ensinados depois.</div>","fidelityText":"Garbage Collector: quando um objeto deixa de ser alcançável a partir das referências vivas do programa, ele fica elegível à coleta. Não existe garantia de quando a coleta ocorrerá. GC administra memória; não substitui o fechamento explícito de arquivos, conexões ou outros recursos externos, ensinados depois."},{"id":"classes-exercise-10","type":"exercise","authorship":"legacy-preserved","title":"Exercício 1.1 — Sua primeira classe","prompt":"Crie uma classe Livro com atributos titulo, autor (String) e paginas (int). Adicione um método resumo() que imprime \"Dom Casmurro, de Machado de Assis (256 páginas)\". Crie dois objetos Livro diferentes no main e chame resumo() em cada.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 1.1 — Sua primeira classefácil Crie uma classe Livro com atributos titulo, autor (String) e paginas (int). Adicione um método resumo() que imprime \"Dom Casmurro, de Machado de Assis (256 páginas)\". Crie dois objetos Livro diferentes no main e chame resumo() em cada. Ver solução public class Livro { String titulo; String autor; int paginas; void resumo() { System.out.println(titulo + \", de \" + autor + \" (\" + paginas + \" páginas)\"); } } public class Main { public static void main(String[] args) { Livro l1 = new Livro(); l1.titulo = \"Dom Casmurro\"; l1.autor = \"Machado de Assis\"; l1.paginas = 256; l1.resumo(); Livro l2 = new Livro(); l2.titulo = \"1984\"; l2.autor = \"George Orwell\"; l2.paginas = 328; l2.resumo(); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 1.1 — Sua primeira classe</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Crie uma classe <code>Livro</code> com atributos <code>titulo</code>, <code>autor</code> (String) e <code>paginas</code> (int). Adicione um método <code>resumo()</code> que imprime <em>\"Dom Casmurro, de Machado de Assis (256 páginas)\"</em>. Crie dois objetos <code>Livro</code> diferentes no <code>main</code> e chame <code>resumo()</code> em cada.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"kw\">String</span> title;\n    <span class=\"kw\">String</span> author;\n    <span class=\"kw\">int</span> pages;\n\n    <span class=\"kw\">void</span> <span class=\"fn\">summary</span>() {\n        System.out.println(title + <span class=\"str\">\", of \"</span> + author + <span class=\"str\">\" (\"</span> + pages + <span class=\"str\">\" pages)\"</span>);\n    }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Main</span> {\n    <span class=\"kw\">public static void</span> <span class=\"fn\">main</span>(<span class=\"kw\">String</span>[] args) {\n        <span class=\"cls\">Book</span> l1 = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>();\n        l1.title = <span class=\"str\">\"Dom Casmurro\"</span>; l1.author = <span class=\"str\">\"Machado de Assis\"</span>; l1.pages = 256;\n        l1.summary();\n\n        <span class=\"cls\">Book</span> l2 = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>();\n        l2.title = <span class=\"str\">\"1984\"</span>; l2.author = <span class=\"str\">\"George Orwell\"</span>; l2.pages = 328;\n        l2.summary();\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"classes-exercise-11","type":"exercise","authorship":"legacy-preserved","title":"Exercício 1.2 — Referência compartilhada","prompt":"Sem executar, preveja a saída e explique o resultado usando identidade de objeto e cópia de referências:","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 1.2 — Referência compartilhadamédio Sem executar, preveja a saída e explique o resultado usando identidade de objeto e cópia de referências: Cafe x = new Cafe(); x.tipo = \"coado\"; Cafe y = new Cafe(); y.tipo = \"coado\"; System.out.println(x == y); y = x; System.out.println(x == y); Ver solução Saída: false depois true. No primeiro ==, x e y guardam referências para dois objetos diferentes, mesmo com o mesmo valor de tipo; == compara a identidade referenciada, não o conteúdo. Depois de y = x, ambas as variáveis passam a apontar para o mesmo objeto, então == retorna true. A comparação de conteúdo por equals() será aprofundada depois do contrato básico herdado de Object.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 1.2 — Referência compartilhada</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Sem executar, preveja a saída e explique o resultado usando identidade de objeto e cópia de referências:</p>\n        <pre class=\"code\"><span class=\"cls\">Coffee</span> x = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>();\nx.type = <span class=\"str\">\"coado\"</span>;\n<span class=\"cls\">Coffee</span> y = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>();\ny.type = <span class=\"str\">\"coado\"</span>;\nSystem.out.println(x == y);\ny = x;\nSystem.out.println(x == y);</pre>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>Saída: <code>false</code> depois <code>true</code>.</strong> No primeiro <code>==</code>, <code>x</code> e <code>y</code> guardam referências para dois objetos <em>diferentes</em>, mesmo com o mesmo valor de <code>tipo</code>; <code>==</code> compara a identidade referenciada, não o conteúdo. Depois de <code>y = x</code>, ambas as variáveis passam a apontar para o mesmo objeto, então <code>==</code> retorna <code>true</code>. A comparação de conteúdo por <code>equals()</code> será aprofundada depois do contrato básico herdado de <code>Object</code>.</p>\n        </div>\n      </div>"},{"id":"classes-model","type":"mental-model","authorship":"authored","title":"Referência não é o objeto","body":"Uma variável de tipo Cafe pode conter null ou uma referência que permite alcançar uma instância. b = a copia essa referência; não executa new nem duplica campos. A especificação exige o comportamento observável, mas não obriga toda variável local a morar literalmente numa região chamada stack.","flow":["Declarar Cafe a não cria Cafe","new Cafe() cria uma instância","a recebe a referência","b = a copia a referência","alterar via b é observado via a"],"lifecycle":["Enquanto alguma raiz alcança o objeto, ele permanece alcançável","Sem referências alcançáveis, torna-se elegível à coleta","O momento da coleta não é garantido e GC não fecha recursos externos"]},{"id":"classes-quiz","type":"quiz","authorship":"authored","conceptId":"identidade-referencia-objeto","prompt":"Duas variáveis recebem new Cafe() separadamente e os campos ficam iguais. O que x == y produz?","options":[{"id":"classes-q-a","label":"false, porque as referências apontam para instâncias distintas.","correct":true,"explanation":"Cada new criou uma identidade; == entre referências verifica identidade, não conteúdo dos campos."},{"id":"classes-q-b","label":"true, porque campos iguais tornam os objetos idênticos.","correct":false,"explanation":"Igualdade de estado não altera a identidade criada por cada new."},{"id":"classes-q-c","label":"Erro de compilação, porque objetos não aceitam ==.","correct":false,"explanation":"Referências compatíveis podem ser comparadas com ==."}]}],"resources":[{"id":"classes-jls-declaration","type":"reference","title":"JLS 8.1: declarações de classe","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.1","reinforces":"Define classe, corpo, superclasse e interfaces no contrato da linguagem.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"classes-jls-instance","type":"reference","title":"JLS 12.5: criação de instâncias","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-12.html#jls-12.5","reinforces":"Descreve alocação, valores padrão, inicialização e construtor.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The classes example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public class Coffee {","instruction":"The classes example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"atributos","moduleId":"oop-modeling","order":1,"title":"Atributos e métodos","summary":"Atributos (campos) guardam o estado do objeto. Métodos definem seu comportamento e podem receber parâmetros, retornar valores, ou apenas executar uma ação.","objectives":["Separar campo, parâmetro e variável local","Colocar comportamento junto ao estado que protege","Distinguir consulta, comando, retorno e efeito"],"whyItExists":"Um objeto útil precisa lembrar estado entre chamadas e oferecer operações coerentes sobre ele; campos soltos e métodos utilitários não expressam quem protege a regra.","prerequisiteChapterIds":["classes"],"conceptIds":["passagem-de-parametros-sempre-por-valor","sobrecarga-de-metodos-overload-e-como-o-compilador-escolhe"],"introducedConceptIds":["estado-comportamento","campo-instancia","comando-consulta"],"usedConceptIds":["classe-instancia-objeto","identidade-referencia-objeto","metodo-contrato","passagem-por-valor"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"atributos-intuition","type":"intuition","authorship":"authored","title":"O objeto lembra e age","body":"Campos registram o estado entre chamadas. Métodos observam ou modificam esse estado sob um contrato. A pergunta de modelagem não é “quais getters criar?”, mas “qual objeto deve garantir esta regra?”.","analogyLimit":"Pensar no objeto como responsável ajuda, mas ele não é uma pessoa; efeitos e dependências ainda precisam estar explícitos no código."},{"id":"atributos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Iniciante-Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#classes\">01 · Classes e objetos</a></div>\n      </div>","fidelityText":"Dificuldade: Iniciante-Intermediário Pré-requisito: 01 · Classes e objetos"},{"id":"atributos-content-2","type":"html","authorship":"legacy-preserved","html":"<p><strong>Atributos</strong> (campos) guardam o estado do objeto. <strong>Métodos</strong> definem seu comportamento e podem receber parâmetros, retornar valores, ou apenas executar uma ação.</p>","fidelityText":"Atributos (campos) guardam o estado do objeto. Métodos definem seu comportamento e podem receber parâmetros, retornar valores, ou apenas executar uma ação."},{"id":"atributos-content-3","type":"html","authorship":"legacy-preserved","html":"<p>Vale nomear uma distinção que vai guiar boas decisões de design mais adiante: um <strong>método de consulta</strong> (<em>query</em>) só lê e devolve informação, sem alterar o estado do objeto (como <code>getSaldo()</code> abaixo); um <strong>método de comando</strong> (<em>command</em>) altera o estado e tipicamente não devolve o próprio valor consultável (como <code>depositar</code> e <code>sacar</code>). Misturar os dois no mesmo método — alterar estado <em>e</em> devolver um valor de consulta relevante — não é proibido, mas dificulta prever o que uma chamada faz só pelo nome.</p>","fidelityText":"Vale nomear uma distinção que vai guiar boas decisões de design mais adiante: um método de consulta (query) só lê e devolve informação, sem alterar o estado do objeto (como getSaldo() abaixo); um método de comando (command) altera o estado e tipicamente não devolve o próprio valor consultável (como depositar e sacar). Misturar os dois no mesmo método — alterar estado e devolver um valor de consulta relevante — não é proibido, mas dificulta prever o que uma chamada faz só pelo nome."},{"id":"atributos-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"public class BankAccount {\n    double balance;\n\n    void deposit(double value) { balance = balance + value; }\n\n    boolean withdraw(double value) {\n        if (value > balance) return false;\n        balance = balance - value;\n        return true;\n    }\n}","fidelityText":"public class ContaBancaria { double saldo; void depositar(double valor) { saldo = saldo + valor; } boolean sacar(double valor) { if (valor > saldo) return false; saldo = saldo - valor; return true; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">BankAccount</span> {\n    <span class=\"kw\">double</span> balance;\n\n    <span class=\"kw\">void</span> <span class=\"fn\">deposit</span>(<span class=\"kw\">double</span> value) { balance = balance + value; }\n\n    <span class=\"kw\">boolean</span> <span class=\"fn\">withdraw</span>(<span class=\"kw\">double</span> value) {\n        <span class=\"kw\">if</span> (value &gt; balance) <span class=\"kw\">return false</span>;\n        balance = balance - value;\n        <span class=\"kw\">return true</span>;\n    }\n}","caption":"Exemplo executável de atributos.","explanation":["Atributos guardam estado; métodos definem comportamento -- depositar/sacar alteram saldo através de regras, nunca por atribuição direta de fora."]},{"id":"atributos-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Passagem de parâmetros: sempre por valor</h2>","fidelityText":"Passagem de parâmetros: sempre por valor"},{"id":"atributos-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Uma fonte comum de confusão: Java <strong>sempre</strong> passa parâmetros por valor — nunca por referência, no sentido estrito da palavra. A pegadinha é que, para objetos, o \"valor\" passado é a <em>referência</em> (o endereço), não o objeto em si.</p>","fidelityText":"Uma fonte comum de confusão: Java sempre passa parâmetros por valor — nunca por referência, no sentido estrito da palavra. A pegadinha é que, para objetos, o \"valor\" passado é a referência (o endereço), não o objeto em si."},{"id":"atributos-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"static void doubleNumber(int n) {\n    n = n * 2; // altera só a cópia local\n}\n\nstatic void renames(Coffee c) {\n    c.type = \"renamed\"; // altera o OBJETO apontado (visível fora!)\n}\n\nstatic void trocaObject(Coffee c) {\n    c = new Coffee(); // só troca para o que a cópia LOCAL da referência aponta\n}\n\n// main:\nint x = 5;\ndoubleNumber(x);\nSystem.out.println(x); // 5 -- inalterado, primitivo copiado por valor\n\nCoffee myCoffee = new Coffee();\nmyCoffee.type = \"original\";\nrenames(myCoffee);\nSystem.out.println(myCoffee.type); // \"renomeado\" -- o objeto no heap foi alterado\n\ntrocaObject(myCoffee);\nSystem.out.println(myCoffee.type); // ainda \"renomeado\" -- reatribuir o parâmetro local não afeta a variável externa","fidelityText":"static void dobra(int n) { n = n * 2; // altera só a cópia local } static void renomeia(Cafe c) { c.tipo = \"renomeado\"; // altera o OBJETO apontado (visível fora!) } static void trocaObjeto(Cafe c) { c = new Cafe(); // só troca para o que a cópia LOCAL da referência aponta } // main: int x = 5; dobra(x); System.out.println(x); // 5 -- inalterado, primitivo copiado por valor Cafe meuCafe = new Cafe(); meuCafe.tipo = \"original\"; renomeia(meuCafe); System.out.println(meuCafe.tipo); // \"renomeado\" -- o objeto no heap foi alterado trocaObjeto(meuCafe); System.out.println(meuCafe.tipo); // ainda \"renomeado\" -- reatribuir o parâmetro local não afeta a variável externa","highlightedHtml":"<span class=\"kw\">static void</span> <span class=\"fn\">doubleNumber</span>(<span class=\"kw\">int</span> n) {\n    n = n * 2; <span class=\"com\">// altera só a cópia local</span>\n}\n\n<span class=\"kw\">static void</span> <span class=\"fn\">renames</span>(<span class=\"cls\">Coffee</span> c) {\n    c.type = <span class=\"str\">\"renamed\"</span>; <span class=\"com\">// altera o OBJETO apontado (visível fora!)</span>\n}\n\n<span class=\"kw\">static void</span> <span class=\"fn\">trocaObject</span>(<span class=\"cls\">Coffee</span> c) {\n    c = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>(); <span class=\"com\">// só troca para o que a cópia LOCAL da referência aponta</span>\n}\n\n<span class=\"com\">// main:</span>\n<span class=\"kw\">int</span> x = 5;\n<span class=\"fn\">doubleNumber</span>(x);\nSystem.out.println(x); <span class=\"com\">// 5 -- inalterado, primitivo copiado por valor</span>\n\n<span class=\"cls\">Coffee</span> myCoffee = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>();\nmyCoffee.type = <span class=\"str\">\"original\"</span>;\n<span class=\"fn\">renames</span>(myCoffee);\nSystem.out.println(myCoffee.type); <span class=\"com\">// \"renomeado\" -- o objeto no heap foi alterado</span>\n\n<span class=\"fn\">trocaObject</span>(myCoffee);\nSystem.out.println(myCoffee.type); <span class=\"com\">// ainda \"renomeado\" -- reatribuir o parâmetro local não afeta a variável externa</span>","caption":"Exemplo executável de atributos.","explanation":["Primitivos são copiados por valor; para objetos, a cópia é da referência -- mutar o objeto apontado é visível fora, mas reatribuir o parâmetro local não é."],"commonMistakes":["Achar que reatribuir o parâmetro de um objeto afeta a variável do chamador"]},{"id":"atributos-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A regra mental que resolve 100% dos casos: <b>a cópia é sempre da referência (do \"endereço\"), nunca do objeto</b>. Se você usa a referência copiada para <em>mudar campos</em> do objeto apontado, o efeito é visível fora do método. Se você usa a referência copiada para <em>apontar para outro objeto</em> (<code>c = new Cafe()</code>), isso só afeta a variável local — a variável de fora continua apontando para o objeto original.</div>","fidelityText":"A regra mental que resolve 100% dos casos: a cópia é sempre da referência (do \"endereço\"), nunca do objeto. Se você usa a referência copiada para mudar campos do objeto apontado, o efeito é visível fora do método. Se você usa a referência copiada para apontar para outro objeto (c = new Cafe()), isso só afeta a variável local — a variável de fora continua apontando para o objeto original."},{"id":"atributos-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Sobrecarga de métodos (overload) e como o compilador escolhe</h2>","fidelityText":"Sobrecarga de métodos (overload) e como o compilador escolhe"},{"id":"atributos-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Java permite vários métodos com o <strong>mesmo nome</strong>, desde que a <strong>assinatura</strong> (tipo e quantidade de parâmetros) seja diferente. O tipo de retorno sozinho não conta.</p>","fidelityText":"Java permite vários métodos com o mesmo nome, desde que a assinatura (tipo e quantidade de parâmetros) seja diferente. O tipo de retorno sozinho não conta."},{"id":"atributos-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Calculator {\n    int sum(int a, int b) { return a + b; }\n    double sum(double a, double b) { return a + b; }\n    int sum(int a, int b, int c) { return a + b + c; }\n\n    // ❌ NÃO compila: mesma assinatura, só o retorno muda\n    // long somar(int a, int b) { return a + b; }\n}","fidelityText":"public class Calculadora { int somar(int a, int b) { return a + b; } double somar(double a, double b) { return a + b; } int somar(int a, int b, int c) { return a + b + c; } // ❌ NÃO compila: mesma assinatura, só o retorno muda // long somar(int a, int b) { return a + b; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Calculator</span> {\n    <span class=\"kw\">int</span> <span class=\"fn\">sum</span>(<span class=\"kw\">int</span> a, <span class=\"kw\">int</span> b) { <span class=\"kw\">return</span> a + b; }\n    <span class=\"kw\">double</span> <span class=\"fn\">sum</span>(<span class=\"kw\">double</span> a, <span class=\"kw\">double</span> b) { <span class=\"kw\">return</span> a + b; }\n    <span class=\"kw\">int</span> <span class=\"fn\">sum</span>(<span class=\"kw\">int</span> a, <span class=\"kw\">int</span> b, <span class=\"kw\">int</span> c) { <span class=\"kw\">return</span> a + b + c; }\n\n    <span class=\"com\">// ❌ NÃO compila: mesma assinatura, só o retorno muda</span>\n    <span class=\"com\">// long somar(int a, int b) { return a + b; }</span>\n}","caption":"Exemplo executável de atributos.","explanation":["Sobrecarga distingue métodos pela assinatura (tipo e quantidade de parâmetros) -- o tipo de retorno sozinho não compila como diferenciador."],"commonMistakes":["Tentar sobrecarregar dois métodos que só diferem no tipo de retorno"]},{"id":"atributos-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Segredo/pegadinha:</b> quando você chama <code>somar(1, 2)</code> com dois <code>int</code> literais, Java escolhe a versão <code>int</code> — mas se um dos argumentos for <code>long</code> e não existir overload exato, o compilador faz um <em>widening</em> automático (ex: <code>int</code>→<code>double</code>) antes de tentar autoboxing (<code>int</code>→<code>Integer</code>). A ordem de preferência do compilador é: 1) correspondência exata, 2) widening primitivo, 3) autoboxing, 4) varargs. Isso raramente importa, até o dia em que causa um bug sutil de overload ambíguo.</div>","fidelityText":"Segredo/pegadinha: quando você chama somar(1, 2) com dois int literais, Java escolhe a versão int — mas se um dos argumentos for long e não existir overload exato, o compilador faz um widening automático (ex: int→double) antes de tentar autoboxing (int→Integer). A ordem de preferência do compilador é: 1) correspondência exata, 2) widening primitivo, 3) autoboxing, 4) varargs. Isso raramente importa, até o dia em que causa um bug sutil de overload ambíguo."},{"id":"atributos-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Exercício 2.1 — Calculadora de retângulo","prompt":"Crie uma classe Retangulo com largura e altura (double). Adicione métodos area() e perimetro(). Teste com um retângulo 4 por 5.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 2.1 — Calculadora de retângulofácil Crie uma classe Retangulo com largura e altura (double). Adicione métodos area() e perimetro(). Teste com um retângulo 4 por 5. Ver solução public class Retangulo { double largura, altura; double area() { return largura * altura; } double perimetro() { return 2 * (largura + altura); } } Retangulo r = new Retangulo(); r.largura = 4; r.altura = 5; System.out.println(r.area()); // 20.0 System.out.println(r.perimetro()); // 18.0","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 2.1 — Calculadora de retângulo</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Crie uma classe <code>Retangulo</code> com <code>largura</code> e <code>altura</code> (double). Adicione métodos <code>area()</code> e <code>perimetro()</code>. Teste com um retângulo 4 por 5.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Rectangle</span> {\n    <span class=\"kw\">double</span> width, height;\n    <span class=\"kw\">double</span> <span class=\"fn\">area</span>() { <span class=\"kw\">return</span> width * height; }\n    <span class=\"kw\">double</span> <span class=\"fn\">perimetro</span>() { <span class=\"kw\">return</span> 2 * (width + height); }\n}\n<span class=\"cls\">Rectangle</span> r = <span class=\"kw\">new</span> <span class=\"cls\">Rectangle</span>();\nr.width = 4; r.height = 5;\nSystem.out.println(r.area());      <span class=\"com\">// 20.0</span>\nSystem.out.println(r.perimetro()); <span class=\"com\">// 18.0</span></pre>\n        </div>\n      </div>"},{"id":"atributos-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 2.2 — Prevendo o comportamento de referência","prompt":"Crie um método renomear(Cafe cafe, String novoTipo) que altera cafe.tipo e outro método substituir(Cafe cafe) que apenas executa cafe = new Cafe(). Preveja o estado do objeto original depois de cada chamada e explique a diferença entre mutar o objeto e reatribuir a cópia local da referência.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 2.2 — Prevendo o comportamento de referênciadifícil Crie um método renomear(Cafe cafe, String novoTipo) que altera cafe.tipo e outro método substituir(Cafe cafe) que apenas executa cafe = new Cafe(). Preveja o estado do objeto original depois de cada chamada e explique a diferença entre mutar o objeto e reatribuir a cópia local da referência. Ver solução static void renomear(Cafe cafe, String novoTipo) { cafe.tipo = novoTipo; // muta o objeto alcançado } static void substituir(Cafe cafe) { cafe = new Cafe(); // reatribui só o parâmetro local } Cafe original = new Cafe(); original.tipo = \"coado\"; renomear(original, \"latte\"); System.out.println(original.tipo); // latte substituir(original); System.out.println(original.tipo); // ainda latte","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 2.2 — Prevendo o comportamento de referência</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie um método <code>renomear(Cafe cafe, String novoTipo)</code> que altera <code>cafe.tipo</code> e outro método <code>substituir(Cafe cafe)</code> que apenas executa <code>cafe = new Cafe()</code>. Preveja o estado do objeto original depois de cada chamada e explique a diferença entre mutar o objeto e reatribuir a cópia local da referência.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">static void</span> <span class=\"fn\">rename</span>(<span class=\"cls\">Coffee</span> coffee, <span class=\"kw\">String</span> newType) {\n    coffee.type = newType; <span class=\"com\">// muta o objeto alcançado</span>\n}\n\n<span class=\"kw\">static void</span> <span class=\"fn\">substituir</span>(<span class=\"cls\">Coffee</span> coffee) {\n    coffee = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>(); <span class=\"com\">// reatribui só o parâmetro local</span>\n}\n\n<span class=\"cls\">Coffee</span> original = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>();\noriginal.type = <span class=\"str\">\"coado\"</span>;\nrename(original, <span class=\"str\">\"latte\"</span>);\nSystem.out.println(original.type); <span class=\"com\">// latte</span>\nsubstituir(original);\nSystem.out.println(original.type); <span class=\"com\">// ainda latte</span></pre>\n        </div>\n      </div>"},{"id":"atributos-comparison","type":"comparison","authorship":"authored","title":"Campo, parâmetro e variável local","criteria":["pertence a","duração","inicialização"],"alternatives":[{"name":"Campo de instância","values":["objeto","enquanto o objeto existe","valor padrão antes do construtor"],"useWhen":"o dado compõe o estado do objeto","avoidWhen":"serve apenas a um cálculo temporário"},{"name":"Parâmetro","values":["chamada","durante o método","recebe cópia do argumento"],"useWhen":"a operação precisa de entrada externa","avoidWhen":"o valor já pertence ao estado"},{"name":"Variável local","values":["bloco","durante o bloco","deve ser inicializada antes do uso"],"useWhen":"guardar resultado intermediário","avoidWhen":"o dado precisa sobreviver entre chamadas"}]},{"id":"atributos-quiz","type":"quiz","authorship":"authored","conceptId":"comando-consulta","prompt":"Qual método deixa seu efeito mais claro para sacar um valor?","options":[{"id":"atributos-q-a","label":"boolean sacar(double valor), que altera saldo apenas se puder e informa o resultado.","correct":true,"explanation":"A assinatura expõe entrada, efeito condicionado e resultado observável."},{"id":"atributos-q-b","label":"double getSaldo(), que também desconta um valor escondido.","correct":false,"explanation":"Uma consulta com efeito oculto viola a expectativa criada pelo nome."},{"id":"atributos-q-c","label":"void processar(), que lê Scanner, altera saldo e imprime tudo.","correct":false,"explanation":"O contrato não revela a operação e mistura fronteira com regra."}]}],"resources":[{"id":"atributos-jls-fields","type":"reference","title":"JLS 8.3: campos","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.3","reinforces":"Define campos de classe, de instância, inicialização e ocultação.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"atributos-jls-methods","type":"reference","title":"JLS 8.4: métodos","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4","reinforces":"Confirma parâmetros, retorno, corpo e assinatura.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The fields example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public class BankAccount {","instruction":"The fields example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"construtores","moduleId":"oop-modeling","order":2,"title":"Construtores","summary":"Um construtor é um método especial, com o mesmo nome da classe e sem tipo de retorno, executado automaticamente no new. Ele existe para garantir que todo objeto nasça em um estado válido, em vez de depender que alguém lembre de preencher cada atributo depois.","objectives":["Garantir estado inicial válido","Prever quando existe construtor padrão","Usar this e this() sem duplicar inicialização"],"whyItExists":"Preencher campos depois de new cria uma janela em que o objeto existe incompleto; o construtor concentra os dados obrigatórios e estabelece a invariante inicial.","prerequisiteChapterIds":["atributos"],"conceptIds":["sobrecarga-de-construtores-e-encadeamento-com-this","construtor-padrao-quando-ele-existe"],"introducedConceptIds":["construtor-invariante","construtor-padrao","this-encadeamento"],"usedConceptIds":["estado-comportamento","campo-instancia","ordem-de-condicoes","metodo-contrato"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"construtores-intuition","type":"intuition","authorship":"authored","title":"new precisa terminar com um objeto utilizável","body":"O construtor não é um método comum nem possui retorno. Ele participa da criação da instância e deve estabelecer todos os campos obrigatórios antes de entregar a referência ao chamador.","analogyLimit":"“Nascimento” comunica estado inicial, mas Java pode criar objetos por mecanismos especiais; aqui estudamos a criação normal com new."},{"id":"construtores-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#atributos\">02 · Atributos e métodos</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: 02 · Atributos e métodos"},{"id":"construtores-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Um <strong>construtor</strong> é um método especial, com o mesmo nome da classe e sem tipo de retorno, executado automaticamente no <code>new</code>. Ele existe para garantir que todo objeto nasça em um estado válido, em vez de depender que alguém lembre de preencher cada atributo depois.</p>","fidelityText":"Um construtor é um método especial, com o mesmo nome da classe e sem tipo de retorno, executado automaticamente no new. Ele existe para garantir que todo objeto nasça em um estado válido, em vez de depender que alguém lembre de preencher cada atributo depois."},{"id":"construtores-content-3","type":"html","authorship":"legacy-preserved","html":"<p>O termo técnico para essa garantia é <strong>invariante</strong>: uma regra que deve permanecer verdadeira durante toda a vida do objeto (por exemplo, \"saldo nunca é negativo\", \"titular nunca é vazio\"). Um construtor que aceita qualquer valor sem validar deixa a porta aberta para um <strong>objeto parcialmente válido</strong> — instanciado com sucesso, mas já nascendo em um estado que a classe deveria ter recusado. Validar no construtor (lançando exceção ou normalizando o valor, como no exercício abaixo) é a primeira e mais barata linha de defesa contra esse problema — bem mais barata do que descobrir o estado inválido três chamadas de método depois.</p>","fidelityText":"O termo técnico para essa garantia é invariante: uma regra que deve permanecer verdadeira durante toda a vida do objeto (por exemplo, \"saldo nunca é negativo\", \"titular nunca é vazio\"). Um construtor que aceita qualquer valor sem validar deixa a porta aberta para um objeto parcialmente válido — instanciado com sucesso, mas já nascendo em um estado que a classe deveria ter recusado. Validar no construtor (lançando exceção ou normalizando o valor, como no exercício abaixo) é a primeira e mais barata linha de defesa contra esse problema — bem mais barata do que descobrir o estado inválido três chamadas de método depois."},{"id":"construtores-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Coffee {\n    String type;\n    boolean withAcucar;\n\n    Coffee(String type, boolean withAcucar) {\n        this.type = type;\n        this.withAcucar = withAcucar;\n    }\n}\nCoffee express = new Coffee(\"express\", false);","fidelityText":"public class Cafe { String tipo; boolean comAcucar; Cafe(String tipo, boolean comAcucar) { this.tipo = tipo; this.comAcucar = comAcucar; } } Cafe expresso = new Cafe(\"expresso\", false);","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Coffee</span> {\n    <span class=\"kw\">String</span> type;\n    <span class=\"kw\">boolean</span> withAcucar;\n\n    <span class=\"fn\">Coffee</span>(<span class=\"kw\">String</span> type, <span class=\"kw\">boolean</span> withAcucar) {\n        <span class=\"kw\">this</span>.type = type;\n        <span class=\"kw\">this</span>.withAcucar = withAcucar;\n    }\n}\n<span class=\"cls\">Coffee</span> express = <span class=\"kw\">new</span> <span class=\"cls\">Coffee</span>(<span class=\"str\">\"express\"</span>, <span class=\"kw\">false</span>);","caption":"Exemplo executável de construtores.","explanation":["O construtor roda automaticamente no new, garantindo que tipo e comAcucar já estejam definidos antes de qualquer uso do objeto."]},{"id":"construtores-content-5","type":"html","authorship":"legacy-preserved","html":"<p><code>this</code> se refere ao próprio objeto sendo construído — aqui, diferencia o parâmetro <code>tipo</code> do atributo <code>tipo</code>.</p>","fidelityText":"this se refere ao próprio objeto sendo construído — aqui, diferencia o parâmetro tipo do atributo tipo."},{"id":"construtores-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Sobrecarga de construtores e encadeamento com this(...)</h2>","fidelityText":"Sobrecarga de construtores e encadeamento com this(...)"},{"id":"construtores-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Coffee {\n    String type;\n    boolean withAcucar;\n\n    Coffee(String type, boolean withAcucar) {\n        this.type = type;\n        this.withAcucar = withAcucar;\n    }\n\n    Coffee(String type) {\n        this(type, false); // delega para o construtor de cima -- deve ser a 1ª linha\n    }\n}","fidelityText":"public class Cafe { String tipo; boolean comAcucar; Cafe(String tipo, boolean comAcucar) { this.tipo = tipo; this.comAcucar = comAcucar; } Cafe(String tipo) { this(tipo, false); // delega para o construtor de cima -- deve ser a 1ª linha } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Coffee</span> {\n    <span class=\"kw\">String</span> type;\n    <span class=\"kw\">boolean</span> withAcucar;\n\n    <span class=\"fn\">Coffee</span>(<span class=\"kw\">String</span> type, <span class=\"kw\">boolean</span> withAcucar) {\n        <span class=\"kw\">this</span>.type = type;\n        <span class=\"kw\">this</span>.withAcucar = withAcucar;\n    }\n\n    <span class=\"fn\">Coffee</span>(<span class=\"kw\">String</span> type) {\n        <span class=\"kw\">this</span>(type, <span class=\"kw\">false</span>); <span class=\"com\">// delega para o construtor de cima -- deve ser a 1ª linha</span>\n    }\n}","caption":"Exemplo executável de construtores.","explanation":["this(...) delega para outro construtor da mesma classe -- precisa ser a primeira instrução do construtor que delega."],"commonMistakes":["Chamar this(...) depois de outras instruções no construtor"]},{"id":"construtores-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Construtor padrão: quando ele existe</h2>","fidelityText":"Construtor padrão: quando ele existe"},{"id":"construtores-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Se uma classe não declara construtor algum, o compilador declara um construtor sem argumentos. Assim que você declara qualquer construtor, esse padrão deixa de ser criado. Portanto, adicionar <code>Produto(String nome)</code> faz uma chamada antiga a <code>new Produto()</code> deixar de compilar, a menos que você também declare conscientemente essa opção.</p>","fidelityText":"Se uma classe não declara construtor algum, o compilador declara um construtor sem argumentos. Assim que você declara qualquer construtor, esse padrão deixa de ser criado. Portanto, adicionar Produto(String nome) faz uma chamada antiga a new Produto() deixar de compilar, a menos que você também declare conscientemente essa opção."},{"id":"construtores-content-10","type":"html","authorship":"legacy-preserved","html":"<ol style=\"color:var(--ink-dim)\">\n        <li><code>class Produto { }</code> recebe um construtor sem argumentos implícito.</li>\n        <li><code>class Produto { Produto(String nome) { ... } }</code> possui apenas o construtor declarado.</li>\n        <li>Construtores podem ser sobrecarregados por listas de parâmetros diferentes.</li>\n        <li>Eles não são métodos, não declaram retorno e não são herdados.</li>\n      </ol>","fidelityText":"class Produto { } recebe um construtor sem argumentos implícito. class Produto { Produto(String nome) { ... } } possui apenas o construtor declarado. Construtores podem ser sobrecarregados por listas de parâmetros diferentes. Eles não são métodos, não declaram retorno e não são herdados."},{"id":"construtores-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Product {\n    String name;\n\n    Product(String name) {\n        this.name = name;\n    }\n\n    Product() {\n        this(\"Without name\");\n    }\n}\n\nProduct a = new Product(\"Caderno\");\nProduct b = new Product();","fidelityText":"public class Produto { String nome; Produto(String nome) { this.nome = nome; } Produto() { this(\"Sem nome\"); } } Produto a = new Produto(\"Caderno\"); Produto b = new Produto();","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Product</span> {\n    <span class=\"kw\">String</span> name;\n\n    <span class=\"fn\">Product</span>(<span class=\"kw\">String</span> name) {\n        <span class=\"kw\">this</span>.name = name;\n    }\n\n    <span class=\"fn\">Product</span>() {\n        <span class=\"kw\">this</span>(<span class=\"str\">\"Without name\"</span>);\n    }\n}\n\n<span class=\"cls\">Product</span> a = <span class=\"kw\">new</span> <span class=\"cls\">Product</span>(<span class=\"str\">\"Caderno\"</span>);\n<span class=\"cls\">Product</span> b = <span class=\"kw\">new</span> <span class=\"cls\">Product</span>();","caption":"Exemplo executável de construtores.","explanation":["Assim que qualquer construtor é declarado, o construtor padrão sem argumentos deixa de existir implicitamente."],"commonMistakes":["Esperar que new Produto() ainda compile depois de declarar um construtor com parâmetros"]},{"id":"construtores-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Não confunda “sem argumentos” com “padrão”. Um construtor <code>Produto()</code> escrito por você é explícito; o construtor padrão é o que o compilador declara somente quando não existe nenhum construtor no código-fonte.</div>","fidelityText":"Não confunda “sem argumentos” com “padrão”. Um construtor Produto() escrito por você é explícito; o construtor padrão é o que o compilador declara somente quando não existe nenhum construtor no código-fonte."},{"id":"construtores-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Evite trabalho com efeito externo no construtor.</b> Ler teclado, imprimir, alterar outros objetos ou depender de recursos externos torna a criação imprevisível. Nesta etapa, use o construtor para receber, validar com regras já conhecidas e atribuir o estado inicial.</div>","fidelityText":"Evite trabalho com efeito externo no construtor. Ler teclado, imprimir, alterar outros objetos ou depender de recursos externos torna a criação imprevisível. Nesta etapa, use o construtor para receber, validar com regras já conhecidas e atribuir o estado inicial."},{"id":"construtores-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 3.1 — Construtor com validação","prompt":"Reescreva ContaBancaria com um construtor que recebe titular e saldoInicial. Se saldoInicial for negativo, defina o saldo como 0.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 3.1 — Construtor com validaçãomédio Reescreva ContaBancaria com um construtor que recebe titular e saldoInicial. Se saldoInicial for negativo, defina o saldo como 0. Ver solução public class ContaBancaria { String titular; double saldo; ContaBancaria(String titular, double saldoInicial) { this.titular = titular; this.saldo = (saldoInicial < 0) ? 0 : saldoInicial; } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 3.1 — Construtor com validação</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Reescreva <code>ContaBancaria</code> com um construtor que recebe <code>titular</code> e <code>saldoInicial</code>. Se <code>saldoInicial</code> for negativo, defina o saldo como 0.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">BankAccount</span> {\n    <span class=\"kw\">String</span> accountHolder;\n    <span class=\"kw\">double</span> balance;\n\n    <span class=\"fn\">BankAccount</span>(<span class=\"kw\">String</span> accountHolder, <span class=\"kw\">double</span> balanceInitial) {\n        <span class=\"kw\">this</span>.accountHolder = accountHolder;\n        <span class=\"kw\">this</span>.balance = (balanceInitial &lt; 0) ? 0 : balanceInitial;\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"construtores-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 3.2 — Contratos de construção","prompt":"Crie Produto com um construtor que exige nome e preço positivo e outro que recebe somente nome e delega usando preço 1. Antes de executar, indique quais chamadas compilam: new Produto(\"Caderno\", 12), new Produto(\"Caderno\") e new Produto().","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 3.2 — Contratos de construçãomédio Crie Produto com um construtor que exige nome e preço positivo e outro que recebe somente nome e delega usando preço 1. Antes de executar, indique quais chamadas compilam: new Produto(\"Caderno\", 12), new Produto(\"Caderno\") e new Produto(). Ver solução As duas primeiras chamadas compilam quando os dois construtores foram declarados. new Produto() não compila: como a classe declarou construtores, o compilador não acrescenta uma terceira opção sem argumentos. A versão de um argumento deve começar com this(nome, 1) para concentrar a atribuição e a validação.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 3.2 — Contratos de construção</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie <code>Produto</code> com um construtor que exige nome e preço positivo e outro que recebe somente nome e delega usando preço 1. Antes de executar, indique quais chamadas compilam: <code>new Produto(\"Caderno\", 12)</code>, <code>new Produto(\"Caderno\")</code> e <code>new Produto()</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>As duas primeiras chamadas compilam quando os dois construtores foram declarados. <code>new Produto()</code> não compila: como a classe declarou construtores, o compilador não acrescenta uma terceira opção sem argumentos. A versão de um argumento deve começar com <code>this(nome, 1)</code> para concentrar a atribuição e a validação.</p>\n        </div>\n      </div>"},{"id":"construtores-error","type":"error-case","authorship":"authored","title":"O construtor sem argumentos desapareceu","scenario":"A classe passa a declarar Produto(String nome), mas o código ainda chama new Produto().","symptom":"Erro de compilação informando que a lista de argumentos não corresponde.","cause":"O compilador só declara o construtor padrão quando a classe não declara construtor algum.","diagnosis":["Liste os construtores declarados","Compare tipos e quantidade de argumentos da chamada"],"correction":"Passe o nome exigido ou declare conscientemente outro construtor que delegue para a regra principal.","prevention":"Trate cada construtor como um contrato de estados iniciais permitidos."},{"id":"construtores-quiz","type":"quiz","authorship":"authored","conceptId":"this-encadeamento","prompt":"Por que this(tipo, false) deve ser a primeira instrução do construtor?","options":[{"id":"construtores-q-a","label":"Porque a linguagem exige que a invocação alternativa de construtor venha primeiro.","correct":true,"explanation":"Assim uma única cadeia de inicialização é estabelecida antes do restante do corpo."},{"id":"construtores-q-b","label":"Porque this sempre significa a classe, não o objeto.","correct":false,"explanation":"this representa o objeto atual; this(...) é uma forma especial de delegação entre construtores."},{"id":"construtores-q-c","label":"Porque construtores retornam o objeto nessa linha.","correct":false,"explanation":"Construtor não declara nem devolve um tipo de retorno."}]}],"resources":[{"id":"construtores-jls","type":"reference","title":"JLS 8.8: declarações de construtor","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.8","reinforces":"Define sintaxe, acesso, sobrecarga, corpo e ausência de herança.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"construtores-default","type":"reference","title":"JLS 8.8.9: construtor padrão","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.8.9","reinforces":"Explica exatamente quando o compilador declara um construtor implícito.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The constructors example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public class Coffee {","instruction":"The constructors example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["Inicialização com herança fica marcada como prévia e não como prática obrigatória."]}},{"id":"encapsulamento","moduleId":"oop-modeling","order":3,"title":"Encapsulamento","summary":"Até agora, qualquer código externo pode escrever conta.saldo = -9999 diretamente. Isso quebra a integridade do objeto. Encapsulamento é esconder os atributos (private) e controlar o acesso por meio de métodos públicos.","objectives":["Proteger invariantes por operações públicas","Escolher a menor visibilidade necessária","Distinguir referência final de objeto imutável"],"whyItExists":"Se qualquer parte do programa pode alterar qualquer campo, nenhuma classe consegue garantir suas próprias regras; encapsulamento cria uma fronteira responsável pelas transições válidas.","prerequisiteChapterIds":["construtores"],"conceptIds":["os-quatro-niveis-de-acesso-em-java","encapsulamento-nao-e-so-botar-getters-e-setters","imutabilidade-o-encapsulamento-levado-ao-extremo","o-vazamento-mais-comum-expor-um-array-ou-outro-objeto-mutavel-interno-po"],"introducedConceptIds":["encapsulamento-invariante","controle-acesso","imutabilidade-objeto"],"usedConceptIds":["construtor-invariante","estado-comportamento","comando-consulta","string-imutavel"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"encapsulamento-intuition","type":"intuition","authorship":"authored","title":"Estado privado permite promessas públicas","body":"private impede alteração externa direta. Métodos como depositar e sacar nomeiam transições e verificam regras antes da mudança; um setter para cada campo devolveria o mesmo risco com outra sintaxe.","analogyLimit":"Uma porta controlada ilustra acesso, mas encapsulamento também reduz conhecimento sobre a representação interna, não apenas bloqueia escrita."},{"id":"encapsulamento-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#construtores\">03 · Construtores</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: 03 · Construtores"},{"id":"encapsulamento-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Até agora, qualquer código externo pode escrever <code>conta.saldo = -9999</code> diretamente. Isso quebra a integridade do objeto. <strong>Encapsulamento</strong> é esconder os atributos (<code>private</code>) e controlar o acesso por meio de métodos públicos.</p>","fidelityText":"Até agora, qualquer código externo pode escrever conta.saldo = -9999 diretamente. Isso quebra a integridade do objeto. Encapsulamento é esconder os atributos (private) e controlar o acesso por meio de métodos públicos."},{"id":"encapsulamento-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"public class BankAccount {\n    private String accountHolder;\n    private double balance;\n\n    public BankAccount(String accountHolder, double balanceInitial) {\n        this.accountHolder = accountHolder;\n        this.balance = Math.max(balanceInitial, 0);\n    }\n\n    public double getBalance() { return balance; }\n\n    public void deposit(double value) {\n        if (value > 0) balance += value;\n    }\n\n    public boolean withdraw(double value) {\n        if (value <= 0 || value > balance) return false;\n        balance -= value;\n        return true;\n    }\n}","fidelityText":"public class ContaBancaria { private String titular; private double saldo; public ContaBancaria(String titular, double saldoInicial) { this.titular = titular; this.saldo = Math.max(saldoInicial, 0); } public double getSaldo() { return saldo; } public void depositar(double valor) { if (valor > 0) saldo += valor; } public boolean sacar(double valor) { if (valor <= 0 || valor > saldo) return false; saldo -= valor; return true; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">BankAccount</span> {\n    <span class=\"kw\">private String</span> accountHolder;\n    <span class=\"kw\">private double</span> balance;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">BankAccount</span>(<span class=\"kw\">String</span> accountHolder, <span class=\"kw\">double</span> balanceInitial) {\n        <span class=\"kw\">this</span>.accountHolder = accountHolder;\n        <span class=\"kw\">this</span>.balance = Math.max(balanceInitial, 0);\n    }\n\n    <span class=\"kw\">public double</span> <span class=\"fn\">getBalance</span>() { <span class=\"kw\">return</span> balance; }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">deposit</span>(<span class=\"kw\">double</span> value) {\n        <span class=\"kw\">if</span> (value &gt; 0) balance += value;\n    }\n\n    <span class=\"kw\">public boolean</span> <span class=\"fn\">withdraw</span>(<span class=\"kw\">double</span> value) {\n        <span class=\"kw\">if</span> (value &lt;= 0 || value &gt; balance) <span class=\"kw\">return false</span>;\n        balance -= value;\n        <span class=\"kw\">return true</span>;\n    }\n}","caption":"Exemplo executável de encapsulamento.","explanation":["Campos privados só mudam pelo construtor e pelas operações públicas.","sacar preserva a regra de valor positivo e saldo suficiente antes da subtração."],"commonMistakes":["Expor setSaldo","Aceitar NaN ou double monetário em produção"]},{"id":"encapsulamento-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>O ganho real:</b> ninguém consegue mais colocar o saldo em estado inválido, porque a única porta de entrada são métodos que aplicam regras. Se a regra mudar, você muda em um único lugar e todo o sistema respeita.</div>","fidelityText":"O ganho real: ninguém consegue mais colocar o saldo em estado inválido, porque a única porta de entrada são métodos que aplicam regras. Se a regra mudar, você muda em um único lugar e todo o sistema respeita."},{"id":"encapsulamento-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Os quatro níveis de acesso em Java</h2>","fidelityText":"Os quatro níveis de acesso em Java"},{"id":"encapsulamento-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Modificador</th><th>Mesma classe</th><th>Mesmo pacote</th><th>Subclasse (outro pacote)</th><th>Qualquer lugar</th></tr>\n        <tr><td><code>private</code></td><td>✅</td><td>❌</td><td>❌</td><td>❌</td></tr>\n        <tr><td><code>(default)</code></td><td>✅</td><td>✅</td><td>❌</td><td>❌</td></tr>\n        <tr><td><code>protected</code></td><td>✅</td><td>✅</td><td>✅</td><td>❌</td></tr>\n        <tr><td><code>public</code></td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr>\n      </tbody></table>","fidelityText":"ModificadorMesma classeMesmo pacoteSubclasse (outro pacote)Qualquer lugar private✅❌❌❌ (default)✅✅❌❌ protected✅✅✅❌ public✅✅✅✅"},{"id":"encapsulamento-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Encapsulamento não é só \"botar getters e setters\"</h2>","fidelityText":"Encapsulamento não é só \"botar getters e setters\""},{"id":"encapsulamento-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Um erro comum: gerar getter e setter <em>public</em> para <strong>todos</strong> os atributos automaticamente (muitas IDEs fazem isso com um clique). Isso é apenas encapsulamento de fachada — na prática, o objeto continua totalmente mutável de fora, só que através de métodos em vez de campos. Encapsulamento de verdade pensa em <em>quais operações</em> fazem sentido no domínio, não em expor 1 getter/setter por campo.</p>","fidelityText":"Um erro comum: gerar getter e setter public para todos os atributos automaticamente (muitas IDEs fazem isso com um clique). Isso é apenas encapsulamento de fachada — na prática, o objeto continua totalmente mutável de fora, só que através de métodos em vez de campos. Encapsulamento de verdade pensa em quais operações fazem sentido no domínio, não em expor 1 getter/setter por campo."},{"id":"encapsulamento-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"// encapsulamento \"de fachada\" -- não resolve nada:\npublic void setBalance(double balance) { this.balance = balance; } // ainda permite saldo negativo!\n\n// encapsulamento de verdade -- expõe operações, não o campo:\npublic boolean withdraw(double value) { /* valida regra de negócio */ }","fidelityText":"// encapsulamento \"de fachada\" -- não resolve nada: public void setSaldo(double saldo) { this.saldo = saldo; } // ainda permite saldo negativo! // encapsulamento de verdade -- expõe operações, não o campo: public boolean sacar(double valor) { /* valida regra de negócio */ }","highlightedHtml":"<span class=\"com\">// encapsulamento \"de fachada\" -- não resolve nada:</span>\n<span class=\"kw\">public void</span> <span class=\"fn\">setBalance</span>(<span class=\"kw\">double</span> balance) { <span class=\"kw\">this</span>.balance = balance; } <span class=\"com\">// ainda permite saldo negativo!</span>\n\n<span class=\"com\">// encapsulamento de verdade -- expõe operações, não o campo:</span>\n<span class=\"kw\">public boolean</span> <span class=\"fn\">withdraw</span>(<span class=\"kw\">double</span> value) { <span class=\"com\">/* valida regra de negócio */</span> }","caption":"Exemplo executável de encapsulamento.","explanation":["O setter genérico permite estado inválido.","A operação sacar comunica intenção e concentra a validação."],"commonMistakes":["Gerar setters indiscriminadamente","Misturar mensagem de interface com regra"]},{"id":"encapsulamento-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Imutabilidade: o encapsulamento levado ao extremo</h2>","fidelityText":"Imutabilidade: o encapsulamento levado ao extremo"},{"id":"encapsulamento-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Um objeto <strong>imutável</strong> não apresenta mudança de estado observável depois de construído. Usar campos privados <code>final</code>, não oferecer operações mutadoras e evitar expor objetos internos mutáveis é uma receita inicial; apenas retirar setters não basta por si só. Esse modelo reduz os caminhos pelos quais o estado pode mudar e facilita raciocinar sobre o objeto.</p>","fidelityText":"Um objeto imutável não apresenta mudança de estado observável depois de construído. Usar campos privados final, não oferecer operações mutadoras e evitar expor objetos internos mutáveis é uma receita inicial; apenas retirar setters não basta por si só. Esse modelo reduz os caminhos pelos quais o estado pode mudar e facilita raciocinar sobre o objeto."},{"id":"encapsulamento-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"public final class Point {\n    private final double x;\n    private final double y;\n\n    public Point(double x, double y) { this.x = x; this.y = y; }\n\n    public double getX() { return x; }\n    public double getY() { return y; }\n\n    // \"modificar\" retorna um NOVO Ponto, nunca altera o atual\n    public Point translate(double dx, double dy) {\n        return new Point(x + dx, y + dy);\n    }\n}","fidelityText":"public final class Ponto { private final double x; private final double y; public Ponto(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } // \"modificar\" retorna um NOVO Ponto, nunca altera o atual public Ponto transladar(double dx, double dy) { return new Ponto(x + dx, y + dy); } }","highlightedHtml":"<span class=\"kw\">public final class</span> <span class=\"cls\">Point</span> {\n    <span class=\"kw\">private final double</span> x;\n    <span class=\"kw\">private final double</span> y;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Point</span>(<span class=\"kw\">double</span> x, <span class=\"kw\">double</span> y) { <span class=\"kw\">this</span>.x = x; <span class=\"kw\">this</span>.y = y; }\n\n    <span class=\"kw\">public double</span> <span class=\"fn\">getX</span>() { <span class=\"kw\">return</span> x; }\n    <span class=\"kw\">public double</span> <span class=\"fn\">getY</span>() { <span class=\"kw\">return</span> y; }\n\n    <span class=\"com\">// \"modificar\" retorna um NOVO Ponto, nunca altera o atual</span>\n    <span class=\"kw\">public</span> <span class=\"cls\">Point</span> <span class=\"fn\">translate</span>(<span class=\"kw\">double</span> dx, <span class=\"kw\">double</span> dy) {\n        <span class=\"kw\">return new</span> <span class=\"cls\">Point</span>(x + dx, y + dy);\n    }\n}","caption":"Exemplo executável de encapsulamento.","explanation":["Campos final são atribuídos no construtor e não mudam depois.","transladar preserva o objeto atual e retorna outra instância."],"commonMistakes":["Confundir ausência de setter com imutabilidade completa","Expor referência mutável interna"]},{"id":"encapsulamento-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Imutabilidade exige olhar também para campos que referenciam objetos mutáveis. Um campo <code>final</code> não pode apontar para outro objeto depois, mas o objeto já apontado ainda pode mudar. Neste exemplo, os campos são números primitivos, então não existe conteúdo interno mutável exposto.</div>","fidelityText":"Imutabilidade exige olhar também para campos que referenciam objetos mutáveis. Um campo final não pode apontar para outro objeto depois, mas o objeto já apontado ainda pode mudar. Neste exemplo, os campos são números primitivos, então não existe conteúdo interno mutável exposto."},{"id":"encapsulamento-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>O vazamento mais comum: expor um array (ou outro objeto mutável interno) por getter</h2>","fidelityText":"O vazamento mais comum: expor um array (ou outro objeto mutável interno) por getter"},{"id":"encapsulamento-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"public class ClassGroup {\n    private final String[] students = new String[3];\n    private int quantity;\n\n    // ❌ vazamento: quem chama getAlunos() recebe a REFERÊNCIA do array interno\n    public String[] getStudents() { return students; }\n}\n\nClassGroup classGroup = new ClassGroup();\nclassGroup.getStudents()[0] = \"Fraude\"; // muda o estado interno de \"turma\" SEM passar por nenhum método dela!","fidelityText":"public class Turma { private final String[] alunos = new String[3]; private int quantidade; // ❌ vazamento: quem chama getAlunos() recebe a REFERÊNCIA do array interno public String[] getAlunos() { return alunos; } } Turma turma = new Turma(); turma.getAlunos()[0] = \"Fraude\"; // muda o estado interno de \"turma\" SEM passar por nenhum método dela!","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">ClassGroup</span> {\n    <span class=\"kw\">private final String</span>[] students = <span class=\"kw\">new</span> <span class=\"kw\">String</span>[3];\n    <span class=\"kw\">private int</span> quantity;\n\n    <span class=\"com\">// ❌ vazamento: quem chama getAlunos() recebe a REFERÊNCIA do array interno</span>\n    <span class=\"kw\">public String</span>[] <span class=\"fn\">getStudents</span>() { <span class=\"kw\">return</span> students; }\n}\n\n<span class=\"cls\">ClassGroup</span> classGroup = <span class=\"kw\">new</span> <span class=\"cls\">ClassGroup</span>();\nclassGroup.getStudents()[<span class=\"num\">0</span>] = <span class=\"str\">\"Fraude\"</span>; <span class=\"com\">// muda o estado interno de \"turma\" SEM passar por nenhum método dela!</span>","caption":"Exemplo executável de encapsulamento.","explanation":["Devolver a referência de um array interno permite alterar o estado do objeto sem passar por nenhum método dele -- o mesmo problema de encapsulamento de fachada, só que via array em vez de setter."],"commonMistakes":["Achar que private no campo já impede mutação externa mesmo quando o getter devolve a referência direta"]},{"id":"encapsulamento-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Marcar o campo <code>private</code> não protege nada se um método devolver a <strong>própria referência</strong> de um array (ou de qualquer objeto mutável) guardado internamente — o código externo consegue alterar posições sem nunca chamar um método de <code>Turma</code>, contornando qualquer regra que ela devesse impor. Isso é encapsulamento de fachada disfarçado: o campo é <code>private</code>, mas o objeto continua totalmente controlável de fora, só que indiretamente.</p>","fidelityText":"Marcar o campo private não protege nada se um método devolver a própria referência de um array (ou de qualquer objeto mutável) guardado internamente — o código externo consegue alterar posições sem nunca chamar um método de Turma, contornando qualquer regra que ela devesse impor. Isso é encapsulamento de fachada disfarçado: o campo é private, mas o objeto continua totalmente controlável de fora, só que indiretamente."},{"id":"encapsulamento-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"// ✅ opção 1: devolver uma cópia -- quem recebe pode ler, mas alterar não afeta o original\npublic String[] getStudents() { return Arrays.copyOf(students, quantity); }\n\n// ✅ opção 2 (a melhor quando possível): não devolver o array -- expor só a operação que faz sentido\npublic boolean contemStudent(String name) {\n    for (int i = 0; i < quantity; i++) if (students[i].equals(name)) return true;\n    return false;\n}","fidelityText":"// ✅ opção 1: devolver uma cópia -- quem recebe pode ler, mas alterar não afeta o original public String[] getAlunos() { return Arrays.copyOf(alunos, quantidade); } // ✅ opção 2 (a melhor quando possível): não devolver o array -- expor só a operação que faz sentido public boolean contemAluno(String nome) { for (int i = 0; i < quantidade; i++) if (alunos[i].equals(nome)) return true; return false; }","highlightedHtml":"<span class=\"com\">// ✅ opção 1: devolver uma cópia -- quem recebe pode ler, mas alterar não afeta o original</span>\n<span class=\"kw\">public String</span>[] <span class=\"fn\">getStudents</span>() { <span class=\"kw\">return</span> Arrays.copyOf(students, quantity); }\n\n<span class=\"com\">// ✅ opção 2 (a melhor quando possível): não devolver o array -- expor só a operação que faz sentido</span>\n<span class=\"kw\">public boolean</span> <span class=\"fn\">contemStudent</span>(<span class=\"kw\">String</span> name) {\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = <span class=\"num\">0</span>; i &lt; quantity; i++) <span class=\"kw\">if</span> (students[i].equals(name)) <span class=\"kw\">return true</span>;\n    <span class=\"kw\">return false</span>;\n}","caption":"Exemplo executável de encapsulamento.","explanation":["Arrays.copyOf devolve uma cópia independente; expor só a operação de consulta (contemAluno) evita devolver a estrutura interna por completo."]},{"id":"encapsulamento-content-18","type":"html","authorship":"legacy-preserved","html":"<p>A mesma regra vale ao <strong>receber</strong> um array ou outro objeto mutável de fora, no construtor ou em um setter: guardar a referência recebida diretamente permite que quem chamou continue mutando o array depois de \"entregá-lo\", surpreendendo o objeto que achava ter uma cópia própria. Uma <strong>cópia defensiva</strong> (<code>Arrays.copyOf(recebido, recebido.length)</code>) na entrada e na saída é o mesmo princípio de proteção de estado, aplicado nas duas direções — o mesmo princípio se aplica depois a coleções (<code>ArrayList</code>, <code>List</code>), quando você as conhecer no módulo de Java Core.</p>","fidelityText":"A mesma regra vale ao receber um array ou outro objeto mutável de fora, no construtor ou em um setter: guardar a referência recebida diretamente permite que quem chamou continue mutando o array depois de \"entregá-lo\", surpreendendo o objeto que achava ter uma cópia própria. Uma cópia defensiva (Arrays.copyOf(recebido, recebido.length)) na entrada e na saída é o mesmo princípio de proteção de estado, aplicado nas duas direções — o mesmo princípio se aplica depois a coleções (ArrayList, List), quando você as conhecer no módulo de Java Core."},{"id":"encapsulamento-exercise-19","type":"exercise","authorship":"legacy-preserved","title":"Exercício 4.1 — Encapsulando Livro","prompt":"Torne Livro totalmente encapsulado: atributos private, construtor com os três valores, getters para todos, e um setter apenas para paginas que rejeite valores ≤ 0.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 4.1 — Encapsulando Livrofácil Torne Livro totalmente encapsulado: atributos private, construtor com os três valores, getters para todos, e um setter apenas para paginas que rejeite valores ≤ 0. Ver solução public class Livro { private String titulo, autor; private int paginas; public Livro(String titulo, String autor, int paginas) { this.titulo = titulo; this.autor = autor; this.paginas = paginas; } public String getTitulo() { return titulo; } public String getAutor() { return autor; } public int getPaginas() { return paginas; } public void setPaginas(int paginas) { if (paginas > 0) this.paginas = paginas; } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 4.1 — Encapsulando Livro</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Torne <code>Livro</code> totalmente encapsulado: atributos <code>private</code>, construtor com os três valores, getters para todos, e um setter apenas para <code>paginas</code> que rejeite valores ≤ 0.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"kw\">private String</span> title, author;\n    <span class=\"kw\">private int</span> pages;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Book</span>(<span class=\"kw\">String</span> title, <span class=\"kw\">String</span> author, <span class=\"kw\">int</span> pages) {\n        <span class=\"kw\">this</span>.title = title; <span class=\"kw\">this</span>.author = author; <span class=\"kw\">this</span>.pages = pages;\n    }\n    <span class=\"kw\">public String</span> <span class=\"fn\">getTitle</span>() { <span class=\"kw\">return</span> title; }\n    <span class=\"kw\">public String</span> <span class=\"fn\">getAuthor</span>()  { <span class=\"kw\">return</span> author; }\n    <span class=\"kw\">public int</span>    <span class=\"fn\">getPages</span>() { <span class=\"kw\">return</span> pages; }\n    <span class=\"kw\">public void</span> <span class=\"fn\">setPages</span>(<span class=\"kw\">int</span> pages) {\n        <span class=\"kw\">if</span> (pages &gt; 0) <span class=\"kw\">this</span>.pages = pages;\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"encapsulamento-exercise-20","type":"exercise","authorship":"legacy-preserved","title":"Exercício 4.2 — Classe imutável","prompt":"Crie uma classe imutável Pontuacao com pontos (int) final e sem setter. O construtor transforma valores negativos em zero. Adicione somar(int bonus), que preserva o objeto atual e retorna uma nova Pontuacao; bônus negativo deve simplesmente devolver uma nova instância com o mesmo valor.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 4.2 — Classe imutávelmédio Crie uma classe imutável Pontuacao com pontos (int) final e sem setter. O construtor transforma valores negativos em zero. Adicione somar(int bonus), que preserva o objeto atual e retorna uma nova Pontuacao; bônus negativo deve simplesmente devolver uma nova instância com o mesmo valor. Ver solução public final class Pontuacao { private final int pontos; public Pontuacao(int pontos) { this.pontos = pontos < 0 ? 0 : pontos; } public Pontuacao somar(int bonus) { if (bonus < 0) return new Pontuacao(pontos); return new Pontuacao(pontos + bonus); } public int getPontos() { return pontos; } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 4.2 — Classe imutável</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie uma classe imutável <code>Pontuacao</code> com <code>pontos</code> (int) final e sem setter. O construtor transforma valores negativos em zero. Adicione <code>somar(int bonus)</code>, que preserva o objeto atual e retorna uma <strong>nova</strong> Pontuacao; bônus negativo deve simplesmente devolver uma nova instância com o mesmo valor.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public final class</span> <span class=\"cls\">Pontuacao</span> {\n    <span class=\"kw\">private final int</span> points;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Pontuacao</span>(<span class=\"kw\">int</span> points) {\n        <span class=\"kw\">this</span>.points = points &lt; 0 ? 0 : points;\n    }\n\n    <span class=\"kw\">public</span> <span class=\"cls\">Pontuacao</span> <span class=\"fn\">sum</span>(<span class=\"kw\">int</span> bonus) {\n        <span class=\"kw\">if</span> (bonus &lt; 0) <span class=\"kw\">return new</span> <span class=\"cls\">Pontuacao</span>(points);\n        <span class=\"kw\">return new</span> <span class=\"cls\">Pontuacao</span>(points + bonus);\n    }\n\n    <span class=\"kw\">public int</span> <span class=\"fn\">getPoints</span>() { <span class=\"kw\">return</span> points; }\n}</pre>\n        </div>\n      </div>"},{"id":"encapsulamento:0","type":"quiz","authorship":"legacy-preserved","conceptId":"o-que-acontece-se-voce-tentar-acessar-um-atributo-private-de-fora-da-cla","prompt":"O que acontece se você tentar acessar um atributo private de fora da classe?","options":[{"id":"encapsulamento:0:option:0","label":"Funciona normalmente, apenas gera um aviso (warning).","correct":false,"explanation":"Acesso private fora da classe é rejeitado em compilação, não apenas avisado."},{"id":"encapsulamento:0:option:1","label":"Erro de compilação — o código não compila.","correct":true,"explanation":"private restringe o acesso e a violação é um erro de compilação."},{"id":"encapsulamento:0:option:2","label":"Funciona, mas o valor retornado é sempre nulo.","correct":false,"explanation":"O membro não se transforma em null; a expressão é inválida antes da execução."}],"sourceIndex":21},{"id":"encapsulamento-model","type":"mental-model","authorship":"authored","title":"A invariante atravessa toda operação","body":"Uma invariante é uma condição que deve ser verdadeira antes e depois de cada operação pública concluída. Construtor estabelece; métodos preservam; visibilidade impede atalhos externos.","flow":["Construtor recebe dados","Valida e estabelece estado","Operação pública recebe intenção","Recusa ou produz nova transição válida","Consulta expõe somente informação necessária"],"ownership":["A classe protege seu estado","O chamador escolhe quando pedir a operação, não como alterar os campos"]},{"id":"encapsulamento-quiz","type":"quiz","authorship":"authored","conceptId":"encapsulamento-invariante","prompt":"Por que setSaldo(double) público não protege a conta mesmo com saldo private?","options":[{"id":"encapsulamento-q-a","label":"Porque continua permitindo qualquer estado sem expressar as regras de depósito e saque.","correct":true,"explanation":"A escrita foi apenas embrulhada; a classe ainda não controla transições válidas."},{"id":"encapsulamento-q-b","label":"Porque métodos públicos nunca podem alterar campos private.","correct":false,"explanation":"Métodos da própria classe acessam seus campos privados normalmente."},{"id":"encapsulamento-q-c","label":"Porque private só funciona em tipos primitivos.","correct":false,"explanation":"Controle de acesso vale para membros de qualquer tipo."}]}],"resources":[{"id":"encapsulamento-jls-access","type":"reference","title":"JLS 6.6: controle de acesso","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-6.html#jls-6.6","reinforces":"Define acessibilidade de membros e construtores.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"encapsulamento-final","type":"reference","title":"JLS 8.3.1.2: campos final","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.3.1.2","reinforces":"Distingue atribuição única de imutabilidade do objeto referenciado.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The encapsulation example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public class BankAccount {","instruction":"The encapsulation example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"static","moduleId":"oop-modeling","order":4,"title":"static, final e this","summary":"Um campo static existe uma única vez, compartilhado por todas as instâncias. Um método static pertence à classe e não acessa atributos de instância diretamente — é por isso que public static void main(...) roda sem que nenhum objeto tenha sido criado ainda.","objectives":["Distinguir membros de classe e de instância","Explicar final em variável, método e classe","Usar this somente com um objeto atual"],"whyItExists":"Alguns dados pertencem ao tipo como um todo, enquanto outros pertencem a cada instância; confundir esses ciclos cria estado global acidental e acessos sem objeto.","prerequisiteChapterIds":["encapsulamento"],"conceptIds":["static-pertence-a-classe-nao-ao-objeto","final-nao-pode-mudar-depois-de-definido","this-referencia-ao-proprio-objeto"],"introducedConceptIds":["membro-estatico","final-referencia-tipo","this-instancia"],"usedConceptIds":["classe-instancia-objeto","campo-instancia","imutabilidade-objeto","this-encadeamento"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"static-intuition","type":"intuition","authorship":"authored","title":"Uma informação por classe ou uma por objeto?","body":"totalCriados descreve o conjunto de instâncias e existe uma vez por classe; id descreve cada instância e existe em cada objeto. Um método static não possui receptor implícito, portanto não possui this.","analogyLimit":"“Compartilhado” não significa seguro para acesso concorrente; sincronização será ensinada depois."},{"id":"static-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#encapsulamento\">Encapsulamento</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: Encapsulamento"},{"id":"static-content-2","type":"html","authorship":"legacy-preserved","html":"<h2>static — pertence à classe, não ao objeto</h2>","fidelityText":"static — pertence à classe, não ao objeto"},{"id":"static-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Counter {\n    private static int totalCreated = 0; // compartilhado entre TODOS os objetos\n    private final int id;\n\n    public Counter() {\n        totalCreated++;\n        this.id = totalCreated;\n    }\n\n    public static int getTotalCreated() { return totalCreated; } // static não usa \"this\"\n}\n// uso: Contador.getTotalCriados() -- chamado na CLASSE, sem \"new\"","fidelityText":"public class Contador { private static int totalCriados = 0; // compartilhado entre TODOS os objetos private final int id; public Contador() { totalCriados++; this.id = totalCriados; } public static int getTotalCriados() { return totalCriados; } // static não usa \"this\" } // uso: Contador.getTotalCriados() -- chamado na CLASSE, sem \"new\"","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Counter</span> {\n    <span class=\"kw\">private static int</span> totalCreated = 0; <span class=\"com\">// compartilhado entre TODOS os objetos</span>\n    <span class=\"kw\">private final int</span> id;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Counter</span>() {\n        totalCreated++;\n        <span class=\"kw\">this</span>.id = totalCreated;\n    }\n\n    <span class=\"kw\">public static int</span> <span class=\"fn\">getTotalCreated</span>() { <span class=\"kw\">return</span> totalCreated; } <span class=\"com\">// static não usa \"this\"</span>\n}\n<span class=\"com\">// uso: Contador.getTotalCriados() -- chamado na CLASSE, sem \"new\"</span>","caption":"Exemplo executável de static.","explanation":["totalCriados é único para a classe; id é final e diferente por objeto.","getTotalCriados é chamado na classe e não depende de this."],"commonMistakes":["Chamar membro static pela instância","Usar contador global onde identidade externa seria necessária"]},{"id":"static-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Um campo <code>static</code> existe uma única vez, compartilhado por todas as instâncias. Um método <code>static</code> pertence à classe e não acessa atributos de instância diretamente — é por isso que <code>public static void main(...)</code> roda sem que nenhum objeto tenha sido criado ainda.</p>","fidelityText":"Um campo static existe uma única vez, compartilhado por todas as instâncias. Um método static pertence à classe e não acessa atributos de instância diretamente — é por isso que public static void main(...) roda sem que nenhum objeto tenha sido criado ainda."},{"id":"static-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>final — não pode mudar depois de definido</h2>","fidelityText":"final — não pode mudar depois de definido"},{"id":"static-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Circle {\n    public static final double PI_APROX = 3.14159; // constante de classe\n    private final double radius; // só pode ser atribuído uma vez\n\n    public Circle(double radius) { this.radius = radius; }\n}","fidelityText":"public class Circulo { public static final double PI_APROX = 3.14159; // constante de classe private final double raio; // só pode ser atribuído uma vez public Circulo(double raio) { this.raio = raio; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Circle</span> {\n    <span class=\"kw\">public static final double</span> PI_APROX = 3.14159; <span class=\"com\">// constante de classe</span>\n    <span class=\"kw\">private final double</span> radius; <span class=\"com\">// só pode ser atribuído uma vez</span>\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Circle</span>(<span class=\"kw\">double</span> radius) { <span class=\"kw\">this</span>.radius = radius; }\n}","caption":"Exemplo executável de static.","explanation":["PI_APROX é constante da classe.","raio é atribuído uma vez em cada objeto pelo construtor."],"commonMistakes":["Achar que final double torna a classe final","Esquecer que final de referência não congela o objeto"]},{"id":"static-content-7","type":"html","authorship":"legacy-preserved","html":"<p><code>final</code> em uma classe (<code>public final class Foo</code>) impede que ela seja estendida. <code>String</code> é <code>final</code> por esse motivo, entre outros ligados a segurança e imutabilidade.</p>","fidelityText":"final em uma classe (public final class Foo) impede que ela seja estendida. String é final por esse motivo, entre outros ligados a segurança e imutabilidade."},{"id":"static-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>final em referência ≠ objeto imutável:</b> <code>final int[] notas = {7, 8};</code> impede fazer <code>notas = outroArray</code>, mas ainda permite <code>notas[0] = 10</code>. <code>final</code> restringe a reatribuição da variável; o array referenciado continua mutável.</div>","fidelityText":"final em referência ≠ objeto imutável: final int[] notas = {7, 8}; impede fazer notas = outroArray, mas ainda permite notas[0] = 10. final restringe a reatribuição da variável; o array referenciado continua mutável."},{"id":"static-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>this — referência ao próprio objeto</h2>","fidelityText":"this — referência ao próprio objeto"},{"id":"static-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Já vimos <code>this</code> para diferenciar parâmetro de atributo e para encadear construtores. Ele também habilita <strong>method chaining</strong>, retornando o próprio objeto:</p>","fidelityText":"Já vimos this para diferenciar parâmetro de atributo e para encadear construtores. Ele também habilita method chaining, retornando o próprio objeto:"},{"id":"static-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"public class OrderBuilder {\n    private String item = \"\";\n    private int quantity = 1;\n\n    public OrderBuilder item(String item) { this.item = item; return this; }\n    public OrderBuilder quantity(int q) { this.quantity = q; return this; }\n}\n// uso: new PedidoBuilder().item(\"Café\").quantidade(2);","fidelityText":"public class PedidoBuilder { private String item = \"\"; private int quantidade = 1; public PedidoBuilder item(String item) { this.item = item; return this; } public PedidoBuilder quantidade(int q) { this.quantidade = q; return this; } } // uso: new PedidoBuilder().item(\"Café\").quantidade(2);","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">OrderBuilder</span> {\n    <span class=\"kw\">private String</span> item = <span class=\"str\">\"\"</span>;\n    <span class=\"kw\">private int</span> quantity = 1;\n\n    <span class=\"kw\">public</span> <span class=\"cls\">OrderBuilder</span> <span class=\"fn\">item</span>(<span class=\"kw\">String</span> item) { <span class=\"kw\">this</span>.item = item; <span class=\"kw\">return this</span>; }\n    <span class=\"kw\">public</span> <span class=\"cls\">OrderBuilder</span> <span class=\"fn\">quantity</span>(<span class=\"kw\">int</span> q) { <span class=\"kw\">this</span>.quantity = q; <span class=\"kw\">return this</span>; }\n}\n<span class=\"com\">// uso: new PedidoBuilder().item(\"Café\").quantidade(2);</span>","caption":"Exemplo executável de static.","explanation":["Cada método atualiza o próprio objeto e retorna this.","O encadeamento é apenas uma sequência de chamadas sobre a mesma referência."],"commonMistakes":["Retornar novo objeto sem intenção","Usar encadeamento para esconder validação"]},{"id":"static-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Esse padrão de retornar <code>this</code> é a base do <strong>Builder Pattern</strong>, muito usado para construir objetos complexos com muitos parâmetros opcionais sem criar dez construtores sobrecarregados. Bibliotecas populares como <code>StringBuilder</code> (<code>.append(a).append(b)</code>) e frameworks de teste usam exatamente essa técnica.</div>","fidelityText":"Esse padrão de retornar this é a base do Builder Pattern, muito usado para construir objetos complexos com muitos parâmetros opcionais sem criar dez construtores sobrecarregados. Bibliotecas populares como StringBuilder (.append(a).append(b)) e frameworks de teste usam exatamente essa técnica."},{"id":"static-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Exercício 9.1 — Fábrica com contador estático","prompt":"Crie Pedido com static int proximoId = 1 e final int id. No construtor, atribua id a partir de proximoId e incremente. Crie três pedidos e confirme ids sequenciais.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 9.1 — Fábrica com contador estáticodifícil Crie Pedido com static int proximoId = 1 e final int id. No construtor, atribua id a partir de proximoId e incremente. Crie três pedidos e confirme ids sequenciais. Ver solução public class Pedido { private static int proximoId = 1; private final int id; public Pedido() { this.id = proximoId++; } public int getId() { return id; } } Pedido p1 = new Pedido(), p2 = new Pedido(), p3 = new Pedido(); System.out.println(p1.getId() + \" \" + p2.getId() + \" \" + p3.getId()); // 1 2 3","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 9.1 — Fábrica com contador estático</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie <code>Pedido</code> com <code>static int proximoId = 1</code> e <code>final int id</code>. No construtor, atribua <code>id</code> a partir de <code>proximoId</code> e incremente. Crie três pedidos e confirme ids sequenciais.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Order</span> {\n    <span class=\"kw\">private static int</span> nextId = 1;\n    <span class=\"kw\">private final int</span> id;\n    <span class=\"kw\">public</span> <span class=\"fn\">Order</span>() { <span class=\"kw\">this</span>.id = nextId++; }\n    <span class=\"kw\">public int</span> <span class=\"fn\">getId</span>() { <span class=\"kw\">return</span> id; }\n}\n<span class=\"cls\">Order</span> p1 = <span class=\"kw\">new</span> <span class=\"cls\">Order</span>(), p2 = <span class=\"kw\">new</span> <span class=\"cls\">Order</span>(), p3 = <span class=\"kw\">new</span> <span class=\"cls\">Order</span>();\nSystem.out.println(p1.getId() + <span class=\"str\">\" \"</span> + p2.getId() + <span class=\"str\">\" \"</span> + p3.getId()); <span class=\"com\">// 1 2 3</span></pre>\n        </div>\n      </div>"},{"id":"static-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 9.2 — Builder encadeável","prompt":"Crie ConfiguracaoPedido com campos entrega e observacao. Crie métodos encadeáveis entrega(String) e observacao(String), ambos retornando this. No final, consulte os valores e confirme que todas as chamadas atuaram na mesma instância.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 9.2 — Builder encadeávelmédio Crie ConfiguracaoPedido com campos entrega e observacao. Crie métodos encadeáveis entrega(String) e observacao(String), ambos retornando this. No final, consulte os valores e confirme que todas as chamadas atuaram na mesma instância. Ver solução public class ConfiguracaoPedido { private String entrega = \"retirada\"; private String observacao = \"\"; public ConfiguracaoPedido entrega(String valor) { this.entrega = valor; return this; } public ConfiguracaoPedido observacao(String valor) { this.observacao = valor; return this; } } ConfiguracaoPedido c = new ConfiguracaoPedido() .entrega(\"delivery\") .observacao(\"sem cebola\");","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 9.2 — Builder encadeável</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie <code>ConfiguracaoPedido</code> com campos <code>entrega</code> e <code>observacao</code>. Crie métodos encadeáveis <code>entrega(String)</code> e <code>observacao(String)</code>, ambos retornando <code>this</code>. No final, consulte os valores e confirme que todas as chamadas atuaram na mesma instância.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">ConfigurationOrder</span> {\n    <span class=\"kw\">private String</span> delivers = <span class=\"str\">\"retirada\"</span>;\n    <span class=\"kw\">private String</span> observacao = <span class=\"str\">\"\"</span>;\n\n    <span class=\"kw\">public</span> <span class=\"cls\">ConfigurationOrder</span> <span class=\"fn\">delivers</span>(<span class=\"kw\">String</span> value) {\n        <span class=\"kw\">this</span>.delivers = value; <span class=\"kw\">return this</span>;\n    }\n    <span class=\"kw\">public</span> <span class=\"cls\">ConfigurationOrder</span> <span class=\"fn\">observacao</span>(<span class=\"kw\">String</span> value) {\n        <span class=\"kw\">this</span>.observacao = value; <span class=\"kw\">return this</span>;\n    }\n}\n\n<span class=\"cls\">ConfigurationOrder</span> c = <span class=\"kw\">new</span> <span class=\"cls\">ConfigurationOrder</span>()\n    .delivers(<span class=\"str\">\"delivery\"</span>)\n    .observacao(<span class=\"str\">\"without cebola\"</span>);</pre>\n        </div>\n      </div>"},{"id":"static-comparison","type":"comparison","authorship":"authored","title":"Três usos de final","criteria":["alvo","restrição","não garante"],"alternatives":[{"name":"Variável final","values":["variável","uma atribuição","imutabilidade do objeto"],"useWhen":"a referência ou valor não deve ser reatribuído","avoidWhen":"você precisa trocar o valor"},{"name":"Método final","values":["método de instância","não pode ser sobrescrito","classe imutável"],"useWhen":"subclasses não podem alterar o contrato","avoidWhen":"variação por subtipo é necessária"},{"name":"Classe final","values":["tipo","não pode ser estendido","campos imutáveis"],"useWhen":"subtipos não fazem parte do contrato","avoidWhen":"há hierarquia deliberada"}]},{"id":"static-quiz","type":"quiz","authorship":"authored","conceptId":"membro-estatico","prompt":"Por que um método static não acessa diretamente o campo de instância id?","options":[{"id":"static-q-a","label":"Porque não existe um objeto atual que determine qual id ler.","correct":true,"explanation":"O método pertence à classe e pode ser chamado sem instância; é preciso receber ou criar uma referência explicitamente."},{"id":"static-q-b","label":"Porque static transforma todos os campos em constantes.","correct":false,"explanation":"static define pertencimento à classe, não constância."},{"id":"static-q-c","label":"Porque id precisa obrigatoriamente ser public.","correct":false,"explanation":"Visibilidade não resolve ausência de receptor de instância."}]}],"resources":[{"id":"static-jls","type":"reference","title":"JLS 8.3.1.1: campos static","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.3.1.1","reinforces":"Define uma variável de classe versus uma variável por instância.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"static-this","type":"reference","title":"JLS 15.8.3: expressão this","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.8.3","reinforces":"Confirma o objeto atual e as restrições em contexto static.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The static example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public class Counter {","instruction":"The static example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"heranca","moduleId":"oop-modeling","order":5,"title":"Herança","summary":"Herança permite que uma classe (subclasse) reaproveite atributos e métodos de outra (superclasse), usando extends. É a ferramenta certa quando existe uma relação genuína de \"é um tipo de\" — um Gerente é um Funcionario.","objectives":["Modelar subtipo verdadeiro com extends","Usar super e sobrescrita corretamente","Escolher composição quando a relação é tem-um"],"whyItExists":"Quando tipos realmente compartilham um contrato de substituição, herança permite tratar a especialização como o tipo mais geral; usada apenas para reaproveitar linhas, ela cria acoplamento e modelos falsos.","prerequisiteChapterIds":["static"],"conceptIds":["toda-classe-herda-de-object-mesmo-sem-dizer","heranca-multinivel-e-a-cadeia-de-super"],"introducedConceptIds":["heranca-subtipo","super-sobrescrita","heranca-composicao"],"usedConceptIds":["controle-acesso","final-referencia-tipo","construtor-invariante","this-instancia"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"heranca-intuition","type":"intuition","authorship":"authored","title":"Subclasse precisa continuar sendo a superclasse","body":"Gerente extends Funcionario afirma que todo Gerente pode ocupar qualquer lugar que espera Funcionario sem quebrar seu contrato. Reuso é consequência; substituição coerente é o critério.","analogyLimit":"A frase “é um” é um filtro inicial, não uma prova: o comportamento prometido também precisa permanecer válido."},{"id":"heranca-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#static\">05 · static e membros de classe</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: 05 · static e membros de classe"},{"id":"heranca-content-2","type":"html","authorship":"legacy-preserved","html":"<p><strong>Herança</strong> permite que uma classe (subclasse) reaproveite atributos e métodos de outra (superclasse), usando <code>extends</code>. É a ferramenta certa quando existe uma relação genuína de \"é um tipo de\" — um <em>Gerente</em> <strong>é um</strong> <em>Funcionario</em>.</p>","fidelityText":"Herança permite que uma classe (subclasse) reaproveite atributos e métodos de outra (superclasse), usando extends. É a ferramenta certa quando existe uma relação genuína de \"é um tipo de\" — um Gerente é um Funcionario."},{"id":"heranca-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Employee {\n    protected String name;\n    protected double salaryBase;\n\n    public Employee(String name, double salaryBase) {\n        this.name = name; this.salaryBase = salaryBase;\n    }\n\n    public double calculateSalary() { return salaryBase; }\n}\n\npublic class Manager extends Employee {\n    private double bonus;\n\n    public Manager(String name, double salaryBase, double bonus) {\n        super(name, salaryBase);\n        this.bonus = bonus;\n    }\n\n    @Override\n    public double calculateSalary() { return salaryBase + bonus; }\n}","fidelityText":"public class Funcionario { protected String nome; protected double salarioBase; public Funcionario(String nome, double salarioBase) { this.nome = nome; this.salarioBase = salarioBase; } public double calcularSalario() { return salarioBase; } } public class Gerente extends Funcionario { private double bonus; public Gerente(String nome, double salarioBase, double bonus) { super(nome, salarioBase); this.bonus = bonus; } @Override public double calcularSalario() { return salarioBase + bonus; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Employee</span> {\n    <span class=\"kw\">protected String</span> name;\n    <span class=\"kw\">protected double</span> salaryBase;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Employee</span>(<span class=\"kw\">String</span> name, <span class=\"kw\">double</span> salaryBase) {\n        <span class=\"kw\">this</span>.name = name; <span class=\"kw\">this</span>.salaryBase = salaryBase;\n    }\n\n    <span class=\"kw\">public double</span> <span class=\"fn\">calculateSalary</span>() { <span class=\"kw\">return</span> salaryBase; }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Manager</span> <span class=\"kw\">extends</span> <span class=\"cls\">Employee</span> {\n    <span class=\"kw\">private double</span> bonus;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Manager</span>(<span class=\"kw\">String</span> name, <span class=\"kw\">double</span> salaryBase, <span class=\"kw\">double</span> bonus) {\n        <span class=\"kw\">super</span>(name, salaryBase);\n        <span class=\"kw\">this</span>.bonus = bonus;\n    }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public double</span> <span class=\"fn\">calculateSalary</span>() { <span class=\"kw\">return</span> salaryBase + bonus; }\n}","caption":"Exemplo executável de heranca.","explanation":["Gerente inicializa a parte Funcionario com super antes de seus próprios campos.","@Override substitui calcularSalario para o subtipo mantendo a mesma operação pública."],"commonMistakes":["Usar protected em todos os campos","Esquecer de preservar o contrato da superclasse"]},{"id":"heranca-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>protected vs private:</b> usamos <code>protected</code> em <code>nome</code> e <code>salarioBase</code> porque <code>Gerente</code> precisa acessá-los diretamente. Java só permite <strong>herança simples</strong> — uma classe tem no máximo uma superclasse direta.</div>","fidelityText":"protected vs private: usamos protected em nome e salarioBase porque Gerente precisa acessá-los diretamente. Java só permite herança simples — uma classe tem no máximo uma superclasse direta."},{"id":"heranca-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Toda classe herda de Object (mesmo sem dizer)</h2>","fidelityText":"Toda classe herda de Object (mesmo sem dizer)"},{"id":"heranca-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Se você não escreve <code>extends</code>, sua classe estende <code>java.lang.Object</code> implicitamente. É de lá que vêm três métodos que você vai sobrescrever o tempo todo:</p>","fidelityText":"Se você não escreve extends, sua classe estende java.lang.Object implicitamente. É de lá que vêm três métodos que você vai sobrescrever o tempo todo:"},{"id":"heranca-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Point {\n    private final int x, y;\n    public Point(int x, int y) { this.x = x; this.y = y; }\n\n    @Override\n    public String toString() {\n        return \"Point(\" + x + \", \" + y + \")\";\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (this == obj) return true;\n        if (!(obj instanceof Point)) return false;\n        Point other = (Point) obj;\n        return this.x == other.x && this.y == other.y;\n    }\n\n    @Override\n    public int hashCode() {\n        return java.util.Objects.hash(x, y);\n    }\n}","fidelityText":"public class Ponto { private final int x, y; public Ponto(int x, int y) { this.x = x; this.y = y; } @Override public String toString() { return \"Ponto(\" + x + \", \" + y + \")\"; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Ponto)) return false; Ponto outro = (Ponto) obj; return this.x == outro.x && this.y == outro.y; } @Override public int hashCode() { return java.util.Objects.hash(x, y); } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Point</span> {\n    <span class=\"kw\">private final int</span> x, y;\n    <span class=\"kw\">public</span> <span class=\"fn\">Point</span>(<span class=\"kw\">int</span> x, <span class=\"kw\">int</span> y) { <span class=\"kw\">this</span>.x = x; <span class=\"kw\">this</span>.y = y; }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public String</span> <span class=\"fn\">toString</span>() {\n        <span class=\"kw\">return</span> <span class=\"str\">\"Point(\"</span> + x + <span class=\"str\">\", \"</span> + y + <span class=\"str\">\")\"</span>;\n    }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public boolean</span> <span class=\"fn\">equals</span>(<span class=\"kw\">Object</span> obj) {\n        <span class=\"kw\">if</span> (<span class=\"kw\">this</span> == obj) <span class=\"kw\">return true</span>;\n        <span class=\"kw\">if</span> (!(obj <span class=\"kw\">instanceof</span> <span class=\"cls\">Point</span>)) <span class=\"kw\">return false</span>;\n        <span class=\"cls\">Point</span> other = (<span class=\"cls\">Point</span>) obj;\n        <span class=\"kw\">return</span> <span class=\"kw\">this</span>.x == other.x &amp;&amp; <span class=\"kw\">this</span>.y == other.y;\n    }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public int</span> <span class=\"fn\">hashCode</span>() {\n        <span class=\"kw\">return</span> java.util.Objects.hash(x, y);\n    }\n}","caption":"Exemplo executável de heranca.","explanation":["Toda classe deriva de Object e pode redefinir representação e igualdade.","O contrato completo de equals/hashCode será aprofundado em Java Core; aqui o exemplo mostra apenas a origem desses métodos."],"commonMistakes":["Confundir equals com identidade","Usar campo mutável em igualdade sem avaliar consequências"]},{"id":"heranca-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Método</th><th>Para que serve</th></tr>\n        <tr><td><code>toString()</code></td><td>Representação textual — usada implicitamente em <code>println(obj)</code> e concatenação com String</td></tr>\n        <tr><td><code>equals(Object)</code></td><td>Igualdade de <em>conteúdo</em> (padrão é <code>==</code>, ou seja, mesma referência)</td></tr>\n        <tr><td><code>hashCode()</code></td><td>Código numérico que deve ser coerente com <code>equals</code>; seu uso em coleções será aprofundado em Java Core</td></tr>\n      </tbody></table>","fidelityText":"MétodoPara que serve toString()Representação textual — usada implicitamente em println(obj) e concatenação com String equals(Object)Igualdade de conteúdo (padrão é ==, ou seja, mesma referência) hashCode()Código numérico que deve ser coerente com equals; seu uso em coleções será aprofundado em Java Core"},{"id":"heranca-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Prévia de um contrato importante:</b> se dois objetos são iguais por <code>equals</code>, precisam produzir o mesmo <code>hashCode</code>. O capítulo de igualdade e coleções mostrará propriedades, implementação e efeitos completos. Neste capítulo, reconheça que esses métodos vêm de <code>Object</code>; eles não são exigidos no exercício de herança.</div>","fidelityText":"Prévia de um contrato importante: se dois objetos são iguais por equals, precisam produzir o mesmo hashCode. O capítulo de igualdade e coleções mostrará propriedades, implementação e efeitos completos. Neste capítulo, reconheça que esses métodos vêm de Object; eles não são exigidos no exercício de herança."},{"id":"heranca-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Herança multinível e a cadeia de super</h2>","fidelityText":"Herança multinível e a cadeia de super"},{"id":"heranca-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"class A { void speak() { System.out.println(\"A\"); } }\nclass B extends A {\n    @Override void speak() { super.speak(); System.out.println(\"B\"); }\n}\nclass C extends B {\n    @Override void speak() { super.speak(); System.out.println(\"C\"); }\n}\n// new C().falar(); imprime \"A\", \"B\", \"C\" nessa ordem","fidelityText":"class A { void falar() { System.out.println(\"A\"); } } class B extends A { @Override void falar() { super.falar(); System.out.println(\"B\"); } } class C extends B { @Override void falar() { super.falar(); System.out.println(\"C\"); } } // new C().falar(); imprime \"A\", \"B\", \"C\" nessa ordem","highlightedHtml":"<span class=\"kw\">class</span> <span class=\"cls\">A</span> { <span class=\"kw\">void</span> <span class=\"fn\">speak</span>() { System.out.println(<span class=\"str\">\"A\"</span>); } }\n<span class=\"kw\">class</span> <span class=\"cls\">B</span> <span class=\"kw\">extends</span> <span class=\"cls\">A</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">void</span> <span class=\"fn\">speak</span>() { <span class=\"kw\">super</span>.speak(); System.out.println(<span class=\"str\">\"B\"</span>); }\n}\n<span class=\"kw\">class</span> <span class=\"cls\">C</span> <span class=\"kw\">extends</span> <span class=\"cls\">B</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">void</span> <span class=\"fn\">speak</span>() { <span class=\"kw\">super</span>.speak(); System.out.println(<span class=\"str\">\"C\"</span>); }\n}\n<span class=\"com\">// new C().falar(); imprime \"A\", \"B\", \"C\" nessa ordem</span>","caption":"Exemplo executável de heranca.","explanation":["Cada super.falar chama a implementação da superclasse imediata.","new C().falar percorre as chamadas explícitas A, B e C."],"commonMistakes":["Achar que super pula diretamente para Object","Criar hierarquias profundas sem necessidade"]},{"id":"heranca-content-12","type":"html","authorship":"legacy-preserved","html":"<p><code>super.metodo()</code> chama a versão do método na superclasse <strong>imediata</strong>, permitindo estender o comportamento herdado em vez de substituí-lo por completo.</p>","fidelityText":"super.metodo() chama a versão do método na superclasse imediata, permitindo estender o comportamento herdado em vez de substituí-lo por completo."},{"id":"heranca-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Exercício 5.1 — Hierarquia de veículos","prompt":"Crie Veiculo com marca, modelo e ligar(). Crie Carro e Moto estendendo, cada uma com um atributo próprio e um método exclusivo.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 5.1 — Hierarquia de veículosmédio Crie Veiculo com marca, modelo e ligar(). Crie Carro e Moto estendendo, cada uma com um atributo próprio e um método exclusivo. Ver solução public class Veiculo { protected String marca, modelo; public Veiculo(String marca, String modelo) { this.marca = marca; this.modelo = modelo; } public void ligar() { System.out.println(modelo + \" ligado\"); } } public class Carro extends Veiculo { private int numeroPortas; public Carro(String marca, String modelo, int numeroPortas) { super(marca, modelo); this.numeroPortas = numeroPortas; } public void abrirPortaMalas() { System.out.println(\"Porta-malas aberto\"); } } public class Moto extends Veiculo { private int cilindradas; public Moto(String marca, String modelo, int cilindradas) { super(marca, modelo); this.cilindradas = cilindradas; } public void empinar() { System.out.println(modelo + \" empinando!\"); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 5.1 — Hierarquia de veículos</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie <code>Veiculo</code> com <code>marca</code>, <code>modelo</code> e <code>ligar()</code>. Crie <code>Carro</code> e <code>Moto</code> estendendo, cada uma com um atributo próprio e um método exclusivo.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Veiculo</span> {\n    <span class=\"kw\">protected String</span> marks, model;\n    <span class=\"kw\">public</span> <span class=\"fn\">Veiculo</span>(<span class=\"kw\">String</span> marks, <span class=\"kw\">String</span> model) { <span class=\"kw\">this</span>.marks = marks; <span class=\"kw\">this</span>.model = model; }\n    <span class=\"kw\">public void</span> <span class=\"fn\">ligar</span>() { System.out.println(model + <span class=\"str\">\" ligado\"</span>); }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Car</span> <span class=\"kw\">extends</span> <span class=\"cls\">Veiculo</span> {\n    <span class=\"kw\">private int</span> numberPortas;\n    <span class=\"kw\">public</span> <span class=\"fn\">Car</span>(<span class=\"kw\">String</span> marks, <span class=\"kw\">String</span> model, <span class=\"kw\">int</span> numberPortas) {\n        <span class=\"kw\">super</span>(marks, model); <span class=\"kw\">this</span>.numberPortas = numberPortas;\n    }\n    <span class=\"kw\">public void</span> <span class=\"fn\">openPortMalas</span>() { System.out.println(<span class=\"str\">\"port-malas open\"</span>); }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Moto</span> <span class=\"kw\">extends</span> <span class=\"cls\">Veiculo</span> {\n    <span class=\"kw\">private int</span> cilindradas;\n    <span class=\"kw\">public</span> <span class=\"fn\">Moto</span>(<span class=\"kw\">String</span> marks, <span class=\"kw\">String</span> model, <span class=\"kw\">int</span> cilindradas) {\n        <span class=\"kw\">super</span>(marks, model); <span class=\"kw\">this</span>.cilindradas = cilindradas;\n    }\n    <span class=\"kw\">public void</span> <span class=\"fn\">empinar</span>() { System.out.println(model + <span class=\"str\">\" empinando!\"</span>); }\n}</pre>\n        </div>\n      </div>"},{"id":"heranca-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 5.2 — Herança ou composição?","prompt":"Modele Carro e Motor. Explique por que Carro extends Motor viola a relação de subtipo e implemente Carro com um campo privado Motor motor. Faça Carro.ligar() delegar a operação ao motor.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 5.2 — Herança ou composição?médio Modele Carro e Motor. Explique por que Carro extends Motor viola a relação de subtipo e implemente Carro com um campo privado Motor motor. Faça Carro.ligar() delegar a operação ao motor. Ver solução public class Motor { public void ligar() { System.out.println(\"Motor ligado\"); } } public class Carro { private final Motor motor; public Carro(Motor motor) { this.motor = motor; } public void ligar() { motor.ligar(); } } Carro carro = new Carro(new Motor()); carro.ligar();","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 5.2 — Herança ou composição?</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Modele <code>Carro</code> e <code>Motor</code>. Explique por que <code>Carro extends Motor</code> viola a relação de subtipo e implemente <code>Carro</code> com um campo privado <code>Motor motor</code>. Faça <code>Carro.ligar()</code> delegar a operação ao motor.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Engine</span> {\n    <span class=\"kw\">public void</span> <span class=\"fn\">ligar</span>() { System.out.println(<span class=\"str\">\"Engine ligado\"</span>); }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Car</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">Engine</span> engine;\n    <span class=\"kw\">public</span> <span class=\"fn\">Car</span>(<span class=\"cls\">Engine</span> engine) { <span class=\"kw\">this</span>.engine = engine; }\n    <span class=\"kw\">public void</span> <span class=\"fn\">ligar</span>() { engine.ligar(); }\n}\n\n<span class=\"cls\">Car</span> car = <span class=\"kw\">new</span> <span class=\"cls\">Car</span>(<span class=\"kw\">new</span> <span class=\"cls\">Engine</span>());\ncar.ligar();</pre>\n        </div>\n      </div>"},{"id":"heranca-comparison","type":"comparison","authorship":"authored","title":"Herança ou composição","criteria":["relação","acoplamento","variação"],"alternatives":[{"name":"Herança","values":["é um subtipo","forte","sobrescrita"],"useWhen":"o subtipo preserva o contrato do tipo geral","avoidWhen":"o objetivo é apenas reutilizar implementação"},{"name":"Composição","values":["tem/usa um colaborador","explícito","troca de colaborador"],"useWhen":"um objeto delega parte do trabalho a outro","avoidWhen":"o consumidor precisa realmente do mesmo tipo abstrato"}]},{"id":"heranca-quiz","type":"quiz","authorship":"authored","conceptId":"heranca-composicao","prompt":"Por que Carro extends Motor é uma modelagem ruim?","options":[{"id":"heranca-q-a","label":"Porque carro tem um motor; não é um tipo de motor.","correct":true,"explanation":"A relação é colaboração/composição, e a substituição Carro por Motor não preserva significado."},{"id":"heranca-q-b","label":"Porque Java não permite nenhuma herança.","correct":false,"explanation":"Java permite uma superclasse direta; o problema é semântico."},{"id":"heranca-q-c","label":"Porque extends só funciona com interfaces.","correct":false,"explanation":"Classes usam extends; interfaces são implementadas com implements por classes."}]}],"resources":[{"id":"heranca-jls-superclass","type":"reference","title":"JLS 8.1.4: superclasses e subclasses","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.1.4","reinforces":"Define herança simples e relação de subtipo entre classes.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"heranca-jls-override","type":"reference","title":"JLS 8.4.8.1: sobrescrita","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4.8.1","reinforces":"Distingue sobrescrita de ocultação e sobrecarga.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The inheritance example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public class Employee {","instruction":"The inheritance example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["equals/hashCode permanece somente como prévia; prática com HashSet foi removida para Java Core."]}},{"id":"polimorfismo","moduleId":"oop-modeling","order":6,"title":"Polimorfismo","summary":"Polimorfismo (\"muitas formas\") é tratar objetos de subclasses diferentes de maneira uniforme através de uma referência do tipo da superclasse — cada um responde de um jeito próprio ao mesmo método chamado.","objectives":["Usar referência de supertipo para objetos de subtipos","Prever despacho de métodos sobrescritos","Reconhecer custo e sinal de design de downcasts"],"whyItExists":"Sem polimorfismo, cada novo subtipo força cadeias de if e conhecimento de classes concretas; um contrato comum permite variar comportamento mantendo o código consumidor estável.","prerequisiteChapterIds":["heranca"],"conceptIds":["binding-estatico-e-dinamico-mecanismos-diferentes","downcasting-e-instanceof"],"introducedConceptIds":["polimorfismo-substituicao","despacho-dinamico","downcast-instanceof"],"usedConceptIds":["heranca-subtipo","super-sobrescrita","array-indice-length"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"polimorfismo-intuition","type":"intuition","authorship":"authored","title":"O tipo da variável limita; o objeto responde","body":"Uma variável Funcionario aceita Funcionario ou qualquer subtipo. O compilador permite somente operações do tipo declarado; ao chamar método de instância sobrescrito, o comportamento corresponde à classe real do objeto.","analogyLimit":"“Mesma mensagem” descreve o contrato, mas Java resolve chamadas por regras precisas; campos e métodos static não participam do mesmo mecanismo."},{"id":"polimorfismo-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#heranca\">05 · Herança</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 05 · Herança"},{"id":"polimorfismo-content-2","type":"html","authorship":"legacy-preserved","html":"<p><strong>Polimorfismo</strong> (\"muitas formas\") é tratar objetos de subclasses diferentes de maneira uniforme através de uma referência do tipo da superclasse — cada um responde de um jeito próprio ao mesmo método chamado.</p>","fidelityText":"Polimorfismo (\"muitas formas\") é tratar objetos de subclasses diferentes de maneira uniforme através de uma referência do tipo da superclasse — cada um responde de um jeito próprio ao mesmo método chamado."},{"id":"polimorfismo-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"Employee[] team = {\n    new Employee(\"Ana\", 3000),\n    new Manager(\"Bruno\", 3000, 1500)\n};\nfor (Employee f : team) System.out.println(f.calculateSalary());\n// 3000.0  (versão de Funcionario)\n// 4500.0  (versão sobrescrita em Gerente)","fidelityText":"Funcionario[] equipe = { new Funcionario(\"Ana\", 3000), new Gerente(\"Bruno\", 3000, 1500) }; for (Funcionario f : equipe) System.out.println(f.calcularSalario()); // 3000.0 (versão de Funcionario) // 4500.0 (versão sobrescrita em Gerente)","highlightedHtml":"<span class=\"cls\">Employee</span>[] team = {\n    <span class=\"kw\">new</span> <span class=\"cls\">Employee</span>(<span class=\"str\">\"Ana\"</span>, 3000),\n    <span class=\"kw\">new</span> <span class=\"cls\">Manager</span>(<span class=\"str\">\"Bruno\"</span>, 3000, 1500)\n};\n<span class=\"kw\">for</span> (<span class=\"cls\">Employee</span> f : team) System.out.println(f.calculateSalary());\n<span class=\"com\">// 3000.0  (versão de Funcionario)\n// 4500.0  (versão sobrescrita em Gerente)</span>","caption":"Exemplo executável de polimorfismo.","explanation":["O array usa o tipo comum Funcionario para armazenar instâncias diferentes.","A mesma chamada executa a implementação correspondente a cada objeto."],"commonMistakes":["Criar if por tipo sem necessidade","Achar que upcast muda o objeto"]},{"id":"polimorfismo-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Isso é <strong>upcasting</strong>: uma variável <code>Funcionario</code> guarda referência para um objeto <code>Gerente</code>. O tipo da <em>variável</em> é <code>Funcionario</code>, mas o tipo do <em>objeto</em> continua <code>Gerente</code> — é o objeto real, não a variável, quem decide qual versão do método rodar (<strong>ligação dinâmica</strong>, dynamic binding).</p>","fidelityText":"Isso é upcasting: uma variável Funcionario guarda referência para um objeto Gerente. O tipo da variável é Funcionario, mas o tipo do objeto continua Gerente — é o objeto real, não a variável, quem decide qual versão do método rodar (ligação dinâmica, dynamic binding)."},{"id":"polimorfismo-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Binding estático e dinâmico — mecanismos diferentes</h2>","fidelityText":"Binding estático e dinâmico — mecanismos diferentes"},{"id":"polimorfismo-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Nem tudo em Java é resolvido em tempo de execução. Métodos <code>static</code>, <code>private</code> e <code>final</code>, além de <strong>atributos</strong>, usam <strong>binding estático</strong> — resolvido em tempo de compilação, pelo tipo da <em>variável</em>, não do objeto.</p>","fidelityText":"Nem tudo em Java é resolvido em tempo de execução. Métodos static, private e final, além de atributos, usam binding estático — resolvido em tempo de compilação, pelo tipo da variável, não do objeto."},{"id":"polimorfismo-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"class Animal {\n    String name = \"animal generic\"; // atributo -- binding ESTÁTICO\n    static void classify() { System.out.println(\"kingdom Animalia\"); } // static -- binding ESTÁTICO\n    void emitSound() { System.out.println(\"...\"); } // instância -- binding DINÂMICO\n}\nclass Dog extends Animal {\n    String name = \"dog\";\n    static void classify() { System.out.println(\"canine\"); }\n    @Override void emitSound() { System.out.println(\"Au au\"); }\n}\n\nAnimal a = new Dog();\nSystem.out.println(a.name);   // \"animal genérico\" -- atributo lido pelo tipo da VARIÁVEL\na.emitSound();                // \"Au au\" -- método pelo tipo do OBJETO\na.classify();               // \"reino Animalia\" -- static pelo tipo da VARIÁVEL (evite chamar static assim!)","fidelityText":"class Animal { String nome = \"animal genérico\"; // atributo -- binding ESTÁTICO static void classificar() { System.out.println(\"reino Animalia\"); } // static -- binding ESTÁTICO void emitirSom() { System.out.println(\"...\"); } // instância -- binding DINÂMICO } class Cachorro extends Animal { String nome = \"cachorro\"; static void classificar() { System.out.println(\"canídeo\"); } @Override void emitirSom() { System.out.println(\"Au au\"); } } Animal a = new Cachorro(); System.out.println(a.nome); // \"animal genérico\" -- atributo lido pelo tipo da VARIÁVEL a.emitirSom(); // \"Au au\" -- método pelo tipo do OBJETO a.classificar(); // \"reino Animalia\" -- static pelo tipo da VARIÁVEL (evite chamar static assim!)","highlightedHtml":"<span class=\"kw\">class</span> <span class=\"cls\">Animal</span> {\n    <span class=\"kw\">String</span> name = <span class=\"str\">\"animal generic\"</span>; <span class=\"com\">// atributo -- binding ESTÁTICO</span>\n    <span class=\"kw\">static void</span> <span class=\"fn\">classify</span>() { System.out.println(<span class=\"str\">\"kingdom Animalia\"</span>); } <span class=\"com\">// static -- binding ESTÁTICO</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">emitSound</span>() { System.out.println(<span class=\"str\">\"...\"</span>); } <span class=\"com\">// instância -- binding DINÂMICO</span>\n}\n<span class=\"kw\">class</span> <span class=\"cls\">Dog</span> <span class=\"kw\">extends</span> <span class=\"cls\">Animal</span> {\n    <span class=\"kw\">String</span> name = <span class=\"str\">\"dog\"</span>;\n    <span class=\"kw\">static void</span> <span class=\"fn\">classify</span>() { System.out.println(<span class=\"str\">\"canine\"</span>); }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">void</span> <span class=\"fn\">emitSound</span>() { System.out.println(<span class=\"str\">\"Au au\"</span>); }\n}\n\n<span class=\"cls\">Animal</span> a = <span class=\"kw\">new</span> <span class=\"cls\">Dog</span>();\nSystem.out.println(a.name);   <span class=\"com\">// \"animal genérico\" -- atributo lido pelo tipo da VARIÁVEL</span>\na.emitSound();                <span class=\"com\">// \"Au au\" -- método pelo tipo do OBJETO</span>\na.classify();               <span class=\"com\">// \"reino Animalia\" -- static pelo tipo da VARIÁVEL (evite chamar static assim!)</span>","caption":"Exemplo executável de polimorfismo.","explanation":["O exemplo contrasta campo ocultado e método sobrescrito.","Chame static pelo nome da classe; acesso por instância torna a leitura enganosa."],"commonMistakes":["Esperar polimorfismo de campos","Chamar static por referência"]},{"id":"polimorfismo-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Este exemplo é a pegadinha número um de provas de Java: <strong>atributos não são polimórficos</strong>. Só métodos de instância sobrescritos (não <code>static</code>, não <code>private</code>, não <code>final</code>) têm ligação dinâmica. Isso é uma razão prática, não só teórica, para nunca acessar campos <code>public</code> diretamente — use getters, que <em>são</em> polimórficos.</div>","fidelityText":"Este exemplo é a pegadinha número um de provas de Java: atributos não são polimórficos. Só métodos de instância sobrescritos (não static, não private, não final) têm ligação dinâmica. Isso é uma razão prática, não só teórica, para nunca acessar campos public diretamente — use getters, que são polimórficos."},{"id":"polimorfismo-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Downcasting e instanceof</h2>","fidelityText":"Downcasting e instanceof"},{"id":"polimorfismo-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"Animal a = new Dog();\n\n// forma clássica\nif (a instanceof Dog) {\n    Dog c = (Dog) a; // downcast explícito\n    c.find();\n}\n\n// pattern matching for instanceof (Java 16+) -- mais direto\nif (a instanceof Dog c) {\n    c.find(); // \"c\" já vem com o cast feito\n}","fidelityText":"Animal a = new Cachorro(); // forma clássica if (a instanceof Cachorro) { Cachorro c = (Cachorro) a; // downcast explícito c.buscar(); } // pattern matching for instanceof (Java 16+) -- mais direto if (a instanceof Cachorro c) { c.buscar(); // \"c\" já vem com o cast feito }","highlightedHtml":"<span class=\"cls\">Animal</span> a = <span class=\"kw\">new</span> <span class=\"cls\">Dog</span>();\n\n<span class=\"com\">// forma clássica</span>\n<span class=\"kw\">if</span> (a <span class=\"kw\">instanceof</span> <span class=\"cls\">Dog</span>) {\n    <span class=\"cls\">Dog</span> c = (<span class=\"cls\">Dog</span>) a; <span class=\"com\">// downcast explícito</span>\n    c.find();\n}\n\n<span class=\"com\">// pattern matching for instanceof (Java 16+) -- mais direto</span>\n<span class=\"kw\">if</span> (a <span class=\"kw\">instanceof</span> <span class=\"cls\">Dog</span> c) {\n    c.find(); <span class=\"com\">// \"c\" já vem com o cast feito</span>\n}","caption":"Exemplo executável de polimorfismo.","explanation":["instanceof confirma o tipo real antes de acessar comportamento específico.","Pattern matching cria a variável já refinada no ramo verdadeiro."],"commonMistakes":["Downcast sem teste","Usar muitos casts em vez de melhorar o contrato comum"]},{"id":"polimorfismo-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>ClassCastException:</b> um downcast para um tipo incompatível com o objeto real compila normalmente, mas <strong>lança exceção em tempo de execução</strong>. Sempre proteja um downcast com <code>instanceof</code> antes, a menos que tenha certeza absoluta do tipo.</div>","fidelityText":"ClassCastException: um downcast para um tipo incompatível com o objeto real compila normalmente, mas lança exceção em tempo de execução. Sempre proteja um downcast com instanceof antes, a menos que tenha certeza absoluta do tipo."},{"id":"polimorfismo-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Sobrecarga versus sobrescrita:</b> <em>sobrecarga</em>, já vista em métodos, é resolvida em compilação a partir das assinaturas aplicáveis; <em>sobrescrita</em> seleciona em execução a implementação de instância pela classe real do objeto. São mecanismos diferentes que apenas reutilizam nomes de método.</div>","fidelityText":"Sobrecarga versus sobrescrita: sobrecarga, já vista em métodos, é resolvida em compilação a partir das assinaturas aplicáveis; sobrescrita seleciona em execução a implementação de instância pela classe real do objeto. São mecanismos diferentes que apenas reutilizam nomes de método."},{"id":"polimorfismo-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Exercício 6.1 — Formas geométricas polimórficas","prompt":"Crie Forma com double area() retornando 0. Crie Circulo e Quadrado sobrescrevendo. Monte um Forma[] misto e some a área total com um for.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 6.1 — Formas geométricas polimórficasdifícil Crie Forma com double area() retornando 0. Crie Circulo e Quadrado sobrescrevendo. Monte um Forma[] misto e some a área total com um for. Ver solução public class Forma { public double area() { return 0; } } public class Circulo extends Forma { private double raio; public Circulo(double raio) { this.raio = raio; } @Override public double area() { return Math.PI * raio * raio; } } public class Quadrado extends Forma { private double lado; public Quadrado(double lado) { this.lado = lado; } @Override public double area() { return lado * lado; } } Forma[] formas = { new Circulo(3), new Quadrado(4) }; double total = 0; for (Forma f : formas) total += f.area(); System.out.println(\"Área total: \" + total);","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 6.1 — Formas geométricas polimórficas</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie <code>Forma</code> com <code>double area()</code> retornando 0. Crie <code>Circulo</code> e <code>Quadrado</code> sobrescrevendo. Monte um <code>Forma[]</code> misto e some a área total com um <code>for</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Shape</span> { <span class=\"kw\">public double</span> <span class=\"fn\">area</span>() { <span class=\"kw\">return</span> 0; } }\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Circle</span> <span class=\"kw\">extends</span> <span class=\"cls\">Shape</span> {\n    <span class=\"kw\">private double</span> radius;\n    <span class=\"kw\">public</span> <span class=\"fn\">Circle</span>(<span class=\"kw\">double</span> radius) { <span class=\"kw\">this</span>.radius = radius; }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public double</span> <span class=\"fn\">area</span>() { <span class=\"kw\">return</span> Math.PI * radius * radius; }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Square</span> <span class=\"kw\">extends</span> <span class=\"cls\">Shape</span> {\n    <span class=\"kw\">private double</span> lado;\n    <span class=\"kw\">public</span> <span class=\"fn\">Square</span>(<span class=\"kw\">double</span> lado) { <span class=\"kw\">this</span>.lado = lado; }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public double</span> <span class=\"fn\">area</span>() { <span class=\"kw\">return</span> lado * lado; }\n}\n\n<span class=\"cls\">Shape</span>[] formas = { <span class=\"kw\">new</span> <span class=\"cls\">Circle</span>(3), <span class=\"kw\">new</span> <span class=\"cls\">Square</span>(4) };\n<span class=\"kw\">double</span> total = 0;\n<span class=\"kw\">for</span> (<span class=\"cls\">Shape</span> f : formas) total += f.area();\nSystem.out.println(<span class=\"str\">\"Area total: \"</span> + total);</pre>\n        </div>\n      </div>"},{"id":"polimorfismo-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 6.2 — Binding estático vs dinâmico","prompt":"Reproduza exatamente o exemplo de Animal/Cachorro mostrado acima na sua IDE. Antes de rodar, escreva no papel o que cada uma das três linhas (a.nome, a.emitirSom(), a.classificar()) vai imprimir e por quê. Depois confira.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 6.2 — Binding estático vs dinâmicodifícil Reproduza exatamente o exemplo de Animal/Cachorro mostrado acima na sua IDE. Antes de rodar, escreva no papel o que cada uma das três linhas (a.nome, a.emitirSom(), a.classificar()) vai imprimir e por quê. Depois confira. Ver solução a.nome → \"animal genérico\" (atributos usam o tipo da variável). a.emitirSom() → \"Au au\" (métodos de instância sobrescritos usam o tipo real do objeto). a.classificar() → \"reino Animalia\" (métodos static são resolvidos pelo tipo da variável, não do objeto — e chamar um static através de uma variável de instância é, aliás, um mau hábito: o certo seria Animal.classificar()).","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 6.2 — Binding estático vs dinâmico</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Reproduza exatamente o exemplo de <code>Animal</code>/<code>Cachorro</code> mostrado acima na sua IDE. Antes de rodar, escreva no papel o que cada uma das três linhas (<code>a.nome</code>, <code>a.emitirSom()</code>, <code>a.classificar()</code>) vai imprimir e por quê. Depois confira.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><code>a.nome</code> → <em>\"animal genérico\"</em> (atributos usam o tipo da variável). <code>a.emitirSom()</code> → <em>\"Au au\"</em> (métodos de instância sobrescritos usam o tipo real do objeto). <code>a.classificar()</code> → <em>\"reino Animalia\"</em> (métodos <code>static</code> são resolvidos pelo tipo da variável, não do objeto — e chamar um <code>static</code> através de uma variável de instância é, aliás, um mau hábito: o certo seria <code>Animal.classificar()</code>).</p>\n        </div>\n      </div>"},{"id":"polimorfismo-model","type":"mental-model","authorship":"authored","title":"Compilação e execução respondem perguntas distintas","body":"Na compilação, Java verifica se o tipo da variável oferece o método. Na execução, escolhe a implementação sobrescrita pela classe real. A especificação exige esse resultado, não uma estrutura interna específica chamada vtable.","flow":["Funcionario f referencia Gerente","Compilador confirma calcularSalario em Funcionario","Programa executa a chamada","Implementação sobrescrita de Gerente responde"],"ownership":["O consumidor conhece o contrato geral","Cada subtipo mantém sua implementação"]},{"id":"polimorfismo-quiz","type":"quiz","authorship":"authored","conceptId":"despacho-dinamico","prompt":"Funcionario f = new Gerente(...); f.calcularSalario() chama qual implementação?","options":[{"id":"polimorfismo-q-a","label":"A sobrescrita em Gerente, pois esse é o tipo real do objeto.","correct":true,"explanation":"O método de instância sobrescrito usa despacho dinâmico após a chamada ser validada pelo tipo Funcionario."},{"id":"polimorfismo-q-b","label":"Sempre Funcionario, porque esse é o tipo da variável.","correct":false,"explanation":"O tipo da variável limita operações disponíveis, mas não fixa a implementação sobrescrita."},{"id":"polimorfismo-q-c","label":"As duas implementações automaticamente.","correct":false,"explanation":"Apenas uma implementação é selecionada; super só executa se o código a chamar explicitamente."}]}],"resources":[{"id":"polimorfismo-jls-runtime","type":"reference","title":"JLS 15.12.4.4: localização do método em runtime","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.12.4.4","reinforces":"Define seleção dinâmica da implementação de método de instância.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"polimorfismo-jls-cast","type":"reference","title":"JLS 15.20.2: instanceof","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.20.2","reinforces":"Confirma teste de compatibilidade de tipo e resultado booleano.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The polymorphism example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"Employee[] team = {","instruction":"The polymorphism example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"abstracao","moduleId":"oop-modeling","order":7,"title":"Classes abstratas","summary":"No exercício anterior, Forma.area() retorna 0 só para existir — nunca faz sentido instanciar uma Forma \"genérica\" sozinha. Uma classe abstrata torna essa intenção explícita: não pode ser instanciada e pode ter métodos abstratos, sem corpo, que as subclasses são obrigadas a implementar.","objectives":["Distinguir abstração de classe abstrata","Combinar estado comum e métodos obrigatórios","Impedir instância de conceito incompleto"],"whyItExists":"Uma superclasse fictícia que devolve zero apenas para compilar mente sobre o contrato; tipos abstratos expressam que existe uma ideia comum, mas somente subclasses completas podem ser instanciadas.","prerequisiteChapterIds":["polimorfismo"],"conceptIds":["uma-classe-abstrata-pode-ter-construtor"],"introducedConceptIds":["classe-metodo-abstrato","contrato-parcial","abstracao-modelagem"],"usedConceptIds":["heranca-subtipo","despacho-dinamico","final-referencia-tipo","construtor-invariante"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"abstracao-intuition","type":"intuition","authorship":"authored","title":"Não invente uma resposta para um conceito incompleto","body":"Se Forma não possui uma área genérica correta, area não deve retornar 0. Um método abstract declara a pergunta sem fingir uma resposta, e cada classe concreta fornece sua implementação.","analogyLimit":"Contrato incompleto não significa classe vazia: classes abstratas podem possuir campos, construtor e métodos concretos compartilhados."},{"id":"abstracao-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#polimorfismo\">06 · Polimorfismo</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: 06 · Polimorfismo"},{"id":"abstracao-content-2","type":"html","authorship":"legacy-preserved","html":"<p>No exercício anterior, <code>Forma.area()</code> retorna 0 só para existir — nunca faz sentido instanciar uma <code>Forma</code> \"genérica\" sozinha. Uma <strong>classe abstrata</strong> torna essa intenção explícita: não pode ser instanciada e pode ter <strong>métodos abstratos</strong>, sem corpo, que as subclasses são obrigadas a implementar.</p>","fidelityText":"No exercício anterior, Forma.area() retorna 0 só para existir — nunca faz sentido instanciar uma Forma \"genérica\" sozinha. Uma classe abstrata torna essa intenção explícita: não pode ser instanciada e pode ter métodos abstratos, sem corpo, que as subclasses são obrigadas a implementar."},{"id":"abstracao-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"public abstract class Shape {\n    public abstract double area(); // sem corpo\n\n    public void describe() { // métodos concretos também são permitidos\n        System.out.println(\"Area: \" + area());\n    }\n}\n\npublic class Circle extends Shape {\n    private double radius;\n    public Circle(double radius) { this.radius = radius; }\n    @Override public double area() { return Math.PI * radius * radius; }\n}\n\n// Forma forma = new Forma();  // ❌ erro de compilação\n// Circulo c = new Circulo(2); // ✅ ok","fidelityText":"public abstract class Forma { public abstract double area(); // sem corpo public void descrever() { // métodos concretos também são permitidos System.out.println(\"Área: \" + area()); } } public class Circulo extends Forma { private double raio; public Circulo(double raio) { this.raio = raio; } @Override public double area() { return Math.PI * raio * raio; } } // Forma forma = new Forma(); // ❌ erro de compilação // Circulo c = new Circulo(2); // ✅ ok","highlightedHtml":"<span class=\"kw\">public abstract class</span> <span class=\"cls\">Shape</span> {\n    <span class=\"kw\">public abstract double</span> <span class=\"fn\">area</span>(); <span class=\"com\">// sem corpo</span>\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">describe</span>() { <span class=\"com\">// métodos concretos também são permitidos</span>\n        System.out.println(<span class=\"str\">\"Area: \"</span> + area());\n    }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Circle</span> <span class=\"kw\">extends</span> <span class=\"cls\">Shape</span> {\n    <span class=\"kw\">private double</span> radius;\n    <span class=\"kw\">public</span> <span class=\"fn\">Circle</span>(<span class=\"kw\">double</span> radius) { <span class=\"kw\">this</span>.radius = radius; }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public double</span> <span class=\"fn\">area</span>() { <span class=\"kw\">return</span> Math.PI * radius * radius; }\n}\n\n<span class=\"com\">// Forma forma = new Forma();  // ❌ erro de compilação\n// Circulo c = new Circulo(2); // ✅ ok</span>","caption":"Exemplo executável de abstracao.","explanation":["Forma não pode ser instanciada e exige area em subclasses concretas.","descrever reutiliza o contrato polimórfico chamando area."],"commonMistakes":["Dar corpo a método abstract","Esquecer de implementar método em subclasse concreta"]},{"id":"abstracao-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Quando usar:</b> quando existe comportamento <em>compartilhado</em> (como <code>descrever()</code>) além de um contrato que cada subclasse implementa do seu jeito. Se não há nenhum comportamento compartilhado — só o contrato — uma <strong>interface</strong> (próximo capítulo) costuma ser melhor.</div>","fidelityText":"Quando usar: quando existe comportamento compartilhado (como descrever()) além de um contrato que cada subclasse implementa do seu jeito. Se não há nenhum comportamento compartilhado — só o contrato — uma interface (próximo capítulo) costuma ser melhor."},{"id":"abstracao-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Uma classe abstrata pode ter construtor</h2>","fidelityText":"Uma classe abstrata pode ter construtor"},{"id":"abstracao-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Parece contraditório (já que ela não pode ser instanciada diretamente), mas faz sentido: o construtor roda quando uma <em>subclasse concreta</em> é instanciada, via <code>super(...)</code>.</p>","fidelityText":"Parece contraditório (já que ela não pode ser instanciada diretamente), mas faz sentido: o construtor roda quando uma subclasse concreta é instanciada, via super(...)."},{"id":"abstracao-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"public abstract class Shape {\n    protected final String name;\n    protected Shape(String name) { this.name = name; } // roda via super() nas subclasses\n    public abstract double area();\n}","fidelityText":"public abstract class Forma { protected final String nome; protected Forma(String nome) { this.nome = nome; } // roda via super() nas subclasses public abstract double area(); }","highlightedHtml":"<span class=\"kw\">public abstract class</span> <span class=\"cls\">Shape</span> {\n    <span class=\"kw\">protected final String</span> name;\n    <span class=\"kw\">protected</span> <span class=\"fn\">Shape</span>(<span class=\"kw\">String</span> name) { <span class=\"kw\">this</span>.name = name; } <span class=\"com\">// roda via super() nas subclasses</span>\n    <span class=\"kw\">public abstract double</span> <span class=\"fn\">area</span>();\n}","caption":"Exemplo executável de abstracao.","explanation":["O construtor protected inicializa estado comum quando uma subclasse é criada.","final no campo garante uma atribuição da referência nome."],"commonMistakes":["Tentar new Forma","Achar que construtor abstrato é herdado"]},{"id":"abstracao-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Uma classe abstrata pode não ter <strong>nenhum</strong> método abstrato — a única exigência da palavra-chave <code>abstract</code> na classe é impedir instanciação direta. Isso é usado, por exemplo, quando você quer forçar que a classe só seja usada através de subclasses, mesmo que hoje todos os métodos já tenham implementação padrão.</div>","fidelityText":"Uma classe abstrata pode não ter nenhum método abstrato — a única exigência da palavra-chave abstract na classe é impedir instanciação direta. Isso é usado, por exemplo, quando você quer forçar que a classe só seja usada através de subclasses, mesmo que hoje todos os métodos já tenham implementação padrão."},{"id":"abstracao-exercise-9","type":"exercise","authorship":"legacy-preserved","title":"Exercício 7.1 — Instrumentos musicais","prompt":"Crie Instrumento abstrata com tocar() abstrato e afinar() concreto (imprime \"Afinando...\"). Implemente Violao e Bateria.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 7.1 — Instrumentos musicaismédio Crie Instrumento abstrata com tocar() abstrato e afinar() concreto (imprime \"Afinando...\"). Implemente Violao e Bateria. Ver solução public abstract class Instrumento { public abstract void tocar(); public void afinar() { System.out.println(\"Afinando...\"); } } public class Violao extends Instrumento { @Override public void tocar() { System.out.println(\"Dedilhando as cordas\"); } } public class Bateria extends Instrumento { @Override public void tocar() { System.out.println(\"Batendo nos tambores\"); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 7.1 — Instrumentos musicais</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie <code>Instrumento</code> abstrata com <code>tocar()</code> abstrato e <code>afinar()</code> concreto (imprime \"Afinando...\"). Implemente <code>Violao</code> e <code>Bateria</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public abstract class</span> <span class=\"cls\">Instrumento</span> {\n    <span class=\"kw\">public abstract void</span> <span class=\"fn\">tocar</span>();\n    <span class=\"kw\">public void</span> <span class=\"fn\">afinar</span>() { System.out.println(<span class=\"str\">\"Afinando...\"</span>); }\n}\n<span class=\"kw\">public class</span> <span class=\"cls\">Violao</span> <span class=\"kw\">extends</span> <span class=\"cls\">Instrumento</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">tocar</span>() { System.out.println(<span class=\"str\">\"Dedilhando the strings\"</span>); }\n}\n<span class=\"kw\">public class</span> <span class=\"cls\">Bateria</span> <span class=\"kw\">extends</span> <span class=\"cls\">Instrumento</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">tocar</span>() { System.out.println(<span class=\"str\">\"Batendo in the tambores\"</span>); }\n}</pre>\n        </div>\n      </div>"},{"id":"abstracao-exercise-10","type":"exercise","authorship":"legacy-preserved","title":"Exercício 7.2 — Fluxo comum com etapas obrigatórias","prompt":"Crie uma classe abstrata ProcessadorPedido. O método final processar() deve chamar, nessa ordem, três operações abstratas: validar(), cobrar() e notificar(). Depois crie ProcessadorPedidoPix e implemente cada operação. Antes de programar, responda: qual parte precisa ser igual para todo pedido e quais partes cada forma de pagamento precisa fornecer? O objetivo é praticar método concreto, método abstrato, sobrescrita e final; o nome formal desse arranjo será estudado apenas quando padrões de projeto forem apresentados.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 7.2 — Fluxo comum com etapas obrigatóriasdifícil Crie uma classe abstrata ProcessadorPedido. O método final processar() deve chamar, nessa ordem, três operações abstratas: validar(), cobrar() e notificar(). Depois crie ProcessadorPedidoPix e implemente cada operação. Antes de programar, responda: qual parte precisa ser igual para todo pedido e quais partes cada forma de pagamento precisa fornecer? O objetivo é praticar método concreto, método abstrato, sobrescrita e final; o nome formal desse arranjo será estudado apenas quando padrões de projeto forem apresentados. Ver solução public abstract class ProcessadorPedido { public final void processar() { // final: subclasses não podem mudar a ordem validar(); cobrar(); notificar(); } protected abstract void validar(); protected abstract void cobrar(); protected abstract void notificar(); } public class ProcessadorPedidoPix extends ProcessadorPedido { @Override protected void validar() { System.out.println(\"Validando chave Pix\"); } @Override protected void cobrar() { System.out.println(\"Gerando QR Code Pix\"); } @Override protected void notificar() { System.out.println(\"Enviando confirmação por e-mail\"); } } // main: new ProcessadorPedidoPix().processar();","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 7.2 — Fluxo comum com etapas obrigatórias</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie uma classe abstrata <code>ProcessadorPedido</code>. O método <strong>final</strong> <code>processar()</code> deve chamar, nessa ordem, três operações abstratas: <code>validar()</code>, <code>cobrar()</code> e <code>notificar()</code>. Depois crie <code>ProcessadorPedidoPix</code> e implemente cada operação. Antes de programar, responda: qual parte precisa ser igual para todo pedido e quais partes cada forma de pagamento precisa fornecer? O objetivo é praticar método concreto, método abstrato, sobrescrita e <code>final</code>; o nome formal desse arranjo será estudado apenas quando padrões de projeto forem apresentados.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public abstract class</span> <span class=\"cls\">ProcessadorOrder</span> {\n    <span class=\"kw\">public final void</span> <span class=\"fn\">process</span>() { <span class=\"com\">// final: subclasses não podem mudar a ordem</span>\n        validate();\n        charge();\n        notify();\n    }\n    <span class=\"kw\">protected abstract void</span> <span class=\"fn\">validate</span>();\n    <span class=\"kw\">protected abstract void</span> <span class=\"fn\">charge</span>();\n    <span class=\"kw\">protected abstract void</span> <span class=\"fn\">notify</span>();\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">PixOrderProcessor</span> <span class=\"kw\">extends</span> <span class=\"cls\">ProcessadorOrder</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">protected void</span> <span class=\"fn\">validate</span>() { System.out.println(<span class=\"str\">\"validating key Pix\"</span>); }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">protected void</span> <span class=\"fn\">charge</span>() { System.out.println(<span class=\"str\">\"Gerando QR Code Pix\"</span>); }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">protected void</span> <span class=\"fn\">notify</span>() { System.out.println(<span class=\"str\">\"Enviando acknowledgment by and-mail\"</span>); }\n}\n\n<span class=\"com\">// main: new ProcessadorPedidoPix().processar();</span></pre>\n        </div>\n      </div>"},{"id":"abstracao-comparison","type":"comparison","authorship":"authored","title":"Abstração e abstract não são sinônimos","criteria":["natureza","onde acontece","resultado"],"alternatives":[{"name":"Abstração de modelagem","values":["decisão","antes e durante o código","modelo relevante ao contexto"],"useWhen":"escolher o que representar e ignorar","avoidWhen":"procurar apenas uma palavra-chave"},{"name":"Classe abstract","values":["recurso da linguagem","declaração Java","tipo não instanciável"],"useWhen":"há base comum parcial para subclasses","avoidWhen":"não existe relação de subtipo"}]},{"id":"abstracao-quiz","type":"quiz","authorship":"authored","conceptId":"classe-metodo-abstrato","prompt":"Por que Forma.area() abstract é melhor que retornar 0 por padrão?","options":[{"id":"abstracao-q-a","label":"Porque obriga cada subtipo concreto a fornecer uma área válida.","correct":true,"explanation":"O compilador impede uma classe concreta incompleta e o contrato não oferece resposta fictícia."},{"id":"abstracao-q-b","label":"Porque métodos abstract executam mais rápido.","correct":false,"explanation":"A decisão é de correção e modelagem, não promessa de desempenho."},{"id":"abstracao-q-c","label":"Porque classes abstratas não podem ter métodos concretos.","correct":false,"explanation":"Elas podem combinar métodos abstratos e concretos."}]}],"resources":[{"id":"abstracao-jls-class","type":"reference","title":"JLS 8.1.1.1: classes abstratas","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.1.1.1","reinforces":"Define não instanciação e obrigação de implementação.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"abstracao-jls-method","type":"reference","title":"JLS 8.4.3.1: métodos abstratos","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4.3.1","reinforces":"Detalha ausência de corpo e implementação por subtipos.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The abstraction example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public abstract class Shape {","instruction":"The abstraction example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"interfaces","moduleId":"oop-modeling","order":8,"title":"Interfaces","summary":"Uma interface declara um contrato de tipo: operações que uma classe implementadora promete oferecer. Ela pode conter métodos abstratos e, sob regras específicas, métodos default, static e private. Uma classe possui uma única superclasse direta, mas pode implementar várias interfaces.","objectives":["Definir capacidade por interface tipada","Implementar múltiplos contratos independentes","Entender default e resolver conflitos explicitamente"],"whyItExists":"Classes sem ancestral comum útil podem oferecer a mesma capacidade; interfaces permitem que consumidores dependam do comportamento prometido, não da implementação concreta.","prerequisiteChapterIds":["abstracao"],"conceptIds":["metodos-default-static-e-private-java-8","interface-vs-classe-abstrata-quando-usar-cada-uma"],"introducedConceptIds":["interface-contrato","implementacao-multipla-interfaces","default-conflito-interface"],"usedConceptIds":["polimorfismo-substituicao","classe-metodo-abstrato","controle-acesso","super-sobrescrita"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"interfaces-intuition","type":"intuition","authorship":"authored","title":"Capacidade sem parentesco de implementação","body":"Pix e Cartao podem implementar MetodoPagamento mesmo sem compartilhar campos ou superclasse específica. O consumidor conhece pagar; cada classe decide como cumprir o contrato.","analogyLimit":"Contrato não garante que a implementação seja correta; testes e invariantes ainda precisam verificar o comportamento prometido."},{"id":"interfaces-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#abstracao\">07 · Classes abstratas</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 07 · Classes abstratas"},{"id":"interfaces-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Uma <strong>interface</strong> declara um contrato de tipo: operações que uma classe implementadora promete oferecer. Ela pode conter métodos abstratos e, sob regras específicas, métodos <code>default</code>, <code>static</code> e <code>private</code>. Uma classe possui uma única superclasse direta, mas pode implementar várias interfaces.</p>","fidelityText":"Uma interface declara um contrato de tipo: operações que uma classe implementadora promete oferecer. Ela pode conter métodos abstratos e, sob regras específicas, métodos default, static e private. Uma classe possui uma única superclasse direta, mas pode implementar várias interfaces."},{"id":"interfaces-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface Precificavel { double priceFinal(); }\npublic interface Printable { String text(); }\n\npublic class Product implements Precificavel, Printable {\n    private double price;\n\n    @Override\n    public double priceFinal() { return price; }\n\n    @Override\n    public String text() { return \"Product: R$\" + price; }\n}","fidelityText":"public interface Precificavel { double precoFinal(); } public interface Imprimivel { String texto(); } public class Produto implements Precificavel, Imprimivel { private double preco; @Override public double precoFinal() { return preco; } @Override public String texto() { return \"Produto: R$\" + preco; } }","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">Precificavel</span> { <span class=\"kw\">double</span> <span class=\"fn\">priceFinal</span>(); }\n<span class=\"kw\">public interface</span> <span class=\"cls\">Printable</span> { <span class=\"kw\">String</span> <span class=\"fn\">text</span>(); }\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Product</span> <span class=\"kw\">implements</span> <span class=\"cls\">Precificavel</span>, <span class=\"cls\">Printable</span> {\n    <span class=\"kw\">private double</span> price;\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public double</span> <span class=\"fn\">priceFinal</span>() { <span class=\"kw\">return</span> price; }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public String</span> <span class=\"fn\">text</span>() { <span class=\"kw\">return</span> <span class=\"str\">\"Product: R$\"</span> + price; }\n}","caption":"Exemplo executável de interfaces.","explanation":["Precificavel e Imprimivel descrevem capacidades independentes com tipos concretos de retorno e parâmetro.","Produto promete os dois contratos e pode ser usado por consumidores que conhecem apenas a capacidade necessária."],"commonMistakes":["Colocar operações sem relação na mesma interface","Confundir implements com extends de classe"]},{"id":"interfaces-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Métodos default, static e private (Java 8+)</h2>","fidelityText":"Métodos default, static e private (Java 8+)"},{"id":"interfaces-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Desde o Java 8, interfaces também podem fornecer métodos <code>default</code>. Isso permite acrescentar um comportamento reutilizável a um contrato existente sem obrigar imediatamente todas as implementações a escrever o mesmo corpo. O método continua fazendo parte do contrato e conflitos entre dois <code>default</code> precisam ser resolvidos explicitamente.</p>","fidelityText":"Desde o Java 8, interfaces também podem fornecer métodos default. Isso permite acrescentar um comportamento reutilizável a um contrato existente sem obrigar imediatamente todas as implementações a escrever o mesmo corpo. O método continua fazendo parte do contrato e conflitos entre dois default precisam ser resolvidos explicitamente."},{"id":"interfaces-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface Notifier {\n    void send(String message); // abstrato -- cada implementação define\n\n    // default: implementação padrão, subclasses podem sobrescrever ou não\n    default void sendUrgent(String message) {\n        send(\"[Urgent] \" + message);\n    }\n\n    // static: utilidade ligada ao contrato, chamada na interface\n    static String markUrgent(String message) {\n        return \"[Urgent] \" + message;\n    }\n}","fidelityText":"public interface Notificador { void enviar(String mensagem); // abstrato -- cada implementação define // default: implementação padrão, subclasses podem sobrescrever ou não default void enviarUrgente(String mensagem) { enviar(\"[URGENTE] \" + mensagem); } // static: utilidade ligada ao contrato, chamada na interface static String marcarUrgente(String mensagem) { return \"[URGENTE] \" + mensagem; } }","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">Notifier</span> {\n    <span class=\"kw\">void</span> <span class=\"fn\">send</span>(<span class=\"kw\">String</span> message); <span class=\"com\">// abstrato -- cada implementação define</span>\n\n    <span class=\"com\">// default: implementação padrão, subclasses podem sobrescrever ou não</span>\n    <span class=\"kw\">default void</span> <span class=\"fn\">sendUrgent</span>(<span class=\"kw\">String</span> message) {\n        send(<span class=\"str\">\"[Urgent] \"</span> + message);\n    }\n\n    <span class=\"com\">// static: utilidade ligada ao contrato, chamada na interface</span>\n    <span class=\"kw\">static String</span> <span class=\"fn\">markUrgent</span>(<span class=\"kw\">String</span> message) {\n        <span class=\"kw\">return</span> <span class=\"str\">\"[Urgent] \"</span> + message;\n    }\n}","caption":"Exemplo executável de interfaces.","explanation":["enviar é abstrato; enviarUrgente reutiliza o contrato em uma implementação default.","marcarUrgente é static, pertence à interface e pode ser chamado sem criar uma implementação."],"commonMistakes":["Achar que default dispensa implementação do método abstrato","Chamar método static pela instância"]},{"id":"interfaces-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Se uma classe implementa duas interfaces que têm um método <code>default</code> com a <strong>mesma assinatura</strong>, o compilador força você a sobrescrever esse método explicitamente na classe — Java não escolhe um dos dois por conta própria. Esse é o único jeito de \"herança múltipla de implementação\" ser tolerado, e ainda assim de forma explícita e segura.</div>","fidelityText":"Se uma classe implementa duas interfaces que têm um método default com a mesma assinatura, o compilador força você a sobrescrever esse método explicitamente na classe — Java não escolhe um dos dois por conta própria. Esse é o único jeito de \"herança múltipla de implementação\" ser tolerado, e ainda assim de forma explícita e segura."},{"id":"interfaces-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Interface vs classe abstrata — quando usar cada uma</h2>","fidelityText":"Interface vs classe abstrata — quando usar cada uma"},{"id":"interfaces-content-9","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Situação</th><th>Use</th></tr>\n        <tr><td>Classes não relacionadas precisam prometer o mesmo comportamento (ex: <code>Voador</code> em Pássaro e Avião)</td><td>Interface</td></tr>\n        <tr><td>Existe hierarquia real (\"é um\") com estado e comportamento compartilhados</td><td>Classe abstrata</td></tr>\n        <tr><td>A classe já herda de outra e ainda precisa do contrato</td><td>Interface (única forma de \"herança múltipla\")</td></tr>\n        <tr><td>Você quer expor só o comportamento, sem se importar como é implementado por trás</td><td>Interface</td></tr>\n      </tbody></table>","fidelityText":"SituaçãoUse Classes não relacionadas precisam prometer o mesmo comportamento (ex: Voador em Pássaro e Avião)Interface Existe hierarquia real (\"é um\") com estado e comportamento compartilhadosClasse abstrata A classe já herda de outra e ainda precisa do contratoInterface (única forma de \"herança múltipla\") Você quer expor só o comportamento, sem se importar como é implementado por trásInterface"},{"id":"interfaces-exercise-10","type":"exercise","authorship":"legacy-preserved","title":"Exercício 8.1 — Contrato de pagamento","prompt":"Crie MetodoPagamento com boolean pagar(double valor). Implemente Pix e CartaoCredito. No main, itere um array chamando pagar(100).","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 8.1 — Contrato de pagamentomédio Crie MetodoPagamento com boolean pagar(double valor). Implemente Pix e CartaoCredito. No main, itere um array chamando pagar(100). Ver solução public interface MetodoPagamento { boolean pagar(double valor); } public class Pix implements MetodoPagamento { @Override public boolean pagar(double valor) { System.out.println(\"Pagando R$\" + valor + \" via Pix\"); return true; } } public class CartaoCredito implements MetodoPagamento { @Override public boolean pagar(double valor) { System.out.println(\"Pagando R$\" + valor + \" no cartão\"); return true; } } MetodoPagamento[] metodos = { new Pix(), new CartaoCredito() }; for (MetodoPagamento m : metodos) m.pagar(100);","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 8.1 — Contrato de pagamento</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie <code>MetodoPagamento</code> com <code>boolean pagar(double valor)</code>. Implemente <code>Pix</code> e <code>CartaoCredito</code>. No <code>main</code>, itere um array chamando <code>pagar(100)</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public interface</span> <span class=\"cls\">MethodPayment</span> { <span class=\"kw\">boolean</span> <span class=\"fn\">pagar</span>(<span class=\"kw\">double</span> value); }\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Pix</span> <span class=\"kw\">implements</span> <span class=\"cls\">MethodPayment</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public boolean</span> <span class=\"fn\">pagar</span>(<span class=\"kw\">double</span> value) {\n        System.out.println(<span class=\"str\">\"Pagando R$\"</span> + value + <span class=\"str\">\" via Pix\"</span>); <span class=\"kw\">return true</span>;\n    }\n}\n<span class=\"kw\">public class</span> <span class=\"cls\">CardCredit</span> <span class=\"kw\">implements</span> <span class=\"cls\">MethodPayment</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public boolean</span> <span class=\"fn\">pagar</span>(<span class=\"kw\">double</span> value) {\n        System.out.println(<span class=\"str\">\"Pagando R$\"</span> + value + <span class=\"str\">\" in the card\"</span>); <span class=\"kw\">return true</span>;\n    }\n}\n\n<span class=\"cls\">MethodPayment</span>[] methods = { <span class=\"kw\">new</span> <span class=\"cls\">Pix</span>(), <span class=\"kw\">new</span> <span class=\"cls\">CardCredit</span>() };\n<span class=\"kw\">for</span> (<span class=\"cls\">MethodPayment</span> m : methods) m.pagar(100);</pre>\n        </div>\n      </div>"},{"id":"interfaces-exercise-11","type":"exercise","authorship":"legacy-preserved","title":"Exercício 8.2 — Método default com conflito","prompt":"Crie duas interfaces, Voador e Nadador, cada uma com um método default String mover() retornando textos diferentes (\"Voando\" e \"Nadando\"). Crie a classe PatoSelvagem implements Voador, Nadador e resolva o conflito de assinatura sobrescrevendo mover() explicitamente para retornar \"Voando e nadando\".","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 8.2 — Método default com conflitodifícil Crie duas interfaces, Voador e Nadador, cada uma com um método default String mover() retornando textos diferentes (\"Voando\" e \"Nadando\"). Crie a classe PatoSelvagem implements Voador, Nadador e resolva o conflito de assinatura sobrescrevendo mover() explicitamente para retornar \"Voando e nadando\". Ver solução public interface Voador { default String mover() { return \"Voando\"; } } public interface Nadador { default String mover() { return \"Nadando\"; } } public class PatoSelvagem implements Voador, Nadador { @Override public String mover() { return \"Voando e nadando\"; // obrigatório resolver o conflito manualmente // também seria possível chamar Voador.super.mover() para reusar uma delas } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 8.2 — Método default com conflito</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie duas interfaces, <code>Voador</code> e <code>Nadador</code>, cada uma com um método <code>default String mover()</code> retornando textos diferentes (\"Voando\" e \"Nadando\"). Crie a classe <code>PatoSelvagem implements Voador, Nadador</code> e resolva o conflito de assinatura sobrescrevendo <code>mover()</code> explicitamente para retornar <em>\"Voando e nadando\"</em>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public interface</span> <span class=\"cls\">Flyer</span> {\n    <span class=\"kw\">default String</span> <span class=\"fn\">mover</span>() { <span class=\"kw\">return</span> <span class=\"str\">\"Voando\"</span>; }\n}\n<span class=\"kw\">public interface</span> <span class=\"cls\">Nadador</span> {\n    <span class=\"kw\">default String</span> <span class=\"fn\">mover</span>() { <span class=\"kw\">return</span> <span class=\"str\">\"Nadando\"</span>; }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">PatoSelvagem</span> <span class=\"kw\">implements</span> <span class=\"cls\">Flyer</span>, <span class=\"cls\">Nadador</span> {\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public String</span> <span class=\"fn\">mover</span>() {\n        <span class=\"kw\">return</span> <span class=\"str\">\"Voando and nadando\"</span>; <span class=\"com\">// obrigatório resolver o conflito manualmente</span>\n        <span class=\"com\">// também seria possível chamar Voador.super.mover() para reusar uma delas</span>\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"interfaces-comparison","type":"comparison","authorship":"authored","title":"Interface ou classe abstrata","criteria":["estado de instância","herança","uso principal"],"alternatives":[{"name":"Interface","values":["não define campos de instância","uma classe implementa várias","capacidade"],"useWhen":"tipos diferentes compartilham um contrato","avoidWhen":"é necessário estado base compartilhado"},{"name":"Classe abstrata","values":["pode manter campos","uma superclasse direta","base parcial"],"useWhen":"subtipos compartilham estado e comportamento","avoidWhen":"a relação existe apenas por uma capacidade"}]},{"id":"interfaces-quiz","type":"quiz","authorship":"authored","conceptId":"default-conflito-interface","prompt":"Uma classe herda o mesmo método default de duas interfaces sem regra mais específica. O que ocorre?","options":[{"id":"interfaces-q-a","label":"A classe deve sobrescrever e resolver o conflito explicitamente.","correct":true,"explanation":"Java rejeita a ambiguidade em compilação e exige uma decisão do tipo implementador."},{"id":"interfaces-q-b","label":"Java escolhe a primeira interface escrita.","correct":false,"explanation":"A ordem textual de implements não decide o método default."},{"id":"interfaces-q-c","label":"Os dois métodos executam em sequência automaticamente.","correct":false,"explanation":"Não existe composição automática das implementações."}]}],"resources":[{"id":"interfaces-jls","type":"reference","title":"JLS 9: interfaces","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html","reinforces":"Define membros, implementação, herança e contratos de interface.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"interfaces-default","type":"reference","title":"JLS 9.4.3: corpo de métodos de interface","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html#jls-9.4.3","reinforces":"Distingue métodos abstract, default, static e private.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The interfaces example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"public interface Precificavel { double priceFinal(); }","instruction":"The interfaces example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21"}},{"id":"mini-biblioteca-cli","moduleId":"oop-modeling","order":11,"title":"Mini-projeto: biblioteca orientada a objetos","summary":"Construa uma biblioteca em memória com livros, revistas, usuários e empréstimos. O desafio não é produzir um menu grande: é fazer os objetos impedirem estados inválidos, independentemente de a operação ser chamada pelo console ou diretamente por outro código.","objectives":["Modelar invariantes antes da interface de console","Implementar relações com arrays e operações de domínio","Demonstrar empréstimo e devolução por evidências manuais"],"whyItExists":"O projeto consolida POO num domínio pequeno: objetos colaboram para impedir empréstimos simultâneos sem depender de coleções, exceções, banco ou frameworks ainda não ensinados.","prerequisiteChapterIds":["associacoes-cardinalidade"],"conceptIds":["antes-de-programar-escreva-o-contrato","entregaveis","sequencia-segura-de-implementacao","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["projeto-poo-incremental","evidencia-invariante-objeto"],"usedConceptIds":["quatro-pilares-cooperacao","decisao-modelagem-poo","encapsulamento-invariante","interface-contrato","parse-validacao-eof","array-indice-length"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"biblioteca-intuition","type":"intuition","authorship":"authored","title":"Primeiro o domínio; depois o menu","body":"Comece criando um item e verificando emprestar/devolver diretamente no main. Só depois adicione arrays de capacidade fixa, busca e a CLI. Assim o terminal demonstra regras já corretas em vez de hospedá-las.","analogyLimit":"Separar camadas não exige framework ou arquitetura complexa; nesta fase bastam classes e métodos com responsabilidades claras."},{"id":"mini-biblioteca-cli-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n    <div class=\"meta-item\">Objetivo: <b>integrar modelagem e POO</b></div>\n    <div class=\"time-est\">Tempo: <b>7–12 horas</b></div>\n    <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#associacoes-cardinalidade\">Associações e cardinalidade</a></div>\n  </div>","fidelityText":"Objetivo: integrar modelagem e POO Tempo: 7–12 horas Pré-requisito: Associações e cardinalidade"},{"id":"mini-biblioteca-cli-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Construa uma biblioteca em memória com livros, revistas, usuários e empréstimos. O desafio não é produzir um menu grande: é fazer os objetos impedirem estados inválidos, independentemente de a operação ser chamada pelo console ou diretamente por outro código.</p>","fidelityText":"Construa uma biblioteca em memória com livros, revistas, usuários e empréstimos. O desafio não é produzir um menu grande: é fazer os objetos impedirem estados inválidos, independentemente de a operação ser chamada pelo console ou diretamente por outro código."},{"id":"mini-biblioteca-cli-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Antes de programar: escreva o contrato</h2>","fidelityText":"Antes de programar: escreva o contrato"},{"id":"mini-biblioteca-cli-checklist-4","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-biblioteca-cli-checklist-0","label":"Todo item tem identificador, título e disponibilidade coerentes."},{"id":"mini-biblioteca-cli-checklist-1","label":"Um empréstimo associa exatamente um usuário e um item."},{"id":"mini-biblioteca-cli-checklist-2","label":"Um item indisponível não pode ser emprestado novamente."},{"id":"mini-biblioteca-cli-checklist-3","label":"Devolver um item disponível é recusado sem alterar o estado."},{"id":"mini-biblioteca-cli-checklist-4","label":"Arrays possuem capacidade definida e nunca são acessados além do limite."}]},{"id":"mini-biblioteca-cli-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>Pergunta de modelagem:</b> quem conhece a disponibilidade do item e consegue preservar essa regra em qualquer interface? Se a validação existir somente no menu, chamadas diretas ao objeto poderão ignorá-la.</div>","fidelityText":"Pergunta de modelagem: quem conhece a disponibilidade do item e consegue preservar essa regra em qualquer interface? Se a validação existir somente no menu, chamadas diretas ao objeto poderão ignorá-la."},{"id":"mini-biblioteca-cli-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Entregáveis</h2>","fidelityText":"Entregáveis"},{"id":"mini-biblioteca-cli-checklist-7","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-biblioteca-cli-checklist-5","label":"Esboço com classes, responsabilidades, direção e cardinalidade das relações."},{"id":"mini-biblioteca-cli-checklist-6","label":"Campos privados, construtores válidos e mudanças feitas por comportamentos."},{"id":"mini-biblioteca-cli-checklist-7","label":"Interface ItemEmprestavel e ao menos duas implementações."},{"id":"mini-biblioteca-cli-checklist-8","label":"Biblioteca armazena itens e empréstimos em arrays com contadores."},{"id":"mini-biblioteca-cli-checklist-9","label":"Aplicação CLI demonstra cadastro, busca, empréstimo, devolução e saída por opção ou EOF."}]},{"id":"mini-biblioteca-cli-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Sequência segura de implementação</h2>","fidelityText":"Sequência segura de implementação"},{"id":"mini-biblioteca-cli-content-9","type":"html","authorship":"legacy-preserved","html":"<ol>\n    <li><strong>Domínio mínimo:</strong> crie um item e prove sua mudança entre disponível e emprestado sem usar <code>Scanner</code>.</li>\n    <li><strong>Variações:</strong> adicione <code>Livro</code> e <code>Revista</code> pelo mesmo contrato e percorra um array de <code>ItemEmprestavel</code>.</li>\n    <li><strong>Relações:</strong> associe usuário, item e empréstimo; centralize a transição que precisa manter os objetos coerentes.</li>\n    <li><strong>Catálogo:</strong> adicione arrays, contadores, busca e tratamento explícito de capacidade cheia.</li>\n    <li><strong>Fronteira CLI:</strong> só então leia texto, converta entradas e chame operações do domínio. O menu não decide regras de empréstimo.</li>\n  </ol>","fidelityText":"Domínio mínimo: crie um item e prove sua mudança entre disponível e emprestado sem usar Scanner. Variações: adicione Livro e Revista pelo mesmo contrato e percorra um array de ItemEmprestavel. Relações: associe usuário, item e empréstimo; centralize a transição que precisa manter os objetos coerentes. Catálogo: adicione arrays, contadores, busca e tratamento explícito de capacidade cheia. Fronteira CLI: só então leia texto, converta entradas e chame operações do domínio. O menu não decide regras de empréstimo."},{"id":"mini-biblioteca-cli-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Critérios de conclusão</h2><ul>\n    <li>Objetos não nascem nem permanecem em estado impossível.</li>\n    <li>Herança só existe onde há relação “é um”; colaboração representa “tem um” ou “usa um”.</li>\n    <li>A regra funciona por chamadas diretas, sem depender do terminal.</li>\n    <li>Entrada inválida e EOF não corrompem o estado.</li>\n  </ul></div>","fidelityText":"Critérios de conclusão Objetos não nascem nem permanecem em estado impossível. Herança só existe onde há relação “é um”; colaboração representa “tem um” ou “usa um”. A regra funciona por chamadas diretas, sem depender do terminal. Entrada inválida e EOF não corrompem o estado."},{"id":"mini-biblioteca-cli-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-biblioteca-cli:0","type":"quiz","authorship":"legacy-preserved","conceptId":"onde-deve-ficar-a-regra-que-impede-emprestar-exemplar-indisponivel","prompt":"Onde deve ficar a regra que impede emprestar exemplar indisponível?","options":[{"id":"mini-biblioteca-cli:0:option:0","label":"No modelo/caso de uso que protege a invariante, não apenas no menu.","correct":true,"explanation":"A operação que muda o estado deve proteger a invariante; o menu apenas coleta e apresenta."},{"id":"mini-biblioteca-cli:0:option:1","label":"Somente na mensagem impressa pelo menu.","correct":false,"explanation":"Mensagem não impede outra chamada de alterar o objeto incorretamente."},{"id":"mini-biblioteca-cli:0:option:2","label":"Em um getter que altera o estado.","correct":false,"explanation":"Consulta que altera estado esconde efeito e não estabelece uma transição clara."}],"sourceIndex":12},{"id":"mini-biblioteca-cli:1","type":"quiz","authorship":"legacy-preserved","conceptId":"livro-e-revista-implementam-itememprestavel-um-array-de-itememprestavel-","prompt":"Livro e Revista implementam ItemEmprestavel. Um array de ItemEmprestavel mistura instâncias dos dois tipos. Percorrendo o array e chamando item.emprestar(usuario) em cada posição, o que acontece?","options":[{"id":"mini-biblioteca-cli:1:option:0","label":"Cada item executa o comportamento correspondente ao seu próprio tipo, sem o código que percorre o array precisar saber se é Livro ou Revista.","correct":true,"explanation":"Isso é despacho polimórfico: o array guarda o tipo da interface, e cada instância executa sua própria implementação."},{"id":"mini-biblioteca-cli:1:option:1","label":"É preciso um if/instanceof para cada tipo antes de chamar emprestar, senão o código não compila.","correct":false,"explanation":"Um contrato bem definido (a interface) é exatamente o que evita precisar de instanceof espalhado pelo código."},{"id":"mini-biblioteca-cli:1:option:2","label":"Só funciona se Livro e Revista tiverem exatamente os mesmos atributos internos.","correct":false,"explanation":"O contrato da interface exige os mesmos métodos, não os mesmos atributos internos."}],"sourceIndex":13},{"id":"mini-biblioteca-cli:2","type":"quiz","authorship":"legacy-preserved","conceptId":"o-array-de-itens-tem-capacidade-fixa-de-100-posicoes-e-o-contador-de-ite","prompt":"O array de itens tem capacidade fixa de 100 posições e o contador de itens cadastrados já é 100. Um novo cadastro é solicitado. O que a implementação correta faz?","options":[{"id":"mini-biblioteca-cli:2:option:0","label":"Recusa o cadastro e avisa que a capacidade foi atingida, sem gravar no índice 100 (fora da faixa válida 0–99).","correct":true,"explanation":"O array já está no limite (índices válidos 0 a 99); gravar no índice 100 lança ArrayIndexOutOfBoundsException."},{"id":"mini-biblioteca-cli:2:option:1","label":"Grava mesmo assim, já que arrays em Java crescem automaticamente quando necessário.","correct":false,"explanation":"Arrays em Java têm tamanho fixo desde a criação -- não crescem sozinhos."},{"id":"mini-biblioteca-cli:2:option:2","label":"Substitui o item da posição 0 pelo novo cadastro.","correct":false,"explanation":"Substituir a posição 0 apaga um item real do catálogo sem que isso seja um requisito do projeto."}],"sourceIndex":14},{"id":"mini-biblioteca-cli:3","type":"quiz","authorship":"legacy-preserved","conceptId":"uma-chamada-tenta-devolver-um-item-que-ja-esta-disponivel-nunca-foi-empr","prompt":"Uma chamada tenta devolver um item que já está disponível (nunca foi emprestado, ou já foi devolvido antes). O que a regra de domínio deve fazer?","options":[{"id":"mini-biblioteca-cli:3:option:0","label":"Recusar a operação e manter o estado anterior, sem deixar o item em uma condição inconsistente.","correct":true,"explanation":"Um item já disponível não tem o que devolver; aceitar a operação sem checar o estado anterior abre espaço para inconsistência."},{"id":"mini-biblioteca-cli:3:option:1","label":"Aceitar a devolução e simplesmente ignorá-la em silêncio.","correct":false,"explanation":"Ignorar em silêncio esconde um uso incorreto da API do domínio de quem chamou a operação."},{"id":"mini-biblioteca-cli:3:option:2","label":"Marcar o item como emprestado, já que houve uma tentativa de movimentação.","correct":false,"explanation":"Marcar como emprestado sem um empréstimo real associado quebra a relação que o domínio deveria proteger."}],"sourceIndex":15},{"id":"mini-biblioteca-cli:4","type":"quiz","authorship":"legacy-preserved","conceptId":"a-cli-le-uma-opcao-de-menu-e-a-entrada-chega-ao-fim-eof-no-meio-da-leitu","prompt":"A CLI lê uma opção de menu e a entrada chega ao fim (EOF) no meio da leitura. Qual comportamento é coerente com a separação entre domínio e fronteira?","options":[{"id":"mini-biblioteca-cli:4:option:0","label":"A CLI encerra de forma limpa; o estado do domínio (itens e empréstimos) permanece exatamente como estava antes da tentativa de leitura.","correct":true,"explanation":"A fronteira (CLI) pode falhar ou encerrar sem que isso deva corromper o estado do domínio que ela apenas invoca."},{"id":"mini-biblioteca-cli:4:option:1","label":"O domínio marca automaticamente todos os empréstimos pendentes como perdidos.","correct":false,"explanation":"Perder empréstimos automaticamente ao encerrar a CLI não tem relação com o que EOF significa (fim da entrada, não falha do domínio)."},{"id":"mini-biblioteca-cli:4:option:2","label":"A CLI trata o EOF como se fosse a primeira opção do menu, escolhendo-a automaticamente.","correct":false,"explanation":"Tratar EOF como uma escolha implícita de menu esconde o encerramento como se fosse uma ação deliberada do usuário."}],"sourceIndex":16},{"id":"mini-biblioteca-cli-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n    <h2>Execução guiada e evidências</h2>\n    <p>Conclua um incremento de cada vez e mantenha o programa compilável. Nesta etapa do curso, a evidência é um roteiro manual reproduzível; testes automatizados serão introduzidos no capítulo próprio.</p>\n    <h2 class=\"sub\">Matriz de verificação manual</h2>\n    <table class=\"cmp\"><tbody>\n      <tr><th>Caso</th><th>Entrada e preparação</th><th>Resultado esperado</th></tr>\n      <tr><td>empréstimo válido</td><td>usuário e item disponível</td><td>empréstimo registrado e item indisponível</td></tr>\n      <tr><td>empréstimo repetido</td><td>mesmo item ainda emprestado</td><td>operação recusada e estado anterior preservado</td></tr>\n      <tr><td>devolução</td><td>item emprestado</td><td>empréstimo encerrado e item disponível</td></tr>\n      <tr><td>capacidade</td><td>array preenchido até a última posição</td><td>próxima inclusão recusada sem acesso inválido</td></tr>\n      <tr><td>entrada inválida ou EOF</td><td>texto inesperado, vazio ou fim da entrada</td><td>mensagem clara ou encerramento limpo, sem mudar o domínio</td></tr>\n    </tbody></table>\n    <ul class=\"checklist\">\n      <li><input type=\"checkbox\"><span>Para cada caso, registrei preparação, ação, esperado e observado.</span></li>\n      <li><input type=\"checkbox\"><span>Compilei e executei após cada incremento.</span></li>\n      <li><input type=\"checkbox\"><span>Consigo demonstrar a regra sem iniciar a CLI.</span></li>\n      <li><input type=\"checkbox\"><span>Anotei uma limitação consciente e o próximo incremento.</span></li>\n    </ul>\n  </div>","fidelityText":"Execução guiada e evidências Conclua um incremento de cada vez e mantenha o programa compilável. Nesta etapa do curso, a evidência é um roteiro manual reproduzível; testes automatizados serão introduzidos no capítulo próprio. Matriz de verificação manual CasoEntrada e preparaçãoResultado esperado empréstimo válidousuário e item disponívelempréstimo registrado e item indisponível empréstimo repetidomesmo item ainda emprestadooperação recusada e estado anterior preservado devoluçãoitem emprestadoempréstimo encerrado e item disponível capacidadearray preenchido até a última posiçãopróxima inclusão recusada sem acesso inválido entrada inválida ou EOFtexto inesperado, vazio ou fim da entradamensagem clara ou encerramento limpo, sem mudar o domínio Para cada caso, registrei preparação, ação, esperado e observado. Compilei e executei após cada incremento. Consigo demonstrar a regra sem iniciar a CLI. Anotei uma limitação consciente e o próximo incremento."},{"id":"biblioteca-exercise-contrato","type":"exercise","authorship":"authored","title":"Antes de codificar: contrato do domínio da biblioteca","prompt":"Antes de escrever qualquer classe, responda por escrito: (1) qual objeto é responsável por saber se um item está disponível, e por que essa resposta não pode ser 'o menu'; (2) o que garante que um Emprestimo sempre associa exatamente um usuário e um item, nunca zero nem mais de um; (3) o que a Biblioteca faz quando o array de itens ou de empréstimos já está na capacidade máxima; (4) que teste manual comprova que a regra de disponibilidade funciona mesmo chamando os objetos diretamente, sem passar pela CLI.","difficulty":"intermediate","criteria":["A resposta 1 aponta uma classe de domínio -- não o menu nem a CLI -- como dona da invariante de disponibilidade.","A resposta 2 descreve como o construtor (ou método de criação) do Emprestimo impede associação incompleta ou múltipla.","A resposta 3 recusa a operação de forma explícita antes de tocar um índice fora da capacidade declarada.","A resposta 4 descreve um teste que chama métodos do domínio diretamente, sem Scanner nem menu."]},{"id":"biblioteca-project","type":"project","authorship":"authored","title":"Biblioteca orientada a objetos","brief":"Modele itens, usuários e empréstimos em memória, com arrays de capacidade declarada e CLI apenas como fronteira de demonstração.","requirements":["ItemEmprestavel define emprestar, devolver e disponibilidade","Livro e Revista implementam o contrato sem exigir herança artificial","Emprestimo associa um usuário e um item e protege transições","Biblioteca guarda itens e empréstimos em arrays com contadores e capacidade","CLI cadastra, busca, empresta, devolve e encerra por opção ou EOF"],"guidance":"supported","acceptanceCriteria":["Objeto não nasce sem dados obrigatórios","O mesmo item não é emprestado duas vezes","Devolução de item disponível é recusada sem corromper estado","Capacidade cheia é tratada antes de acessar o array","Regra funciona por chamadas diretas sem Scanner ou println","Roteiro registra entrada, esperado e observado"],"knowledgeMatrix":[{"requirement":"Tipos e instâncias","conceptIds":["classe-instancia-objeto","construtor-invariante"],"chapterIds":["classes","construtores"],"expectedEvidence":"Criar dois itens com identidades e estados independentes."},{"requirement":"Estado válido","conceptIds":["encapsulamento-invariante","comando-consulta"],"chapterIds":["atributos","encapsulamento"],"expectedEvidence":"Tentar emprestar duas vezes e comparar estado antes/depois."},{"requirement":"Variação de itens","conceptIds":["interface-contrato","polimorfismo-substituicao"],"chapterIds":["polimorfismo","interfaces"],"expectedEvidence":"Livro e Revista respondem pelo mesmo contrato em um array."},{"requirement":"Relações","conceptIds":["associacao-direcao","cardinalidade-objeto","consistencia-relacao"],"chapterIds":["associacoes-cardinalidade"],"expectedEvidence":"Empréstimo liga exatamente um usuário e um item; item registra sua disponibilidade coerente."},{"requirement":"Armazenamento limitado","conceptIds":["array-indice-length","fronteira-off-by-one"],"chapterIds":["arrays-matrizes","lacos-repeticao"],"expectedEvidence":"Preencher a última posição e recusar uma além da capacidade."},{"requirement":"CLI","conceptIds":["parse-validacao-eof","separacao-io-regra"],"chapterIds":["entrada-console","mini-caixa-eletronico"],"expectedEvidence":"Entrada inválida e EOF não violam as regras do domínio."}]}],"resources":[{"id":"biblioteca-dev-objects","type":"guide","title":"dev.java: criação e uso de objetos","url":"https://dev.java/learn/classes-objects/creating-objects/","reinforces":"Revisa instanciação, referências e chamadas usadas no projeto.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"biblioteca-dev-inheritance","type":"guide","title":"dev.java: herança","url":"https://dev.java/learn/inheritance/","reinforces":"Ajuda a revisar subtipo, sobrescrita e alternativas de modelagem antes de justificar a hierarquia.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The object-oriented library CLI example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"// Observe how names reveal the object-oriented library CLI contract.","instruction":"The object-oriented library CLI example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["Projeto reordenado depois de associações e limitado a arrays, validação e evidência manual."]},"editorialReview":{"requiredTopics":["invariante-de-disponibilidade-no-dominio","polimorfismo-entre-implementacoes-do-contrato","relacao-emprestimo-usuario-item","catalogo-em-arrays-de-capacidade-fixa","fronteira-cli-nao-corrompe-dominio"],"evidenceBlocks":{"invariante-de-disponibilidade-no-dominio":["mini-biblioteca-cli-content-5","mini-biblioteca-cli:0","biblioteca-exercise-contrato"],"polimorfismo-entre-implementacoes-do-contrato":["mini-biblioteca-cli-checklist-7","mini-biblioteca-cli:1","biblioteca-project"],"relacao-emprestimo-usuario-item":["mini-biblioteca-cli-checklist-4","mini-biblioteca-cli:3","biblioteca-project"],"catalogo-em-arrays-de-capacidade-fixa":["mini-biblioteca-cli-checklist-4","mini-biblioteca-cli:2","mini-biblioteca-cli-content-17"],"fronteira-cli-nao-corrompe-dominio":["mini-biblioteca-cli-content-9","mini-biblioteca-cli:4","biblioteca-exercise-contrato"]},"primarySources":["dev.java: criação e uso de objetos -- https://dev.java/learn/classes-objects/creating-objects/","dev.java: herança -- https://dev.java/learn/inheritance/"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"pacotes","moduleId":"java-core","order":0,"title":"Pacotes & organização de projeto","summary":"Quando duas classes possuem o mesmo nome ou dezenas de arquivos precisam colaborar, um único espaço de nomes deixa de ser suficiente. Um pacote agrupa tipos relacionados e participa do nome completo de cada classe. Assim, com.exemplo.biblioteca.modelo.Livro e com.exemplo.loja.modelo.Livro são tipos diferentes.","objectives":["Distinguir pacote, diretório e namespace","Resolver nomes com import e nome qualificado","Compilar e executar múltiplos pacotes com classpath"],"whyItExists":"O projeto de biblioteca já possui classes que colaboram; agora precisa evitar colisões, controlar visibilidade e ser compilado como mais de um arquivo sem depender de Maven ou framework.","prerequisiteChapterIds":["mini-biblioteca-cli"],"conceptIds":["nome-qualificado-e-import","compilacao-de-dois-pacotes-sem-ferramenta-de-build","visibilidade-dentro-do-pacote","convencao-de-nomes"],"introducedConceptIds":["pacote-namespace","import-resolucao","classpath-compilacao"],"usedConceptIds":["controle-acesso","source-bytecode-runtime"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"pacotes-intuition","type":"intuition","authorship":"authored","title":"O nome precisa indicar a qual grupo o tipo pertence","body":"Duas bibliotecas podem declarar Book. O pacote torna o nome completo inequívoco e estabelece uma fronteira de acesso entre grupos de tipos.","analogyLimit":"Pacote lembra um sobrenome, mas não é apenas uma etiqueta: participa da resolução de nomes e da acessibilidade."},{"id":"pacotes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n    <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante–Intermediário</b></div>\n    <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#mini-biblioteca-cli\">Mini-projeto: biblioteca orientada a objetos</a></div>\n  </div>","fidelityText":"Dificuldade: Iniciante–Intermediário Pré-requisito: Mini-projeto: biblioteca orientada a objetos"},{"id":"pacotes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Quando duas classes possuem o mesmo nome ou dezenas de arquivos precisam colaborar, um único espaço de nomes deixa de ser suficiente. Um <strong>pacote</strong> agrupa tipos relacionados e participa do nome completo de cada classe. Assim, <code>com.exemplo.biblioteca.modelo.Livro</code> e <code>com.exemplo.loja.modelo.Livro</code> são tipos diferentes.</p>","fidelityText":"Quando duas classes possuem o mesmo nome ou dezenas de arquivos precisam colaborar, um único espaço de nomes deixa de ser suficiente. Um pacote agrupa tipos relacionados e participa do nome completo de cada classe. Assim, com.exemplo.biblioteca.modelo.Livro e com.exemplo.loja.modelo.Livro são tipos diferentes."},{"id":"pacotes-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"// source file: src/com/example/library/model/Book.java\npackage com.example.library.model;\n\npublic class Book {\n    private final String title;\n    public Book(String title) { this.title = title; }\n    public String title() { return title; }\n}","fidelityText":"// source file: src/com/example/library/model/Book.java package com.example.library.model; public class Book { private final String title; public Book(String title) { this.title = title; } public String title() { return title; } }","highlightedHtml":"<span class=\"com\">// source file: src/com/example/library/model/Book.java</span>\n<span class=\"kw\">package</span> com.example.library.model;\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">String</span> title;\n    <span class=\"kw\">public</span> <span class=\"fn\">Book</span>(<span class=\"cls\">String</span> title) { <span class=\"kw\">this</span>.title = title; }\n    <span class=\"kw\">public</span> <span class=\"cls\">String</span> <span class=\"fn\">title</span>() { <span class=\"kw\">return</span> title; }\n}","caption":"Exemplo executável de pacotes.","explanation":["package passa a compor o nome de Book.","O caminho espelha o pacote por convenção operacional da raiz de fontes."],"commonMistakes":["Compilar de uma raiz incoerente","Colocar duas classes public no mesmo arquivo"]},{"id":"pacotes-content-4","type":"html","authorship":"legacy-preserved","html":"<p>A declaração <code>package</code>, quando existe, aparece antes das declarações de tipos e depois apenas de comentários e espaços. O nome do pacote não é “a pasta” por definição da linguagem; ele pertence ao código-fonte. Na prática, compiladores e ferramentas mapeiam esse nome para diretórios, e manter <code>com.exemplo.biblioteca.modelo</code> em <code>com/exemplo/biblioteca/modelo</code> torna a compilação e a localização previsíveis.</p>","fidelityText":"A declaração package, quando existe, aparece antes das declarações de tipos e depois apenas de comentários e espaços. O nome do pacote não é “a pasta” por definição da linguagem; ele pertence ao código-fonte. Na prática, compiladores e ferramentas mapeiam esse nome para diretórios, e manter com.exemplo.biblioteca.modelo em com/exemplo/biblioteca/modelo torna a compilação e a localização previsíveis."},{"id":"pacotes-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Nome qualificado e import</h2>","fidelityText":"Nome qualificado e import"},{"id":"pacotes-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"package com.example.library.app;\n\nimport com.example.library.model.Book;\n\npublic class Principal {\n    public static void main(String[] args) {\n        Book book = new Book(\"Dune\");\n        System.out.println(book.title());\n    }\n}","fidelityText":"package com.example.library.app; import com.example.library.model.Book; public class Principal { public static void main(String[] args) { Book book = new Book(\"Dune\"); System.out.println(book.title()); } }","highlightedHtml":"<span class=\"kw\">package</span> com.example.library.app;\n\n<span class=\"kw\">import</span> com.example.library.model.Book;\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Principal</span> {\n    <span class=\"kw\">public static void</span> <span class=\"fn\">main</span>(<span class=\"cls\">String</span>[] args) {\n        <span class=\"cls\">Book</span> book = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Dune\"</span>);\n        System.out.println(book.title());\n    }\n}","caption":"Exemplo executável de pacotes.","explanation":["import libera o nome simples Book dentro desta unidade.","A classe continua pertencendo ao pacote model; nada é copiado."],"commonMistakes":["Achar que import inclui subpacotes","Importar dois Book sem qualificar"]},{"id":"pacotes-content-7","type":"html","authorship":"legacy-preserved","html":"<p><code>import</code> permite escrever o nome simples <code>Livro</code>; ele não copia código, não carrega a classe e não inclui subpacotes. <code>import com.exemplo.biblioteca.*</code> importa tipos diretamente daquele pacote, não tipos de <code>modelo</code> ou outros subpacotes. Se dois imports fornecerem o mesmo nome simples, use ao menos um nome totalmente qualificado no código.</p>","fidelityText":"import permite escrever o nome simples Livro; ele não copia código, não carrega a classe e não inclui subpacotes. import com.exemplo.biblioteca.* importa tipos diretamente daquele pacote, não tipos de modelo ou outros subpacotes. Se dois imports fornecerem o mesmo nome simples, use ao menos um nome totalmente qualificado no código."},{"id":"pacotes-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Compilação de dois pacotes sem ferramenta de build</h2>","fidelityText":"Compilação de dois pacotes sem ferramenta de build"},{"id":"pacotes-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"project/\n└── src/\n    └── com/example/library/\n        ├── app/Principal.java\n        └── model/Book.java\n\n# execute dentro de projeto\njavac -d out src/com/example/library/model/Book.java src/com/example/library/app/Principal.java\njava -cp out com.example.library.app.Principal","fidelityText":"projeto/ └── src/ └── com/example/library/ ├── app/Principal.java └── modelo/Livro.java # execute dentro de projeto javac -d out src/com/example/library/model/Book.java src/com/example/library/app/Principal.java java -cp out com.example.library.app.Principal","highlightedHtml":"project/\n└── src/\n    └── com/example/library/\n        ├── app/Principal.java\n        └── model/Book.java\n\n<span class=\"com\"># execute dentro de projeto</span>\njavac -d out src/com/example/library/model/Book.java src/com/example/library/app/Principal.java\njava -cp out com.example.library.app.Principal","caption":"Exemplo executável de pacotes.","explanation":["-d separa saída compilada das fontes.","-cp aponta para a raiz acima de com e o launcher recebe o nome qualificado."],"commonMistakes":["Adicionar com ao classpath","Executar com sufixo .class"]},{"id":"pacotes-content-10","type":"html","authorship":"legacy-preserved","html":"<p><code>-d out</code> pede ao compilador que escreva os arquivos <code>.class</code> na árvore de pacotes dentro de <code>out</code>. <code>-cp out</code> define o <em>classpath</em>: as raízes nas quais o launcher procura nomes de classe. O argumento de <code>java</code> é o nome qualificado da classe, sem <code>.class</code>.</p>","fidelityText":"-d out pede ao compilador que escreva os arquivos .class na árvore de pacotes dentro de out. -cp out define o classpath: as raízes nas quais o launcher procura nomes de classe. O argumento de java é o nome qualificado da classe, sem .class."},{"id":"pacotes-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Visibilidade dentro do pacote</h2>","fidelityText":"Visibilidade dentro do pacote"},{"id":"pacotes-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Um tipo ou membro sem modificador de acesso explícito possui acesso <em>package-private</em>: pode ser usado por código do mesmo pacote, mas não por código de outro pacote. Isso permite ocultar auxiliares de implementação sem torná-los <code>private</code> dentro de outra classe nem expô-los como <code>public</code>. Pacote organiza e controla acesso; ele não substitui a modelagem por responsabilidades.</p>","fidelityText":"Um tipo ou membro sem modificador de acesso explícito possui acesso package-private: pode ser usado por código do mesmo pacote, mas não por código de outro pacote. Isso permite ocultar auxiliares de implementação sem torná-los private dentro de outra classe nem expô-los como public. Pacote organiza e controla acesso; ele não substitui a modelagem por responsabilidades."},{"id":"pacotes-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Convenção de nomes</h2>","fidelityText":"Convenção de nomes"},{"id":"pacotes-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Pacotes costumam usar componentes minúsculos e um domínio invertido, como <code>br.com.empresa.produto</code>, para reduzir colisões. Depois do prefixo estável, escolha nomes pelo domínio ou pela funcionalidade que permanece coesa. Evite criar uma árvore profunda apenas para classificar cada classe individualmente.</p>","fidelityText":"Pacotes costumam usar componentes minúsculos e um domínio invertido, como br.com.empresa.produto, para reduzir colisões. Depois do prefixo estável, escolha nomes pelo domínio ou pela funcionalidade que permanece coesa. Evite criar uma árvore profunda apenas para classificar cada classe individualmente."},{"id":"pacotes-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 18.1 — Separando a biblioteca em pacotes","prompt":"Separe o projeto anterior em com.exemplo.biblioteca.modelo para Livro, Revista, Usuario e Emprestimo; com.exemplo.biblioteca.servico para Biblioteca; e com.exemplo.biblioteca.app para Principal. Não crie ainda pacote de exceções. Compile com javac -d out, execute pelo nome qualificado e prove que uma classe package-private de servico não pode ser instanciada por app.","difficulty":"intermediate","criteria":["Cada arquivo declara o pacote correspondente.","Imports são explícitos e não há wildcard desnecessário.","A saída contém a árvore de pacotes gerada pelo compilador.","A tentativa de acessar o auxiliar package-private falha em compilação e o diagnóstico é registrado."],"fidelityText":"Exercício 18.1 — Separando a biblioteca em pacotesmédio Separe o projeto anterior em com.exemplo.biblioteca.modelo para Livro, Revista, Usuario e Emprestimo; com.exemplo.biblioteca.servico para Biblioteca; e com.exemplo.biblioteca.app para Principal. Não crie ainda pacote de exceções. Compile com javac -d out, execute pelo nome qualificado e prove que uma classe package-private de servico não pode ser instanciada por app. Ver critérios Cada arquivo declara o pacote correspondente.Imports são explícitos e não há wildcard desnecessário.A saída contém a árvore de pacotes gerada pelo compilador.A tentativa de acessar o auxiliar package-private falha em compilação e o diagnóstico é registrado.","sourceHtml":"<div class=\"exercise\">\n    <div class=\"exercise-head\"><h2>Exercício 18.1 — Separando a biblioteca em pacotes</h2><span class=\"exercise-tag m\">médio</span></div>\n    <p>Separe o projeto anterior em <code>com.exemplo.biblioteca.modelo</code> para <code>Livro</code>, <code>Revista</code>, <code>Usuario</code> e <code>Emprestimo</code>; <code>com.exemplo.biblioteca.servico</code> para <code>Biblioteca</code>; e <code>com.exemplo.biblioteca.app</code> para <code>Principal</code>. Não crie ainda pacote de exceções. Compile com <code>javac -d out</code>, execute pelo nome qualificado e prove que uma classe package-private de <code>servico</code> não pode ser instanciada por <code>app</code>.</p>\n    <button class=\"reveal-btn\">Ver critérios</button>\n    <div class=\"solution\"><ul><li>Cada arquivo declara o pacote correspondente.</li><li>Imports são explícitos e não há wildcard desnecessário.</li><li>A saída contém a árvore de pacotes gerada pelo compilador.</li><li>A tentativa de acessar o auxiliar package-private falha em compilação e o diagnóstico é registrado.</li></ul></div>\n  </div>"},{"id":"pacotes-model","type":"mental-model","authorship":"authored","title":"Do source root ao nome qualificado","body":"A ferramenta parte de uma raiz de fontes, encontra a declaração package e grava classes numa raiz de saída. O launcher combina classpath e nome qualificado.","flow":["src é a raiz de fontes","package declara com.example.library.model","javac -d out cria a árvore de saída","java -cp out procura o nome qualificado"],"ownership":["package pertence à unidade de compilação","classpath pertence ao comando/runtime"]},{"id":"pacotes-quiz","type":"quiz","authorship":"authored","conceptId":"classpath-compilacao","prompt":"Book.class está em out/com/example/library/model. Qual execução respeita classpath e nome qualificado?","options":[{"id":"pacotes-q-a","label":"java -cp out com.example.library.model.Book","correct":true,"explanation":"out é a raiz de busca e o argumento identifica a classe pelo nome binário qualificado."},{"id":"pacotes-q-b","label":"java out/com/example/library/model/Book.class","correct":false,"explanation":"O launcher não recebe o caminho do arquivo .class como nome da classe principal."},{"id":"pacotes-q-c","label":"java -cp out Book","correct":false,"explanation":"Book não está no pacote sem nome; falta o nome qualificado."}]}],"resources":[{"id":"pacotes-jls7","type":"reference","title":"JLS 7: Packages and Modules","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-7.html","reinforces":"Define unidades de compilação, package, imports, nomes e acessibilidade.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"pacotes-javac","type":"reference","title":"javac: diretórios, classpath e saída","url":"https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html","reinforces":"Documenta -d, --class-path e compilação de fontes em pacotes.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A packages operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a packages operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this packages chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"// source file: src/com/example/library/model/Book.java","instruction":"A packages operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this packages chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"excecoes","moduleId":"java-core","order":1,"title":"Exceções","summary":"Exceções em Java também são objetos — instâncias de classes que herdam de Throwable. Você pode criar suas próprias exceções especializando Exception, exatamente como especializaria qualquer outra classe.","objectives":["Rastrear propagação e seleção de catch","Distinguir checked e unchecked sem slogans","Encerrar recursos e preservar causas"],"whyItExists":"Falhas já apareceram como parsing inválido e estados recusados. Exceções fornecem transferência explícita de controle e contratos de falha, mas só ajudam quando captura, propagação e cleanup possuem política.","prerequisiteChapterIds":["pacotes"],"conceptIds":["a-hierarquia-completa","try-catch-finally-try-with-resources","encadeamento-de-excecoes-exception-chaining"],"introducedConceptIds":["controle-abrupto-excecao","checked-unchecked-contrato","try-resource-lifecycle","causa-excecao"],"usedConceptIds":["heranca-subtipo","interface-contrato","parse-validacao-eof"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"excecoes-intuition","type":"intuition","authorship":"authored","title":"Uma falha muda o caminho normal","body":"throw interrompe o caminho atual e procura um handler compatível nas chamadas em andamento. Se ninguém tratar, a thread termina com a falha não capturada.","analogyLimit":"Não é um retorno alternativo invisível: construção, finally e recursos possuem regras próprias durante a conclusão abrupta."},{"id":"excecoes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#pacotes\">Pacotes e organização</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: Pacotes e organização"},{"id":"excecoes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Exceções em Java também são <strong>objetos</strong> — instâncias de classes que herdam de <code>Throwable</code>. Você pode criar suas próprias exceções especializando <code>Exception</code>, exatamente como especializaria qualquer outra classe.</p>","fidelityText":"Exceções em Java também são objetos — instâncias de classes que herdam de Throwable. Você pode criar suas próprias exceções especializando Exception, exatamente como especializaria qualquer outra classe."},{"id":"excecoes-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>A hierarquia completa</h2>","fidelityText":"A hierarquia completa"},{"id":"excecoes-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"Throwable\n├── Error                    // falhas que aplicações comuns normalmente não recuperam\n└── Exception\n    ├── RuntimeException      // UNCHECKED -- não exige declaração ou captura\n    │   ├── NullPointerException\n    │   ├── ArrayIndexOutOfBoundsException\n    │   ├── ArithmeticException\n    │   ├── ClassCastException\n    │   └── IllegalArgumentException\n    └── (demais)              // CHECKED -- participa da verificação do compilador","fidelityText":"Throwable ├── Error // falhas que aplicações comuns normalmente não recuperam └── Exception ├── RuntimeException // UNCHECKED -- não exige declaração ou captura │ ├── NullPointerException │ ├── ArrayIndexOutOfBoundsException │ ├── ArithmeticException │ ├── ClassCastException │ └── IllegalArgumentException └── (demais) // CHECKED -- participa da verificação do compilador","highlightedHtml":"Throwable\n├── Error                    <span class=\"com\">// falhas que aplicações comuns normalmente não recuperam</span>\n└── Exception\n    ├── RuntimeException      <span class=\"com\">// UNCHECKED -- não exige declaração ou captura</span>\n    │   ├── NullPointerException\n    │   ├── ArrayIndexOutOfBoundsException\n    │   ├── ArithmeticException\n    │   ├── ClassCastException\n    │   └── IllegalArgumentException\n    └── (demais)              <span class=\"com\">// CHECKED -- participa da verificação do compilador</span>","caption":"Exemplo executável de excecoes.","explanation":["Throwable divide falhas em Error e Exception; RuntimeException e Error são unchecked.","Checked descreve a obrigação do compilador, não recuperabilidade garantida."],"commonMistakes":["Capturar Error como rotina","Chamar toda RuntimeException de bug inevitável"]},{"id":"excecoes-content-5","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Tipo</th><th>Compilador obriga tratar?</th><th>Quando usar</th></tr>\n        <tr><td><strong>Checked</strong> (subclasse de <code>Exception</code>, exceto <code>RuntimeException</code>)</td><td>Sim — capturar ou declarar com <code>throws</code></td><td>O contrato quer obrigar o chamador a considerar a falha</td></tr>\n        <tr><td><strong>Unchecked</strong> (<code>RuntimeException</code> e subclasses)</td><td>Não</td><td>O compilador não obriga tratamento; ainda precisa haver significado, contexto e política coerentes</td></tr>\n      </tbody></table>","fidelityText":"TipoCompilador obriga tratar?Quando usar Checked (subclasse de Exception, exceto RuntimeException)Sim — capturar ou declarar com throwsO contrato quer obrigar o chamador a considerar a falha Unchecked (RuntimeException e subclasses)NãoO compilador não obriga tratamento; ainda precisa haver significado, contexto e política coerentes"},{"id":"excecoes-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>try / catch / finally / try-with-resources</h2>","fidelityText":"try / catch / finally / try-with-resources"},{"id":"excecoes-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"public class InsufficientBalanceException extends Exception {\n    public InsufficientBalanceException(String message) { super(message); }\n}\n\npublic class BankAccount {\n    private double balance;\n\n    public void withdraw(double amount) throws InsufficientBalanceException {\n        if (amount > balance) {\n            throw new InsufficientBalanceException(\"Insufficient balance: \" + amount);\n        }\n        balance -= amount;\n    }\n}\n\ntry {\n    account.withdraw(500);\n} catch (InsufficientBalanceException exception) {\n    System.out.println(\"Error: \" + exception.getMessage());\n} finally {\n    System.out.println(\"Operation finished\"); // runs on normal or abrupt completion of this try\n}","fidelityText":"public class InsufficientBalanceException extends Exception { public InsufficientBalanceException(String message) { super(message); } } public class ContaBancaria { private double balance; public void withdraw(double amount) throws InsufficientBalanceException { if (amount > balance) { throw new InsufficientBalanceException(\"Insufficient balance: \" + amount); } balance -= amount; } } try { account.withdraw(500); } catch (InsufficientBalanceException exception) { System.out.println(\"Error: \" + exception.getMessage()); } finally { System.out.println(\"Operation finished\"); // runs on normal or abrupt completion of this try }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">InsufficientBalanceException</span> <span class=\"kw\">extends</span> <span class=\"cls\">Exception</span> {\n    <span class=\"kw\">public</span> <span class=\"fn\">InsufficientBalanceException</span>(<span class=\"kw\">String</span> message) { <span class=\"kw\">super</span>(message); }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">BankAccount</span> {\n    <span class=\"kw\">private double</span> balance;\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">withdraw</span>(<span class=\"kw\">double</span> amount) <span class=\"kw\">throws</span> <span class=\"cls\">InsufficientBalanceException</span> {\n        <span class=\"kw\">if</span> (amount &gt; balance) {\n            <span class=\"kw\">throw new</span> <span class=\"cls\">InsufficientBalanceException</span>(<span class=\"str\">\"Insufficient balance: \"</span> + amount);\n        }\n        balance -= amount;\n    }\n}\n\n<span class=\"kw\">try</span> {\n    account.withdraw(500);\n} <span class=\"kw\">catch</span> (<span class=\"cls\">InsufficientBalanceException</span> exception) {\n    System.out.println(<span class=\"str\">\"Error: \"</span> + exception.getMessage());\n} <span class=\"kw\">finally</span> {\n    System.out.println(<span class=\"str\">\"Operation finished\"</span>); <span class=\"com\">// runs on normal or abrupt completion of this try</span>\n}","caption":"Exemplo executável de excecoes.","explanation":["throw cria o objeto de falha e throws declara o contrato checked.","finally normalmente observa a saída do try/catch, mas não deve retornar ou esconder a falha."],"commonMistakes":["Continuar alterando saldo após throw","Capturar e fingir sucesso"]},{"id":"excecoes-content-8","type":"html","authorship":"legacy-preserved","html":"<p><code>throw</code> cria uma conclusão abrupta e transfere o controle; <code>throws</code> declara parte do contrato de um método. <code>finally</code> normalmente executa ao sair do <code>try</code>/<code>catch</code>, inclusive com <code>return</code> ou nova exceção, mas não é uma garantia contra término da JVM ou falha do processo. Não coloque em <code>finally</code> um <code>return</code> que esconda o resultado ou a exceção original.</p>","fidelityText":"throw cria uma conclusão abrupta e transfere o controle; throws declara parte do contrato de um método. finally normalmente executa ao sair do try/catch, inclusive com return ou nova exceção, mas não é uma garantia contra término da JVM ou falha do processo. Não coloque em finally um return que esconda o resultado ou a exceção original."},{"id":"excecoes-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Um objeto que implementa <code>AutoCloseable</code> representa um recurso cujo ciclo de vida precisa terminar. O <strong>try-with-resources</strong> chama <code>close()</code> em ordem inversa à abertura, inclusive quando o corpo falha:</p>","fidelityText":"Um objeto que implementa AutoCloseable representa um recurso cujo ciclo de vida precisa terminar. O try-with-resources chama close() em ordem inversa à abertura, inclusive quando o corpo falha:"},{"id":"excecoes-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"final class Session implements AutoCloseable {\n    void execute() { System.out.println(\"using\"); }\n    @Override public void close() { System.out.println(\"closing\"); }\n}\n\ntry (Session session = new Session()) {\n    session.execute();\n} // close() is called automatically","fidelityText":"final class Session implements AutoCloseable { void execute() { System.out.println(\"using\"); } @Override public void close() { System.out.println(\"closing\"); } } try (Session session = new Session()) { session.execute(); } // close() is called automatically","highlightedHtml":"<span class=\"kw\">final class</span> <span class=\"cls\">Session</span> <span class=\"kw\">implements</span> <span class=\"cls\">AutoCloseable</span> {\n    <span class=\"kw\">void</span> <span class=\"fn\">execute</span>() { System.out.println(<span class=\"str\">\"using\"</span>); }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">close</span>() { System.out.println(<span class=\"str\">\"closing\"</span>); }\n}\n\n<span class=\"kw\">try</span> (<span class=\"cls\">Session</span> session = <span class=\"kw\">new</span> <span class=\"cls\">Session</span>()) {\n    session.execute();\n} <span class=\"com\">// close() is called automatically</span>","caption":"Exemplo executável de excecoes.","explanation":["Session cumpre AutoCloseable e o recurso é fechado ao sair do bloco.","Recursos múltiplos seriam fechados na ordem inversa da criação."],"commonMistakes":["Achar que GC chama close no momento necessário","Reutilizar recurso já fechado"]},{"id":"excecoes-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Encadeamento de exceções (exception chaining)</h2>","fidelityText":"Encadeamento de exceções (exception chaining)"},{"id":"excecoes-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Ao relançar uma exceção como outro tipo, preserve a causa original — perder a stack trace original é um dos erros mais frustrantes de depurar em produção.</p>","fidelityText":"Ao relançar uma exceção como outro tipo, preserve a causa original — perder a stack trace original é um dos erros mais frustrantes de depurar em produção."},{"id":"excecoes-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"try {\n    int age = Integer.parseInt(text);\n} catch (NumberFormatException cause) {\n    throw new IllegalArgumentException(\"age must be an integer\", cause);\n}\n// getCause() preserves the original conversion failure","fidelityText":"try { int age = Integer.parseInt(text); } catch (NumberFormatException cause) { throw new IllegalArgumentException(\"age must be an integer\", cause); } // getCause() preserves the original conversion failure","highlightedHtml":"<span class=\"kw\">try</span> {\n    <span class=\"kw\">int</span> age = Integer.parseInt(text);\n} <span class=\"kw\">catch</span> (<span class=\"cls\">NumberFormatException</span> cause) {\n    <span class=\"kw\">throw new</span> <span class=\"cls\">IllegalArgumentException</span>(<span class=\"str\">\"age must be an integer\"</span>, cause);\n}\n<span class=\"com\">// getCause() preserves the original conversion failure</span>","caption":"Exemplo executável de excecoes.","explanation":["A fronteira converte texto e traduz a falha com contexto de idade.","cause preserva a NumberFormatException original."],"commonMistakes":["Lançar nova exceção sem causa","Registrar e relançar em todas as camadas duplicando diagnóstico"]},{"id":"excecoes-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Um <code>catch (Exception e) {}</code> vazio engole a falha: o chamador recebe aparência de sucesso sem resultado válido nem diagnóstico. Quando a política for ignorar uma condição específica, capture o tipo específico e documente por que a ausência de ação é correta.</div>","fidelityText":"Um catch (Exception e) {} vazio engole a falha: o chamador recebe aparência de sucesso sem resultado válido nem diagnóstico. Quando a política for ignorar uma condição específica, capture o tipo específico e documente por que a ausência de ação é correta."},{"id":"excecoes-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Capture somente o que consegue tratar.</b> Um <code>catch</code> amplo pode ser legítimo numa fronteira que transforma falhas em um resultado final, mas não deve fingir recuperação nem capturar <code>Throwable</code> indiscriminadamente. Dentro da regra de negócio, tipos específicos deixam explícita a falha considerada.</div>","fidelityText":"Capture somente o que consegue tratar. Um catch amplo pode ser legítimo numa fronteira que transforma falhas em um resultado final, mas não deve fingir recuperação nem capturar Throwable indiscriminadamente. Dentro da regra de negócio, tipos específicos deixam explícita a falha considerada."},{"id":"excecoes-exercise-16","type":"exercise","authorship":"legacy-preserved","title":"Exercício 10.1 — Idade inválida","prompt":"Crie IdadeInvalidaException (unchecked). No construtor de Pessoa, lance-a se a idade for negativa ou > 130. Teste em um try/catch.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 10.1 — Idade inválidamédio Crie IdadeInvalidaException (unchecked). No construtor de Pessoa, lance-a se a idade for negativa ou > 130. Teste em um try/catch. Ver solução public class IdadeInvalidaException extends RuntimeException { public IdadeInvalidaException(String msg) { super(msg); } } public class Pessoa { private String nome; private int idade; public Pessoa(String nome, int idade) { if (idade < 0 || idade > 130) throw new IdadeInvalidaException(\"Idade inválida: \" + idade); this.nome = nome; this.idade = idade; } } try { new Pessoa(\"Felipy\", 200); } catch (IdadeInvalidaException e) { System.out.println(\"Erro: \" + e.getMessage()); }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 10.1 — Idade inválida</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie <code>IdadeInvalidaException</code> (unchecked). No construtor de <code>Pessoa</code>, lance-a se a idade for negativa ou &gt; 130. Teste em um <code>try/catch</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">AgeInvalidException</span> <span class=\"kw\">extends</span> <span class=\"cls\">RuntimeException</span> {\n    <span class=\"kw\">public</span> <span class=\"fn\">AgeInvalidException</span>(<span class=\"kw\">String</span> msg) { <span class=\"kw\">super</span>(msg); }\n}\n<span class=\"kw\">public class</span> <span class=\"cls\">Person</span> {\n    <span class=\"kw\">private String</span> name;\n    <span class=\"kw\">private int</span> age;\n    <span class=\"kw\">public</span> <span class=\"fn\">Person</span>(<span class=\"kw\">String</span> name, <span class=\"kw\">int</span> age) {\n        <span class=\"kw\">if</span> (age &lt; 0 || age &gt; 130) <span class=\"kw\">throw new</span> <span class=\"cls\">AgeInvalidException</span>(<span class=\"str\">\"invalid age: \"</span> + age);\n        <span class=\"kw\">this</span>.name = name; <span class=\"kw\">this</span>.age = age;\n    }\n}\n<span class=\"kw\">try</span> {\n    <span class=\"kw\">new</span> <span class=\"cls\">Person</span>(<span class=\"str\">\"Felipy\"</span>, 200);\n} <span class=\"kw\">catch</span> (<span class=\"cls\">AgeInvalidException</span> e) {\n    System.out.println(<span class=\"str\">\"Error: \"</span> + e.getMessage());\n}</pre>\n        </div>\n      </div>"},{"id":"excecoes-exercise-17","type":"exercise","authorship":"legacy-preserved","title":"Exercício 10.2 — Hierarquia de exceções de domínio","prompt":"Crie uma exceção base checked PedidoException e duas subclasses, EstoqueInsuficienteException e PagamentoRecusadoException. Escreva um método finalizarPedido(int qtd, boolean pagamentoOk) que lança a exceção adequada, e um main com um único bloco try e dois blocos catch específicos (um para cada subtipo), mais um catch (PedidoException e) genérico por último como rede de segurança.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 10.2 — Hierarquia de exceções de domíniodifícil Crie uma exceção base checked PedidoException e duas subclasses, EstoqueInsuficienteException e PagamentoRecusadoException. Escreva um método finalizarPedido(int qtd, boolean pagamentoOk) que lança a exceção adequada, e um main com um único bloco try e dois blocos catch específicos (um para cada subtipo), mais um catch (PedidoException e) genérico por último como rede de segurança. Ver solução public class PedidoException extends Exception { public PedidoException(String msg) { super(msg); } } public class EstoqueInsuficienteException extends PedidoException { public EstoqueInsuficienteException(String msg) { super(msg); } } public class PagamentoRecusadoException extends PedidoException { public PagamentoRecusadoException(String msg) { super(msg); } } static void finalizarPedido(int qtd, boolean pagamentoOk) throws PedidoException { if (qtd > 10) throw new EstoqueInsuficienteException(\"Estoque insuficiente\"); if (!pagamentoOk) throw new PagamentoRecusadoException(\"Pagamento recusado\"); System.out.println(\"Pedido finalizado!\"); } // main: try { finalizarPedido(20, true); } catch (EstoqueInsuficienteException e) { System.out.println(\"Sem estoque: \" + e.getMessage()); } catch (PagamentoRecusadoException e) { System.out.println(\"Pagamento falhou: \" + e.getMessage()); } catch (PedidoException e) { System.out.println(\"Erro genérico de pedido: \" + e.getMessage()); } Repare que os catch mais específicos precisam vir antes do genérico — Java não compila se um catch de superclasse aparecer antes de um de subclasse, porque o primeiro nunca deixaria o segundo ser alcançado.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 10.2 — Hierarquia de exceções de domínio</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie uma exceção base checked <code>PedidoException</code> e duas subclasses, <code>EstoqueInsuficienteException</code> e <code>PagamentoRecusadoException</code>. Escreva um método <code>finalizarPedido(int qtd, boolean pagamentoOk)</code> que lança a exceção adequada, e um <code>main</code> com um único bloco <code>try</code> e <strong>dois</strong> blocos <code>catch</code> específicos (um para cada subtipo), mais um <code>catch (PedidoException e)</code> genérico por último como rede de segurança.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">OrderException</span> <span class=\"kw\">extends</span> <span class=\"cls\">Exception</span> {\n    <span class=\"kw\">public</span> <span class=\"fn\">OrderException</span>(<span class=\"kw\">String</span> msg) { <span class=\"kw\">super</span>(msg); }\n}\n<span class=\"kw\">public class</span> <span class=\"cls\">InventoryInsufficientException</span> <span class=\"kw\">extends</span> <span class=\"cls\">OrderException</span> {\n    <span class=\"kw\">public</span> <span class=\"fn\">InventoryInsufficientException</span>(<span class=\"kw\">String</span> msg) { <span class=\"kw\">super</span>(msg); }\n}\n<span class=\"kw\">public class</span> <span class=\"cls\">PaymentDeclinedException</span> <span class=\"kw\">extends</span> <span class=\"cls\">OrderException</span> {\n    <span class=\"kw\">public</span> <span class=\"fn\">PaymentDeclinedException</span>(<span class=\"kw\">String</span> msg) { <span class=\"kw\">super</span>(msg); }\n}\n\n<span class=\"kw\">static void</span> <span class=\"fn\">completeOrder</span>(<span class=\"kw\">int</span> quantity, <span class=\"kw\">boolean</span> paymentOk) <span class=\"kw\">throws</span> <span class=\"cls\">OrderException</span> {\n    <span class=\"kw\">if</span> (quantity &gt; 10) <span class=\"kw\">throw new</span> <span class=\"cls\">InventoryInsufficientException</span>(<span class=\"str\">\"insufficient inventory\"</span>);\n    <span class=\"kw\">if</span> (!paymentOk) <span class=\"kw\">throw new</span> <span class=\"cls\">PaymentDeclinedException</span>(<span class=\"str\">\"payment declined\"</span>);\n    System.out.println(<span class=\"str\">\"Order completed!\"</span>);\n}\n\n<span class=\"com\">// main:</span>\n<span class=\"kw\">try</span> {\n    completeOrder(20, <span class=\"kw\">true</span>);\n} <span class=\"kw\">catch</span> (<span class=\"cls\">InventoryInsufficientException</span> e) {\n    System.out.println(<span class=\"str\">\"Without inventory: \"</span> + e.getMessage());\n} <span class=\"kw\">catch</span> (<span class=\"cls\">PaymentDeclinedException</span> e) {\n    System.out.println(<span class=\"str\">\"Payment failed: \"</span> + e.getMessage());\n} <span class=\"kw\">catch</span> (<span class=\"cls\">OrderException</span> e) {\n    System.out.println(<span class=\"str\">\"Error generic of order: \"</span> + e.getMessage());\n}</pre>\n          <p style=\"margin-top:12px\">Repare que os <code>catch</code> mais específicos precisam vir <strong>antes</strong> do genérico — Java não compila se um <code>catch</code> de superclasse aparecer antes de um de subclasse, porque o primeiro nunca deixaria o segundo ser alcançado.</p>\n        </div>\n      </div>"},{"id":"excecoes-comparison","type":"comparison","authorship":"authored","title":"Capturar, declarar ou traduzir","criteria":["responsabilidade","informação preservada","efeito"],"alternatives":[{"name":"capturar","values":["há recuperação local","tipo específico","continua com resultado válido"],"useWhen":"o método consegue cumprir seu contrato apesar da falha","avoidWhen":"só esconderia o problema"},{"name":"declarar","values":["chamador decide","causa intacta","método não conclui normalmente"],"useWhen":"a política pertence a uma camada acima","avoidWhen":"vaza detalhe sem significado"},{"name":"traduzir","values":["muda abstração","preserva cause","adiciona contexto"],"useWhen":"a falha técnica precisa virar contrato da fronteira","avoidWhen":"a causa seria descartada"}]},{"id":"excecoes-quiz","type":"quiz","authorship":"authored","conceptId":"try-resource-lifecycle","prompt":"O corpo do try e close() lançam exceções. Qual comportamento preserva as duas falhas no try-with-resources?","options":[{"id":"excecoes-q-a","label":"A falha do corpo é propagada e a de close fica como suppressed.","correct":true,"explanation":"O mecanismo mantém a falha principal e associa as falhas de fechamento para diagnóstico."},{"id":"excecoes-q-b","label":"A falha de close sempre substitui silenciosamente a do corpo.","correct":false,"explanation":"Essa perda pode ocorrer em cleanup manual mal escrito, não é o contrato do try-with-resources."},{"id":"excecoes-q-c","label":"As duas são ignoradas porque close é automático.","correct":false,"explanation":"Automático descreve a chamada de close, não supressão de falhas."}]}],"resources":[{"id":"excecoes-jls11","type":"reference","title":"JLS 11: Exceptions","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-11.html","reinforces":"Define tipos, causas, verificação e busca de handlers.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"excecoes-jls-try","type":"reference","title":"JLS 14.20: try statements","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-14.20","reinforces":"Especifica catch, finally e try-with-resources.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A exceptions operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a exceptions operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this exceptions chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"Throwable","instruction":"A exceptions operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this exceptions chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"colecoes","moduleId":"java-core","order":2,"title":"Coleções & ArrayList","summary":"Arrays em Java têm tamanho fixo. Na prática, o dia a dia pede estruturas que crescem e encolhem — é para isso que existe o Java Collections Framework. As três interfaces mais usadas são List, Set e Map.","objectives":["Escolher List, Set ou Map por contrato","Implementar igualdade e hash coerentes","Distinguir cópia, visão e mutabilidade","Ordenar sem antecipar lambdas"],"whyItExists":"Arrays tornaram capacidade e índices visíveis; aplicações também precisam de tamanho dinâmico, unicidade, busca por chave e ordenações. A estrutura só funciona corretamente quando igualdade, mutabilidade e ordem estão declaradas.","prerequisiteChapterIds":["excecoes"],"conceptIds":["a-hierarquia-por-tras-das-tres-interfaces","list-e-arraylist","igualdade-e-hashing-antes-de-set-e-map","set-sem-duplicatas","queue-e-deque-filas-em-duas-variacoes","map-pares-chave-valor","comparable-vs-comparator-dois-jeitos-de-ordenar"],"introducedConceptIds":["contrato-collection-map","list-set-map-semantica","equals-hashcode-contrato","chave-hash-estavel","ordem-comparable-comparator","copia-visao-imutabilidade"],"usedConceptIds":["array-indice-length","interface-contrato","identidade-conteudo","imutabilidade-objeto"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"colecoes-intuition","type":"intuition","authorship":"authored","title":"Escolha pela pergunta feita aos dados","body":"List responde posição e sequência; Set responde pertencimento sem duplicatas; Map localiza valor por chave. A implementação vem depois do contrato.","analogyLimit":"Os nomes sugerem usos, mas ordem, null, custo e mutabilidade dependem do contrato e da implementação escolhida."},{"id":"colecoes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#excecoes\">10 · Exceções</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: 10 · Exceções"},{"id":"colecoes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Arrays em Java têm tamanho fixo. Na prática, o dia a dia pede estruturas que crescem e encolhem — é para isso que existe o <strong>Java Collections Framework</strong>. As três interfaces mais usadas são <code>List</code>, <code>Set</code> e <code>Map</code>.</p>","fidelityText":"Arrays em Java têm tamanho fixo. Na prática, o dia a dia pede estruturas que crescem e encolhem — é para isso que existe o Java Collections Framework. As três interfaces mais usadas são List, Set e Map."},{"id":"colecoes-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>A hierarquia por trás das três interfaces</h2>","fidelityText":"A hierarquia por trás das três interfaces"},{"id":"colecoes-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"Iterable<E>                       // só garante \"posso percorrer com for-each\"\n  └── Collection<E>                // add, remove, size, contains -- contrato comum\n        ├── List<E>                // ordem de inserção + acesso por índice + duplicatas permitidas\n        ├── Set<E>                 // sem duplicatas -- ordem depende da implementação\n        └── Queue<E>                // fila -- foco em inserir de um lado e remover do outro\n              └── Deque<E>          // fila de duas pontas -- pilha ou fila, dependendo do uso\n\nMap<K,V>                           // NÃO estende Collection -- guarda pares chave/valor, não elementos soltos","fidelityText":"Iterable<E> // só garante \"posso percorrer com for-each\" └── Collection<E> // add, remove, size, contains -- contrato comum ├── List<E> // ordem de inserção + acesso por índice + duplicatas permitidas ├── Set<E> // sem duplicatas -- ordem depende da implementação └── Queue<E> // fila -- foco em inserir de um lado e remover do outro └── Deque<E> // fila de duas pontas -- pilha ou fila, dependendo do uso Map<K,V> // NÃO estende Collection -- guarda pares chave/valor, não elementos soltos","highlightedHtml":"Iterable&lt;E&gt;                       <span class=\"com\">// só garante \"posso percorrer com for-each\"</span>\n  └── Collection&lt;E&gt;                <span class=\"com\">// add, remove, size, contains -- contrato comum</span>\n        ├── List&lt;E&gt;                <span class=\"com\">// ordem de inserção + acesso por índice + duplicatas permitidas</span>\n        ├── Set&lt;E&gt;                 <span class=\"com\">// sem duplicatas -- ordem depende da implementação</span>\n        └── Queue&lt;E&gt;                <span class=\"com\">// fila -- foco em inserir de um lado e remover do outro</span>\n              └── Deque&lt;E&gt;          <span class=\"com\">// fila de duas pontas -- pilha ou fila, dependendo do uso</span>\n\nMap&lt;K,V&gt;                           <span class=\"com\">// NÃO estende Collection -- guarda pares chave/valor, não elementos soltos</span>","caption":"Exemplo executável de colecoes.","explanation":["Collection é o contrato comum de List/Set/Queue; Iterable só garante for-each; Map é separado por guardar pares, não elementos soltos."]},{"id":"colecoes-content-5","type":"html","authorship":"legacy-preserved","html":"<p><code>List</code>, <code>Set</code> e <code>Queue</code> compartilham o contrato de <code>Collection</code> (que por sua vez só promete ser <code>Iterable</code>, ou seja, percorrível em um <code>for-each</code>). <code>Map</code> é deliberadamente <strong>separado</strong> dessa hierarquia — ele não guarda \"elementos\", guarda associações chave→valor, e por isso não tem <code>add</code> nem é percorrido diretamente em um <code>for-each</code> (você percorre <code>map.entrySet()</code>, <code>map.keySet()</code> ou <code>map.values()</code>, que <em>essas</em> sim são <code>Collection</code>).</p>","fidelityText":"List, Set e Queue compartilham o contrato de Collection (que por sua vez só promete ser Iterable, ou seja, percorrível em um for-each). Map é deliberadamente separado dessa hierarquia — ele não guarda \"elementos\", guarda associações chave→valor, e por isso não tem add nem é percorrido diretamente em um for-each (você percorre map.entrySet(), map.keySet() ou map.values(), que essas sim são Collection)."},{"id":"colecoes-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>List e ArrayList</h2>","fidelityText":"List e ArrayList"},{"id":"colecoes-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"List<String> names = new ArrayList<>(); // programe contra a interface, instancie a implementação\nnames.add(\"Ana\");\nnames.add(\"Bruno\");\nnames.add(0, \"Zeca\");      // insere na posição 0\nnames.remove(\"Bruno\");       // remove pelo valor\nnames.remove(0);            // remove pelo índice -- CUIDADO com ambiguidade int vs Integer!\nSystem.out.println(names.get(0));\nSystem.out.println(names.size());\nSystem.out.println(names.contains(\"Ana\"));\n\nfor (String name : names) System.out.println(name); // for-each","fidelityText":"List<String> nomes = new ArrayList<>(); // programe contra a interface, instancie a implementação nomes.add(\"Ana\"); nomes.add(\"Bruno\"); nomes.add(0, \"Zeca\"); // insere na posição 0 nomes.remove(\"Bruno\"); // remove pelo valor nomes.remove(0); // remove pelo índice -- CUIDADO com ambiguidade int vs Integer! System.out.println(nomes.get(0)); System.out.println(nomes.size()); System.out.println(nomes.contains(\"Ana\")); for (String nome : nomes) System.out.println(nome); // for-each","highlightedHtml":"List&lt;<span class=\"kw\">String</span>&gt; names = <span class=\"kw\">new</span> ArrayList&lt;&gt;(); <span class=\"com\">// programe contra a interface, instancie a implementação</span>\nnames.add(<span class=\"str\">\"Ana\"</span>);\nnames.add(<span class=\"str\">\"Bruno\"</span>);\nnames.add(0, <span class=\"str\">\"Zeca\"</span>);      <span class=\"com\">// insere na posição 0</span>\nnames.remove(<span class=\"str\">\"Bruno\"</span>);       <span class=\"com\">// remove pelo valor</span>\nnames.remove(0);            <span class=\"com\">// remove pelo índice -- CUIDADO com ambiguidade int vs Integer!</span>\nSystem.out.println(names.get(0));\nSystem.out.println(names.size());\nSystem.out.println(names.contains(<span class=\"str\">\"Ana\"</span>));\n\n<span class=\"kw\">for</span> (<span class=\"kw\">String</span> name : names) System.out.println(name); <span class=\"com\">// for-each</span>","caption":"Exemplo executável de colecoes.","explanation":["List declara sequência indexada; ArrayList é a implementação escolhida.","size conta elementos e não expõe a capacidade interna."],"commonMistakes":["Confundir remove por índice e valor","Acessar size como último índice"]},{"id":"colecoes-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Pegadinha clássica:</b> <code>lista.remove(1)</code> remove o elemento no <strong>índice</strong> 1 se a lista é <code>List&lt;Integer&gt;</code> — mas para remover o <em>valor</em> <code>1</code>, você precisa de <code>lista.remove(Integer.valueOf(1))</code>, forçando autoboxing. Essa ambiguidade entre <code>remove(int)</code> e <code>remove(Object)</code> já causou bugs sutis em produção.</div>","fidelityText":"Pegadinha clássica: lista.remove(1) remove o elemento no índice 1 se a lista é List<Integer> — mas para remover o valor 1, você precisa de lista.remove(Integer.valueOf(1)), forçando autoboxing. Essa ambiguidade entre remove(int) e remove(Object) já causou bugs sutis em produção."},{"id":"colecoes-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Igualdade e hashing antes de Set e Map</h2>","fidelityText":"Igualdade e hashing antes de Set e Map"},{"id":"colecoes-content-10","type":"html","authorship":"legacy-preserved","html":"<p><code>List.contains</code>, <code>HashSet</code> e as chaves de <code>HashMap</code> precisam decidir quando dois objetos representam o mesmo valor lógico. O contrato mínimo é: igualdade reflexiva, simétrica, transitiva, consistente e falsa para <code>null</code>. Além disso, objetos iguais por <code>equals</code> devem produzir o mesmo <code>hashCode</code>. Objetos diferentes podem compartilhar um hash; a coleção confirma a igualdade depois.</p>","fidelityText":"List.contains, HashSet e as chaves de HashMap precisam decidir quando dois objetos representam o mesmo valor lógico. O contrato mínimo é: igualdade reflexiva, simétrica, transitiva, consistente e falsa para null. Além disso, objetos iguais por equals devem produzir o mesmo hashCode. Objetos diferentes podem compartilhar um hash; a coleção confirma a igualdade depois."},{"id":"colecoes-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"public final class Product {\n    private final String code;\n\n    public Product(String code) { this.code = code; }\n\n    @Override public boolean equals(Object other) {\n        if (this == other) return true;\n        if (!(other instanceof Product)) return false;\n        Product product = (Product) other;\n        return code.equals(product.code);\n    }\n\n    @Override public int hashCode() { return code.hashCode(); }\n}","fidelityText":"public final class Produto { private final String codigo; public Produto(String codigo) { this.codigo = codigo; } @Override public boolean equals(Object outro) { if (this == outro) return true; if (!(outro instanceof Produto)) return false; Produto produto = (Produto) outro; return codigo.equals(produto.codigo); } @Override public int hashCode() { return codigo.hashCode(); } }","highlightedHtml":"<span class=\"kw\">public final class</span> <span class=\"cls\">Product</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">String</span> code;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Product</span>(<span class=\"cls\">String</span> code) { <span class=\"kw\">this</span>.code = code; }\n\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public boolean</span> <span class=\"fn\">equals</span>(<span class=\"cls\">Object</span> other) {\n        <span class=\"kw\">if</span> (<span class=\"kw\">this</span> == other) <span class=\"kw\">return true</span>;\n        <span class=\"kw\">if</span> (!(other <span class=\"kw\">instanceof</span> <span class=\"cls\">Product</span>)) <span class=\"kw\">return false</span>;\n        <span class=\"cls\">Product</span> product = (<span class=\"cls\">Product</span>) other;\n        <span class=\"kw\">return</span> code.equals(product.code);\n    }\n\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public int</span> <span class=\"fn\">hashCode</span>() { <span class=\"kw\">return</span> code.hashCode(); }\n}","caption":"Exemplo executável de colecoes.","explanation":["Produto define igualdade lógica somente pelo código estável.","hashCode usa a mesma informação de equals; colisões continuam permitidas."],"commonMistakes":["Sobrescrever apenas equals","Usar campo mutável na identidade"]},{"id":"colecoes-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não altere campos usados por <code>equals</code>/<code>hashCode</code> enquanto o objeto for chave ou elemento de coleção baseada em hash.</b> A estrutura escolheu uma região com o hash antigo; depois da mutação, uma busca pode não encontrar o próprio objeto. Prefira uma identidade lógica estável e imutável.</div>","fidelityText":"Não altere campos usados por equals/hashCode enquanto o objeto for chave ou elemento de coleção baseada em hash. A estrutura escolheu uma região com o hash antigo; depois da mutação, uma busca pode não encontrar o próprio objeto. Prefira uma identidade lógica estável e imutável."},{"id":"colecoes-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Set — sem duplicatas</h2>","fidelityText":"Set — sem duplicatas"},{"id":"colecoes-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"Set<String> tags = new HashSet<>();\ntags.add(\"java\"); tags.add(\"java\"); // segunda chamada é ignorada\nSystem.out.println(tags.size()); // 1\n\nSet<String> sorted = new TreeSet<>(tags);   // mantém ordem natural (alfabética)\nSet<String> insertion = new LinkedHashSet<>(tags); // mantém ordem de inserção","fidelityText":"Set<String> tags = new HashSet<>(); tags.add(\"java\"); tags.add(\"java\"); // segunda chamada é ignorada System.out.println(tags.size()); // 1 Set<String> ordenado = new TreeSet<>(tags); // mantém ordem natural (alfabética) Set<String> insercao = new LinkedHashSet<>(tags); // mantém ordem de inserção","highlightedHtml":"Set&lt;<span class=\"kw\">String</span>&gt; tags = <span class=\"kw\">new</span> HashSet&lt;&gt;();\ntags.add(<span class=\"str\">\"java\"</span>); tags.add(<span class=\"str\">\"java\"</span>); <span class=\"com\">// segunda chamada é ignorada</span>\nSystem.out.println(tags.size()); <span class=\"com\">// 1</span>\n\nSet&lt;<span class=\"kw\">String</span>&gt; sorted = <span class=\"kw\">new</span> TreeSet&lt;&gt;(tags);   <span class=\"com\">// mantém ordem natural (alfabética)</span>\nSet&lt;<span class=\"kw\">String</span>&gt; insertion = <span class=\"kw\">new</span> LinkedHashSet&lt;&gt;(tags); <span class=\"com\">// mantém ordem de inserção</span>","caption":"Exemplo executável de colecoes.","explanation":["HashSet não garante ordem; TreeSet usa ordem e LinkedHashSet preserva inserção.","add devolve false quando o contrato considera o elemento duplicado."],"commonMistakes":["Confiar na ordem observada de HashSet","Confundir comparação zero com identidade sem decidir o contrato"]},{"id":"colecoes-content-15","type":"html","authorship":"legacy-preserved","html":"<p><code>HashSet</code> usa o contrato de igualdade para impedir duplicatas; <code>TreeSet</code> usa comparação. Se a comparação retornar zero para objetos que <code>equals</code> considera diferentes, os dois conjuntos podem discordar sobre o que é duplicado. Defina conscientemente a noção de identidade de cada estrutura.</p>","fidelityText":"HashSet usa o contrato de igualdade para impedir duplicatas; TreeSet usa comparação. Se a comparação retornar zero para objetos que equals considera diferentes, os dois conjuntos podem discordar sobre o que é duplicado. Defina conscientemente a noção de identidade de cada estrutura."},{"id":"colecoes-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>Queue e Deque — filas em duas variações</h2>","fidelityText":"Queue e Deque — filas em duas variações"},{"id":"colecoes-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"Queue<String> queue = new LinkedList<>(); // LinkedList implementa tanto List quanto Deque\nqueue.offer(\"first\");  // insere no fim -- offer não lança exceção se falhar (retorna false)\nqueue.offer(\"second\");\nSystem.out.println(queue.peek());  // \"primeiro\" -- consulta a cabeça SEM remover; null se vazia\nSystem.out.println(queue.poll());  // \"primeiro\" -- remove e devolve a cabeça; null se vazia (não lança exceção)\n\nDeque<String> stack = new ArrayDeque<>();\nstack.push(\"A\"); stack.push(\"B\");  // push insere no início\nSystem.out.println(stack.pop());          // \"B\" -- pop remove do início -- comportamento de PILHA (LIFO)","fidelityText":"Queue<String> fila = new LinkedList<>(); // LinkedList implementa tanto List quanto Deque fila.offer(\"primeiro\"); // insere no fim -- offer não lança exceção se falhar (retorna false) fila.offer(\"segundo\"); System.out.println(fila.peek()); // \"primeiro\" -- consulta a cabeça SEM remover; null se vazia System.out.println(fila.poll()); // \"primeiro\" -- remove e devolve a cabeça; null se vazia (não lança exceção) Deque<String> pilha = new ArrayDeque<>(); pilha.push(\"A\"); pilha.push(\"B\"); // push insere no início System.out.println(pilha.pop()); // \"B\" -- pop remove do início -- comportamento de PILHA (LIFO)","highlightedHtml":"Queue&lt;<span class=\"kw\">String</span>&gt; queue = <span class=\"kw\">new</span> LinkedList&lt;&gt;(); <span class=\"com\">// LinkedList implementa tanto List quanto Deque</span>\nqueue.offer(<span class=\"str\">\"first\"</span>);  <span class=\"com\">// insere no fim -- offer não lança exceção se falhar (retorna false)</span>\nqueue.offer(<span class=\"str\">\"second\"</span>);\nSystem.out.println(queue.peek());  <span class=\"com\">// \"primeiro\" -- consulta a cabeça SEM remover; null se vazia</span>\nSystem.out.println(queue.poll());  <span class=\"com\">// \"primeiro\" -- remove e devolve a cabeça; null se vazia (não lança exceção)</span>\n\nDeque&lt;<span class=\"kw\">String</span>&gt; stack = <span class=\"kw\">new</span> ArrayDeque&lt;&gt;();\nstack.push(<span class=\"str\">\"A\"</span>); stack.push(<span class=\"str\">\"B\"</span>);  <span class=\"com\">// push insere no início</span>\nSystem.out.println(stack.pop());          <span class=\"com\">// \"B\" -- pop remove do início -- comportamento de PILHA (LIFO)</span>","caption":"Exemplo executável de colecoes.","explanation":["offer/poll/peek devolvem valor de sinalização (false/null) em vez de lançar exceção -- mais previsíveis que add/remove/element.","Deque insere e remove dos dois lados, servindo tanto como fila (FIFO) quanto pilha (LIFO com push/pop)."],"commonMistakes":["Usar poll()/peek() esperando exceção em vez de null quando vazia","Preferir a classe legada Stack em vez de ArrayDeque"]},{"id":"colecoes-content-18","type":"html","authorship":"legacy-preserved","html":"<p><code>Queue</code> modela FIFO (o primeiro que entra é o primeiro que sai) — <code>offer</code>/<code>poll</code>/<code>peek</code> devolvem um valor de sinalização (<code>false</code>/<code>null</code>) em vez de lançar exceção quando a operação não é possível, o que os torna mais previsíveis que os métodos \"estritos\" equivalentes (<code>add</code>/<code>remove</code>/<code>element</code>, que lançam exceção). <code>Deque</code> (\"double-ended queue\") permite inserir e remover dos <strong>dois</strong> lados — por isso serve tanto como fila quanto como pilha (LIFO, com <code>push</code>/<code>pop</code>). <code>ArrayDeque</code> costuma ser a escolha padrão para pilha/fila em memória; evite usar a classe legada <code>Stack</code> (sincronizada, mais lenta, e tecnicamente uma subclasse de <code>Vector</code> por razões históricas).</p>","fidelityText":"Queue modela FIFO (o primeiro que entra é o primeiro que sai) — offer/poll/peek devolvem um valor de sinalização (false/null) em vez de lançar exceção quando a operação não é possível, o que os torna mais previsíveis que os métodos \"estritos\" equivalentes (add/remove/element, que lançam exceção). Deque (\"double-ended queue\") permite inserir e remover dos dois lados — por isso serve tanto como fila quanto como pilha (LIFO, com push/pop). ArrayDeque costuma ser a escolha padrão para pilha/fila em memória; evite usar a classe legada Stack (sincronizada, mais lenta, e tecnicamente uma subclasse de Vector por razões históricas)."},{"id":"colecoes-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Map — pares chave/valor</h2>","fidelityText":"Map — pares chave/valor"},{"id":"colecoes-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"Map<String, Double> prices = new HashMap<>();\nprices.put(\"coffee\", 8.5);\nprices.put(\"tea\", 6.0);\nprices.put(\"coffee\", 9.0); // sobrescreve o valor anterior da mesma chave\n\nSystem.out.println(prices.get(\"coffee\"));           // 9.0\nSystem.out.println(prices.getOrDefault(\"juice\", 0.0)); // 0.0 -- não lança exceção\n\nfor (Map.Entry<String, Double> e : prices.entrySet()) {\n    System.out.println(e.getKey() + \" -> \" + e.getValue());\n}","fidelityText":"Map<String, Double> precos = new HashMap<>(); precos.put(\"café\", 8.5); precos.put(\"chá\", 6.0); precos.put(\"café\", 9.0); // sobrescreve o valor anterior da mesma chave System.out.println(precos.get(\"café\")); // 9.0 System.out.println(precos.getOrDefault(\"suco\", 0.0)); // 0.0 -- não lança exceção for (Map.Entry<String, Double> e : precos.entrySet()) { System.out.println(e.getKey() + \" -> \" + e.getValue()); }","highlightedHtml":"Map&lt;<span class=\"kw\">String</span>, <span class=\"kw\">Double</span>&gt; prices = <span class=\"kw\">new</span> HashMap&lt;&gt;();\nprices.put(<span class=\"str\">\"coffee\"</span>, 8.5);\nprices.put(<span class=\"str\">\"tea\"</span>, 6.0);\nprices.put(<span class=\"str\">\"coffee\"</span>, 9.0); <span class=\"com\">// sobrescreve o valor anterior da mesma chave</span>\n\nSystem.out.println(prices.get(<span class=\"str\">\"coffee\"</span>));           <span class=\"com\">// 9.0</span>\nSystem.out.println(prices.getOrDefault(<span class=\"str\">\"juice\"</span>, 0.0)); <span class=\"com\">// 0.0 -- não lança exceção</span>\n\n<span class=\"kw\">for</span> (Map.Entry&lt;<span class=\"kw\">String</span>, <span class=\"kw\">Double</span>&gt; e : prices.entrySet()) {\n    System.out.println(e.getKey() + <span class=\"str\">\" -&gt; \"</span> + e.getValue());\n}","caption":"Exemplo executável de colecoes.","explanation":["put associa chave e valor e substitui o valor da mesma chave.","getOrDefault evita NullPointerException/lógica extra para chave ausente."],"commonMistakes":["Usar get para distinguir ausência quando null é valor permitido","Mutar uma chave após put"]},{"id":"colecoes-content-21","type":"html","authorship":"legacy-preserved","html":"<h2>Comparable vs Comparator — dois jeitos de ordenar</h2>","fidelityText":"Comparable vs Comparator — dois jeitos de ordenar"},{"id":"colecoes-code-22","type":"code","authorship":"legacy-preserved","language":"java","source":"public class ComparableProduct implements Comparable<ComparableProduct> {\n    String name; double price;\n\n    @Override\n    public int compareTo(ComparableProduct other) { // natural order of the type\n        return Double.compare(this.price, other.price);\n    }\n}\nCollections.sort(products); // usa compareTo\n\n// Comparator pode declarar uma ordem alternativa fora da classe\nproducts.sort(new Comparator<ComparableProduct>() {\n    @Override public int compare(ComparableProduct a, ComparableProduct b) {\n        return a.name.compareTo(b.name);\n    }\n});","fidelityText":"public class ComparableProduct implements Comparable<ComparableProduct> { String nome; double preco; @Override public int compareTo(ComparableProduct outro) { // natural order of the type return Double.compare(this.preco, outro.preco); } } Collections.sort(produtos); // usa compareTo // Comparator pode declarar uma ordem alternativa fora da classe produtos.sort(new Comparator<ComparableProduct>() { @Override public int compare(ComparableProduct a, ComparableProduct b) { return a.nome.compareTo(b.nome); } });","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">ComparableProduct</span> <span class=\"kw\">implements</span> Comparable&lt;<span class=\"cls\">ComparableProduct</span>&gt; {\n    <span class=\"kw\">String</span> name; <span class=\"kw\">double</span> price;\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public int</span> <span class=\"fn\">compareTo</span>(<span class=\"cls\">ComparableProduct</span> other) { <span class=\"com\">// natural order of the type</span>\n        <span class=\"kw\">return</span> Double.compare(<span class=\"kw\">this</span>.price, other.price);\n    }\n}\nCollections.sort(products); <span class=\"com\">// usa compareTo</span>\n\n<span class=\"com\">// Comparator pode declarar uma ordem alternativa fora da classe</span>\nproducts.sort(<span class=\"kw\">new</span> Comparator&lt;<span class=\"cls\">ComparableProduct</span>&gt;() {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public int</span> <span class=\"fn\">compare</span>(<span class=\"cls\">ComparableProduct</span> a, <span class=\"cls\">ComparableProduct</span> b) {\n        <span class=\"kw\">return</span> a.name.compareTo(b.name);\n    }\n});","caption":"Exemplo executável de colecoes.","explanation":["Comparable declara uma ordem natural; Comparator fornece uma ordem contextual, alternativa e reaproveitável fora da classe.","Double.compare evita erros de subtração, NaN e sinal."],"commonMistakes":["Retornar apenas -1 ou 1 e nunca zero","Usar subtração sujeita a overflow"]},{"id":"colecoes-content-23","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Use <code>Comparable</code> quando existe uma ordem \"óbvia e única\" para o tipo (números por valor, datas cronologicamente). Use <code>Comparator</code> quando precisar de várias formas de ordenar o mesmo tipo dependendo do contexto — e prefira <code>Comparator</code> mesmo assim quando a ordenação for uma decisão de \"quem está usando a lista\", não do objeto em si, para não acoplar a classe a uma única forma de comparação.</div>","fidelityText":"Use Comparable quando existe uma ordem \"óbvia e única\" para o tipo (números por valor, datas cronologicamente). Use Comparator quando precisar de várias formas de ordenar o mesmo tipo dependendo do contexto — e prefira Comparator mesmo assim quando a ordenação for uma decisão de \"quem está usando a lista\", não do objeto em si, para não acoplar a classe a uma única forma de comparação."},{"id":"colecoes-content-24","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Implementação</th><th>Ordem</th><th>Permite duplicata?</th><th>Permite null?</th><th>Acesso por índice</th><th>Inserir/remover no meio</th></tr>\n        <tr><td><code>ArrayList</code></td><td>Inserção</td><td>Sim</td><td>Sim</td><td>O(1)</td><td>O(n) -- desloca elementos</td></tr>\n        <tr><td><code>LinkedList</code></td><td>Inserção</td><td>Sim</td><td>Sim</td><td>O(n) -- percorre nó a nó</td><td>O(1) uma vez já no nó, O(n) para achar o nó</td></tr>\n        <tr><td><code>HashSet</code></td><td>Nenhuma garantida</td><td>Não</td><td>Um null</td><td>—</td><td>O(1) amortizado</td></tr>\n        <tr><td><code>TreeSet</code></td><td>Ordenada</td><td>Não</td><td>NÃO permite null (NPE)</td><td>—</td><td>O(log n)</td></tr>\n        <tr><td><code>HashMap</code></td><td>Nenhuma garantida</td><td>Chaves não</td><td>Uma chave null; valores null OK</td><td>—</td><td>O(1) amortizado</td></tr>\n        <tr><td><code>TreeMap</code></td><td>Ordenada por chave</td><td>Chaves não</td><td>NÃO permite chave null (NPE)</td><td>—</td><td>O(log n)</td></tr>\n        <tr><td><code>LinkedHashMap</code></td><td>Inserção</td><td>Chaves não</td><td>Uma chave null; valores null OK</td><td>—</td><td>O(1) amortizado</td></tr>\n      </tbody></table>","fidelityText":"ImplementaçãoOrdemPermite duplicata?Permite null?Acesso por índiceInserir/remover no meio ArrayListInserçãoSimSimO(1)O(n) -- desloca elementos LinkedListInserçãoSimSimO(n) -- percorre nó a nóO(1) uma vez já no nó, O(n) para achar o nó HashSetNenhuma garantidaNãoUm null—O(1) amortizado TreeSetOrdenadaNãoNÃO permite null (NPE)—O(log n) HashMapNenhuma garantidaChaves nãoUma chave null; valores null OK—O(1) amortizado TreeMapOrdenada por chaveChaves nãoNÃO permite chave null (NPE)—O(log n) LinkedHashMapInserçãoChaves nãoUma chave null; valores null OK—O(1) amortizado"},{"id":"colecoes-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>O mito de que \"LinkedList é melhor para inserir/remover\"</b> só é verdade se você <em>já está</em> no nó certo (via <code>ListIterator</code>) — na prática, para inserir/remover em uma posição por <em>índice</em>, o custo de <strong>percorrer</strong> até lá em uma <code>LinkedList</code> é O(n), exatamente como deslocar elementos em uma <code>ArrayList</code>. Somado a isso, <code>ArrayList</code> tem muito melhor localidade de cache (os elementos ficam contíguos em memória), o que na prática a torna mais rápida que <code>LinkedList</code> na maioria dos casos reais, mesmo em inserção/remoção — o cenário onde <code>LinkedList</code> genuinamente vence é quando você já navega com um <code>Iterator</code>/<code>ListIterator</code> e insere/remove ali mesmo, sem re-percorrer. Na dúvida, <code>ArrayList</code> é o padrão certo; meça antes de trocar por <code>LinkedList</code> alegando performance.</div>","fidelityText":"O mito de que \"LinkedList é melhor para inserir/remover\" só é verdade se você já está no nó certo (via ListIterator) — na prática, para inserir/remover em uma posição por índice, o custo de percorrer até lá em uma LinkedList é O(n), exatamente como deslocar elementos em uma ArrayList. Somado a isso, ArrayList tem muito melhor localidade de cache (os elementos ficam contíguos em memória), o que na prática a torna mais rápida que LinkedList na maioria dos casos reais, mesmo em inserção/remoção — o cenário onde LinkedList genuinamente vence é quando você já navega com um Iterator/ListIterator e insere/remove ali mesmo, sem re-percorrer. Na dúvida, ArrayList é o padrão certo; meça antes de trocar por LinkedList alegando performance."},{"id":"colecoes-content-26","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Políticas de <code>null</code> divergem entre implementações — e isso quebra código na troca de uma pela outra.</b> <code>ArrayList</code>/<code>HashMap</code> aceitam <code>null</code> tranquilamente; <code>TreeSet</code>/<code>TreeMap</code> lançam <code>NullPointerException</code> ao tentar inserir <code>null</code>, porque precisam comparar o valor para posicioná-lo e não há \"menor ou maior que null\" definido. Trocar silenciosamente um <code>HashMap</code> por um <code>TreeMap</code> (por exemplo, para ganhar ordenação) pode quebrar em produção exatamente no primeiro <code>null</code> que aparecer.</div>","fidelityText":"Políticas de null divergem entre implementações — e isso quebra código na troca de uma pela outra. ArrayList/HashMap aceitam null tranquilamente; TreeSet/TreeMap lançam NullPointerException ao tentar inserir null, porque precisam comparar o valor para posicioná-lo e não há \"menor ou maior que null\" definido. Trocar silenciosamente um HashMap por um TreeMap (por exemplo, para ganhar ordenação) pode quebrar em produção exatamente no primeiro null que aparecer."},{"id":"colecoes-content-27","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Modificável não é o mesmo que profundamente imutável.</b> <code>List.of(...)</code> e <code>List.copyOf(...)</code> não permitem adicionar, remover ou substituir posições, mas os objetos armazenados ainda podem mudar se forem mutáveis. <code>Collections.unmodifiableList(lista)</code> cria uma visão: mudanças feitas na lista original continuam visíveis nela. Além disso, as fábricas <code>List.of(...)</code>/<code>Set.of(...)</code>/<code>Map.of(...)</code> <strong>não aceitam <code>null</code></strong> como elemento, chave ou valor — lançam <code>NullPointerException</code> na criação, diferente de <code>ArrayList</code>/<code>HashMap</code> mutáveis.</div>","fidelityText":"Modificável não é o mesmo que profundamente imutável. List.of(...) e List.copyOf(...) não permitem adicionar, remover ou substituir posições, mas os objetos armazenados ainda podem mudar se forem mutáveis. Collections.unmodifiableList(lista) cria uma visão: mudanças feitas na lista original continuam visíveis nela. Além disso, as fábricas List.of(...)/Set.of(...)/Map.of(...) não aceitam null como elemento, chave ou valor — lançam NullPointerException na criação, diferente de ArrayList/HashMap mutáveis."},{"id":"colecoes-exercise-28","type":"exercise","authorship":"legacy-preserved","title":"Exercício 11.1 — Carrinho de compras","prompt":"Use um Map<String, Integer> para representar um carrinho (produto → quantidade). Escreva um método adicionar(Map<String,Integer> carrinho, String produto, int qtd) que soma à quantidade existente se o produto já estiver no carrinho, ou insere se for novo (dica: getOrDefault).","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 11.1 — Carrinho de comprasfácil Use um Map<String, Integer> para representar um carrinho (produto → quantidade). Escreva um método adicionar(Map<String,Integer> carrinho, String produto, int qtd) que soma à quantidade existente se o produto já estiver no carrinho, ou insere se for novo (dica: getOrDefault). Ver solução static void adicionar(Map<String, Integer> carrinho, String produto, int qtd) { carrinho.put(produto, carrinho.getOrDefault(produto, 0) + qtd); } // main: Map<String, Integer> carrinho = new HashMap<>(); adicionar(carrinho, \"café\", 2); adicionar(carrinho, \"café\", 1); System.out.println(carrinho.get(\"café\")); // 3","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 11.1 — Carrinho de compras</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Use um <code>Map&lt;String, Integer&gt;</code> para representar um carrinho (produto → quantidade). Escreva um método <code>adicionar(Map&lt;String,Integer&gt; carrinho, String produto, int qtd)</code> que soma à quantidade existente se o produto já estiver no carrinho, ou insere se for novo (dica: <code>getOrDefault</code>).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">static void</span> <span class=\"fn\">add</span>(Map&lt;<span class=\"kw\">String</span>, <span class=\"kw\">Integer</span>&gt; cart, <span class=\"kw\">String</span> product, <span class=\"kw\">int</span> quantity) {\n    cart.put(product, cart.getOrDefault(product, 0) + quantity);\n}\n<span class=\"com\">// main:</span>\nMap&lt;<span class=\"kw\">String</span>, <span class=\"kw\">Integer</span>&gt; cart = <span class=\"kw\">new</span> HashMap&lt;&gt;();\nadd(cart, <span class=\"str\">\"coffee\"</span>, 2);\nadd(cart, <span class=\"str\">\"coffee\"</span>, 1);\nSystem.out.println(cart.get(<span class=\"str\">\"coffee\"</span>)); <span class=\"com\">// 3</span></pre>\n        </div>\n      </div>"},{"id":"colecoes-exercise-29","type":"exercise","authorship":"legacy-preserved","title":"Exercício 11.2 — Ordenando com Comparator","prompt":"Usando a classe Livro (título, autor, páginas), crie uma List<Livro> com pelo menos 4 livros. Escreva um Comparator<Livro> por páginas com classe anônima. Depois escreva outro por autor e, em caso de empate, por título. Lambdas serão apresentadas no capítulo próprio.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 11.2 — Ordenando com Comparatormédio Usando a classe Livro (título, autor, páginas), crie uma List<Livro> com pelo menos 4 livros. Escreva um Comparator<Livro> por páginas com classe anônima. Depois escreva outro por autor e, em caso de empate, por título. Lambdas serão apresentadas no capítulo próprio. Ver solução Comparator<Livro> porPaginas = new Comparator<>() { @Override public int compare(Livro a, Livro b) { return Integer.compare(a.getPaginas(), b.getPaginas()); } }; livros.sort(porPaginas); Comparator<Livro> porAutorETitulo = new Comparator<>() { @Override public int compare(Livro a, Livro b) { int autor = a.getAutor().compareTo(b.getAutor()); return autor != 0 ? autor : a.getTitulo().compareTo(b.getTitulo()); } };","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 11.2 — Ordenando com Comparator</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Usando a classe <code>Livro</code> (título, autor, páginas), crie uma <code>List&lt;Livro&gt;</code> com pelo menos 4 livros. Escreva um <code>Comparator&lt;Livro&gt;</code> por páginas com classe anônima. Depois escreva outro por autor e, em caso de empate, por título. Lambdas serão apresentadas no capítulo próprio.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">Comparator&lt;<span class=\"cls\">Book</span>&gt; byPages = <span class=\"kw\">new</span> Comparator&lt;&gt;() {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public int</span> <span class=\"fn\">compare</span>(<span class=\"cls\">Book</span> a, <span class=\"cls\">Book</span> b) {\n        <span class=\"kw\">return</span> Integer.compare(a.getPages(), b.getPages());\n    }\n};\nbooks.sort(byPages);\n\nComparator&lt;<span class=\"cls\">Book</span>&gt; byAuthorAndTitle = <span class=\"kw\">new</span> Comparator&lt;&gt;() {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public int</span> <span class=\"fn\">compare</span>(<span class=\"cls\">Book</span> a, <span class=\"cls\">Book</span> b) {\n        <span class=\"kw\">int</span> author = a.getAuthor().compareTo(b.getAuthor());\n        <span class=\"kw\">return</span> author != 0 ? author : a.getTitle().compareTo(b.getTitle());\n    }\n};</pre>\n        </div>\n      </div>"},{"id":"colecoes-exercise-30","type":"exercise","authorship":"legacy-preserved","title":"Exercício 11.3 — ConcurrentModificationException","prompt":"Escreva um for-each que tenta remover elementos pares de uma List<Integer> diretamente dentro do laço (lista.remove(...)). Rode e observe a exceção. Depois corrija usando um Iterator explícito com iterator.remove().","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 11.3 — ConcurrentModificationExceptiondifícil Escreva um for-each que tenta remover elementos pares de uma List<Integer> diretamente dentro do laço (lista.remove(...)). Rode e observe a exceção. Depois corrija usando um Iterator explícito com iterator.remove(). Ver solução // ❌ lança ConcurrentModificationException: List<Integer> numeros = new ArrayList<>(List.of(1,2,3,4,5,6)); for (Integer n : numeros) { if (n % 2 == 0) numeros.remove(n); // modifica a lista durante a iteração } // ✅ correto -- Iterator sabe que a remoção está acontecendo: Iterator<Integer> it = numeros.iterator(); while (it.hasNext()) { if (it.next() % 2 == 0) it.remove(); } O for-each usa um Iterator por baixo dos panos. Muitas coleções implementam detecção fail-fast de alterações estruturais por meio de um contador interno, frequentemente chamado modCount. Alterar a coleção fora do próprio iterator pode invalidar essa contagem e lançar a exceção. Isso ajuda a revelar uso incorreto, mas não oferece sincronização nem segurança entre threads.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 11.3 — ConcurrentModificationException</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Escreva um <code>for-each</code> que tenta remover elementos pares de uma <code>List&lt;Integer&gt;</code> diretamente dentro do laço (<code>lista.remove(...)</code>). Rode e observe a exceção. Depois corrija usando um <code>Iterator</code> explícito com <code>iterator.remove()</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"com\">// ❌ lança ConcurrentModificationException:</span>\nList&lt;<span class=\"kw\">Integer</span>&gt; numbers = <span class=\"kw\">new</span> ArrayList&lt;&gt;(List.of(1,2,3,4,5,6));\n<span class=\"kw\">for</span> (<span class=\"kw\">Integer</span> n : numbers) {\n    <span class=\"kw\">if</span> (n % 2 == 0) numbers.remove(n); <span class=\"com\">// modifica a lista durante a iteração</span>\n}\n\n<span class=\"com\">// ✅ correto -- Iterator sabe que a remoção está acontecendo:</span>\nIterator&lt;<span class=\"kw\">Integer</span>&gt; it = numbers.iterator();\n<span class=\"kw\">while</span> (it.hasNext()) {\n    <span class=\"kw\">if</span> (it.next() % 2 == 0) it.remove();\n}</pre>\n          <p style=\"margin-top:12px\">O for-each usa um <code>Iterator</code> por baixo dos panos. Muitas coleções implementam detecção <em>fail-fast</em> de alterações estruturais por meio de um contador interno, frequentemente chamado <code>modCount</code>. Alterar a coleção fora do próprio iterator pode invalidar essa contagem e lançar a exceção. Isso ajuda a revelar uso incorreto, mas não oferece sincronização nem segurança entre threads.</p>\n        </div>\n      </div>"},{"id":"colecoes-exercise-31","type":"exercise","authorship":"legacy-preserved","title":"Exercício 11.4 — Fila de atendimento com Deque","prompt":"Modele uma fila de atendimento com Deque<String> (não Queue puro): clientes normais entram no fim com offerLast, mas um cliente prioritário deve furar a fila com offerFirst. Atenda sempre pela frente com pollFirst. Teste: fila vazia (o que pollFirst devolve?), um cliente prioritário chegando no meio do atendimento, e a fila esvaziando por completo.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 11.4 — Fila de atendimento com Dequemédio Modele uma fila de atendimento com Deque<String> (não Queue puro): clientes normais entram no fim com offerLast, mas um cliente prioritário deve furar a fila com offerFirst. Atenda sempre pela frente com pollFirst. Teste: fila vazia (o que pollFirst devolve?), um cliente prioritário chegando no meio do atendimento, e a fila esvaziando por completo. Ver solução Deque<String> atendimento = new ArrayDeque<>(); atendimento.offerLast(\"Ana\"); atendimento.offerLast(\"Bruno\"); atendimento.offerFirst(\"Prioridade: Carla\"); // fura a fila System.out.println(atendimento.pollFirst()); // \"Prioridade: Carla\" System.out.println(atendimento.pollFirst()); // \"Ana\" System.out.println(atendimento.pollFirst()); // \"Bruno\" System.out.println(atendimento.pollFirst()); // null -- fila vazia, pollFirst não lança exceção","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 11.4 — Fila de atendimento com Deque</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Modele uma fila de atendimento com <code>Deque&lt;String&gt;</code> (não <code>Queue</code> puro): clientes normais entram no fim com <code>offerLast</code>, mas um cliente prioritário deve furar a fila com <code>offerFirst</code>. Atenda sempre pela frente com <code>pollFirst</code>. Teste: fila vazia (o que <code>pollFirst</code> devolve?), um cliente prioritário chegando no meio do atendimento, e a fila esvaziando por completo.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">Deque&lt;<span class=\"kw\">String</span>&gt; atendimento = <span class=\"kw\">new</span> ArrayDeque&lt;&gt;();\natendimento.offerLast(<span class=\"str\">\"Ana\"</span>);\natendimento.offerLast(<span class=\"str\">\"Bruno\"</span>);\natendimento.offerFirst(<span class=\"str\">\"Prioridade: Carla\"</span>); <span class=\"com\">// fura a fila</span>\n\nSystem.out.println(atendimento.pollFirst()); <span class=\"com\">// \"Prioridade: Carla\"</span>\nSystem.out.println(atendimento.pollFirst()); <span class=\"com\">// \"Ana\"</span>\nSystem.out.println(atendimento.pollFirst()); <span class=\"com\">// \"Bruno\"</span>\nSystem.out.println(atendimento.pollFirst()); <span class=\"com\">// null -- fila vazia, pollFirst não lança exceção</span></pre>\n        </div>\n      </div>"},{"id":"colecoes-table","type":"table","authorship":"authored","title":"Pergunta antes da estrutura","headers":["Pergunta","Contrato inicial","Risco a testar"],"rows":[["qual elemento está no índice?","List","limites e remoção int/Integer"],["este valor já existe?","Set","equals/hashCode ou comparação"],["qual valor pertence a esta chave?","Map","chave mutável e substituição"],["preciso impedir mudanças estruturais?","copyOf/of","elementos internos ainda podem mudar"]],"caption":"Complexidade será formalizada no módulo de algoritmos; aqui a escolha começa pela semântica."},{"id":"colecoes-quiz","type":"quiz","authorship":"authored","conceptId":"chave-hash-estavel","prompt":"Produto usa código mutável em equals/hashCode e o código muda após entrar num HashSet. O que pode ocorrer?","options":[{"id":"colecoes-q-a","label":"contains pode não encontrá-lo porque a busca usa o novo hash, mas a posição foi escolhida pelo antigo.","correct":true,"explanation":"Mudar a identidade lógica quebra a estabilidade exigida enquanto o elemento permanece na estrutura hash."},{"id":"colecoes-q-b","label":"HashSet recalcula e move automaticamente todos os elementos mutados.","correct":false,"explanation":"A coleção não observa alterações arbitrárias nos campos dos objetos."},{"id":"colecoes-q-c","label":"Nada, porque equals nunca participa de HashSet.","correct":false,"explanation":"HashSet usa hash para localizar candidatos e equals para confirmar igualdade."}]}],"resources":[{"id":"colecoes-dev","type":"guide","title":"dev.java: The Collections Framework","url":"https://dev.java/learn/api/collections-framework/","reinforces":"Organiza interfaces, implementações, iteração, factories, listas, sets e maps.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"colecoes-object","type":"reference","title":"Object: equals e hashCode","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html","reinforces":"Define os contratos oficiais de igualdade e hash usados pelas coleções.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A collections operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a collections operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this collections chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"Iterable<E>                       // só garante \"posso percorrer com for-each\"","instruction":"A collections operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this collections chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"generics","moduleId":"java-core","order":3,"title":"Generics","summary":"Você já usou generics em List<String>. Agora vamos escrever suas próprias classes e métodos genéricos — código que funciona com qualquer tipo, mantendo segurança de tipo em tempo de compilação.","objectives":["Modelar tipos parametrizados sem raw types","Declarar classes, métodos e bounds","Escolher wildcards pelo fluxo de dados","Explicar erasure sem slogans"],"whyItExists":"Collections mostrou List<String> como proteção de elementos. Generics explica como essa proteção é declarada, por que List<Integer> não vira List<Number> e quais limites permanecem em runtime.","prerequisiteChapterIds":["colecoes"],"conceptIds":["por-que-generics-existem","classe-generica-propria","metodo-generico","bounded-types-limitando-quais-tipos-sao-aceitos","generics-sao-invariantes-list-object-nao-e-list-string","multiplos-bounds","varargs-genericos-e-heap-pollution","por-que-new-t-e-new-t-10-nao-compilam","wildcards-extends-e-super"],"introducedConceptIds":["tipo-parametrizado","declaracao-generica-bound","wildcard-variancia","erasure-reificacao"],"usedConceptIds":["contrato-collection-map","heranca-subtipo","variavel-tipo-estatico"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"generics-intuition","type":"intuition","authorship":"authored","title":"O compilador acompanha o tipo que entra e sai","body":"Caixa<T> não guarda apenas Object: cada uso escolhe um contrato, como Caixa<String>, e o compilador impede inserir Integer nesse mesmo uso.","analogyLimit":"T não cria uma classe nova por argumento nem permanece totalmente disponível em runtime; erasure e tipos reificáveis limitam certas operações."},{"id":"generics-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#colecoes\">11 · Coleções &amp; ArrayList</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 11 · Coleções & ArrayList"},{"id":"generics-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Você já usou generics em <code>List&lt;String&gt;</code>. Agora vamos escrever <em>suas próprias</em> classes e métodos genéricos — código que funciona com qualquer tipo, mantendo segurança de tipo em tempo de compilação.</p>","fidelityText":"Você já usou generics em List<String>. Agora vamos escrever suas próprias classes e métodos genéricos — código que funciona com qualquer tipo, mantendo segurança de tipo em tempo de compilação."},{"id":"generics-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Por que generics existem</h2>","fidelityText":"Por que generics existem"},{"id":"generics-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"// SEM generics (como era antes do Java 5): Object genérico, cast manual, perigoso\nList list = new ArrayList();\nlist.add(\"text\");\nlist.add(42); // compila! nada impede misturar tipos\nString s = (String) list.get(1); // ClassCastException em RUNTIME, só descoberto ao rodar\n\n// COM generics: o compilador barra o erro antes de rodar\nList<String> list2 = new ArrayList<>();\nlist2.add(\"text\");\n// lista2.add(42); // ❌ erro de COMPILAÇÃO -- muito mais barato que descobrir em produção","fidelityText":"// SEM generics (como era antes do Java 5): Object genérico, cast manual, perigoso List lista = new ArrayList(); lista.add(\"texto\"); lista.add(42); // compila! nada impede misturar tipos String s = (String) lista.get(1); // ClassCastException em RUNTIME, só descoberto ao rodar // COM generics: o compilador barra o erro antes de rodar List<String> lista2 = new ArrayList<>(); lista2.add(\"texto\"); // lista2.add(42); // ❌ erro de COMPILAÇÃO -- muito mais barato que descobrir em produção","highlightedHtml":"<span class=\"com\">// SEM generics (como era antes do Java 5): Object genérico, cast manual, perigoso</span>\nList list = <span class=\"kw\">new</span> ArrayList();\nlist.add(<span class=\"str\">\"text\"</span>);\nlist.add(42); <span class=\"com\">// compila! nada impede misturar tipos</span>\n<span class=\"kw\">String</span> s = (<span class=\"kw\">String</span>) list.get(1); <span class=\"com\">// ClassCastException em RUNTIME, só descoberto ao rodar</span>\n\n<span class=\"com\">// COM generics: o compilador barra o erro antes de rodar</span>\nList&lt;<span class=\"kw\">String</span>&gt; list2 = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\nlist2.add(<span class=\"str\">\"text\"</span>);\n<span class=\"com\">// lista2.add(42); // ❌ erro de COMPILAÇÃO -- muito mais barato que descobrir em produção</span>","caption":"Exemplo executável de generics.","explanation":["Raw List perde verificação do elemento e adia a falha para o cast.","List<String> transporta o contrato até add e get."],"commonMistakes":["Ignorar warning unchecked","Usar Object e casts para simular generic"]},{"id":"generics-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Classe genérica própria</h2>","fidelityText":"Classe genérica própria"},{"id":"generics-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Atm<T> { // T = \"type parameter\", convenção de nome de uma letra\n    private T content;\n\n    public void guardar(T item) { this.content = item; }\n    public T open() { return content; }\n}\n\nAtm<String> atmOfText = new Atm<>();\natmOfText.guardar(\"secret\");\nString value = atmOfText.open(); // sem cast necessário!","fidelityText":"public class Caixa<T> { // T = \"type parameter\", convenção de nome de uma letra private T conteudo; public void guardar(T item) { this.conteudo = item; } public T abrir() { return conteudo; } } Caixa<String> caixaDeTexto = new Caixa<>(); caixaDeTexto.guardar(\"segredo\"); String valor = caixaDeTexto.abrir(); // sem cast necessário!","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Atm</span>&lt;T&gt; { <span class=\"com\">// T = \"type parameter\", convenção de nome de uma letra</span>\n    <span class=\"kw\">private</span> T content;\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">guardar</span>(T item) { <span class=\"kw\">this</span>.content = item; }\n    <span class=\"kw\">public</span> T <span class=\"fn\">open</span>() { <span class=\"kw\">return</span> content; }\n}\n\n<span class=\"cls\">Atm</span>&lt;<span class=\"kw\">String</span>&gt; atmOfText = <span class=\"kw\">new</span> <span class=\"cls\">Atm</span>&lt;&gt;();\natmOfText.guardar(<span class=\"str\">\"secret\"</span>);\n<span class=\"kw\">String</span> value = atmOfText.open(); <span class=\"com\">// sem cast necessário!</span>","caption":"Exemplo executável de generics.","explanation":["T conecta o tipo aceito por guardar ao retornado por abrir.","O diamond infere String a partir do lado esquerdo."],"commonMistakes":["Usar T sem declará-lo","Criar um type parameter quando um tipo concreto basta"]},{"id":"generics-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Método genérico</h2>","fidelityText":"Método genérico"},{"id":"generics-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"public static <T> T first(List<T> list) {\n    return list.isEmpty() ? null : list.get(0);\n}\n// uso: String s = primeiro(listaDeStrings); Integer i = primeiro(listaDeInteiros);","fidelityText":"public static <T> T primeiro(List<T> lista) { return lista.isEmpty() ? null : lista.get(0); } // uso: String s = primeiro(listaDeStrings); Integer i = primeiro(listaDeInteiros);","highlightedHtml":"<span class=\"kw\">public static</span> &lt;T&gt; T <span class=\"fn\">first</span>(List&lt;T&gt; list) {\n    <span class=\"kw\">return</span> list.isEmpty() ? <span class=\"kw\">null</span> : list.get(0);\n}\n<span class=\"com\">// uso: String s = primeiro(listaDeStrings); Integer i = primeiro(listaDeInteiros);</span>","caption":"Exemplo executável de generics.","explanation":["<T> antes do retorno declara um parâmetro pertencente ao método.","Retornar null ainda é uma escolha de contrato; Optional virá depois."],"commonMistakes":["Escrever T sem <T>","Acessar primeiro sem decidir lista vazia"]},{"id":"generics-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Bounded types — limitando quais tipos são aceitos</h2>","fidelityText":"Bounded types — limitando quais tipos são aceitos"},{"id":"generics-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"// T precisa ser algum tipo que implementa Comparable<T>\npublic static <T extends Comparable<T>> T greater(List<T> list) {\n    T greater = list.get(0);\n    for (T item : list) {\n        if (item.compareTo(greater) > 0) greater = item;\n    }\n    return greater;\n}","fidelityText":"// T precisa ser algum tipo que implementa Comparable<T> public static <T extends Comparable<T>> T maior(List<T> lista) { T maior = lista.get(0); for (T item : lista) { if (item.compareTo(maior) > 0) maior = item; } return maior; }","highlightedHtml":"<span class=\"com\">// T precisa ser algum tipo que implementa Comparable&lt;T&gt;</span>\n<span class=\"kw\">public static</span> &lt;T <span class=\"kw\">extends</span> Comparable&lt;T&gt;&gt; T <span class=\"fn\">greater</span>(List&lt;T&gt; list) {\n    T greater = list.get(0);\n    <span class=\"kw\">for</span> (T item : list) {\n        <span class=\"kw\">if</span> (item.compareTo(greater) &gt; 0) greater = item;\n    }\n    <span class=\"kw\">return</span> greater;\n}","caption":"Exemplo executável de generics.","explanation":["O bound oferece compareTo dentro do método.","A lista precisa ter ao menos um elemento ou contrato alternativo."],"commonMistakes":["Confundir extends bound com herança exclusiva de classe","Ignorar entrada vazia"]},{"id":"generics-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Generics são invariantes — <code>List&lt;Object&gt;</code> não é <code>List&lt;String&gt;</code></h2>","fidelityText":"Generics são invariantes — List<Object> não é List<String>"},{"id":"generics-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"String[] textos = new String[3];\nObject[] objects = textos; // arrays são COVARIANTES -- compila (e pode explodir em runtime)\nobjects[0] = 42; // ArrayStoreException em runtime -- o array \"lembra\" que é de String\n\nList<String> listTextos = new ArrayList<>();\n// List<Object> listaObjetos = listaTextos; // ❌ NÃO compila -- generics são INVARIANTES","fidelityText":"String[] textos = new String[3]; Object[] objetos = textos; // arrays são COVARIANTES -- compila (e pode explodir em runtime) objetos[0] = 42; // ArrayStoreException em runtime -- o array \"lembra\" que é de String List<String> listaTextos = new ArrayList<>(); // List<Object> listaObjetos = listaTextos; // ❌ NÃO compila -- generics são INVARIANTES","highlightedHtml":"<span class=\"kw\">String</span>[] textos = <span class=\"kw\">new</span> <span class=\"kw\">String</span>[3];\n<span class=\"kw\">Object</span>[] objects = textos; <span class=\"com\">// arrays são COVARIANTES -- compila (e pode explodir em runtime)</span>\nobjects[<span class=\"num\">0</span>] = <span class=\"num\">42</span>; <span class=\"com\">// ArrayStoreException em runtime -- o array \"lembra\" que é de String</span>\n\nList&lt;<span class=\"kw\">String</span>&gt; listTextos = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n<span class=\"com\">// List&lt;Object&gt; listaObjetos = listaTextos; // ❌ NÃO compila -- generics são INVARIANTES</span>","caption":"Exemplo executável de generics.","explanation":["Arrays são covariantes (String[] vira Object[]) e só detectam a inconsistência em runtime com ArrayStoreException.","Generics são invariantes: List<String> e List<Object> não têm relação de subtipo, e essa checagem acontece em COMPILAÇÃO."],"commonMistakes":["Achar que List<Object> aceita uma List<String> por String ser subtipo de Object"]},{"id":"generics-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Diferente de arrays (que são covariantes — <code>String[]</code> pode ser tratado como <code>Object[]</code>, com risco de estourar em runtime), tipos genéricos são <strong>invariantes</strong>: <code>List&lt;String&gt;</code> e <code>List&lt;Object&gt;</code> não têm nenhuma relação de subtipo entre si, mesmo <code>String</code> sendo subtipo de <code>Object</code>. Isso não é uma limitação arbitrária — é o que evita exatamente o mesmo problema do array acima, mas detectado em <strong>compilação</strong> em vez de estourar em runtime. Quando você realmente precisa dessa flexibilidade (aceitar <code>List</code> de qualquer subtipo), é para isso que existem os wildcards, a seguir.</p>","fidelityText":"Diferente de arrays (que são covariantes — String[] pode ser tratado como Object[], com risco de estourar em runtime), tipos genéricos são invariantes: List<String> e List<Object> não têm nenhuma relação de subtipo entre si, mesmo String sendo subtipo de Object. Isso não é uma limitação arbitrária — é o que evita exatamente o mesmo problema do array acima, mas detectado em compilação em vez de estourar em runtime. Quando você realmente precisa dessa flexibilidade (aceitar List de qualquer subtipo), é para isso que existem os wildcards, a seguir."},{"id":"generics-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Múltiplos bounds</h2>","fidelityText":"Múltiplos bounds"},{"id":"generics-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"// T precisa satisfazer AMBOS os contratos -- Comparable primeiro, interfaces depois, separados por &\npublic static <T extends Comparable<T> & java.io.Serializable> T greaterSerializavel(List<T> list) {\n    return list.stream().max(Comparable::compareTo).orElseThrow();\n}","fidelityText":"// T precisa satisfazer AMBOS os contratos -- Comparable primeiro, interfaces depois, separados por & public static <T extends Comparable<T> & java.io.Serializable> T maiorSerializavel(List<T> lista) { return lista.stream().max(Comparable::compareTo).orElseThrow(); }","highlightedHtml":"<span class=\"com\">// T precisa satisfazer AMBOS os contratos -- Comparable primeiro, interfaces depois, separados por &amp;</span>\n<span class=\"kw\">public static</span> &lt;T <span class=\"kw\">extends</span> Comparable&lt;T&gt; &amp; java.io.Serializable&gt; T <span class=\"fn\">greaterSerializavel</span>(List&lt;T&gt; list) {\n    <span class=\"kw\">return</span> list.stream().max(Comparable::compareTo).orElseThrow();\n}","caption":"Exemplo executável de generics.","explanation":["Múltiplos bounds exigem no máximo uma classe (primeiro) e quantas interfaces forem necessárias, separadas por &."]},{"id":"generics-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Um parâmetro de tipo pode exigir múltiplos contratos com <code>&amp;</code> — no máximo uma classe (que deve vir primeiro, se houver) e quantas interfaces forem necessárias. Isso é menos comum no dia a dia, mas aparece quando um método realmente precisa de duas capacidades independentes do mesmo tipo.</p>","fidelityText":"Um parâmetro de tipo pode exigir múltiplos contratos com & — no máximo uma classe (que deve vir primeiro, se houver) e quantas interfaces forem necessárias. Isso é menos comum no dia a dia, mas aparece quando um método realmente precisa de duas capacidades independentes do mesmo tipo."},{"id":"generics-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Varargs genéricos e heap pollution</h2>","fidelityText":"Varargs genéricos e heap pollution"},{"id":"generics-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"@SafeVarargs // promessa do autor: este método não faz nada inseguro com o array varargs\npublic static <T> List<T> listOf(T... items) {\n    return Arrays.asList(items);\n}\n\nList<String> names = listOf(\"Ana\", \"Bruno\"); // funciona bem para o uso comum","fidelityText":"@SafeVarargs // promessa do autor: este método não faz nada inseguro com o array varargs public static <T> List<T> listaDe(T... itens) { return Arrays.asList(itens); } List<String> nomes = listaDe(\"Ana\", \"Bruno\"); // funciona bem para o uso comum","highlightedHtml":"<span class=\"annotation\">@SafeVarargs</span> <span class=\"com\">// promessa do autor: este método não faz nada inseguro com o array varargs</span>\n<span class=\"kw\">public static</span> &lt;T&gt; List&lt;T&gt; <span class=\"fn\">listOf</span>(T... items) {\n    <span class=\"kw\">return</span> Arrays.asList(items);\n}\n\nList&lt;<span class=\"kw\">String</span>&gt; names = listOf(<span class=\"str\">\"Ana\"</span>, <span class=\"str\">\"Bruno\"</span>); <span class=\"com\">// funciona bem para o uso comum</span>","caption":"Exemplo executável de generics.","explanation":["Varargs genérico cria um array por baixo -- arrays genéricos reificados não existem, daí o aviso de unchecked generic array creation.","@SafeVarargs é uma promessa do autor de que o array não é exposto/alterado de forma insegura -- não corrige um método perigoso, só silencia o aviso."],"commonMistakes":["Anotar @SafeVarargs em um método que expõe ou muta o array varargs"]},{"id":"generics-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Heap pollution:</b> por baixo, um parâmetro varargs genérico (<code>T...</code>) cria um array — e arrays genéricos reificados não existem (é o mesmo motivo de <code>new T[]</code> não compilar, a seguir). O compilador cria um <code>Object[]</code> internamente e finge que é <code>T[]</code>, emitindo um aviso de \"unchecked generic array creation\". <strong>Heap pollution</strong> é quando essa premissa se quebra — uma variável de um tipo genérico aponta para um objeto de tipo diferente do esperado, gerando <code>ClassCastException</code> mais tarde, longe de onde o problema realmente começou. A anotação <code>@SafeVarargs</code> só deve ser usada quando o autor do método <strong>garante</strong> que o array varargs não é exposto nem alterado de forma insegura — ela silencia o aviso, não corrige um método que de fato faz algo perigoso com o array.</div>","fidelityText":"Heap pollution: por baixo, um parâmetro varargs genérico (T...) cria um array — e arrays genéricos reificados não existem (é o mesmo motivo de new T[] não compilar, a seguir). O compilador cria um Object[] internamente e finge que é T[], emitindo um aviso de \"unchecked generic array creation\". Heap pollution é quando essa premissa se quebra — uma variável de um tipo genérico aponta para um objeto de tipo diferente do esperado, gerando ClassCastException mais tarde, longe de onde o problema realmente começou. A anotação @SafeVarargs só deve ser usada quando o autor do método garante que o array varargs não é exposto nem alterado de forma insegura — ela silencia o aviso, não corrige um método que de fato faz algo perigoso com o array."},{"id":"generics-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Por que <code>new T()</code> e <code>new T[10]</code> não compilam</h2>","fidelityText":"Por que new T() e new T[10] não compilam"},{"id":"generics-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Factory<T> {\n    // T item = new T();       // ❌ erro de compilação -- T não existe em runtime (type erasure)\n    // T[] itens = new T[10];  // ❌ erro de compilação -- mesmo motivo\n\n    // soluções reais: receber uma fábrica, ou aceitar Object[] com cast documentado\n    public T create(Supplier<T> factory) { return factory.get(); } // delega a criação para quem sabe o tipo real\n}","fidelityText":"public class Fabrica<T> { // T item = new T(); // ❌ erro de compilação -- T não existe em runtime (type erasure) // T[] itens = new T[10]; // ❌ erro de compilação -- mesmo motivo // soluções reais: receber uma fábrica, ou aceitar Object[] com cast documentado public T criar(Supplier<T> fabrica) { return fabrica.get(); } // delega a criação para quem sabe o tipo real }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Factory</span>&lt;T&gt; {\n    <span class=\"com\">// T item = new T();       // ❌ erro de compilação -- T não existe em runtime (type erasure)</span>\n    <span class=\"com\">// T[] itens = new T[10];  // ❌ erro de compilação -- mesmo motivo</span>\n\n    <span class=\"com\">// soluções reais: receber uma fábrica, ou aceitar Object[] com cast documentado</span>\n    <span class=\"kw\">public</span> T <span class=\"fn\">create</span>(Supplier&lt;T&gt; factory) { <span class=\"kw\">return</span> factory.get(); } <span class=\"com\">// delega a criação para quem sabe o tipo real</span>\n}","caption":"Exemplo executável de generics.","explanation":["new T() e new T[10] não compilam porque T não é reificável (type erasure) -- o compilador não sabe qual construtor/array criar.","Receber um Supplier<T> (ou Classe::new) delega a criação para quem conhece o tipo concreto."],"commonMistakes":["Tentar contornar com new Object() e cast para T (falha em runtime de forma sutil)"]},{"id":"generics-content-22","type":"html","authorship":"legacy-preserved","html":"<p>Como <code>T</code> não é <strong>reificável</strong> (não existe como informação de tipo em runtime, por causa do <em>type erasure</em> visto acima), o compilador não tem como saber qual construtor chamar em <code>new T()</code>, nem qual tipo de array criar em <code>new T[10]</code> — ambos exigiriam informação que já foi apagada. A solução usual é receber um <code>Supplier&lt;T&gt;</code> (ou uma referência de construtor, <code>Classe::new</code>) e delegar a criação para quem efetivamente conhece o tipo concreto.</p>","fidelityText":"Como T não é reificável (não existe como informação de tipo em runtime, por causa do type erasure visto acima), o compilador não tem como saber qual construtor chamar em new T(), nem qual tipo de array criar em new T[10] — ambos exigiriam informação que já foi apagada. A solução usual é receber um Supplier<T> (ou uma referência de construtor, Classe::new) e delegar a criação para quem efetivamente conhece o tipo concreto."},{"id":"generics-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>Wildcards: ? extends e ? super</h2>","fidelityText":"Wildcards: ? extends e ? super"},{"id":"generics-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"// ? extends Number: o tipo exato é desconhecido, mas produz valores como Number\nstatic double sumAll(List<? extends Number> numbers) {\n    double sum = 0;\n    for (Number n : numbers) sum += n.doubleValue();\n    return sum;\n}\n// funciona com List<Integer>, List<Double>, List<Number>...\n\n// ? super Integer: aceita receber Integer; ao ler, o tipo garantido é apenas Object\nstatic void addIntegers(List<? super Integer> list) {\n    list.add(1);\n    list.add(2);\n}","fidelityText":"// ? extends Number: o tipo exato é desconhecido, mas produz valores como Number static double somarTodos(List<? extends Number> numeros) { double soma = 0; for (Number n : numeros) soma += n.doubleValue(); return soma; } // funciona com List<Integer>, List<Double>, List<Number>... // ? super Integer: aceita receber Integer; ao ler, o tipo garantido é apenas Object static void adicionarInteiros(List<? super Integer> lista) { lista.add(1); lista.add(2); }","highlightedHtml":"<span class=\"com\">// ? extends Number: o tipo exato é desconhecido, mas produz valores como Number</span>\n<span class=\"kw\">static double</span> <span class=\"fn\">sumAll</span>(List&lt;? <span class=\"kw\">extends</span> Number&gt; numbers) {\n    <span class=\"kw\">double</span> sum = 0;\n    <span class=\"kw\">for</span> (Number n : numbers) sum += n.doubleValue();\n    <span class=\"kw\">return</span> sum;\n}\n<span class=\"com\">// funciona com List&lt;Integer&gt;, List&lt;Double&gt;, List&lt;Number&gt;...</span>\n\n<span class=\"com\">// ? super Integer: aceita receber Integer; ao ler, o tipo garantido é apenas Object</span>\n<span class=\"kw\">static void</span> <span class=\"fn\">addIntegers</span>(List&lt;? <span class=\"kw\">super</span> <span class=\"kw\">Integer</span>&gt; list) {\n    list.add(1);\n    list.add(2);\n}","caption":"Exemplo executável de generics.","explanation":["extends permite ler como Number, mas o tipo capturado impede inserção arbitrária.","super permite inserir Integer e ler somente como Object."],"commonMistakes":["Chamar extends de imutável","Devolver wildcard e transferir captura ao chamador"]},{"id":"generics-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O mnemônico <b>PECS — Producer Extends, Consumer Super</b> ajuda a escolher limites. Em <code>List&lt;? extends Number&gt;</code>, você pode obter <code>Number</code>, mas não inserir um número arbitrário porque o tipo capturado pode ser <code>Integer</code> ou <code>Double</code>. Em <code>List&lt;? super Integer&gt;</code>, pode inserir <code>Integer</code>, mas ao ler só há garantia de <code>Object</code>. Se precisa ler e escrever o mesmo <code>T</code>, declare um parâmetro de tipo em vez de wildcard.</div>","fidelityText":"O mnemônico PECS — Producer Extends, Consumer Super ajuda a escolher limites. Em List<? extends Number>, você pode obter Number, mas não inserir um número arbitrário porque o tipo capturado pode ser Integer ou Double. Em List<? super Integer>, pode inserir Integer, mas ao ler só há garantia de Object. Se precisa ler e escrever o mesmo T, declare um parâmetro de tipo em vez de wildcard."},{"id":"generics-content-26","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Type erasure:</b> instâncias de tipos genéricos normalmente não carregam seus argumentos de tipo de forma reificada: em runtime, <code>new ArrayList&lt;String&gt;()</code> e <code>new ArrayList&lt;Integer&gt;()</code> pertencem à mesma classe <code>ArrayList</code>. O compilador insere casts e preserva algumas assinaturas genéricas como metadados do arquivo <code>.class</code>, que reflection pode consultar em campos e métodos. A ausência de um tipo <code>T</code> reificado explica por que não se pode usar diretamente <code>new T()</code>, <code>new T[10]</code> ou <code>obj instanceof List&lt;String&gt;</code>.</div>","fidelityText":"Type erasure: instâncias de tipos genéricos normalmente não carregam seus argumentos de tipo de forma reificada: em runtime, new ArrayList<String>() e new ArrayList<Integer>() pertencem à mesma classe ArrayList. O compilador insere casts e preserva algumas assinaturas genéricas como metadados do arquivo .class, que reflection pode consultar em campos e métodos. A ausência de um tipo T reificado explica por que não se pode usar diretamente new T(), new T[10] ou obj instanceof List<String>."},{"id":"generics-exercise-27","type":"exercise","authorship":"legacy-preserved","title":"Exercício 12.1 — Par genérico","prompt":"Crie uma classe genérica Par<A, B> (dois parâmetros de tipo!) com atributos primeiro e segundo, construtor, getters, e um método inverter() que retorna um novo Par<B, A> com os valores trocados.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 12.1 — Par genéricomédio Crie uma classe genérica Par<A, B> (dois parâmetros de tipo!) com atributos primeiro e segundo, construtor, getters, e um método inverter() que retorna um novo Par<B, A> com os valores trocados. Ver solução public class Par<A, B> { private final A primeiro; private final B segundo; public Par(A primeiro, B segundo) { this.primeiro = primeiro; this.segundo = segundo; } public A getPrimeiro() { return primeiro; } public B getSegundo() { return segundo; } public Par<B, A> inverter() { return new Par<>(segundo, primeiro); } } // uso: Par<String,Integer> p = new Par<>(\"idade\", 25); Par<Integer,String> inv = p.inverter();","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 12.1 — Par genérico</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie uma classe genérica <code>Par&lt;A, B&gt;</code> (dois parâmetros de tipo!) com atributos <code>primeiro</code> e <code>segundo</code>, construtor, getters, e um método <code>inverter()</code> que retorna um novo <code>Par&lt;B, A&gt;</code> com os valores trocados.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Pair</span>&lt;A, B&gt; {\n    <span class=\"kw\">private final</span> A first;\n    <span class=\"kw\">private final</span> B second;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Pair</span>(A first, B second) { <span class=\"kw\">this</span>.first = first; <span class=\"kw\">this</span>.second = second; }\n\n    <span class=\"kw\">public</span> A <span class=\"fn\">getFirst</span>() { <span class=\"kw\">return</span> first; }\n    <span class=\"kw\">public</span> B <span class=\"fn\">getSecond</span>() { <span class=\"kw\">return</span> second; }\n\n    <span class=\"kw\">public</span> <span class=\"cls\">Pair</span>&lt;B, A&gt; <span class=\"fn\">reverse</span>() {\n        <span class=\"kw\">return new</span> <span class=\"cls\">Pair</span>&lt;&gt;(second, first);\n    }\n}\n<span class=\"com\">// uso: Par&lt;String,Integer&gt; p = new Par&lt;&gt;(\"idade\", 25); Par&lt;Integer,String&gt; inv = p.inverter();</span></pre>\n        </div>\n      </div>"},{"id":"generics-exercise-28","type":"exercise","authorship":"legacy-preserved","title":"Exercício 12.2 — Repositório genérico","prompt":"Crie uma classe Repositorio<T> com uma List<T> interna e métodos salvar(T item), listar() (retorna uma cópia não modificável com List.copyOf(...)) e buscarIguais(T procurado). Implemente a busca com for e equals; comportamentos passados como dados serão estudados em lambdas.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 12.2 — Repositório genéricodifícil Crie uma classe Repositorio<T> com uma List<T> interna e métodos salvar(T item), listar() (retorna uma cópia não modificável com List.copyOf(...)) e buscarIguais(T procurado). Implemente a busca com for e equals; comportamentos passados como dados serão estudados em lambdas. Ver solução public class Repositorio<T> { private final List<T> itens = new ArrayList<>(); public void salvar(T item) { itens.add(item); } public List<T> listar() { return List.copyOf(itens); } public List<T> buscarIguais(T procurado) { List<T> resultado = new ArrayList<>(); for (T item : itens) { if (java.util.Objects.equals(item, procurado)) resultado.add(item); } return resultado; } } // uso: repo.buscarIguais(livroProcurado);","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 12.2 — Repositório genérico</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie uma classe <code>Repositorio&lt;T&gt;</code> com uma <code>List&lt;T&gt;</code> interna e métodos <code>salvar(T item)</code>, <code>listar()</code> (retorna uma cópia não modificável com <code>List.copyOf(...)</code>) e <code>buscarIguais(T procurado)</code>. Implemente a busca com <code>for</code> e <code>equals</code>; comportamentos passados como dados serão estudados em lambdas.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Repository</span>&lt;T&gt; {\n    <span class=\"kw\">private final</span> List&lt;T&gt; items = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">save</span>(T item) { items.add(item); }\n\n    <span class=\"kw\">public</span> List&lt;T&gt; <span class=\"fn\">listar</span>() { <span class=\"kw\">return</span> List.copyOf(items); }\n\n    <span class=\"kw\">public</span> List&lt;T&gt; <span class=\"fn\">findIguais</span>(T procurado) {\n        List&lt;T&gt; result = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n        <span class=\"kw\">for</span> (T item : items) {\n            <span class=\"kw\">if</span> (java.util.Objects.equals(item, procurado)) result.add(item);\n        }\n        <span class=\"kw\">return</span> result;\n    }\n}\n<span class=\"com\">// uso: repo.buscarIguais(livroProcurado);</span></pre>\n        </div>\n      </div>"},{"id":"generics-exercise-29","type":"exercise","authorship":"legacy-preserved","title":"Exercício 12.3 — Invariância na prática","prompt":"Escreva um método somarTodos(List<? extends Number> numeros) que soma qualquer List de subtipo de Number. Depois, tente (e explique por que não compila) escrever uma versão que recebesse List<Number> diretamente e chamá-la com uma List<Integer>.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 12.3 — Invariância na práticamédio Escreva um método somarTodos(List<? extends Number> numeros) que soma qualquer List de subtipo de Number. Depois, tente (e explique por que não compila) escrever uma versão que recebesse List<Number> diretamente e chamá-la com uma List<Integer>. Ver solução static double somarTodos(List<? extends Number> numeros) { double soma = 0; for (Number n : numeros) soma += n.doubleValue(); return soma; } List<Integer> inteiros = List.of(1, 2, 3); somarTodos(inteiros); // funciona -- List<Integer> é aceita por List<? extends Number> // static double somarTodosRigido(List<Number> numeros) { ... } // somarTodosRigido(inteiros); // NÃO compila -- List<Integer> não é List<Number>, // generics são invariantes; só o wildcard aceita subtipos","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 12.3 — Invariância na prática</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva um método <code>somarTodos(List&lt;? extends Number&gt; numeros)</code> que soma qualquer <code>List</code> de subtipo de <code>Number</code>. Depois, tente (e explique por que não compila) escrever uma versão que recebesse <code>List&lt;Number&gt;</code> diretamente e chamá-la com uma <code>List&lt;Integer&gt;</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">static double</span> <span class=\"fn\">sumAll</span>(List&lt;? <span class=\"kw\">extends</span> Number&gt; numbers) {\n    <span class=\"kw\">double</span> sum = 0;\n    <span class=\"kw\">for</span> (Number n : numbers) sum += n.doubleValue();\n    <span class=\"kw\">return</span> sum;\n}\n\nList&lt;<span class=\"kw\">Integer</span>&gt; integers = List.of(1, 2, 3);\nsumAll(integers); <span class=\"com\">// funciona -- List&lt;Integer&gt; é aceita por List&lt;? extends Number&gt;</span>\n\n<span class=\"com\">// static double somarTodosRigido(List&lt;Number&gt; numeros) { ... }\n// somarTodosRigido(inteiros); // NÃO compila -- List&lt;Integer&gt; não é List&lt;Number&gt;,\n// generics são invariantes; só o wildcard aceita subtipos</span></pre>\n        </div>\n      </div>"},{"id":"generics-model","type":"mental-model","authorship":"authored","title":"Declaração, uso e runtime respondem perguntas diferentes","body":"A declaração introduz T; o uso fornece String; o compilador verifica operações; o runtime normalmente executa uma classe erasure-compatible.","flow":["class Caixa<T> declara relação","Caixa<String> escolhe argumento","compilador rejeita guardar Integer","bytecode preserva comportamento compatível e metadados limitados"]},{"id":"generics-quiz","type":"quiz","authorship":"authored","conceptId":"wildcard-variancia","prompt":"Um método só soma valores de List<Integer>, List<Double> ou List<Number>. Qual parâmetro expressa o fluxo produtor?","options":[{"id":"generics-q-a","label":"List<? extends Number>","correct":true,"explanation":"O tipo capturado produz valores que podem ser lidos como Number, sem permitir inserir Number arbitrário."},{"id":"generics-q-b","label":"List<Number>","correct":false,"explanation":"List<Integer> não é subtipo de List<Number> porque isso permitiria inserir Double numa lista de inteiros."},{"id":"generics-q-c","label":"List<? super Number>","correct":false,"explanation":"Esse limite permite consumir Number, mas a leitura garantida seria apenas Object."}]}],"resources":[{"id":"generics-dev","type":"guide","title":"dev.java: Generics","url":"https://dev.java/learn/generics/","reinforces":"Cobre tipos e métodos genéricos, inferência, wildcards, erasure e restrições.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"generics-jls4","type":"reference","title":"JLS 4.5: Parameterized Types","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.5","reinforces":"Define argumentos, subtipagem, raw types, wildcards e tipos reificáveis.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A generics operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a generics operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this generics chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"// SEM generics (como era antes do Java 5): Object genérico, cast manual, perigoso","instruction":"A generics operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this generics chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"javamoderno","moduleId":"java-core","order":5,"title":"Java moderno: Optional, var, Date/Time","summary":"Versões modernas de Java acrescentaram formas mais explícitas de representar ausência, inferir tipos locais, trabalhar com tempo e declarar portadores de dados. Cada recurso resolve um problema diferente; “moderno” não significa substituir automaticamente o código claro que você já sabe escrever.","objectives":["Modelar ausência com Optional","Usar var sem perder legibilidade","Escolher tipos java.time corretamente","Distinguir record raso e imutabilidade profunda"],"whyItExists":"Generics e lambdas permitem compreender Optional sem decorar chamadas. O capítulo também reúne tipos modernos que tornam intenção local, tempo e dados explícitos, com limites que evitam aplicar novidade por moda.","prerequisiteChapterIds":["streams"],"conceptIds":["optional-ausencia-explicita-no-retorno","a-api-completa-do-optional-metodo-a-metodo","var-inferencia-de-tipo-local","java-time-datas-modernas","records-e-text-blocks"],"introducedConceptIds":["optional-zero-ou-um","inferencia-var-local","java-time-semantica","record-text-block-base"],"usedConceptIds":["tipo-parametrizado","lambda-target-typing","pipeline-lazy-terminal","equals-hashcode-contrato"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"javamoderno-intuition","type":"intuition","authorship":"authored","title":"Tipos carregam decisões que comentários esquecem","body":"Optional diz que uma busca pode não produzir valor; LocalDate diz que só existe data civil; record declara os componentes que formam o valor.","analogyLimit":"Tipos mais expressivos não escolhem a política pelo programador: ausência, fuso, mutabilidade interna e validação continuam exigindo contrato."},{"id":"javamoderno-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#streams\">Lambdas e Streams</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: Lambdas e Streams"},{"id":"javamoderno-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Versões modernas de Java acrescentaram formas mais explícitas de representar ausência, inferir tipos locais, trabalhar com tempo e declarar portadores de dados. Cada recurso resolve um problema diferente; “moderno” não significa substituir automaticamente o código claro que você já sabe escrever.</p>","fidelityText":"Versões modernas de Java acrescentaram formas mais explícitas de representar ausência, inferir tipos locais, trabalhar com tempo e declarar portadores de dados. Cada recurso resolve um problema diferente; “moderno” não significa substituir automaticamente o código claro que você já sabe escrever."},{"id":"javamoderno-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Optional — ausência explícita no retorno</h2>","fidelityText":"Optional — ausência explícita no retorno"},{"id":"javamoderno-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ o chamador precisa \"adivinhar\" que pode vir null:\npublic User findById(int id) {\n    return found ? user : null;\n}\n\n// ✅ o TIPO já avisa: \"isso pode não existir\"\npublic Optional<User> findById(int id) {\n    return found ? Optional.of(user) : Optional.empty();\n}\n\n// uso:\nOptional<User> result = repository.findById(5);\nresult.ifPresent(u -> System.out.println(u.getName()));\nUser u = result.orElse(User.ANONYMOUS);\nUser u2 = result.orElseThrow(() -> new UserNotFoundException(id));","fidelityText":"// ❌ o chamador precisa \"adivinhar\" que pode vir null: public Usuario buscarPorId(int id) { return encontrado ? usuario : null; } // ✅ o TIPO já avisa: \"isso pode não existir\" public Optional<Usuario> buscarPorId(int id) { return encontrado ? Optional.of(usuario) : Optional.empty(); } // uso: Optional<Usuario> resultado = repositorio.buscarPorId(5); resultado.ifPresent(u -> System.out.println(u.getNome())); Usuario u = resultado.orElse(Usuario.ANONIMO); Usuario u2 = resultado.orElseThrow(() -> new UsuarioNaoEncontradoException(id));","highlightedHtml":"<span class=\"com\">// ❌ o chamador precisa \"adivinhar\" que pode vir null:</span>\n<span class=\"kw\">public</span> <span class=\"cls\">User</span> <span class=\"fn\">findById</span>(<span class=\"kw\">int</span> id) {\n    <span class=\"kw\">return</span> found ? user : <span class=\"kw\">null</span>;\n}\n\n<span class=\"com\">// ✅ o TIPO já avisa: \"isso pode não existir\"</span>\n<span class=\"kw\">public</span> Optional&lt;<span class=\"cls\">User</span>&gt; <span class=\"fn\">findById</span>(<span class=\"kw\">int</span> id) {\n    <span class=\"kw\">return</span> found ? Optional.of(user) : Optional.empty();\n}\n\n<span class=\"com\">// uso:</span>\nOptional&lt;<span class=\"cls\">User</span>&gt; result = repository.findById(5);\nresult.ifPresent(u -&gt; System.out.println(u.getName()));\n<span class=\"cls\">User</span> u = result.orElse(<span class=\"cls\">User</span>.ANONYMOUS);\n<span class=\"cls\">User</span> u2 = result.orElseThrow(() -&gt; <span class=\"kw\">new</span> <span class=\"cls\">UserNotFoundException</span>(id));","caption":"Exemplo executável de javamoderno.","explanation":["Optional torna zero-ou-um visível no tipo de retorno, em vez de deixar o chamador adivinhar se null pode acontecer.","ifPresent, orElse e orElseThrow são três políticas diferentes para o mesmo problema de ausência."],"commonMistakes":["Chamar get() sem provar presença antes","Voltar a usar null em vez de Optional.empty()"]},{"id":"javamoderno-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>Optional&lt;T&gt;</code> representa zero ou um resultado. Antes de escolher entre <code>orElse</code>, <code>orElseGet</code>, <code>map</code> e <code>orElseThrow</code>, decida o contrato do chamador: usar um padrão, transformar o valor, propagar ausência ou considerar a ausência uma falha.</div>","fidelityText":"Optional<T> representa zero ou um resultado. Antes de escolher entre orElse, orElseGet, map e orElseThrow, decida o contrato do chamador: usar um padrão, transformar o valor, propagar ausência ou considerar a ausência uma falha."},{"id":"javamoderno-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>A API completa do Optional, método a método</h2>","fidelityText":"A API completa do Optional, método a método"},{"id":"javamoderno-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"Optional<User> present = Optional.of(user);       // exige valor não-null -- lança NPE se receber null\nOptional<User> empty = Optional.empty();               // representa explicitamente \"nenhum valor\"\nOptional<User> talvez = Optional.ofNullable(userOrNull); // aceita null -- vira Optional.empty() quando recebe null\n\npresent.isPresent();  // true -- tem valor?\npresent.isEmpty();    // false -- é a negação de isPresent(), só mais legível em alguns contextos","fidelityText":"Optional<Usuario> presente = Optional.of(usuario); // exige valor não-null -- lança NPE se receber null Optional<Usuario> vazio = Optional.empty(); // representa explicitamente \"nenhum valor\" Optional<Usuario> talvez = Optional.ofNullable(usuarioOuNull); // aceita null -- vira Optional.empty() quando recebe null presente.isPresent(); // true -- tem valor? presente.isEmpty(); // false -- é a negação de isPresent(), só mais legível em alguns contextos","highlightedHtml":"Optional&lt;<span class=\"cls\">User</span>&gt; present = Optional.of(user);       <span class=\"com\">// exige valor não-null -- lança NPE se receber null</span>\nOptional&lt;<span class=\"cls\">User</span>&gt; empty = Optional.empty();               <span class=\"com\">// representa explicitamente \"nenhum valor\"</span>\nOptional&lt;<span class=\"cls\">User</span>&gt; talvez = Optional.ofNullable(userOrNull); <span class=\"com\">// aceita null -- vira Optional.empty() quando recebe null</span>\n\npresent.isPresent();  <span class=\"com\">// true -- tem valor?</span>\npresent.isEmpty();    <span class=\"com\">// false -- é a negação de isPresent(), só mais legível em alguns contextos</span>","caption":"Exemplo executável de javamoderno.","explanation":["of() exige valor não-null e lança NPE se receber null; ofNullable() aceita null e vira empty() nesse caso.","isPresent()/isEmpty() são a checagem explícita, mas normalmente map/orElseThrow evitam precisar delas."],"commonMistakes":["Usar Optional.of() com um valor que pode ser null (lança NPE imediatamente, não depois)"]},{"id":"javamoderno-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"present.ifPresent(u -> System.out.println(u.getName()));           // executa só se houver valor; não faz nada se vazio\n\npresent.ifPresentOrElse(\n    u -> System.out.println(\"Found: \" + u.getName()),\n    () -> System.out.println(\"No user\")                // executa só se VAZIO -- o \"senão\" que ifPresent sozinho não tem\n);","fidelityText":"presente.ifPresent(u -> System.out.println(u.getNome())); // executa só se houver valor; não faz nada se vazio presente.ifPresentOrElse( u -> System.out.println(\"Encontrado: \" + u.getNome()), () -> System.out.println(\"Nenhum usuário\") // executa só se VAZIO -- o \"senão\" que ifPresent sozinho não tem );","highlightedHtml":"present.ifPresent(u -&gt; System.out.println(u.getName()));           <span class=\"com\">// executa só se houver valor; não faz nada se vazio</span>\n\npresent.ifPresentOrElse(\n    u -&gt; System.out.println(<span class=\"str\">\"Found: \"</span> + u.getName()),\n    () -&gt; System.out.println(<span class=\"str\">\"No user\"</span>)                <span class=\"com\">// executa só se VAZIO -- o \"senão\" que ifPresent sozinho não tem</span>\n);","caption":"Exemplo executável de javamoderno.","explanation":["ifPresent executa só quando há valor e não faz nada se vazio -- não tem um \"senão\".","ifPresentOrElse acrescenta o segundo Runnable, executado exatamente quando o Optional está vazio."],"commonMistakes":["Usar ifPresent quando a lógica também precisa reagir ao caso vazio (é isso que ifPresentOrElse resolve)"]},{"id":"javamoderno-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"Optional<String> name = present.map(User::getName); // transforma o valor SE presente; continua Optional.empty() se vazio\n\nOptional<User> found = present.filter(u -> u.isActive()); // vira Optional.empty() se o valor presente não satisfizer o predicado\n\n// flatMap evita Optional<Optional<T>> quando a função já devolve um Optional\nOptional<Address> address = present.flatMap(User::findAddressPrincipal); // buscarEnderecoPrincipal() já retorna Optional<Endereco>","fidelityText":"Optional<String> nome = presente.map(Usuario::getNome); // transforma o valor SE presente; continua Optional.empty() se vazio Optional<Usuario> encontrado = presente.filter(u -> u.isAtivo()); // vira Optional.empty() se o valor presente não satisfizer o predicado // flatMap evita Optional<Optional<T>> quando a função já devolve um Optional Optional<Endereco> endereco = presente.flatMap(Usuario::buscarEnderecoPrincipal); // buscarEnderecoPrincipal() já retorna Optional<Endereco>","highlightedHtml":"Optional&lt;<span class=\"kw\">String</span>&gt; name = present.map(<span class=\"cls\">User</span>::getName); <span class=\"com\">// transforma o valor SE presente; continua Optional.empty() se vazio</span>\n\nOptional&lt;<span class=\"cls\">User</span>&gt; found = present.filter(u -&gt; u.isActive()); <span class=\"com\">// vira Optional.empty() se o valor presente não satisfizer o predicado</span>\n\n<span class=\"com\">// flatMap evita Optional&lt;Optional&lt;T&gt;&gt; quando a função já devolve um Optional</span>\nOptional&lt;<span class=\"cls\">Address</span>&gt; address = present.flatMap(<span class=\"cls\">User</span>::findAddressPrincipal); <span class=\"com\">// buscarEnderecoPrincipal() já retorna Optional&lt;Endereco&gt;</span>","caption":"Exemplo executável de javamoderno.","explanation":["map transforma o valor presente e mantém o Optional; filter mantém o valor só se ele satisfizer o predicado, senão vira empty.","flatMap evita Optional aninhado quando a função já devolve um Optional -- o mesmo raciocínio de map/flatMap em Stream."],"commonMistakes":["Usar map quando a função já devolve Optional (produz Optional<Optional<T>> -- use flatMap)"]},{"id":"javamoderno-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"User a = present.orElse(User.ANONYMOUS);            // valor padrão -- SEMPRE avaliado, mesmo se presente já tiver valor\nUser b = present.orElseGet(() -> createUserPadrao()); // padrão preguiçoso -- só executa o Supplier se realmente vazio\nUser c = present.orElseThrow();                       // lança NoSuchElementException se vazio (sem mensagem customizada)\nUser d = present.orElseThrow(() -> new UserNotFoundException(id)); // exceção customizada\n\n// stream() -- ponte para compor com pipelines de Stream, tratando ausência como zero elementos\nList<User> users = List.of(present, empty).stream()\n    .flatMap(Optional::stream) // achata: Optional presente vira 1 elemento, vazio vira 0\n    .toList();","fidelityText":"Usuario a = presente.orElse(Usuario.ANONIMO); // valor padrão -- SEMPRE avaliado, mesmo se presente já tiver valor Usuario b = presente.orElseGet(() -> criarUsuarioPadrao()); // padrão preguiçoso -- só executa o Supplier se realmente vazio Usuario c = presente.orElseThrow(); // lança NoSuchElementException se vazio (sem mensagem customizada) Usuario d = presente.orElseThrow(() -> new UsuarioNaoEncontradoException(id)); // exceção customizada // stream() -- ponte para compor com pipelines de Stream, tratando ausência como zero elementos List<Usuario> usuarios = List.of(presente, vazio).stream() .flatMap(Optional::stream) // achata: Optional presente vira 1 elemento, vazio vira 0 .toList();","highlightedHtml":"<span class=\"cls\">User</span> a = present.orElse(<span class=\"cls\">User</span>.ANONYMOUS);            <span class=\"com\">// valor padrão -- SEMPRE avaliado, mesmo se presente já tiver valor</span>\n<span class=\"cls\">User</span> b = present.orElseGet(() -&gt; createUserPadrao()); <span class=\"com\">// padrão preguiçoso -- só executa o Supplier se realmente vazio</span>\n<span class=\"cls\">User</span> c = present.orElseThrow();                       <span class=\"com\">// lança NoSuchElementException se vazio (sem mensagem customizada)</span>\n<span class=\"cls\">User</span> d = present.orElseThrow(() -&gt; <span class=\"kw\">new</span> <span class=\"cls\">UserNotFoundException</span>(id)); <span class=\"com\">// exceção customizada</span>\n\n<span class=\"com\">// stream() -- ponte para compor com pipelines de Stream, tratando ausência como zero elementos</span>\nList&lt;<span class=\"cls\">User</span>&gt; users = List.of(present, empty).stream()\n    .flatMap(Optional::stream) <span class=\"com\">// achata: Optional presente vira 1 elemento, vazio vira 0</span>\n    .toList();","caption":"Exemplo executável de javamoderno.","explanation":["orElse sempre avalia o argumento, mesmo quando há valor presente; orElseGet só chama o Supplier quando realmente vazio.","stream() trata o Optional como zero ou um elemento, permitindo compor com pipelines de Stream."],"commonMistakes":["Usar orElse(calcularCaro()) em vez de orElseGet(() -> calcularCaro()) e pagar o custo mesmo quando desnecessário"]},{"id":"javamoderno-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>orElse</code> vs <code>orElseGet</code>:</b> <code>orElse(valorPadrao)</code> <strong>sempre</strong> avalia <code>valorPadrao</code> — mesmo quando o <code>Optional</code> já tem um valor presente e o padrão será descartado. Se calcular o padrão for caro (uma consulta ao banco, uma chamada de rede), isso desperdiça trabalho. <code>orElseGet(() -&gt; ...)</code> recebe um <code>Supplier</code> e só o executa quando realmente precisa — prefira <code>orElseGet</code> sempre que o valor padrão não for um literal ou constante já calculada.</div>","fidelityText":"orElse vs orElseGet: orElse(valorPadrao) sempre avalia valorPadrao — mesmo quando o Optional já tem um valor presente e o padrão será descartado. Se calcular o padrão for caro (uma consulta ao banco, uma chamada de rede), isso desperdiça trabalho. orElseGet(() -> ...) recebe um Supplier e só o executa quando realmente precisa — prefira orElseGet sempre que o valor padrão não for um literal ou constante já calculada."},{"id":"javamoderno-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Optional não é substituto universal de <code>null</code>.</b> Seu uso principal é o retorno que pode não produzir valor. Em campos, parâmetros ou coleções, costuma aumentar estados e camadas sem esclarecer o contrato. Uma coleção vazia já representa “nenhum elemento”; <code>List&lt;Optional&lt;T&gt;&gt;</code> normalmente mistura duas ausências diferentes sem necessidade. E <code>optional.get()</code> sem checagem prévia (<code>isPresent()</code>, ou melhor, sem nunca precisar checar por já usar <code>map</code>/<code>orElseThrow</code>) é um <em>code smell</em>: se a presença não está provada pelo fluxo do código, é exatamente o mesmo risco do <code>null</code> que o <code>Optional</code> deveria eliminar.</div>","fidelityText":"Optional não é substituto universal de null. Seu uso principal é o retorno que pode não produzir valor. Em campos, parâmetros ou coleções, costuma aumentar estados e camadas sem esclarecer o contrato. Uma coleção vazia já representa “nenhum elemento”; List<Optional<T>> normalmente mistura duas ausências diferentes sem necessidade. E optional.get() sem checagem prévia (isPresent(), ou melhor, sem nunca precisar checar por já usar map/orElseThrow) é um code smell: se a presença não está provada pelo fluxo do código, é exatamente o mesmo risco do null que o Optional deveria eliminar."},{"id":"javamoderno-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>var — inferência de tipo local</h2>","fidelityText":"var — inferência de tipo local"},{"id":"javamoderno-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"var list = new ArrayList<Book>(); // o compilador infere: List<Livro>... na verdade ArrayList<Livro>\nfor (var book : list) { ... }             // livro é inferido como Livro\n\n// var só existe em variáveis LOCAIS -- nunca em campos, parâmetros ou retornos","fidelityText":"var lista = new ArrayList<Livro>(); // o compilador infere: List<Livro>... na verdade ArrayList<Livro> for (var livro : lista) { ... } // livro é inferido como Livro // var só existe em variáveis LOCAIS -- nunca em campos, parâmetros ou retornos","highlightedHtml":"var list = <span class=\"kw\">new</span> ArrayList&lt;<span class=\"cls\">Book</span>&gt;(); <span class=\"com\">// o compilador infere: List&lt;Livro&gt;... na verdade ArrayList&lt;Livro&gt;</span>\n<span class=\"kw\">for</span> (var book : list) { ... }             <span class=\"com\">// livro é inferido como Livro</span>\n\n<span class=\"com\">// var só existe em variáveis LOCAIS -- nunca em campos, parâmetros ou retornos</span>","caption":"Exemplo executável de javamoderno.","explanation":["var omite a escrita do tipo local, mas o compilador fixa ArrayList<Livro>.","Ele não pode nomear campo, parâmetro ou retorno."],"commonMistakes":["Achar que a variável muda de tipo","Usar var quando o inicializador não comunica intenção"]},{"id":"javamoderno-content-15","type":"html","authorship":"legacy-preserved","html":"<p><code>var</code> não torna Java \"dinamicamente tipado\" — o tipo ainda é fixado em compilação, só a <em>escrita</em> é opcional quando o tipo já é óbvio pelo lado direito da atribuição. Use com moderação: <code>var x = calcular();</code> esconde o tipo de retorno e pode prejudicar a leitura.</p>","fidelityText":"var não torna Java \"dinamicamente tipado\" — o tipo ainda é fixado em compilação, só a escrita é opcional quando o tipo já é óbvio pelo lado direito da atribuição. Use com moderação: var x = calcular(); esconde o tipo de retorno e pode prejudicar a leitura."},{"id":"javamoderno-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>java.time — datas modernas</h2>","fidelityText":"java.time — datas modernas"},{"id":"javamoderno-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"LocalDate today = LocalDate.now();\nLocalDate birthDate = LocalDate.of(1998, 5, 23);\nPeriod age = Period.between(birthDate, today);\nSystem.out.println(age.getYears() + \" years\");\n\nLocalDateTime agora = LocalDateTime.now();\nLocalDateTime fromNow1Hora = agora.plusHours(1);\n\nDateTimeFormatter format = DateTimeFormatter.ofPattern(\"dd/MM/yyyy\");\nSystem.out.println(today.format(format));","fidelityText":"LocalDate hoje = LocalDate.now(); LocalDate nascimento = LocalDate.of(1998, 5, 23); Period idade = Period.between(nascimento, hoje); System.out.println(idade.getYears() + \" anos\"); LocalDateTime agora = LocalDateTime.now(); LocalDateTime daqui1Hora = agora.plusHours(1); DateTimeFormatter formato = DateTimeFormatter.ofPattern(\"dd/MM/yyyy\"); System.out.println(hoje.format(formato));","highlightedHtml":"LocalDate today = LocalDate.now();\nLocalDate birthDate = LocalDate.of(1998, 5, 23);\nPeriod age = Period.between(birthDate, today);\nSystem.out.println(age.getYears() + <span class=\"str\">\" years\"</span>);\n\nLocalDateTime agora = LocalDateTime.now();\nLocalDateTime fromNow1Hora = agora.plusHours(1);\n\nDateTimeFormatter format = DateTimeFormatter.ofPattern(<span class=\"str\">\"dd/MM/yyyy\"</span>);\nSystem.out.println(today.format(format));","caption":"Exemplo executável de javamoderno.","explanation":["LocalDate, Period e DateTimeFormatter possuem papéis distintos.","As classes são imutáveis; plusHours devolve outro valor."],"commonMistakes":["Ignorar Clock em testes","Usar LocalDateTime para instante entre fusos"]},{"id":"javamoderno-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Prefira a API moderna:</b> <code>java.util.Date</code> e <code>java.util.Calendar</code> ainda existem por compatibilidade, mas são mutáveis e possuem APIs propensas a erro. Em código novo, prefira <code>LocalDate</code>, <code>LocalDateTime</code>, <code>Instant</code>, <code>OffsetDateTime</code> e <code>ZonedDateTime</code>. Em integrações legadas, converta explicitamente na fronteira e mantenha o domínio em <code>java.time</code>.</div>","fidelityText":"Prefira a API moderna: java.util.Date e java.util.Calendar ainda existem por compatibilidade, mas são mutáveis e possuem APIs propensas a erro. Em código novo, prefira LocalDate, LocalDateTime, Instant, OffsetDateTime e ZonedDateTime. Em integrações legadas, converta explicitamente na fronteira e mantenha o domínio em java.time."},{"id":"javamoderno-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Escolha o tipo pela pergunta.</b> <code>LocalDate</code> é uma data civil sem horário; <code>LocalDateTime</code> não identifica um instante global; <code>Instant</code> representa um ponto na linha do tempo; <code>ZonedDateTime</code> aplica regras de fuso, inclusive mudanças de horário. Não converta automaticamente todo horário para <code>LocalDateTime</code>.</div>","fidelityText":"Escolha o tipo pela pergunta. LocalDate é uma data civil sem horário; LocalDateTime não identifica um instante global; Instant representa um ponto na linha do tempo; ZonedDateTime aplica regras de fuso, inclusive mudanças de horário. Não converta automaticamente todo horário para LocalDateTime."},{"id":"javamoderno-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Records e text blocks</h2>","fidelityText":"Records e text blocks"},{"id":"javamoderno-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"public record Coordinate(double latitude, double longitude) {}\n// declara construtor canônico, acessores latitude()/longitude(), equals, hashCode e toString\n\nString json = \"\"\"\n    {\n      \"name\": \"Felipy\",\n      \"city\": \"Custodia\"\n    }\n    \"\"\"; // text block -- string multilinha, útil para JSON/SQL de exemplo","fidelityText":"public record Coordenada(double latitude, double longitude) {} // declara construtor canônico, acessores latitude()/longitude(), equals, hashCode e toString String json = \"\"\" { \"nome\": \"Felipy\", \"cidade\": \"Custódia\" } \"\"\"; // text block -- string multilinha, útil para JSON/SQL de exemplo","highlightedHtml":"<span class=\"kw\">public record</span> <span class=\"cls\">Coordinate</span>(<span class=\"kw\">double</span> latitude, <span class=\"kw\">double</span> longitude) {}\n<span class=\"com\">// declara construtor canônico, acessores latitude()/longitude(), equals, hashCode e toString</span>\n\n<span class=\"kw\">String</span> json = <span class=\"str\">\"\"\"\n    {\n      \"name\": \"Felipy\",\n      \"city\": \"Custodia\"\n    }\n    \"\"\"</span>; <span class=\"com\">// text block -- string multilinha, útil para JSON/SQL de exemplo</span>","caption":"Exemplo executável de javamoderno.","explanation":["Coordenada declara componentes e acessores sem prefixo get.","Text block é String; a indentação incidental é processada pelas regras da linguagem."],"commonMistakes":["Achar record profundamente imutável","Confundir text block com template interpolado"]},{"id":"javamoderno-exercise-22","type":"exercise","authorship":"legacy-preserved","title":"Exercício 21.1 — Repositório com Optional","prompt":"Reescreva a busca da biblioteca criando buscarPorCodigo(String codigo), que retorna Optional<ItemEmprestavel> quando o código pode não existir. Teste os caminhos orElseThrow, orElse, ifPresent e map. Explique em qual caso cada política faz sentido.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 21.1 — Repositório com Optionalmédio Reescreva a busca da biblioteca criando buscarPorCodigo(String codigo), que retorna Optional<ItemEmprestavel> quando o código pode não existir. Teste os caminhos orElseThrow, orElse, ifPresent e map. Explique em qual caso cada política faz sentido. Ver solução public Optional<ItemAcervo> buscarPorCodigo(String codigo) { return acervo.stream() .filter(i -> i.getCodigo().equals(codigo)) .findFirst(); // findFirst() JÁ retorna Optional<T> } // uso: biblioteca.buscarPorCodigo(\"L001\").ifPresent(i -> System.out.println(i.descricaoCompleta())); ItemAcervo item = biblioteca.buscarPorCodigo(\"L999\") .orElseThrow(() -> new NoSuchElementException(\"Item não encontrado\"));","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 21.1 — Repositório com Optional</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Reescreva a busca da biblioteca criando <code>buscarPorCodigo(String codigo)</code>, que retorna <code>Optional&lt;ItemEmprestavel&gt;</code> quando o código pode não existir. Teste os caminhos <code>orElseThrow</code>, <code>orElse</code>, <code>ifPresent</code> e <code>map</code>. Explique em qual caso cada política faz sentido.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public</span> Optional&lt;<span class=\"cls\">ItemCatalog</span>&gt; <span class=\"fn\">findByCode</span>(<span class=\"kw\">String</span> code) {\n    <span class=\"kw\">return</span> catalog.stream()\n        .filter(i -&gt; i.getCode().equals(code))\n        .findFirst(); <span class=\"com\">// findFirst() JÁ retorna Optional&lt;T&gt;</span>\n}\n\n<span class=\"com\">// uso:</span>\nlibrary.findByCode(<span class=\"str\">\"L001\"</span>).ifPresent(i -&gt; System.out.println(i.descriptionComplete()));\n\n<span class=\"cls\">ItemCatalog</span> item = library.findByCode(<span class=\"str\">\"L999\"</span>)\n    .orElseThrow(() -&gt; <span class=\"kw\">new</span> <span class=\"cls\">InSuchElementException</span>(<span class=\"str\">\"Item not found\"</span>));</pre>\n        </div>\n      </div>"},{"id":"javamoderno-table","type":"table","authorship":"authored","title":"Tipo temporal pela informação disponível","headers":["Informação","Tipo inicial","Não promete"],"rows":[["dia do calendário","LocalDate","hora ou instante"],["data e hora sem zona","LocalDateTime","posição global na linha do tempo"],["instante UTC","Instant","regra civil de uma região"],["data/hora e região","ZonedDateTime","que todo dia tenha exatamente 24 horas"]]},{"id":"javamoderno-quiz","type":"quiz","authorship":"authored","conceptId":"optional-zero-ou-um","prompt":"Criar o valor padrão é caro e ele só deve existir se Optional estiver vazio. Qual operação preserva lazy evaluation?","options":[{"id":"javamoderno-q-a","label":"resultado.orElseGet(() -> criarPadrao())","correct":true,"explanation":"O Supplier só é invocado quando o Optional está vazio."},{"id":"javamoderno-q-b","label":"resultado.orElse(criarPadrao())","correct":false,"explanation":"O argumento é avaliado antes da chamada, mesmo quando há valor presente."},{"id":"javamoderno-q-c","label":"resultado.get()","correct":false,"explanation":"get não fornece padrão e falha quando o Optional está vazio."}]}],"resources":[{"id":"modern-optional","type":"reference","title":"Optional API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html","reinforces":"Define zero-ou-um, lazy fallbacks, transformação e falhas.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"modern-time","type":"reference","title":"java.time package — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/package-summary.html","reinforces":"Distingue datas, instantes, offsets, zonas, períodos e durações.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A modern Java operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a modern Java operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this modern Java chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"// ❌ o chamador precisa \"adivinhar\" que pode vir null:","instruction":"A modern Java operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this modern Java chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"java-21-profundo","moduleId":"java-core","order":8,"title":"Java 21: records, sealed types, pattern matching e virtual threads","summary":"Java 21 reúne recursos finalizados em versões diferentes: records tornaram-se permanentes no Java 16, sealed classes no 17, e record patterns e pattern matching para switch no 21. JDKs de diversos fornecedores oferecem suporte prolongado à versão 21; “LTS” descreve a política de suporte da distribuição, não uma regra da linguagem. O objetivo é escolher cada recurso pelo problema que resolve.","objectives":["Proteger invariantes em records","Modelar hierarquias fechadas e switch exaustivo","Distinguir recurso permanente, preview e LTS","Usar virtual threads apenas no caso básico já suportado"],"whyItExists":"Com igualdade, generics, lambdas e JVM já compreendidos, os recursos modernos podem ser avaliados por semântica e versão. Virtual threads entram somente como primeiro contato; contratos de concorrência permanecem no módulo próprio.","prerequisiteChapterIds":["jvm-profundo"],"conceptIds":["primeiro-contato-com-os-quatro-recursos","records-como-portadores-de-dados-imutaveis","hierarquias-seladas-e-pattern-matching","virtual-threads-primeiro-o-caso-minimo","muitas-tarefas-independentes-sem-pool-de-virtual-threads"],"introducedConceptIds":["record-invariante-copia","sealed-exaustividade","pattern-matching-record-switch","virtual-thread-throughput","versao-preview-lts"],"usedConceptIds":["record-text-block-base","copia-visao-imutabilidade","lambda-target-typing","carregamento-execucao-classe"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"java21-intuition","type":"intuition","authorship":"authored","title":"Recurso novo precisa reduzir um problema real","body":"Record reduz cerimônia de valores; sealed delimita subtipos; patterns decompõem casos conhecidos; virtual threads representam muitas tarefas bloqueantes.","analogyLimit":"Sintaxe recente não melhora automaticamente o design, e recursos finalizados em 16 ou 17 não nasceram todos no Java 21."},{"id":"java-21-profundo-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-java\">Java</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~3h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#jvm-profundo\">JVM, bytecode e memória</a></div></div>","fidelityText":"JavaDificuldade: Avançado⏱ ~3h de estudo e práticaPré-requisito: JVM, bytecode e memória"},{"id":"java-21-profundo-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Java 21 reúne recursos finalizados em versões diferentes: records tornaram-se permanentes no Java 16, sealed classes no 17, e record patterns e pattern matching para <code>switch</code> no 21. JDKs de diversos fornecedores oferecem suporte prolongado à versão 21; “LTS” descreve a política de suporte da distribuição, não uma regra da linguagem. O objetivo é escolher cada recurso pelo problema que resolve.</p>","fidelityText":"Java 21 reúne recursos finalizados em versões diferentes: records tornaram-se permanentes no Java 16, sealed classes no 17, e record patterns e pattern matching para switch no 21. JDKs de diversos fornecedores oferecem suporte prolongado à versão 21; “LTS” descreve a política de suporte da distribuição, não uma regra da linguagem. O objetivo é escolher cada recurso pelo problema que resolve."},{"id":"java-21-profundo-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Primeiro contato com os quatro recursos</h2></div>\n    <p>Veja o problema que cada recurso resolve antes de estudar suas restrições e APIs avançadas.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>LTS</dt><dd><em>Long-Term Support</em>: versão para a qual fornecedores costumam oferecer manutenção prolongada. Não significa que toda funcionalidade experimental virou estável.</dd></div><div class=\"concept-card\"><dt>Record</dt><dd>Forma compacta de declarar uma classe cujo foco é carregar um conjunto fixo de dados.</dd></div><div class=\"concept-card\"><dt>Sealed type</dt><dd>Classe ou interface que declara quais tipos podem estendê-la ou implementá-la.</dd></div><div class=\"concept-card\"><dt>Pattern matching</dt><dd>Sintaxe que testa o formato ou tipo de um valor e já disponibiliza suas partes para uso.</dd></div><div class=\"concept-card\"><dt>Thread</dt><dd>Linha de execução de instruções. Uma aplicação pode ter várias tarefas progredindo concorrentemente.</dd></div><div class=\"concept-card\"><dt>Virtual thread</dt><dd>Thread gerenciada pela JVM com custo baixo de criação, útil principalmente quando muitas tarefas passam tempo aguardando entrada e saída.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoPrimeiro contato com os quatro recursos Veja o problema que cada recurso resolve antes de estudar suas restrições e APIs avançadas. LTSLong-Term Support: versão para a qual fornecedores costumam oferecer manutenção prolongada. Não significa que toda funcionalidade experimental virou estável.RecordForma compacta de declarar uma classe cujo foco é carregar um conjunto fixo de dados.Sealed typeClasse ou interface que declara quais tipos podem estendê-la ou implementá-la.Pattern matchingSintaxe que testa o formato ou tipo de um valor e já disponibiliza suas partes para uso.ThreadLinha de execução de instruções. Uma aplicação pode ter várias tarefas progredindo concorrentemente.Virtual threadThread gerenciada pela JVM com custo baixo de criação, útil principalmente quando muitas tarefas passam tempo aguardando entrada e saída."},{"id":"java-21-profundo-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Records como portadores de dados imutáveis</h2>","fidelityText":"Records como portadores de dados imutáveis"},{"id":"java-21-profundo-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"public record Cash(BigDecimal value, Currency currency) {\n    public Cash {\n        Objects.requireNonNull(value);\n        Objects.requireNonNull(currency);\n        if (value.signum() < 0) throw new IllegalArgumentException(\"value negative\");\n        value = value.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_EVEN);\n    }\n}","fidelityText":"public record Dinheiro(BigDecimal valor, Currency moeda) { public Dinheiro { Objects.requireNonNull(valor); Objects.requireNonNull(moeda); if (valor.signum() < 0) throw new IllegalArgumentException(\"valor negativo\"); valor = valor.setScale(moeda.getDefaultFractionDigits(), RoundingMode.HALF_EVEN); } }","highlightedHtml":"<span class=\"kw\">public record</span> Cash(BigDecimal value, Currency currency) {\n    <span class=\"kw\">public</span> Cash {\n        Objects.requireNonNull(value);\n        Objects.requireNonNull(currency);\n        <span class=\"kw\">if</span> (value.signum() &lt; 0) <span class=\"kw\">throw new</span> IllegalArgumentException(<span class=\"str\">\"value negative\"</span>);\n        value = value.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_EVEN);\n    }\n}","caption":"Exemplo executável de java-21-profundo.","explanation":["O construtor compacto valida e normaliza os componentes antes de formar o estado.","BigDecimal e Currency são tratados como valor; componente mutável exigiria cópia defensiva."],"commonMistakes":["Achar record profundamente imutável","Usar double para dinheiro"]},{"id":"java-21-profundo-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Um record declara acessores, construtor canônico, <code>equals</code>, <code>hashCode</code> e <code>toString</code> a partir dos componentes. Isso não o torna profundamente imutável: se um componente referencia uma lista mutável, a mutabilidade continua existindo. Quando o contrato exigir isolamento, normalize no construtor com uma cópia defensiva, como <code>List.copyOf(itens)</code>.</p>","fidelityText":"Um record declara acessores, construtor canônico, equals, hashCode e toString a partir dos componentes. Isso não o torna profundamente imutável: se um componente referencia uma lista mutável, a mutabilidade continua existindo. Quando o contrato exigir isolamento, normalize no construtor com uma cópia defensiva, como List.copyOf(itens)."},{"id":"java-21-profundo-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Hierarquias seladas e pattern matching</h2>","fidelityText":"Hierarquias seladas e pattern matching"},{"id":"java-21-profundo-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"sealed interface Payment permits Pix, Card, BankSlip {}\nrecord Pix(String key) implements Payment {}\nrecord Card(String token, int installments) implements Payment {}\nrecord BankSlip(LocalDate dueDate) implements Payment {}\n\nstatic String description(Payment payment) {\n    return switch (payment) {\n        case Pix(String key) -> \"PIX \" + key;\n        case Card(var token, var installments) -> installments + \"x\";\n        case BankSlip(LocalDate dueDate) -> \"wins in \" + dueDate;\n    };\n}","fidelityText":"sealed interface Pagamento permits Pix, Cartao, Boleto {} record Pix(String chave) implements Pagamento {} record Cartao(String token, int parcelas) implements Pagamento {} record Boleto(LocalDate vencimento) implements Pagamento {} static String descricao(Pagamento pagamento) { return switch (pagamento) { case Pix(String chave) -> \"PIX \" + chave; case Cartao(var token, var parcelas) -> parcelas + \"x\"; case Boleto(LocalDate vencimento) -> \"vence em \" + vencimento; }; }","highlightedHtml":"<span class=\"kw\">sealed interface</span> Payment <span class=\"kw\">permits</span> Pix, Card, BankSlip {}\n<span class=\"kw\">record</span> Pix(String key) <span class=\"kw\">implements</span> Payment {}\n<span class=\"kw\">record</span> Card(String token, <span class=\"kw\">int</span> installments) <span class=\"kw\">implements</span> Payment {}\n<span class=\"kw\">record</span> BankSlip(LocalDate dueDate) <span class=\"kw\">implements</span> Payment {}\n\n<span class=\"kw\">static</span> String description(Payment payment) {\n    <span class=\"kw\">return switch</span> (payment) {\n        <span class=\"kw\">case</span> Pix(String key) -&gt; <span class=\"str\">\"PIX \"</span> + key;\n        <span class=\"kw\">case</span> Card(<span class=\"kw\">var</span> token, <span class=\"kw\">var</span> installments) -&gt; installments + <span class=\"str\">\"x\"</span>;\n        <span class=\"kw\">case</span> BankSlip(LocalDate dueDate) -&gt; <span class=\"str\">\"wins in \"</span> + dueDate;\n    };\n}","caption":"Exemplo executável de java-21-profundo.","explanation":["Pagamento limita subtipos e o switch cobre os três records sem default.","Record patterns extraem componentes sob o tipo correspondente."],"commonMistakes":["Adicionar default e esconder evolução da hierarquia","Esquecer política para null"]},{"id":"java-21-profundo-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Como a hierarquia é fechada, o compilador consegue verificar a exaustividade desse <code>switch</code>. Quando um novo subtipo permitido é adicionado, decisões incompletas deixam de compilar. Use sealed types para um universo deliberadamente fechado; não sele hierarquias cujo requisito é aceitar implementações externas. <code>null</code> continua sendo um caso separado e precisa de política explícita.</p>","fidelityText":"Como a hierarquia é fechada, o compilador consegue verificar a exaustividade desse switch. Quando um novo subtipo permitido é adicionado, decisões incompletas deixam de compilar. Use sealed types para um universo deliberadamente fechado; não sele hierarquias cujo requisito é aceitar implementações externas. null continua sendo um caso separado e precisa de política explícita."},{"id":"java-21-profundo-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Virtual threads: primeiro o caso mínimo</h2>","fidelityText":"Virtual threads: primeiro o caso mínimo"},{"id":"java-21-profundo-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"Thread task = Thread.startVirtualThread(() ->\n    System.out.println(\"Executando in another thread\")\n);\ntask.join(); // aguarda a tarefa terminar","fidelityText":"Thread tarefa = Thread.startVirtualThread(() -> System.out.println(\"Executando em outra thread\") ); tarefa.join(); // aguarda a tarefa terminar","highlightedHtml":"Thread task = Thread.startVirtualThread(() -&gt;\n    System.out.println(<span class=\"str\">\"Executando in another thread\"</span>)\n);\ntask.join(); <span class=\"com\">// aguarda a tarefa terminar</span>","caption":"Exemplo executável de java-21-profundo.","explanation":["A lambda é Runnable alvo e startVirtualThread inicia uma Thread virtual.","join faz a thread chamadora aguardar a conclusão e pode lançar InterruptedException."],"commonMistakes":["Omitir join num exemplo cujo processo pode terminar","Compartilhar estado mutável sem conhecer sincronização"]},{"id":"java-21-profundo-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Concorrência significa que tarefas podem progredir em períodos sobrepostos; não significa necessariamente executar no mesmo instante. Enquanto uma virtual thread espera uma operação bloqueante de entrada ou saída, o runtime pode liberar a thread de sistema operacional que a carregava. Isso permite representar muitas tarefas de espera com uma thread por tarefa. Virtual threads aumentam capacidade para esse perfil; não tornam uma tarefa CPU-bound mais rápida e não reduzem automaticamente a latência.</p>","fidelityText":"Concorrência significa que tarefas podem progredir em períodos sobrepostos; não significa necessariamente executar no mesmo instante. Enquanto uma virtual thread espera uma operação bloqueante de entrada ou saída, o runtime pode liberar a thread de sistema operacional que a carregava. Isso permite representar muitas tarefas de espera com uma thread por tarefa. Virtual threads aumentam capacidade para esse perfil; não tornam uma tarefa CPU-bound mais rápida e não reduzem automaticamente a latência."},{"id":"java-21-profundo-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Muitas tarefas independentes, sem pool de virtual threads</h2>","fidelityText":"Muitas tarefas independentes, sem pool de virtual threads"},{"id":"java-21-profundo-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"List<Thread> tasks = new ArrayList<>();\n\nfor (int id = 0; id < 100; id++) {\n    int taskId = id;\n    tasks.add(Thread.ofVirtual().start(() ->\n        System.out.println(\"task \" + taskId)\n    ));\n}\n\nfor (Thread task : tasks) task.join();","fidelityText":"List<Thread> tarefas = new ArrayList<>(); for (int id = 0; id < 100; id++) { int tarefaId = id; tarefas.add(Thread.ofVirtual().start(() -> System.out.println(\"tarefa \" + tarefaId) )); } for (Thread tarefa : tarefas) tarefa.join();","highlightedHtml":"List&lt;Thread&gt; tasks = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n\n<span class=\"kw\">for</span> (<span class=\"kw\">int</span> id = 0; id &lt; 100; id++) {\n    <span class=\"kw\">int</span> taskId = id;\n    tasks.add(Thread.ofVirtual().start(() -&gt;\n        System.out.println(<span class=\"str\">\"task \"</span> + taskId)\n    ));\n}\n\n<span class=\"kw\">for</span> (Thread task : tasks) task.join();","caption":"Exemplo executável de java-21-profundo.","explanation":["Cada iteração cria uma tarefa independente e guarda a Thread para aguardar depois.","tarefaId é efetivamente final por iteração e pode ser capturado pela lambda."],"commonMistakes":["Usar pool pequeno de virtual threads","Interpretar impressão fora de ordem como erro"]},{"id":"java-21-profundo-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Cada virtual thread representa uma tarefa, não um trabalhador reutilizado de um pool pequeno. Este exemplo só demonstra criação e espera; ele não ensina compartilhamento de estado, sincronização, interrupção, cancelamento ou limitação de recursos. Esses contratos precisam do módulo de concorrência. Até lá, mantenha as tarefas independentes e não use este exemplo como benchmark.</p>","fidelityText":"Cada virtual thread representa uma tarefa, não um trabalhador reutilizado de um pool pequeno. Este exemplo só demonstra criação e espera; ele não ensina compartilhamento de estado, sincronização, interrupção, cancelamento ou limitação de recursos. Esses contratos precisam do módulo de concorrência. Até lá, mantenha as tarefas independentes e não use este exemplo como benchmark."},{"id":"java-21-profundo-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Versões e previews:</b> o curso usa apenas recursos finalizados no Java 21 neste capítulo. Recursos preview exigem flags de compilação e execução e não devem aparecer silenciosamente em código de produção.</div>","fidelityText":"Versões e previews: o curso usa apenas recursos finalizados no Java 21 neste capítulo. Recursos preview exigem flags de compilação e execução e não devem aparecer silenciosamente em código de produção."},{"id":"java-21-profundo-exercise-17","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — modelagem moderna sem moda","prompt":"Modele o resultado de uma importação como uma hierarquia selada com Sucesso e Falha, ambos records. Use record pattern em um switch exaustivo para produzir a mensagem final. Em separado, execute três tarefas independentes de impressão em virtual threads e aguarde todas com join. Não compartilhe estado mutável.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — modelagem moderna sem modadifícilModele o resultado de uma importação como uma hierarquia selada com Sucesso e Falha, ambos records. Use record pattern em um switch exaustivo para produzir a mensagem final. Em separado, execute três tarefas independentes de impressão em virtual threads e aguarde todas com join. Não compartilhe estado mutável.Ver critériosA hierarquia representa todos os estados permitidos, records validam componentes no construtor compacto, o switch não usa default para esconder subtipo e a demonstração de threads não afirma ganho de velocidade nem antecipa sincronização.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — modelagem moderna sem moda</h2><span class=\"exercise-tag d\">difícil</span></div><p>Modele o resultado de uma importação como uma hierarquia selada com <code>Sucesso</code> e <code>Falha</code>, ambos records. Use record pattern em um <code>switch</code> exaustivo para produzir a mensagem final. Em separado, execute três tarefas independentes de impressão em virtual threads e aguarde todas com <code>join</code>. Não compartilhe estado mutável.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>A hierarquia representa todos os estados permitidos, records validam componentes no construtor compacto, o switch não usa <code>default</code> para esconder subtipo e a demonstração de threads não afirma ganho de velocidade nem antecipa sincronização.</p></div></div>"},{"id":"java21-table","type":"table","authorship":"authored","title":"Versão em que o recurso ficou permanente","headers":["Recurso","Release permanente","Problema principal"],"rows":[["text blocks","15","texto multilinha"],["records e instanceof patterns","16","valores e casts condicionais"],["sealed classes","17","universo de subtipos"],["record patterns e pattern switch","21","decomposição e decisão exaustiva"],["virtual threads","21","escala de tarefas bloqueantes"]]},{"id":"java21-quiz","type":"quiz","authorship":"authored","conceptId":"virtual-thread-throughput","prompt":"Uma tarefa faz cálculo CPU-bound por dez segundos. Trocar platform thread por virtual thread reduz automaticamente esses dez segundos?","options":[{"id":"java21-q-a","label":"Não; virtual threads favorecem escala de muitas tarefas bloqueantes, não aceleram o cálculo individual.","correct":true,"explanation":"Elas liberam carrier durante espera suportada; CPU continua sendo recurso finito."},{"id":"java21-q-b","label":"Sim; toda virtual thread executa numa CPU virtual exclusiva.","correct":false,"explanation":"Virtual thread é agendada pelo runtime sobre threads de plataforma e CPUs reais."},{"id":"java21-q-c","label":"Sim; startVirtualThread paraleliza automaticamente o algoritmo interno.","correct":false,"explanation":"Criar uma thread não divide o trabalho nem torna o algoritmo paralelo."}]}],"resources":[{"id":"java21-language","type":"reference","title":"Java Language Changes Summary","url":"https://docs.oracle.com/en/java/javase/21/language/java-language-changes-summary.html","reinforces":"Distingue releases permanentes e recursos preview do Java 9 ao 21.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"java21-virtual","type":"guide","title":"Java 21 Virtual Threads","url":"https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html","reinforces":"Explica platform/virtual threads, tarefa por thread, blocking I/O, throughput e limites.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A java 21 deep operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a java 21 deep operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this java 21 deep chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"public record Cash(BigDecimal value, Currency currency) {","instruction":"A java 21 deep operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this java 21 deep chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22","notes":["Timeout, Future, Semaphore, cancelamento e laboratório concorrente foram adiados até concorrência."]}},{"id":"streams","moduleId":"java-core","order":4,"title":"Lambdas & Streams","summary":"Uma interface funcional possui um único método abstrato, embora possa herdar métodos de Object e declarar métodos default/static. Uma lambda é uma expressão cujo tipo vem do contrato funcional esperado: os parâmetros e o retorno precisam ser compatíveis com esse método. Ela não existe sem um tipo-alvo.","objectives":["Entender lambda por tipo-alvo e captura","Rastrear pipeline lazy até terminal","Escolher map ou flatMap pela cardinalidade","Usar reduce e collect sob contratos válidos"],"whyItExists":"Comparator mostrou comportamento como objeto verboso. Lambdas tornam contratos funcionais concisos; Streams compõem transformações, mas exigem domínio de lazy evaluation, consumo único, cardinalidade e efeitos.","prerequisiteChapterIds":["generics"],"conceptIds":["interfaces-funcionais-e-lambdas","captura-de-variaveis-lambda-precisa-de-effectively-final","as-interfaces-funcionais-prontas-do-java-util-function","method-references-lambdas-ainda-mais-curtas","streams-pipeline-de-processamento-de-dados","mais-operacoes-intermediarias-distinct-limit-skip-takewhile-dropwhile","consultas-curto-circuito-anymatch-allmatch-nonematch-findfirst-findany","map-flatmap-e-cardinalidade","reduce-a-operacao-mais-generica","collectors-avancados-partitioningby-e-tomap","parallelstream-nem-sempre-mais-rapido"],"introducedConceptIds":["lambda-target-typing","captura-efetivamente-final","pipeline-lazy-terminal","map-flatmap-cardinalidade","reduce-collect-contrato","stream-nao-interferencia"],"usedConceptIds":["ordem-comparable-comparator","tipo-parametrizado","list-set-map-semantica"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"streams-intuition","type":"intuition","authorship":"authored","title":"Descreva o que acontece com cada elemento","body":"Um pipeline pode selecionar livros, transformar cada livro em título e reunir o resultado. A fonte continua sendo uma coleção; Stream representa o processamento, não outro armazenamento.","analogyLimit":"Pipeline lembra uma esteira, mas operações podem ser lazy, curto-circuitar e fundir etapas; não imagine uma nova coleção materializada após cada método."},{"id":"streams-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#generics\">12 · Generics</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 12 · Generics"},{"id":"streams-content-2","type":"html","authorship":"legacy-preserved","html":"<h2>Interfaces funcionais e lambdas</h2>","fidelityText":"Interfaces funcionais e lambdas"},{"id":"streams-content-3","type":"html","authorship":"legacy-preserved","html":"<p>Uma <strong>interface funcional</strong> possui um único método abstrato, embora possa herdar métodos de <code>Object</code> e declarar métodos <code>default</code>/<code>static</code>. Uma lambda é uma expressão cujo tipo vem do contrato funcional esperado: os parâmetros e o retorno precisam ser compatíveis com esse método. Ela não existe sem um tipo-alvo.</p>","fidelityText":"Uma interface funcional possui um único método abstrato, embora possa herdar métodos de Object e declarar métodos default/static. Uma lambda é uma expressão cujo tipo vem do contrato funcional esperado: os parâmetros e o retorno precisam ser compatíveis com esse método. Ela não existe sem um tipo-alvo."},{"id":"streams-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"@FunctionalInterface\npublic interface Operation {\n    int apply(int a, int b);\n}\n\n// forma \"clássica\" com classe anônima:\nOperation sum1 = new Operation() {\n    @Override\n    public int apply(int a, int b) { return a + b; }\n};\n\n// mesma coisa com lambda -- muito mais direto:\nOperation sum2 = (a, b) -> a + b;\n\nSystem.out.println(sum2.apply(2, 3)); // 5","fidelityText":"@FunctionalInterface public interface Operacao { int aplicar(int a, int b); } // forma \"clássica\" com classe anônima: Operacao soma1 = new Operacao() { @Override public int aplicar(int a, int b) { return a + b; } }; // mesma coisa com lambda -- muito mais direto: Operacao soma2 = (a, b) -> a + b; System.out.println(soma2.aplicar(2, 3)); // 5","highlightedHtml":"<span class=\"annotation\">@FunctionalInterface</span>\n<span class=\"kw\">public interface</span> <span class=\"cls\">Operation</span> {\n    <span class=\"kw\">int</span> <span class=\"fn\">apply</span>(<span class=\"kw\">int</span> a, <span class=\"kw\">int</span> b);\n}\n\n<span class=\"com\">// forma \"clássica\" com classe anônima:</span>\n<span class=\"cls\">Operation</span> sum1 = <span class=\"kw\">new</span> <span class=\"cls\">Operation</span>() {\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public int</span> <span class=\"fn\">apply</span>(<span class=\"kw\">int</span> a, <span class=\"kw\">int</span> b) { <span class=\"kw\">return</span> a + b; }\n};\n\n<span class=\"com\">// mesma coisa com lambda -- muito mais direto:</span>\n<span class=\"cls\">Operation</span> sum2 = (a, b) -&gt; a + b;\n\nSystem.out.println(sum2.apply(2, 3)); <span class=\"com\">// 5</span>","caption":"Exemplo executável de streams.","explanation":["Operacao é o tipo-alvo; aplicar define parâmetros e retorno da lambda.","A classe anônima e a lambda cumprem o mesmo contrato funcional."],"commonMistakes":["Achar que lambda existe sem interface funcional","Adicionar segundo método abstrato"]},{"id":"streams-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Captura de variáveis: lambda precisa de \"effectively final\"</h2>","fidelityText":"Captura de variáveis: lambda precisa de \"effectively final\""},{"id":"streams-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"int limite = 10; // effectively final -- sem reatribuição posterior\nPredicate<Integer> belowOfLimite = n -> n < limite; // a lambda CAPTURA \"limite\" do escopo ao redor\n\n// int contador = 0;\n// Runnable incrementar = () -> contador++; // ❌ NÃO compila: contador é reatribuída, não é effectively final","fidelityText":"int limite = 10; // effectively final -- sem reatribuição posterior Predicate<Integer> abaixoDoLimite = n -> n < limite; // a lambda CAPTURA \"limite\" do escopo ao redor // int contador = 0; // Runnable incrementar = () -> contador++; // ❌ NÃO compila: contador é reatribuída, não é effectively final","highlightedHtml":"<span class=\"kw\">int</span> limite = <span class=\"num\">10</span>; <span class=\"com\">// effectively final -- sem reatribuição posterior</span>\nPredicate&lt;<span class=\"kw\">Integer</span>&gt; belowOfLimite = n -&gt; n &lt; limite; <span class=\"com\">// a lambda CAPTURA \"limite\" do escopo ao redor</span>\n\n<span class=\"com\">// int contador = 0;\n// Runnable incrementar = () -&gt; contador++; // ❌ NÃO compila: contador é reatribuída, não é effectively final</span>","caption":"Exemplo executável de streams.","explanation":["Uma lambda só captura variáveis locais effectively final -- nunca reatribuídas depois da inicialização.","Reatribuir a variável capturada não compila; para estado mutável entre chamadas, use um wrapper ou repense a necessidade de mutação compartilhada."],"commonMistakes":["Tentar reatribuir uma variável capturada por lambda (não compila)"]},{"id":"streams-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Uma lambda pode <strong>capturar</strong> variáveis locais do escopo onde foi criada, mas só se elas forem <em>effectively final</em> — nunca reatribuídas depois da inicialização, mesmo sem a palavra-chave <code>final</code> explícita. Isso não é uma limitação arbitrária: a lambda pode ser executada mais tarde, possivelmente em outra thread, e permitir reatribuição criaria ambiguidade sobre qual valor ela deveria enxergar. Se você precisa de um contador mutável através de chamadas, use um array de um elemento, uma classe wrapper, ou (melhor ainda) repense para uma solução sem estado mutável compartilhado — o problema volta com força total quando chega em concorrência.</p>","fidelityText":"Uma lambda pode capturar variáveis locais do escopo onde foi criada, mas só se elas forem effectively final — nunca reatribuídas depois da inicialização, mesmo sem a palavra-chave final explícita. Isso não é uma limitação arbitrária: a lambda pode ser executada mais tarde, possivelmente em outra thread, e permitir reatribuição criaria ambiguidade sobre qual valor ela deveria enxergar. Se você precisa de um contador mutável através de chamadas, use um array de um elemento, uma classe wrapper, ou (melhor ainda) repense para uma solução sem estado mutável compartilhado — o problema volta com força total quando chega em concorrência."},{"id":"streams-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>As interfaces funcionais prontas do java.util.function</h2>","fidelityText":"As interfaces funcionais prontas do java.util.function"},{"id":"streams-content-9","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Interface</th><th>Assinatura</th><th>Uso típico</th></tr>\n        <tr><td><code>Predicate&lt;T&gt;</code></td><td><code>boolean test(T t)</code></td><td>Filtros (<code>stream.filter(...)</code>)</td></tr>\n        <tr><td><code>Function&lt;T,R&gt;</code></td><td><code>R apply(T t)</code></td><td>Transformação (<code>stream.map(...)</code>)</td></tr>\n        <tr><td><code>Consumer&lt;T&gt;</code></td><td><code>void accept(T t)</code></td><td>Efeito colateral (<code>stream.forEach(...)</code>)</td></tr>\n        <tr><td><code>Supplier&lt;T&gt;</code></td><td><code>T get()</code></td><td>Fornecer valor sob demanda (lazy)</td></tr>\n        <tr><td><code>BiFunction&lt;T,U,R&gt;</code></td><td><code>R apply(T t, U u)</code></td><td>Combinar dois valores</td></tr>\n      </tbody></table>","fidelityText":"InterfaceAssinaturaUso típico Predicate<T>boolean test(T t)Filtros (stream.filter(...)) Function<T,R>R apply(T t)Transformação (stream.map(...)) Consumer<T>void accept(T t)Efeito colateral (stream.forEach(...)) Supplier<T>T get()Fornecer valor sob demanda (lazy) BiFunction<T,U,R>R apply(T t, U u)Combinar dois valores"},{"id":"streams-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Method references — lambdas ainda mais curtas</h2>","fidelityText":"Method references — lambdas ainda mais curtas"},{"id":"streams-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"books.forEach(System.out::println);          // referência a método de instância\nbooks.stream().map(Book::getTitle);       // referência a método de instância \"não vinculado\"\nList<String> names = List.of(\"a\",\"b\").stream().map(String::toUpperCase).toList();","fidelityText":"livros.forEach(System.out::println); // referência a método de instância livros.stream().map(Livro::getTitulo); // referência a método de instância \"não vinculado\" List<String> nomes = List.of(\"a\",\"b\").stream().map(String::toUpperCase).toList();","highlightedHtml":"books.forEach(System.out::println);          <span class=\"com\">// referência a método de instância</span>\nbooks.stream().map(<span class=\"cls\">Book</span>::getTitle);       <span class=\"com\">// referência a método de instância \"não vinculado\"</span>\nList&lt;<span class=\"kw\">String</span>&gt; names = List.of(<span class=\"str\">\"a\"</span>,<span class=\"str\">\"b\"</span>).stream().map(<span class=\"kw\">String</span>::toUpperCase).toList();","caption":"Exemplo executável de streams.","explanation":["Method reference é forma alternativa quando a lambda só encaminha argumentos.","O compilador ainda verifica o tipo-alvo Function ou Consumer."],"commonMistakes":["Usar referência quando esconde qual overload foi escolhido","Confundir método vinculado e não vinculado"]},{"id":"streams-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Streams — pipeline de processamento de dados</h2>","fidelityText":"Streams — pipeline de processamento de dados"},{"id":"streams-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Um <code>Stream</code> não é uma estrutura de dados — é um <strong>pipeline</strong> de operações sobre uma fonte de dados (geralmente uma coleção), avaliado de forma preguiçosa (<em>lazy</em>) e, em geral, sem alterar a coleção original.</p>","fidelityText":"Um Stream não é uma estrutura de dados — é um pipeline de operações sobre uma fonte de dados (geralmente uma coleção), avaliado de forma preguiçosa (lazy) e, em geral, sem alterar a coleção original."},{"id":"streams-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"List<Book> books = ...;\n\nList<String> titlesGrandes = books.stream()\n    .filter(l -> l.getPages() > 300)      // operação intermediária: mantém só alguns\n    .sorted(Comparator.comparing(Book::getTitle))\n    .map(Book::getTitle)              // operação intermediária: transforma o tipo\n    .collect(Collectors.toList());          // operação TERMINAL: dispara o processamento\n\ndouble averagePages = books.stream()\n    .mapToInt(Book::getPages)\n    .average()\n    .orElse(0);\n\nMap<String, List<Book>> byAuthor = books.stream()\n    .collect(Collectors.groupingBy(Book::getAuthor));","fidelityText":"List<Livro> livros = ...; List<String> titulosGrandes = livros.stream() .filter(l -> l.getPaginas() > 300) // operação intermediária: mantém só alguns .sorted(Comparator.comparing(Livro::getTitulo)) .map(Livro::getTitulo) // operação intermediária: transforma o tipo .collect(Collectors.toList()); // operação TERMINAL: dispara o processamento double mediaPaginas = livros.stream() .mapToInt(Livro::getPaginas) .average() .orElse(0); Map<String, List<Livro>> porAutor = livros.stream() .collect(Collectors.groupingBy(Livro::getAutor));","highlightedHtml":"List&lt;<span class=\"cls\">Book</span>&gt; books = ...;\n\nList&lt;<span class=\"kw\">String</span>&gt; titlesGrandes = books.stream()\n    .filter(l -&gt; l.getPages() &gt; 300)      <span class=\"com\">// operação intermediária: mantém só alguns</span>\n    .sorted(Comparator.comparing(<span class=\"cls\">Book</span>::getTitle))\n    .map(<span class=\"cls\">Book</span>::getTitle)              <span class=\"com\">// operação intermediária: transforma o tipo</span>\n    .collect(Collectors.toList());          <span class=\"com\">// operação TERMINAL: dispara o processamento</span>\n\n<span class=\"kw\">double</span> averagePages = books.stream()\n    .mapToInt(<span class=\"cls\">Book</span>::getPages)\n    .average()\n    .orElse(0);\n\nMap&lt;<span class=\"kw\">String</span>, List&lt;<span class=\"cls\">Book</span>&gt;&gt; byAuthor = books.stream()\n    .collect(Collectors.groupingBy(<span class=\"cls\">Book</span>::getAuthor));","caption":"Exemplo executável de streams.","explanation":["filter mantém Livro, sorted ordena, map transforma para String e collect materializa.","average devolve OptionalDouble porque a fonte pode estar vazia."],"commonMistakes":["Alterar livros dentro do pipeline","Reutilizar o mesmo stream depois do terminal"]},{"id":"streams-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Streams são <strong>preguiçosos</strong>: nenhuma operação intermediária (<code>filter</code>, <code>map</code>, <code>sorted</code>...) realmente executa até que uma operação <strong>terminal</strong> (<code>collect</code>, <code>forEach</code>, <code>count</code>, <code>reduce</code>...) seja chamada. Isso permite otimizações — por exemplo, <code>filter().findFirst()</code> pode parar no primeiro item que bate, sem processar a coleção inteira. Também significa que um <code>Stream</code> só pode ser <strong>consumido uma vez</strong>: chamar uma segunda operação terminal no mesmo stream lança <code>IllegalStateException</code>.</div>","fidelityText":"Streams são preguiçosos: nenhuma operação intermediária (filter, map, sorted...) realmente executa até que uma operação terminal (collect, forEach, count, reduce...) seja chamada. Isso permite otimizações — por exemplo, filter().findFirst() pode parar no primeiro item que bate, sem processar a coleção inteira. Também significa que um Stream só pode ser consumido uma vez: chamar uma segunda operação terminal no mesmo stream lança IllegalStateException."},{"id":"streams-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Streams não substituem loops sempre:</b> para lógica simples e sequencial, um <code>for</code> tradicional costuma ser mais legível e até mais rápido (sem overhead de boxing/lambdas). Streams brilham quando há uma cadeia real de transformações (filtrar → mapear → agrupar → coletar) que ficaria confusa em loops aninhados.</div>","fidelityText":"Streams não substituem loops sempre: para lógica simples e sequencial, um for tradicional costuma ser mais legível e até mais rápido (sem overhead de boxing/lambdas). Streams brilham quando há uma cadeia real de transformações (filtrar → mapear → agrupar → coletar) que ficaria confusa em loops aninhados."},{"id":"streams-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Mais operações intermediárias: distinct, limit, skip, takeWhile, dropWhile</h2>","fidelityText":"Mais operações intermediárias: distinct, limit, skip, takeWhile, dropWhile"},{"id":"streams-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"List<Integer> numbers = List.of(1, 2, 2, 3, 4, 4, 5, 1, 9, 2);\n\nnumbers.stream().distinct().toList();          // [1, 2, 3, 4, 5, 9] -- remove duplicatas por equals(), preserva 1ª ocorrência\nnumbers.stream().limit(3).toList();            // [1, 2, 2] -- só os 3 primeiros elementos do encounter order\nnumbers.stream().skip(3).toList();             // [3, 4, 4, 5, 1, 9, 2] -- pula os 3 primeiros\n\nnumbers.stream().takeWhile(n -> n < 4).toList(); // [1, 2, 2, 3] -- para na PRIMEIRA falha, mesmo que apareça n<4 depois\nnumbers.stream().dropWhile(n -> n < 4).toList(); // [4, 4, 5, 1, 9, 2] -- descarta até a primeira falha, mantém o resto","fidelityText":"List<Integer> numeros = List.of(1, 2, 2, 3, 4, 4, 5, 1, 9, 2); numeros.stream().distinct().toList(); // [1, 2, 3, 4, 5, 9] -- remove duplicatas por equals(), preserva 1ª ocorrência numeros.stream().limit(3).toList(); // [1, 2, 2] -- só os 3 primeiros elementos do encounter order numeros.stream().skip(3).toList(); // [3, 4, 4, 5, 1, 9, 2] -- pula os 3 primeiros numeros.stream().takeWhile(n -> n < 4).toList(); // [1, 2, 2, 3] -- para na PRIMEIRA falha, mesmo que apareça n<4 depois numeros.stream().dropWhile(n -> n < 4).toList(); // [4, 4, 5, 1, 9, 2] -- descarta até a primeira falha, mantém o resto","highlightedHtml":"List&lt;<span class=\"kw\">Integer</span>&gt; numbers = List.of(1, 2, 2, 3, 4, 4, 5, 1, 9, 2);\n\nnumbers.stream().distinct().toList();          <span class=\"com\">// [1, 2, 3, 4, 5, 9] -- remove duplicatas por equals(), preserva 1ª ocorrência</span>\nnumbers.stream().limit(3).toList();            <span class=\"com\">// [1, 2, 2] -- só os 3 primeiros elementos do encounter order</span>\nnumbers.stream().skip(3).toList();             <span class=\"com\">// [3, 4, 4, 5, 1, 9, 2] -- pula os 3 primeiros</span>\n\nnumbers.stream().takeWhile(n -&gt; n &lt; 4).toList(); <span class=\"com\">// [1, 2, 2, 3] -- para na PRIMEIRA falha, mesmo que apareça n&lt;4 depois</span>\nnumbers.stream().dropWhile(n -&gt; n &lt; 4).toList(); <span class=\"com\">// [4, 4, 5, 1, 9, 2] -- descarta até a primeira falha, mantém o resto</span>","caption":"Exemplo executável de streams.","explanation":["distinct remove duplicatas por equals(); limit/skip cortam pelo encounter order.","takeWhile/dropWhile param na primeira falha da condição -- diferente de filter, que avalia cada elemento independentemente."],"commonMistakes":["Achar que takeWhile é equivalente a filter (para na primeira falha, não filtra tudo)"]},{"id":"streams-content-19","type":"html","authorship":"legacy-preserved","html":"<p><code>takeWhile</code>/<code>dropWhile</code> são frequentemente confundidos com <code>filter</code>: a diferença é que eles avaliam a condição <strong>na ordem</strong> e param/começam na primeira falha — repare que <code>takeWhile(n -&gt; n &lt; 4)</code> não inclui o <code>1</code> que aparece depois do <code>9</code>, porque já tinha parado antes. <code>filter</code> avaliaria cada elemento independentemente e incluiria esse <code>1</code>.</p>","fidelityText":"takeWhile/dropWhile são frequentemente confundidos com filter: a diferença é que eles avaliam a condição na ordem e param/começam na primeira falha — repare que takeWhile(n -> n < 4) não inclui o 1 que aparece depois do 9, porque já tinha parado antes. filter avaliaria cada elemento independentemente e incluiria esse 1."},{"id":"streams-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Consultas curto-circuito: anyMatch, allMatch, noneMatch, findFirst, findAny</h2>","fidelityText":"Consultas curto-circuito: anyMatch, allMatch, noneMatch, findFirst, findAny"},{"id":"streams-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"boolean temGrande = books.stream().anyMatch(l -> l.getPages() > 500);   // para no PRIMEIRO true encontrado\nboolean allGrandes = books.stream().allMatch(l -> l.getPages() > 100); // para no PRIMEIRO false encontrado\nboolean nenhumEmpty = books.stream().noneMatch(l -> l.getPages() == 0);  // para no PRIMEIRO true encontrado (inverte)\n\nOptional<Book> first = books.stream().filter(l -> l.getPages() > 300).findFirst(); // respeita encounter order\nOptional<Book> qualquer = books.stream().filter(l -> l.getPages() > 300).findAny();   // pode ignorar ordem -- mais rápido em stream paralelo","fidelityText":"boolean temGrande = livros.stream().anyMatch(l -> l.getPaginas() > 500); // para no PRIMEIRO true encontrado boolean todosGrandes = livros.stream().allMatch(l -> l.getPaginas() > 100); // para no PRIMEIRO false encontrado boolean nenhumVazio = livros.stream().noneMatch(l -> l.getPaginas() == 0); // para no PRIMEIRO true encontrado (inverte) Optional<Livro> primeiro = livros.stream().filter(l -> l.getPaginas() > 300).findFirst(); // respeita encounter order Optional<Livro> qualquer = livros.stream().filter(l -> l.getPaginas() > 300).findAny(); // pode ignorar ordem -- mais rápido em stream paralelo","highlightedHtml":"<span class=\"kw\">boolean</span> temGrande = books.stream().anyMatch(l -&gt; l.getPages() &gt; 500);   <span class=\"com\">// para no PRIMEIRO true encontrado</span>\n<span class=\"kw\">boolean</span> allGrandes = books.stream().allMatch(l -&gt; l.getPages() &gt; 100); <span class=\"com\">// para no PRIMEIRO false encontrado</span>\n<span class=\"kw\">boolean</span> nenhumEmpty = books.stream().noneMatch(l -&gt; l.getPages() == 0);  <span class=\"com\">// para no PRIMEIRO true encontrado (inverte)</span>\n\nOptional&lt;<span class=\"cls\">Book</span>&gt; first = books.stream().filter(l -&gt; l.getPages() &gt; 300).findFirst(); <span class=\"com\">// respeita encounter order</span>\nOptional&lt;<span class=\"cls\">Book</span>&gt; qualquer = books.stream().filter(l -&gt; l.getPages() &gt; 300).findAny();   <span class=\"com\">// pode ignorar ordem -- mais rápido em stream paralelo</span>","caption":"Exemplo executável de streams.","explanation":["anyMatch/allMatch/noneMatch/findFirst/findAny são curto-circuito -- param assim que a resposta já é conhecida, sem processar o restante.","allMatch em stream vazio devolve true (vacuously true)."],"commonMistakes":["Achar que allMatch em coleção vazia lança exceção ou devolve false"]},{"id":"streams-content-22","type":"html","authorship":"legacy-preserved","html":"<p>Essas cinco operações são <strong>curto-circuito</strong>: não precisam processar o stream inteiro para responder — <code>anyMatch</code> para assim que encontra o primeiro <code>true</code>, sem avaliar o restante. Todas devolvem <code>Optional</code> (no caso de <code>findFirst</code>/<code>findAny</code>) ou <code>boolean</code>, nunca lançam exceção por stream vazio: um <code>allMatch</code> em stream vazio devolve <code>true</code> (vacuously true — não há nenhum contraexemplo).</p>","fidelityText":"Essas cinco operações são curto-circuito: não precisam processar o stream inteiro para responder — anyMatch para assim que encontra o primeiro true, sem avaliar o restante. Todas devolvem Optional (no caso de findFirst/findAny) ou boolean, nunca lançam exceção por stream vazio: um allMatch em stream vazio devolve true (vacuously true — não há nenhum contraexemplo)."},{"id":"streams-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>map, flatMap e cardinalidade</h2>","fidelityText":"map, flatMap e cardinalidade"},{"id":"streams-content-24","type":"html","authorship":"legacy-preserved","html":"<p><code>map</code> produz um resultado para cada elemento recebido. Se a função já devolve vários elementos ou outro contexto, <code>map</code> cria uma camada aninhada; <code>flatMap</code> transforma e achata uma camada. Antes de escolher, escreva a cardinalidade: um-para-um, um-para-zero-ou-um, ou um-para-muitos.</p>","fidelityText":"map produz um resultado para cada elemento recebido. Se a função já devolve vários elementos ou outro contexto, map cria uma camada aninhada; flatMap transforma e achata uma camada. Antes de escolher, escreva a cardinalidade: um-para-um, um-para-zero-ou-um, ou um-para-muitos."},{"id":"streams-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Pipeline não é lugar para mutação escondida.</b> Não altere a fonte nem estado externo dentro de <code>map</code>/<code>filter</code>. Além de dificultar testes, interferência pode produzir resultado não especificado. Crie e consuma o stream no mesmo fluxo; depois de uma operação terminal ele não pode ser reutilizado.</div>","fidelityText":"Pipeline não é lugar para mutação escondida. Não altere a fonte nem estado externo dentro de map/filter. Além de dificultar testes, interferência pode produzir resultado não especificado. Crie e consuma o stream no mesmo fluxo; depois de uma operação terminal ele não pode ser reutilizado."},{"id":"streams-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>reduce — a operação mais genérica</h2>","fidelityText":"reduce — a operação mais genérica"},{"id":"streams-code-27","type":"code","authorship":"legacy-preserved","language":"java","source":"int total = List.of(1,2,3,4).stream()\n    .reduce(0, (accumulator, current) -> accumulator + current); // 10\n\n// a identidade 0 e a soma são associativas para este resultado sequencial","fidelityText":"int total = List.of(1,2,3,4).stream() .reduce(0, (acumulado, atual) -> acumulado + atual); // 10 // a identidade 0 e a soma são associativas para este resultado sequencial","highlightedHtml":"<span class=\"kw\">int</span> total = List.of(1,2,3,4).stream()\n    .reduce(0, (accumulator, current) -&gt; accumulator + current); <span class=\"com\">// 10</span>\n\n<span class=\"com\">// a identidade 0 e a soma são associativas para este resultado sequencial</span>","caption":"Exemplo executável de streams.","explanation":["Zero é identidade da soma e a operação combina acumulado e elemento.","Uma redução correta precisa de operação compatível com a execução pretendida."],"commonMistakes":["Usar identidade que altera o resultado","Mutar contêiner externo dentro de reduce"]},{"id":"streams-content-28","type":"html","authorship":"legacy-preserved","html":"<p><code>reduce</code> combina elementos num único valor. A identidade precisa ser neutra e a operação precisa respeitar o contrato, especialmente se um dia o pipeline for paralelo. Para acumular em um contêiner mutável, como <code>List</code> ou <code>Map</code>, prefira <code>collect</code> e um coletor adequado em vez de mutar uma lista externa dentro de <code>reduce</code>.</p>","fidelityText":"reduce combina elementos num único valor. A identidade precisa ser neutra e a operação precisa respeitar o contrato, especialmente se um dia o pipeline for paralelo. Para acumular em um contêiner mutável, como List ou Map, prefira collect e um coletor adequado em vez de mutar uma lista externa dentro de reduce."},{"id":"streams-content-29","type":"html","authorship":"legacy-preserved","html":"<h2>Collectors avançados: partitioningBy e toMap</h2>","fidelityText":"Collectors avançados: partitioningBy e toMap"},{"id":"streams-code-30","type":"code","authorship":"legacy-preserved","language":"java","source":"Map<Boolean, List<Book>> particionado = books.stream()\n    .collect(Collectors.partitioningBy(l -> l.getPages() > 300)); // as duas chaves true/false sempre existem\nList<Book> grandes = particionado.get(true);\nList<Book> pequenos = particionado.get(false); // devolve lista vazia quando não há elementos, não devolve null\n\nMap<String, Integer> pagesByTitle = books.stream()\n    .collect(Collectors.toMap(Book::getTitle, Book::getPages));\n// se dois livros tiverem o MESMO título, toMap lança IllegalStateException por padrão --\n// resolva com um terceiro argumento (BinaryOperator) dizendo o que fazer no conflito:\nMap<String, Integer> withoutConflict = books.stream()\n    .collect(Collectors.toMap(Book::getTitle, Book::getPages, (existente, new) -> existente)); // mantém o primeiro quando a chave se repete","fidelityText":"Map<Boolean, List<Livro>> particionado = livros.stream() .collect(Collectors.partitioningBy(l -> l.getPaginas() > 300)); // as duas chaves true/false sempre existem List<Livro> grandes = particionado.get(true); List<Livro> pequenos = particionado.get(false); // devolve lista vazia quando não há elementos, não devolve null Map<String, Integer> paginasPorTitulo = livros.stream() .collect(Collectors.toMap(Livro::getTitulo, Livro::getPaginas)); // se dois livros tiverem o MESMO título, toMap lança IllegalStateException por padrão -- // resolva com um terceiro argumento (BinaryOperator) dizendo o que fazer no conflito: Map<String, Integer> semConflito = livros.stream() .collect(Collectors.toMap(Livro::getTitulo, Livro::getPaginas, (existente, novo) -> existente)); // mantém o primeiro quando a chave se repete","highlightedHtml":"Map&lt;<span class=\"kw\">Boolean</span>, List&lt;<span class=\"cls\">Book</span>&gt;&gt; particionado = books.stream()\n    .collect(Collectors.partitioningBy(l -&gt; l.getPages() &gt; 300)); <span class=\"com\">// as duas chaves true/false sempre existem</span>\nList&lt;<span class=\"cls\">Book</span>&gt; grandes = particionado.get(<span class=\"kw\">true</span>);\nList&lt;<span class=\"cls\">Book</span>&gt; pequenos = particionado.get(<span class=\"kw\">false</span>); <span class=\"com\">// devolve lista vazia quando não há elementos, não devolve null</span>\n\nMap&lt;<span class=\"kw\">String</span>, <span class=\"kw\">Integer</span>&gt; pagesByTitle = books.stream()\n    .collect(Collectors.toMap(<span class=\"cls\">Book</span>::getTitle, <span class=\"cls\">Book</span>::getPages));\n<span class=\"com\">// se dois livros tiverem o MESMO título, toMap lança IllegalStateException por padrão --\n// resolva com um terceiro argumento (BinaryOperator) dizendo o que fazer no conflito:</span>\nMap&lt;<span class=\"kw\">String</span>, <span class=\"kw\">Integer</span>&gt; withoutConflict = books.stream()\n    .collect(Collectors.toMap(<span class=\"cls\">Book</span>::getTitle, <span class=\"cls\">Book</span>::getPages, (existente, new) -&gt; existente)); <span class=\"com\">// mantém o primeiro quando a chave se repete</span>","caption":"Exemplo executável de streams.","explanation":["partitioningBy sempre produz as duas chaves (true/false), mesmo vazias -- diferente de groupingBy, que só cria chaves que apareceram.","toMap lança exceção em chave duplicada por padrão; um terceiro argumento (BinaryOperator) resolve o conflito explicitamente."],"commonMistakes":["Assumir que toMap sobrescreve silenciosamente como Map.put em caso de chave repetida"]},{"id":"streams-content-31","type":"html","authorship":"legacy-preserved","html":"<p><code>partitioningBy</code> é um caso especial de <code>groupingBy</code> com uma condição <code>boolean</code>: sempre produz exatamente duas entradas (<code>true</code>/<code>false</code>), mesmo que uma fique vazia — diferente de <code>groupingBy</code>, que só cria chaves para valores que realmente apareceram. <code>toMap</code> exige que você pense em <strong>chaves duplicadas</strong> desde o início: sem uma função de merge, uma chave repetida lança exceção em vez de sobrescrever silenciosamente (comportamento deliberadamente diferente de <code>Map.put</code>, que sobrescreve sem avisar).</p>","fidelityText":"partitioningBy é um caso especial de groupingBy com uma condição boolean: sempre produz exatamente duas entradas (true/false), mesmo que uma fique vazia — diferente de groupingBy, que só cria chaves para valores que realmente apareceram. toMap exige que você pense em chaves duplicadas desde o início: sem uma função de merge, uma chave repetida lança exceção em vez de sobrescrever silenciosamente (comportamento deliberadamente diferente de Map.put, que sobrescreve sem avisar)."},{"id":"streams-content-32","type":"html","authorship":"legacy-preserved","html":"<h2>parallelStream: nem sempre mais rápido</h2>","fidelityText":"parallelStream: nem sempre mais rápido"},{"id":"streams-code-33","type":"code","authorship":"legacy-preserved","language":"java","source":"long total = numbers.parallelStream() // divide o trabalho entre threads do ForkJoinPool comum\n    .filter(n -> n % 2 == 0)\n    .count();","fidelityText":"long total = numeros.parallelStream() // divide o trabalho entre threads do ForkJoinPool comum .filter(n -> n % 2 == 0) .count();","highlightedHtml":"<span class=\"kw\">long</span> total = numbers.parallelStream() <span class=\"com\">// divide o trabalho entre threads do ForkJoinPool comum</span>\n    .filter(n -&gt; n % 2 == 0)\n    .count();","caption":"Exemplo executável de streams.","explanation":["parallelStream divide o trabalho no ForkJoinPool comum -- overhead de particionamento pode ser maior que o ganho em coleções pequenas.","Operações precisam ser non-interfering e associativas quando paralelizadas; efeito colateral compartilhado quebra em paralelo mesmo funcionando sequencialmente."],"commonMistakes":["Trocar stream() por parallelStream() sem medir com carga representativa"]},{"id":"streams-content-34","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Paralelizar tem custo — meça antes de usar.</b> <code>parallelStream()</code> divide o trabalho entre threads do <code>ForkJoinPool</code> comum (compartilhado com o resto da aplicação, inclusive outros usos de <code>parallelStream</code>), o que introduz overhead de particionamento e sincronização. Para coleções pequenas ou operações rápidas por elemento, esse overhead costuma ser <strong>maior</strong> que o ganho, tornando o pipeline paralelo mais lento que o sequencial. Além disso, a operação precisa ser <em>non-interfering</em> e, se usar <code>reduce</code>/<code>collect</code>, a função de combinação precisa ser associativa — efeitos colaterais compartilhados (escrever em uma lista externa, por exemplo) produzem resultado incorreto ou race condition quando paralelizados, mesmo que funcionassem \"por sorte\" sequencialmente. Nunca troque <code>stream()</code> por <code>parallelStream()</code> sem medir com dados e carga representativos.</div>","fidelityText":"Paralelizar tem custo — meça antes de usar. parallelStream() divide o trabalho entre threads do ForkJoinPool comum (compartilhado com o resto da aplicação, inclusive outros usos de parallelStream), o que introduz overhead de particionamento e sincronização. Para coleções pequenas ou operações rápidas por elemento, esse overhead costuma ser maior que o ganho, tornando o pipeline paralelo mais lento que o sequencial. Além disso, a operação precisa ser non-interfering e, se usar reduce/collect, a função de combinação precisa ser associativa — efeitos colaterais compartilhados (escrever em uma lista externa, por exemplo) produzem resultado incorreto ou race condition quando paralelizados, mesmo que funcionassem \"por sorte\" sequencialmente. Nunca troque stream() por parallelStream() sem medir com dados e carga representativos."},{"id":"streams-exercise-35","type":"exercise","authorship":"legacy-preserved","title":"Exercício 13.1 — Pipeline básico","prompt":"Dada uma List<Livro>, use streams para obter uma List<String> apenas com os títulos dos livros com mais de 200 páginas, em ordem alfabética.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 13.1 — Pipeline básicomédio Dada uma List<Livro>, use streams para obter uma List<String> apenas com os títulos dos livros com mais de 200 páginas, em ordem alfabética. Ver solução List<String> resultado = livros.stream() .filter(l -> l.getPaginas() > 200) .map(Livro::getTitulo) .sorted() .collect(Collectors.toList());","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 13.1 — Pipeline básico</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Dada uma <code>List&lt;Livro&gt;</code>, use streams para obter uma <code>List&lt;String&gt;</code> apenas com os títulos dos livros com mais de 200 páginas, em ordem alfabética.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">List&lt;<span class=\"kw\">String</span>&gt; result = books.stream()\n    .filter(l -&gt; l.getPages() &gt; 200)\n    .map(<span class=\"cls\">Book</span>::getTitle)\n    .sorted()\n    .collect(Collectors.toList());</pre>\n        </div>\n      </div>"},{"id":"streams-exercise-36","type":"exercise","authorship":"legacy-preserved","title":"Exercício 13.2 — Agrupando e contando","prompt":"Dada uma List<Livro>, produza um Map<String, Long> com a contagem de livros por autor, usando Collectors.groupingBy combinado com Collectors.counting(). Depois, encontre o autor com mais livros usando reduce ou Stream.max sobre as entradas do map.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 13.2 — Agrupando e contandodifícil Dada uma List<Livro>, produza um Map<String, Long> com a contagem de livros por autor, usando Collectors.groupingBy combinado com Collectors.counting(). Depois, encontre o autor com mais livros usando reduce ou Stream.max sobre as entradas do map. Ver solução Map<String, Long> contagemPorAutor = livros.stream() .collect(Collectors.groupingBy(Livro::getAutor, Collectors.counting())); String autorComMais = contagemPorAutor.entrySet().stream() .max(Map.Entry.comparingByValue()) .map(Map.Entry::getKey) .orElse(\"nenhum\");","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 13.2 — Agrupando e contando</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Dada uma <code>List&lt;Livro&gt;</code>, produza um <code>Map&lt;String, Long&gt;</code> com a contagem de livros por autor, usando <code>Collectors.groupingBy</code> combinado com <code>Collectors.counting()</code>. Depois, encontre o autor com mais livros usando <code>reduce</code> ou <code>Stream.max</code> sobre as entradas do map.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">Map&lt;<span class=\"kw\">String</span>, <span class=\"kw\">Long</span>&gt; contagemByAuthor = books.stream()\n    .collect(Collectors.groupingBy(<span class=\"cls\">Book</span>::getAuthor, Collectors.counting()));\n\n<span class=\"cls\">String</span> authorWithMais = contagemByAuthor.entrySet().stream()\n    .max(Map.Entry.comparingByValue())\n    .map(Map.Entry::getKey)\n    .orElse(<span class=\"str\">\"nenhum\"</span>);</pre>\n        </div>\n      </div>"},{"id":"streams-exercise-37","type":"exercise","authorship":"legacy-preserved","title":"Exercício 13.3 — Curto-circuito e partição","prompt":"Dada uma List<Livro>: (1) verifique com anyMatch se existe algum livro com mais de 400 páginas; (2) particione a lista em \"grandes\" (>300 páginas) e \"pequenos\" com partitioningBy; (3) explique por que particionado.get(false) nunca lança exceção, mesmo se todos os livros forem grandes.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 13.3 — Curto-circuito e partiçãomédio Dada uma List<Livro>: (1) verifique com anyMatch se existe algum livro com mais de 400 páginas; (2) particione a lista em \"grandes\" (>300 páginas) e \"pequenos\" com partitioningBy; (3) explique por que particionado.get(false) nunca lança exceção, mesmo se todos os livros forem grandes. Ver solução boolean temExtenso = livros.stream().anyMatch(l -> l.getPaginas() > 400); Map<Boolean, List<Livro>> particionado = livros.stream() .collect(Collectors.partitioningBy(l -> l.getPaginas() > 300)); List<Livro> pequenos = particionado.get(false); partitioningBy sempre inicializa as duas chaves (true e false) com uma lista, mesmo vazia — diferente de um Map comum onde uma chave sem valor associado simplesmente não existe. Por isso get(false) nunca devolve null nesse coletor específico.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 13.3 — Curto-circuito e partição</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Dada uma <code>List&lt;Livro&gt;</code>: (1) verifique com <code>anyMatch</code> se existe algum livro com mais de 400 páginas; (2) particione a lista em \"grandes\" (&gt;300 páginas) e \"pequenos\" com <code>partitioningBy</code>; (3) explique por que <code>particionado.get(false)</code> nunca lança exceção, mesmo se todos os livros forem grandes.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">boolean</span> temExtenso = books.stream().anyMatch(l -&gt; l.getPages() &gt; 400);\n\nMap&lt;<span class=\"kw\">Boolean</span>, List&lt;<span class=\"cls\">Book</span>&gt;&gt; particionado = books.stream()\n    .collect(Collectors.partitioningBy(l -&gt; l.getPages() &gt; 300));\nList&lt;<span class=\"cls\">Book</span>&gt; pequenos = particionado.get(<span class=\"kw\">false</span>);</pre>\n          <p style=\"margin-top:12px\"><code>partitioningBy</code> sempre inicializa as duas chaves (<code>true</code> e <code>false</code>) com uma lista, mesmo vazia — diferente de um <code>Map</code> comum onde uma chave sem valor associado simplesmente não existe. Por isso <code>get(false)</code> nunca devolve <code>null</code> nesse coletor específico.</p>\n        </div>\n      </div>"},{"id":"streams-error","type":"error-case","authorship":"authored","title":"Pipeline sem terminal não produz o efeito esperado","scenario":"filter imprime diagnóstico, mas nenhum terminal é chamado","symptom":"nenhuma mensagem aparece","cause":"operações intermediárias são lazy e só formam a descrição do pipeline","diagnosis":["localizar a fonte","classificar cada operação","identificar se existe terminal"],"correction":"Adicionar uma operação terminal que represente o resultado necessário, sem usar forEach apenas para forçar execução.","prevention":"Desenhar fonte → intermediárias → terminal e o tipo resultante antes do código."},{"id":"streams-quiz","type":"quiz","authorship":"authored","conceptId":"map-flatmap-cardinalidade","prompt":"Cada Pedido possui List<Item> e o resultado precisa ser Stream<Item> único. Qual operação evita Stream<List<Item>>?","options":[{"id":"streams-q-a","label":"flatMap(pedido -> pedido.itens().stream())","correct":true,"explanation":"A função produz um stream por pedido e flatMap remove uma camada para formar um único stream de itens."},{"id":"streams-q-b","label":"map(Pedido::itens)","correct":false,"explanation":"map preserva uma saída List<Item> por entrada, produzindo Stream<List<Item>>."},{"id":"streams-q-c","label":"filter(Pedido::itens)","correct":false,"explanation":"filter exige Predicate<Pedido>, não transforma Pedido em itens."}]}],"resources":[{"id":"streams-dev","type":"guide","title":"dev.java: Stream API","url":"https://dev.java/learn/api/streams/","reinforces":"Cobre map/filter/reduce, operações, collectors, Optional e paralelização.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"lambdas-dev","type":"guide","title":"dev.java: Lambda Expressions","url":"https://dev.java/learn/lambdas/","reinforces":"Apresenta interfaces funcionais, target typing, captura e composição.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A streams operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a streams operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this streams chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"@FunctionalInterface","instruction":"A streams operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this streams chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"java-io","moduleId":"io-cli-serialization","order":0,"title":"Arquivos, Paths e Java I/O","summary":"Aplicações reais atravessam uma fronteira que não pertence à memória do programa: o sistema de arquivos. Um arquivo pode não existir, estar sem permissão, usar outra codificação, mudar durante a leitura ou ser grande demais para carregar inteiro. Por isso Java I/O começa com contrato, não com receita.","objectives":["Distinguir caminho, arquivo, diretório, byte, caractere e charset","Escolher leitura total ou incremental pelo tamanho e ciclo de vida","Usar Files/NIO com tratamento explícito de IOException","Gravar saídas sem depender de caminhos ou charset da máquina"],"whyItExists":"Depois de exceções, coleções, streams e Java moderno, o aluno pode atravessar a fronteira entre memória e sistema de arquivos sem misturar parsing, regra e recurso aberto.","prerequisiteChapterIds":["java-21-profundo"],"conceptIds":["path-nao-e-so-uma-string","bytes-caracteres-e-codificacao","texto-pequeno-versus-fluxo-de-dados","falhas-de-i-o-precisam-de-politica","escrita-segura-de-saida","pratica"],"introducedConceptIds":["path-filesystem","byte-char-charset","stream-resource-lifecycle","files-nio-atomicidade"],"usedConceptIds":["try-resource-lifecycle","checked-unchecked-contrato","pipeline-lazy-terminal"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"java-io-intuition","type":"intuition","authorship":"authored","title":"Arquivo é fronteira, não variável grande","body":"Quando o programa lê um arquivo, ele conversa com um recurso externo que pode não existir, mudar, estar bloqueado, usar outro charset ou ser grande demais para memória.","analogyLimit":"Pensar em arquivo como caderno ajuda pouco: o filesystem tem permissões, nomes relativos, links, concorrência e falhas parciais."},{"id":"java-io-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n    <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n    <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#java-21-profundo\">Java Core aprovado</a></div>\n  </div>","fidelityText":"Dificuldade: Intermediário Pré-requisitos: Java Core aprovado"},{"id":"java-io-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Aplicações reais atravessam uma fronteira que não pertence à memória do programa: o sistema de arquivos. Um arquivo pode não existir, estar sem permissão, usar outra codificação, mudar durante a leitura ou ser grande demais para carregar inteiro. Por isso Java I/O começa com contrato, não com receita.</p>","fidelityText":"Aplicações reais atravessam uma fronteira que não pertence à memória do programa: o sistema de arquivos. Um arquivo pode não existir, estar sem permissão, usar outra codificação, mudar durante a leitura ou ser grande demais para carregar inteiro. Por isso Java I/O começa com contrato, não com receita."},{"id":"java-io-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Path não é só uma String</h2>","fidelityText":"Path não é só uma String"},{"id":"java-io-content-4","type":"html","authorship":"legacy-preserved","html":"<p><code>Path</code> representa um caminho no filesystem. Ele pode ser relativo ao diretório atual ou absoluto. Use <code>Path.of</code> para montar partes do caminho sem fixar <code>/</code> ou <code>\\</code> manualmente. O caminho apenas aponta para uma localização; ele não garante que o arquivo exista.</p>","fidelityText":"Path representa um caminho no filesystem. Ele pode ser relativo ao diretório atual ou absoluto. Use Path.of para montar partes do caminho sem fixar / ou \\ manualmente. O caminho apenas aponta para uma localização; ele não garante que o arquivo exista."},{"id":"java-io-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"Path entrada = Path.of(\"data\", \"orders.csv\");\nPath saida = Path.of(\"reports\", \"summary.txt\");\n\nif (!Files.exists(entrada)) {\n    throw new IllegalArgumentException(\"file of input not found: \" + entrada);\n}","fidelityText":"Path entrada = Path.of(\"dados\", \"pedidos.csv\"); Path saida = Path.of(\"relatorios\", \"resumo.txt\"); if (!Files.exists(entrada)) { throw new IllegalArgumentException(\"arquivo de entrada não encontrado: \" + entrada); }","highlightedHtml":"Path entrada = Path.of(<span class=\"str\">\"data\"</span>, <span class=\"str\">\"orders.csv\"</span>);\nPath saida = Path.of(<span class=\"str\">\"reports\"</span>, <span class=\"str\">\"summary.txt\"</span>);\n\n<span class=\"kw\">if</span> (!Files.exists(entrada)) {\n    <span class=\"kw\">throw new</span> IllegalArgumentException(<span class=\"str\">\"file of input not found: \"</span> + entrada);\n}","caption":"Exemplo executável de java-io.","explanation":["Path.of monta partes do caminho sem acoplar o exemplo ao separador do sistema.","Files.exists consulta o filesystem; não transforma o Path em garantia de existência futura."],"commonMistakes":["Concatenar caminho com barra fixa","Gravar caminho absoluto da máquina local"]},{"id":"java-io-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Evite gravar caminhos absolutos da sua máquina em exemplos e projetos. Um colega, servidor de CI ou GitHub Pages não terá a mesma pasta local.</p>","fidelityText":"Evite gravar caminhos absolutos da sua máquina em exemplos e projetos. Um colega, servidor de CI ou GitHub Pages não terá a mesma pasta local."},{"id":"java-io-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Bytes, caracteres e codificação</h2>","fidelityText":"Bytes, caracteres e codificação"},{"id":"java-io-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Arquivo armazena bytes. Texto surge quando esses bytes são interpretados por um <code>Charset</code>. Se um CSV foi salvo em UTF-8 e você lê com outro charset, acentos e símbolos podem quebrar. Declare o charset quando o formato exigir comportamento reproduzível.</p>","fidelityText":"Arquivo armazena bytes. Texto surge quando esses bytes são interpretados por um Charset. Se um CSV foi salvo em UTF-8 e você lê com outro charset, acentos e símbolos podem quebrar. Declare o charset quando o formato exigir comportamento reproduzível."},{"id":"java-io-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"String text = Files.readString(entrada, StandardCharsets.UTF_8);\nFiles.writeString(saida, text.strip(), StandardCharsets.UTF_8);","fidelityText":"String texto = Files.readString(entrada, StandardCharsets.UTF_8); Files.writeString(saida, texto.strip(), StandardCharsets.UTF_8);","highlightedHtml":"String text = Files.readString(entrada, StandardCharsets.UTF_8);\nFiles.writeString(saida, text.strip(), StandardCharsets.UTF_8);","caption":"Exemplo executável de java-io.","explanation":["readString e writeString trabalham com texto completo.","StandardCharsets.UTF_8 torna a leitura e escrita reproduzíveis."],"commonMistakes":["Confiar no charset padrão","Usar API textual para arquivo binário"]},{"id":"java-io-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não confie no charset padrão da máquina.</b> Ele pode variar por sistema operacional, configuração e versão do Java. Curso, teste e projeto profissional devem deixar essa decisão explícita.</div>","fidelityText":"Não confie no charset padrão da máquina. Ele pode variar por sistema operacional, configuração e versão do Java. Curso, teste e projeto profissional devem deixar essa decisão explícita."},{"id":"java-io-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Texto pequeno versus fluxo de dados</h2>","fidelityText":"Texto pequeno versus fluxo de dados"},{"id":"java-io-content-12","type":"html","authorship":"legacy-preserved","html":"<p><code>readString</code> e <code>readAllLines</code> carregam tudo na memória e são bons para arquivos pequenos e conhecidos. Para arquivo grande, processe gradualmente. <code>Files.lines</code> devolve um <code>Stream&lt;String&gt;</code>, mas esse stream mantém um arquivo aberto e precisa ser fechado.</p>","fidelityText":"readString e readAllLines carregam tudo na memória e são bons para arquivos pequenos e conhecidos. Para arquivo grande, processe gradualmente. Files.lines devolve um Stream<String>, mas esse stream mantém um arquivo aberto e precisa ser fechado."},{"id":"java-io-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"Path file = Path.of(\"data\", \"orders.csv\");\nList<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8);\n\ntry (Stream<String> stream = Files.lines(file, StandardCharsets.UTF_8)) {\n    long validas = stream\n        .filter(line -> !line.isBlank())\n        .count();\n}","fidelityText":"Path arquivo = Path.of(\"dados\", \"pedidos.csv\"); List<String> linhas = Files.readAllLines(arquivo, StandardCharsets.UTF_8); try (Stream<String> stream = Files.lines(arquivo, StandardCharsets.UTF_8)) { long validas = stream .filter(linha -> !linha.isBlank()) .count(); }","highlightedHtml":"Path file = Path.of(<span class=\"str\">\"data\"</span>, <span class=\"str\">\"orders.csv\"</span>);\nList&lt;String&gt; lines = Files.readAllLines(file, StandardCharsets.UTF_8);\n\n<span class=\"kw\">try</span> (Stream&lt;String&gt; stream = Files.lines(file, StandardCharsets.UTF_8)) {\n    <span class=\"kw\">long</span> validas = stream\n        .filter(line -&gt; !line.isBlank())\n        .count();\n}","caption":"Exemplo executável de java-io.","explanation":["readAllLines materializa tudo e serve para entradas pequenas.","Files.lines produz stream lazy ligado a recurso aberto, por isso fica dentro do try-with-resources."],"commonMistakes":["Usar readAllLines para arquivo grande","Retornar Stream de um método depois de fechar o recurso"]},{"id":"java-io-content-14","type":"html","authorship":"legacy-preserved","html":"<p>O <code>try-with-resources</code> fecha o recurso mesmo quando ocorre exceção dentro do bloco. Isso não é detalhe cosmético: arquivo aberto consome recurso do sistema operacional.</p>","fidelityText":"O try-with-resources fecha o recurso mesmo quando ocorre exceção dentro do bloco. Isso não é detalhe cosmético: arquivo aberto consome recurso do sistema operacional."},{"id":"java-io-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Falhas de I/O precisam de política</h2>","fidelityText":"Falhas de I/O precisam de política"},{"id":"java-io-content-16","type":"html","authorship":"legacy-preserved","html":"<p><code>IOException</code> representa falha externa: permissão, arquivo ausente, disco, caminho inválido, encerramento inesperado, entre outras. Não capture e continue como se nada tivesse acontecido. Decida se a falha vira mensagem para o usuário, exceção de aplicação com causa preservada ou tentativa controlada.</p>","fidelityText":"IOException representa falha externa: permissão, arquivo ausente, disco, caminho inválido, encerramento inesperado, entre outras. Não capture e continue como se nada tivesse acontecido. Decida se a falha vira mensagem para o usuário, exceção de aplicação com causa preservada ou tentativa controlada."},{"id":"java-io-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"static List<String> readLines(Path file) {\n    try {\n        return Files.readAllLines(file, StandardCharsets.UTF_8);\n    } catch (IOException e) {\n        throw new IllegalStateException(\"not was possible read \" + file, e);\n    }\n}","fidelityText":"static List<String> lerLinhas(Path arquivo) { try { return Files.readAllLines(arquivo, StandardCharsets.UTF_8); } catch (IOException e) { throw new IllegalStateException(\"não foi possível ler \" + arquivo, e); } }","highlightedHtml":"<span class=\"kw\">static</span> List&lt;String&gt; readLines(Path file) {\n    <span class=\"kw\">try</span> {\n        <span class=\"kw\">return</span> Files.readAllLines(file, StandardCharsets.UTF_8);\n    } <span class=\"kw\">catch</span> (IOException e) {\n        <span class=\"kw\">throw new</span> IllegalStateException(<span class=\"str\">\"not was possible read \"</span> + file, e);\n    }\n}","caption":"Exemplo executável de java-io.","explanation":["IOException é traduzida para exceção de aplicação com contexto do arquivo.","A causa original é preservada no construtor para diagnóstico."],"commonMistakes":["Capturar IOException e retornar lista vazia","Perder a causa original ao lançar outra exceção"]},{"id":"java-io-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"good\"><b>Regra prática:</b> falha esperada do domínio vira resultado de validação; falha do ambiente permanece visível com contexto e causa.</div>","fidelityText":"Regra prática: falha esperada do domínio vira resultado de validação; falha do ambiente permanece visível com contexto e causa."},{"id":"java-io-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Escrita segura de saída</h2>","fidelityText":"Escrita segura de saída"},{"id":"java-io-content-20","type":"html","authorship":"legacy-preserved","html":"<p>Gravar relatório também pode falhar. Crie diretórios necessários, use charset declarado e pense se o arquivo final pode ficar parcial. Para projetos pequenos, escrever em arquivo temporário e mover para o destino reduz a chance de deixar um resultado quebrado visível.</p>","fidelityText":"Gravar relatório também pode falhar. Crie diretórios necessários, use charset declarado e pense se o arquivo final pode ficar parcial. Para projetos pequenos, escrever em arquivo temporário e mover para o destino reduz a chance de deixar um resultado quebrado visível."},{"id":"java-io-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"Path directory = Path.of(\"reports\");\nFiles.createDirectories(directory);\n\nPath temporary = Files.createTempFile(directory, \"summary-\", \".tmp\");\nFiles.writeString(temporary, summary, StandardCharsets.UTF_8);\nFiles.move(temporary, directory.resolve(\"summary.txt\"), StandardCopyOption.REPLACE_EXISTING);","fidelityText":"Path diretorio = Path.of(\"relatorios\"); Files.createDirectories(diretorio); Path temporario = Files.createTempFile(diretorio, \"resumo-\", \".tmp\"); Files.writeString(temporario, resumo, StandardCharsets.UTF_8); Files.move(temporario, diretorio.resolve(\"resumo.txt\"), StandardCopyOption.REPLACE_EXISTING);","highlightedHtml":"Path directory = Path.of(<span class=\"str\">\"reports\"</span>);\nFiles.createDirectories(directory);\n\nPath temporary = Files.createTempFile(directory, <span class=\"str\">\"summary-\"</span>, <span class=\"str\">\".tmp\"</span>);\nFiles.writeString(temporary, summary, StandardCharsets.UTF_8);\nFiles.move(temporary, directory.resolve(<span class=\"str\">\"summary.txt\"</span>), StandardCopyOption.REPLACE_EXISTING);","caption":"Exemplo executável de java-io.","explanation":["createDirectories prepara a pasta de saída de forma idempotente.","Escrever temporário e mover reduz a chance de expor relatório parcial."],"commonMistakes":["Assumir que diretório já existe","Prometer atomicidade universal sem verificar filesystem/opções"]},{"id":"java-io-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>Prática</h2>","fidelityText":"Prática"},{"id":"java-io-exercise-23","type":"exercise","authorship":"legacy-preserved","title":"Leitor de log","prompt":"Leia um arquivo grande linha a linha, conte ocorrências por nível e grave um resumo em outro arquivo. Prove que arquivo inexistente, linha malformada e diretório de saída ausente recebem tratamentos diferentes.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Leitor de logObrigatório Leia um arquivo grande linha a linha, conte ocorrências por nível e grave um resumo em outro arquivo. Prove que arquivo inexistente, linha malformada e diretório de saída ausente recebem tratamentos diferentes.","sourceHtml":"<div class=\"exercise\">\n    <div class=\"exercise-head\"><h2>Leitor de log</h2><span class=\"exercise-tag m\">Obrigatório</span></div>\n    <p>Leia um arquivo grande linha a linha, conte ocorrências por nível e grave um resumo em outro arquivo. Prove que arquivo inexistente, linha malformada e diretório de saída ausente recebem tratamentos diferentes.</p>\n  </div>"},{"id":"java-io-content-24","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Critério de domínio</h2><ul>\n    <li>Distingue caminho, arquivo, byte, caractere e charset.</li>\n    <li>Escolhe entre carregar tudo e processar por fluxo.</li>\n    <li>Fecha recursos automaticamente.</li>\n    <li>Não perde a causa original ao tratar falha de I/O.</li>\n    <li>Produz saída reproduzível, sem depender de caminho local secreto.</li>\n  </ul></div>","fidelityText":"Critério de domínio Distingue caminho, arquivo, byte, caractere e charset. Escolhe entre carregar tudo e processar por fluxo. Fecha recursos automaticamente. Não perde a causa original ao tratar falha de I/O. Produz saída reproduzível, sem depender de caminho local secreto."},{"id":"java-io-model","type":"mental-model","authorship":"authored","title":"Do caminho ao valor confiável","body":"A fronteira começa no Path, passa por bytes ou caracteres, produz texto ou objetos e só então entra na regra de negócio.","flow":["Path identifica localização","Files abre recurso","Charset transforma bytes em caracteres","parser transforma texto em valores","validação decide se o valor serve","try-with-resources encerra o recurso"],"ownership":["filesystem pertence ao ambiente","recurso aberto pertence ao bloco que o abriu","valor validado pertence ao domínio"]},{"id":"java-io-quiz","type":"quiz","authorship":"authored","conceptId":"stream-resource-lifecycle","prompt":"Qual escolha é mais segura para processar um arquivo de log muito grande?","options":[{"id":"java-io-q-a","label":"Usar Files.lines em try-with-resources e processar linha a linha.","correct":true,"explanation":"O stream mantém recurso aberto e precisa ser fechado; linha a linha evita carregar tudo na memória."},{"id":"java-io-q-b","label":"Usar readAllLines sempre, porque é mais moderno.","correct":false,"explanation":"readAllLines materializa todo o conteúdo e pode estourar memória em arquivos grandes."},{"id":"java-io-q-c","label":"Confiar no garbage collector para fechar o arquivo.","correct":false,"explanation":"GC não é contrato de fechamento de recurso externo; fechamento deve ser explícito."}]}],"resources":[{"id":"java-io-files-api","type":"reference","title":"Files API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html","reinforces":"Define leitura, escrita, cópia, movimentação, criação e observações de atomicidade/falha.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"java-io-charset-api","type":"reference","title":"StandardCharsets API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/charset/StandardCharsets.html","reinforces":"Fornece charsets obrigatórios como UTF-8 para contratos textuais portáveis.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A java io operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a java io operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this java io chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"Path entrada = Path.of(\"data\", \"orders.csv\");","instruction":"A java io operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this java io chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mini-analisador-vendas","moduleId":"io-cli-serialization","order":1,"title":"Mini-projeto: analisador de vendas com Streams","summary":"Partindo de um arquivo pequeno de vendas, gere rankings, agrupamentos e métricas. O objetivo não é decorar Collectors: é transformar dados de entrada em relatório reprodutível, com ordem, desempate, ausências e erros de formato tratados por contrato.","objectives":["Ler dataset textual pequeno e declarado","Comparar laços e Streams por evidência","Gerar métricas com desempate e ordem determinística","Separar parsing, consulta e relatório"],"whyItExists":"O projeto transforma streams e I/O em uma ferramenta reproduzível: dados entram por arquivo, consultas viram relatório e cada decisão precisa sobreviver a dataset vazio, duplicidade e ordem instável.","prerequisiteChapterIds":["java-io"],"conceptIds":["formato-de-entrada","sequencia-de-implementacao","consultas-obrigatorias","saida-esperada","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["csv-fronteira-textual","relatorio-dados-deterministico"],"usedConceptIds":["pipeline-lazy-terminal","reduce-collect-contrato","files-nio-atomicidade","java-time-semantica"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"vendas-intuition","type":"intuition","authorship":"authored","title":"Relatório bom é repetível","body":"Se duas execuções com os mesmos dados produzem rankings em ordem diferente, o relatório não é uma evidência confiável. Defina ordenação, desempate e tratamento de ausências antes da API de Stream.","analogyLimit":"Ranking parece lista simples, mas sem desempate estável ele vira comportamento acidental da estrutura usada."},{"id":"mini-analisador-vendas-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>transformação de dados</b></div><div class=\"time-est\">Tempo: <b>6–10 horas</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#streams\">Streams</a></div></div>","fidelityText":"Objetivo: transformação de dadosTempo: 6–10 horasPré-requisito: Streams"},{"id":"mini-analisador-vendas-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Partindo de um arquivo pequeno de vendas, gere rankings, agrupamentos e métricas. O objetivo não é decorar <code>Collectors</code>: é transformar dados de entrada em relatório reprodutível, com ordem, desempate, ausências e erros de formato tratados por contrato.</p>","fidelityText":"Partindo de um arquivo pequeno de vendas, gere rankings, agrupamentos e métricas. O objetivo não é decorar Collectors: é transformar dados de entrada em relatório reprodutível, com ordem, desempate, ausências e erros de formato tratados por contrato."},{"id":"mini-analisador-vendas-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Formato de entrada</h2>","fidelityText":"Formato de entrada"},{"id":"mini-analisador-vendas-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Use CSV simples e documentado. Nesta fase, não tente aceitar todos os dialetos possíveis de CSV. Defina uma versão mínima: uma venda por linha, colunas em ordem fixa, UTF-8, cabeçalho obrigatório e valores monetários com ponto decimal.</p>","fidelityText":"Use CSV simples e documentado. Nesta fase, não tente aceitar todos os dialetos possíveis de CSV. Defina uma versão mínima: uma venda por linha, colunas em ordem fixa, UTF-8, cabeçalho obrigatório e valores monetários com ponto decimal."},{"id":"mini-analisador-vendas-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"id,date,customer,product,category,value\nv001,2026-08-01,Ana,Mouse,Perifericos,89.90\nv002,2026-08-01,Bia,Teclado,Perifericos,199.90","fidelityText":"id,data,cliente,produto,categoria,valor v001,2026-08-01,Ana,Mouse,Perifericos,89.90 v002,2026-08-01,Bia,Teclado,Perifericos,199.90","highlightedHtml":"id,date,customer,product,category,value\nv001,2026-08-01,Ana,Mouse,Perifericos,89.90\nv002,2026-08-01,Bia,Teclado,Perifericos,199.90","caption":"Exemplo executável de mini-analisador-vendas.","explanation":["O exemplo declara cabeçalho e linhas em formato CSV mínimo.","O contrato permite split simples somente porque proíbe vírgula dentro de campo."],"commonMistakes":["Generalizar split para qualquer CSV real","Não fixar charset e formato monetário"]},{"id":"mini-analisador-vendas-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não use <code>split(\",\")</code> como solução universal de CSV.</b> Para este projeto didático ele funciona apenas porque o contrato proíbe vírgula dentro dos campos. Se o contrato aceitar aspas, escapes e separador dentro de texto, use uma biblioteca de CSV ou implemente um parser próprio com testes claros.</div>","fidelityText":"Não use split(\",\") como solução universal de CSV. Para este projeto didático ele funciona apenas porque o contrato proíbe vírgula dentro dos campos. Se o contrato aceitar aspas, escapes e separador dentro de texto, use uma biblioteca de CSV ou implemente um parser próprio com testes claros."},{"id":"mini-analisador-vendas-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Sequência de implementação</h2>","fidelityText":"Sequência de implementação"},{"id":"mini-analisador-vendas-content-8","type":"html","authorship":"legacy-preserved","html":"<ol>\n        <li><strong>Modelo:</strong> crie <code>Venda</code> com <code>LocalDate</code>, <code>BigDecimal</code> e campos obrigatórios.</li>\n        <li><strong>Parser:</strong> transforme uma linha em sucesso ou rejeição com número da linha e motivo.</li>\n        <li><strong>Consultas com laços:</strong> implemente primeiro de modo explícito para validar semântica.</li>\n        <li><strong>Consultas com Streams:</strong> refatore uma consulta por vez e compare clareza, não só tamanho do código.</li>\n        <li><strong>Relatório:</strong> gere saída ordenada e reproduzível.</li>\n      </ol>","fidelityText":"Modelo: crie Venda com LocalDate, BigDecimal e campos obrigatórios. Parser: transforme uma linha em sucesso ou rejeição com número da linha e motivo. Consultas com laços: implemente primeiro de modo explícito para validar semântica. Consultas com Streams: refatore uma consulta por vez e compare clareza, não só tamanho do código. Relatório: gere saída ordenada e reproduzível."},{"id":"mini-analisador-vendas-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Consultas obrigatórias</h2>","fidelityText":"Consultas obrigatórias"},{"id":"mini-analisador-vendas-checklist-10","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-analisador-vendas-checklist-0","label":"Faturamento por mês e por categoria."},{"id":"mini-analisador-vendas-checklist-1","label":"Três produtos com maior receita, com desempate definido."},{"id":"mini-analisador-vendas-checklist-2","label":"Clientes inativos em um intervalo."},{"id":"mini-analisador-vendas-checklist-3","label":"Ticket médio sem divisão por zero."},{"id":"mini-analisador-vendas-checklist-4","label":"Relatório de linhas rejeitadas com número, campo e motivo."},{"id":"mini-analisador-vendas-checklist-5","label":"Versões sequencial e paralela medidas somente depois de existir dataset grande o bastante."}]},{"id":"mini-analisador-vendas-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>Não presuma que paralelo é mais rápido.</b> Registre tamanho do conjunto, aquecimento e tempos; explique por que um microteste ingênuo engana. Para dataset pequeno, a versão paralela normalmente só adiciona custo e risco de efeito compartilhado.</div>","fidelityText":"Não presuma que paralelo é mais rápido. Registre tamanho do conjunto, aquecimento e tempos; explique por que um microteste ingênuo engana. Para dataset pequeno, a versão paralela normalmente só adiciona custo e risco de efeito compartilhado."},{"id":"mini-analisador-vendas-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Saída esperada</h2>","fidelityText":"Saída esperada"},{"id":"mini-analisador-vendas-content-13","type":"html","authorship":"legacy-preserved","html":"<p>O relatório deve declarar a origem, quantidade de vendas aceitas, quantidade de rejeições, período coberto e critérios de ordenação. Se dois produtos empatam, o desempate precisa estar no código e no README.</p>","fidelityText":"O relatório deve declarar a origem, quantidade de vendas aceitas, quantidade de rejeições, período coberto e critérios de ordenação. Se dois produtos empatam, o desempate precisa estar no código e no README."},{"id":"mini-analisador-vendas-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-analisador-vendas:0","type":"quiz","authorship":"legacy-preserved","conceptId":"qual-uso-de-stream-e-seguro","prompt":"Qual uso de Stream é seguro?","options":[{"id":"mini-analisador-vendas:0:option:0","label":"Operações sem efeitos colaterais compartilhados e coleta explícita do resultado.","correct":true,"explanation":"Pipeline sem efeitos compartilhados preserva determinismo e materializa resultado explicitamente."},{"id":"mini-analisador-vendas:0:option:1","label":"Alterar uma ArrayList externa dentro de parallelStream.","correct":false,"explanation":"Mutar lista externa em parallelStream cria condição de corrida e resultado não determinístico."},{"id":"mini-analisador-vendas:0:option:2","label":"Reutilizar o mesmo Stream depois de uma operação terminal.","correct":false,"explanation":"Stream consumido por terminal não pode ser reutilizado; crie outro pipeline a partir da fonte."}],"sourceIndex":15},{"id":"mini-analisador-vendas:1","type":"quiz","authorship":"legacy-preserved","conceptId":"uma-linha-do-csv-tem-o-campo-valor-vazio-o-que-a-implementacao-correta-d","prompt":"Uma linha do CSV tem o campo valor vazio. O que a implementação correta do parser deve fazer?","options":[{"id":"mini-analisador-vendas:1:option:0","label":"Rejeitar a linha, registrando o número da linha e o motivo (campo valor vazio), sem interromper o processamento das demais linhas.","correct":true,"explanation":"Rejeição por linha com motivo é o que permite processar um arquivo real, que quase sempre tem alguma linha problemática, sem perder as vendas válidas."},{"id":"mini-analisador-vendas:1:option:1","label":"Ignorar o campo e calcular o relatório como se o valor fosse zero.","correct":false,"explanation":"Tratar campo vazio como zero distorce silenciosamente o faturamento reportado."},{"id":"mini-analisador-vendas:1:option:2","label":"Lançar uma exceção não tratada que interrompe a leitura de todo o arquivo.","correct":false,"explanation":"Uma exceção não tratada interrompendo tudo transforma um problema de uma linha em indisponibilidade do relatório inteiro."}],"sourceIndex":16},{"id":"mini-analisador-vendas:2","type":"quiz","authorship":"legacy-preserved","conceptId":"o-relatorio-calcula-o-ticket-medio-receita-total-dividida-pelo-numero-de","prompt":"O relatório calcula o ticket médio (receita total dividida pelo número de vendas). O filtro para um cliente específico não retorna nenhuma venda. O que a implementação correta faz?","options":[{"id":"mini-analisador-vendas:2:option:0","label":"Trata o caso de zero vendas explicitamente (por exemplo, um valor ausente ou zero documentado), sem executar uma divisão por zero.","correct":true,"explanation":"Um conjunto vazio é um caso-limite esperado, não uma falha -- o contrato precisa declarar o que o relatório mostra nesse caso."},{"id":"mini-analisador-vendas:2:option:1","label":"Deixa o programa lançar ArithmeticException e falhar o relatório inteiro.","correct":false,"explanation":"ArithmeticException não tratada derruba o relatório por causa de um cliente sem vendas no período, um caso plausível e comum."},{"id":"mini-analisador-vendas:2:option:2","label":"Assume que o ticket médio é sempre 1 quando não há vendas para dividir.","correct":false,"explanation":"Inventar o valor 1 esconde a ausência de dados como se fosse um resultado real."}],"sourceIndex":17},{"id":"mini-analisador-vendas:3","type":"quiz","authorship":"legacy-preserved","conceptId":"antes-de-medir-alguem-afirma-que-a-versao-paralela-do-relatorio-com-cert","prompt":"Antes de medir, alguém afirma que a versão paralela do relatório \"com certeza\" é mais rápida que a sequencial. Qual atitude é coerente com o projeto?","options":[{"id":"mini-analisador-vendas:3:option:0","label":"Medir com o dataset real (tamanho, aquecimento, repetições) antes de afirmar qualquer coisa -- para datasets pequenos, o paralelismo costuma só adicionar overhead.","correct":true,"explanation":"Overhead de coordenação entre threads costuma superar o ganho em datasets pequenos; só a medição real revela isso."},{"id":"mini-analisador-vendas:3:option:1","label":"Aceitar a afirmação, já que parallelStream sempre distribui o trabalho entre os núcleos disponíveis.","correct":false,"explanation":"parallelStream distribuir trabalho não implica que o resultado seja mais rápido -- depende de tamanho do dataset e custo de coordenação."},{"id":"mini-analisador-vendas:3:option:2","label":"Comparar apenas uma execução de cada versão, sem aquecimento, e usar esse número como conclusão final.","correct":false,"explanation":"Uma única execução sem aquecimento mede ruído de JIT/cache, não o comportamento estável do programa."}],"sourceIndex":18},{"id":"mini-analisador-vendas-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"vendas-table","type":"table","authorship":"authored","title":"Contrato mínimo do dataset","headers":["Campo","Tipo esperado","Falha planejada"],"rows":[["idVenda","texto não vazio","linha rejeitada"],["data","LocalDate ISO","linha rejeitada com número"],["categoria/produto","texto normalizado","categoria desconhecida explícita"],["valor","BigDecimal positivo","linha rejeitada"]]},{"id":"vendas-quiz","type":"quiz","authorship":"authored","conceptId":"relatorio-dados-deterministico","prompt":"Dois produtos empataram em receita. O que torna o ranking reprodutível?","options":[{"id":"vendas-q-a","label":"Declarar desempate, por exemplo receita desc e nome asc.","correct":true,"explanation":"A ordem deixa de depender do acaso da estrutura ou da ordem de entrada."},{"id":"vendas-q-b","label":"Usar HashMap, porque ele sempre ordena chaves alfabeticamente.","correct":false,"explanation":"HashMap não promete ordem de iteração."},{"id":"vendas-q-c","label":"Rodar o relatório várias vezes e escolher a saída mais bonita.","correct":false,"explanation":"Repetição não define contrato; apenas esconde a instabilidade."}]},{"id":"vendas-exercise-contrato","type":"exercise","authorship":"authored","title":"Antes de codificar: contrato do relatório de vendas","prompt":"Antes de escrever o parser, responda por escrito: (1) o que a implementação faz quando uma linha do CSV tem um campo faltando ou com formato inválido -- ela para o programa inteiro ou segue processando as demais linhas; (2) como o desempate de um ranking (por exemplo, os três produtos com maior receita) fica determinístico entre execuções; (3) o que acontece quando o ticket médio é calculado para um conjunto sem nenhuma venda; (4) que evidência, além de captura de tela, prova que a versão paralela foi medida antes de ser adotada.","difficulty":"intermediate","criteria":["A resposta 1 descreve rejeição por linha com número e motivo, sem interromper o processamento das linhas restantes.","A resposta 2 declara um critério de desempate explícito (ex.: receita desc, nome asc), não a ordem acidental de uma estrutura de dados.","A resposta 3 trata a divisão por zero de forma explícita, sem deixar o programa lançar exceção não tratada.","A resposta 4 aponta números reais de tempo, tamanho do dataset e aquecimento, não uma afirmação sem medição."]},{"id":"vendas-project","type":"project","authorship":"authored","title":"Analisador de vendas reproduzível","brief":"Leia um CSV de vendas segundo o contrato mínimo definido, produza consultas com Streams e gere um relatório determinístico, com rejeições, desempate e medição honesta de paralelismo.","requirements":["Parser transforma cada linha em Venda válida ou rejeição com número da linha e motivo","Consultas cobrem faturamento por mês/categoria, top-3 produtos com desempate, clientes inativos e ticket médio sem divisão por zero","Relatório declara origem, quantidade aceita, quantidade rejeitada, período coberto e critério de ordenação","Versão paralela só é comparada à sequencial com medição real de tempo em dataset de tamanho relevante","Segredos e dados pessoais não vazam para logs do relatório"],"guidance":"supported","acceptanceCriteria":["Linha malformada gera rejeição registrada, sem derrubar o processamento das demais","Execuções repetidas com os mesmos dados produzem exatamente a mesma ordem no ranking","Ticket médio de um conjunto vazio não lança exceção não tratada","README documenta o contrato de CSV, o critério de desempate e os comandos reproduzíveis","Comparação paralelo vs sequencial registra tamanho do dataset, aquecimento e tempos medidos"],"knowledgeMatrix":[{"requirement":"Parsing resiliente por linha","conceptIds":["csv-fronteira-textual"],"chapterIds":["java-io"],"expectedEvidence":"Linha com campo vazio ou formato inválido vira rejeição com número e motivo, e as demais linhas continuam sendo processadas."},{"requirement":"Consultas com Streams","conceptIds":["pipeline-lazy-terminal","reduce-collect-contrato"],"chapterIds":["streams"],"expectedEvidence":"Consulta implementada primeiro com laço explícito e depois refatorada para Stream, comparando clareza."},{"requirement":"Ranking determinístico","conceptIds":["relatorio-dados-deterministico"],"chapterIds":["mini-analisador-vendas"],"expectedEvidence":"Duas execuções com os mesmos dados produzem exatamente a mesma ordem, incluindo o desempate."},{"requirement":"Datas e valores monetários","conceptIds":["java-time-semantica"],"chapterIds":["javamoderno"],"expectedEvidence":"Datas usam LocalDate e valores usam BigDecimal, sem aritmética de ponto flutuante em dinheiro."},{"requirement":"Medição honesta de paralelismo","conceptIds":["pipeline-lazy-terminal"],"chapterIds":["streams"],"expectedEvidence":"Comparação sequencial vs paralela registra tamanho do dataset, aquecimento e tempos, não suposição."}]}],"resources":[{"id":"vendas-streams-dev","type":"guide","title":"dev.java: Stream API","url":"https://dev.java/learn/api/streams/","reinforces":"Base oficial para operações de agregação, coleta e pipeline usadas no projeto.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"vendas-comparator-api","type":"reference","title":"Comparator API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Comparator.html","reinforces":"Define composição de critérios de ordenação e desempate.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A Stream sales analyzer operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a Stream sales analyzer operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this Stream sales analyzer chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"id,date,customer,product,category,value","instruction":"A Stream sales analyzer operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this Stream sales analyzer chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["parsing-resiliente-por-linha","ranking-deterministico-com-desempate","ticket-medio-sem-divisao-por-zero","medicao-honesta-de-paralelismo","separacao-parser-consulta-relatorio"],"evidenceBlocks":{"parsing-resiliente-por-linha":["mini-analisador-vendas-content-6","mini-analisador-vendas:1","vendas-project"],"ranking-deterministico-com-desempate":["mini-analisador-vendas-checklist-10","vendas-quiz","vendas-project"],"ticket-medio-sem-divisao-por-zero":["mini-analisador-vendas-checklist-10","mini-analisador-vendas:2","vendas-exercise-contrato"],"medicao-honesta-de-paralelismo":["mini-analisador-vendas-content-11","mini-analisador-vendas:3","vendas-exercise-contrato"],"separacao-parser-consulta-relatorio":["mini-analisador-vendas-content-8","vendas-project","vendas-exercise-contrato"]},"primarySources":["dev.java: Stream API -- https://dev.java/learn/api/streams/","Comparator API -- Java 21 -- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Comparator.html"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"big-o","moduleId":"algorithms-data-structures","order":0,"title":"Complexidade de Algoritmos (Big O)","summary":"O capítulo 34 já mostrou EXPLAIN ANALYZE comparando Seq Scan com Index Scan. Big O é a linguagem formal para descrever exatamente esse tipo de diferença de performance — não em milissegundos (que variam por máquina), mas em como o tempo cresce conforme o tamanho da entrada cresce.","objectives":["Explicar crescimento assintótico de tempo e espaço","Classificar loops, buscas e uso de coleções sem prometer milissegundos","Separar Big O, medição e otimização prática"],"whyItExists":"Depois de coleções, streams e projetos com dados, o aluno precisa de vocabulário para comparar soluções sem depender de achismo de performance.","prerequisiteChapterIds":["zenith-cli-inicial"],"conceptIds":["as-complexidades-mais-comuns-da-melhor-para-a-pior","complexidade-de-espaco-nao-so-de-tempo"],"introducedConceptIds":["complexidade-assintotica","tempo-espaco-tradeoff","medicao-evidencia-performance"],"usedConceptIds":["contrato-do-laco","contrato-collection-map","bytecode-jit-perfil"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"big-o-intuition","type":"intuition","authorship":"authored","title":"A pergunta é como cresce","body":"Big O não mede o tempo de uma execução. Ele descreve como o trabalho tende a crescer quando a entrada aumenta e permite comparar estratégias antes de medir no ambiente real.","analogyLimit":"A lista telefônica ajuda a enxergar busca linear e binária, mas hardware, cache, JIT e distribuição da entrada ainda influenciam a medição real."},{"id":"big-o-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-java\">Algoritmos</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#logica-programacao\">79 · Lógica de programação</a>, <a class=\"prereq-tag\" href=\"#colecoes\">11 · Coleções</a></div>\n      </div>","fidelityText":"Algoritmos Dificuldade: Intermediário ⏱ ~2h de estudo Pré-requisitos: 79 · Lógica de programação, 11 · Coleções"},{"id":"big-o-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 34 já mostrou <code>EXPLAIN ANALYZE</code> comparando <em>Seq Scan</em> com <em>Index Scan</em>. Big O é a linguagem formal para descrever exatamente esse tipo de diferença de performance — não em milissegundos (que variam por máquina), mas em <strong>como o tempo cresce</strong> conforme o tamanho da entrada cresce.</p>","fidelityText":"O capítulo 34 já mostrou EXPLAIN ANALYZE comparando Seq Scan com Index Scan. Big O é a linguagem formal para descrever exatamente esse tipo de diferença de performance — não em milissegundos (que variam por máquina), mas em como o tempo cresce conforme o tamanho da entrada cresce."},{"id":"big-o-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Imagine procurar um nome em uma lista telefônica. Se a lista <strong>não está ordenada</strong>, você precisa olhar nome por nome até achar — dobrar o tamanho da lista dobra o tempo de busca. Se a lista <strong>está ordenada</strong> (como uma lista telefônica de verdade), você pode abrir no meio, decidir se o nome está antes ou depois, e repetir — dobrar o tamanho da lista adiciona só <strong>um</strong> passo a mais de busca, não o dobro. Essa diferença de comportamento, não o tempo exato em segundos, é o que Big O descreve.</div>","fidelityText":"Imagine procurar um nome em uma lista telefônica. Se a lista não está ordenada, você precisa olhar nome por nome até achar — dobrar o tamanho da lista dobra o tempo de busca. Se a lista está ordenada (como uma lista telefônica de verdade), você pode abrir no meio, decidir se o nome está antes ou depois, e repetir — dobrar o tamanho da lista adiciona só um passo a mais de busca, não o dobro. Essa diferença de comportamento, não o tempo exato em segundos, é o que Big O descreve."},{"id":"big-o-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>As complexidades mais comuns, da melhor para a pior</h2>","fidelityText":"As complexidades mais comuns, da melhor para a pior"},{"id":"big-o-content-5","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Notação</th><th>Nome</th><th>Exemplo</th></tr>\n        <tr><td><code>O(1)</code></td><td>Constante</td><td>Acessar <code>array[5]</code>, ou <code>HashMap.get(chave)</code> — capítulo 11</td></tr>\n        <tr><td><code>O(log n)</code></td><td>Logarítmica</td><td>Busca binária, <code>TreeMap</code>, um índice de banco (capítulo 34)</td></tr>\n        <tr><td><code>O(n)</code></td><td>Linear</td><td>Percorrer uma lista uma vez — um <code>for</code> simples</td></tr>\n        <tr><td><code>O(n log n)</code></td><td>Log-linear</td><td>Algoritmos de ordenação eficientes (capítulo 83)</td></tr>\n        <tr><td><code>O(n²)</code></td><td>Quadrática</td><td>Loop dentro de loop sobre a mesma coleção</td></tr>\n        <tr><td><code>O(2ⁿ)</code></td><td>Exponencial</td><td>Recursão ingênua de Fibonacci (capítulo 81)</td></tr>\n      </tbody></table>","fidelityText":"NotaçãoNomeExemplo O(1)ConstanteAcessar array[5], ou HashMap.get(chave) — capítulo 11 O(log n)LogarítmicaBusca binária, TreeMap, um índice de banco (capítulo 34) O(n)LinearPercorrer uma lista uma vez — um for simples O(n log n)Log-linearAlgoritmos de ordenação eficientes (capítulo 83) O(n²)QuadráticaLoop dentro de loop sobre a mesma coleção O(2ⁿ)ExponencialRecursão ingênua de Fibonacci (capítulo 81)"},{"id":"big-o-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"// O(1) -- não importa o tamanho de \"lista\", sempre um passo:\nint first = list.get(0);\n\n// O(n) -- um passo POR elemento:\nfor (int x : list) { System.out.println(x); }\n\n// O(n²) -- para cada elemento, percorre TODOS de novo -- CUIDADO:\nfor (int a : list) {\n    for (int b : list) {\n        if (a == b) System.out.println(\"found pair\");\n    }\n}","fidelityText":"// O(1) -- não importa o tamanho de \"lista\", sempre um passo: int primeiro = lista.get(0); // O(n) -- um passo POR elemento: for (int x : lista) { System.out.println(x); } // O(n²) -- para cada elemento, percorre TODOS de novo -- CUIDADO: for (int a : lista) { for (int b : lista) { if (a == b) System.out.println(\"achou par\"); } }","highlightedHtml":"<span class=\"com\">// O(1) -- não importa o tamanho de \"lista\", sempre um passo:</span>\n<span class=\"kw\">int</span> first = list.get(0);\n\n<span class=\"com\">// O(n) -- um passo POR elemento:</span>\n<span class=\"kw\">for</span> (<span class=\"kw\">int</span> x : list) { System.out.println(x); }\n\n<span class=\"com\">// O(n²) -- para cada elemento, percorre TODOS de novo -- CUIDADO:</span>\n<span class=\"kw\">for</span> (<span class=\"kw\">int</span> a : list) {\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> b : list) {\n        <span class=\"kw\">if</span> (a == b) System.out.println(<span class=\"str\">\"found pair\"</span>);\n    }\n}","caption":"Exemplo executável de big-o.","explanation":["Acesso por índice em lista adequada é constante em relação ao tamanho da entrada.","Um loop simples toca cada elemento uma vez e cresce linearmente.","Dois loops sobre a mesma entrada multiplicam o trabalho e tendem a custo quadrático."],"commonMistakes":["Tratar toda lista como acesso O(1) sem considerar implementação","Ignorar o custo da operação dentro do loop","Confundir número de linhas com complexidade"]},{"id":"big-o-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Essa é exatamente a mesma matemática por trás do problema N+1 (capítulo 46) — se buscar 1 autor custa <code>O(1)</code> mas você faz isso <strong>dentro</strong> de um loop de N livros, o custo total vira <code>O(n)</code> chamadas ao banco, cada uma com seu próprio custo de rede, em vez de <code>O(1)</code> chamada com um <code>JOIN</code>. Big O não é só teoria acadêmica — é literalmente o vocabulário formal para explicar por que aquele bug de performance específico acontece.</div>","fidelityText":"Essa é exatamente a mesma matemática por trás do problema N+1 (capítulo 46) — se buscar 1 autor custa O(1) mas você faz isso dentro de um loop de N livros, o custo total vira O(n) chamadas ao banco, cada uma com seu próprio custo de rede, em vez de O(1) chamada com um JOIN. Big O não é só teoria acadêmica — é literalmente o vocabulário formal para explicar por que aquele bug de performance específico acontece."},{"id":"big-o-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Complexidade de espaço, não só de tempo</h2>","fidelityText":"Complexidade de espaço, não só de tempo"},{"id":"big-o-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Big O também mede memória usada, não só tempo de execução — um algoritmo pode ser rápido (<code>O(n)</code> em tempo) mas gastar memória proporcional ao tamanho da entrada (<code>O(n)</code> em espaço) para conseguir isso, um trade-off clássico entre os dois recursos.</p>","fidelityText":"Big O também mede memória usada, não só tempo de execução — um algoritmo pode ser rápido (O(n) em tempo) mas gastar memória proporcional ao tamanho da entrada (O(n) em espaço) para conseguir isso, um trade-off clássico entre os dois recursos."},{"id":"big-o-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não otimize prematuramente com base só em Big O.</b> Um algoritmo <code>O(n²)</code> rodando em uma coleção de 50 itens é instantâneo na prática — otimizar isso \"porque O(n²) é ruim\" sem medir é desperdiçar esforço em algo que nunca seria um problema real. Big O importa quando <code>n</code> é (ou pode vir a ser) genuinamente grande — a mesma lição prática do <code>EXPLAIN ANALYZE</code> do capítulo 34: meça antes de otimizar.</div>","fidelityText":"Não otimize prematuramente com base só em Big O. Um algoritmo O(n²) rodando em uma coleção de 50 itens é instantâneo na prática — otimizar isso \"porque O(n²) é ruim\" sem medir é desperdiçar esforço em algo que nunca seria um problema real. Big O importa quando n é (ou pode vir a ser) genuinamente grande — a mesma lição prática do EXPLAIN ANALYZE do capítulo 34: meça antes de otimizar."},{"id":"big-o-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Ao analisar a complexidade de um trecho de código, pergunte: \"para cada elemento da entrada, quantas vezes eu toco em outros elementos?\" Se a resposta é \"uma vez, no máximo\" → provavelmente <code>O(n)</code>. Se é \"para cada elemento, eu olho todos os outros de novo\" → provavelmente <code>O(n²)</code>. Esse hábito de perguntar resolve a maioria das análises do dia a dia sem precisar de matemática formal.</div>","fidelityText":"Ao analisar a complexidade de um trecho de código, pergunte: \"para cada elemento da entrada, quantas vezes eu toco em outros elementos?\" Se a resposta é \"uma vez, no máximo\" → provavelmente O(n). Se é \"para cada elemento, eu olho todos os outros de novo\" → provavelmente O(n²). Esse hábito de perguntar resolve a maioria das análises do dia a dia sem precisar de matemática formal."},{"id":"big-o-exercise-12","type":"exercise","authorship":"legacy-preserved","title":"Exercício 80.1 — Classificando complexidade","prompt":"Para cada trecho, identifique a complexidade Big O e justifique: (a) buscar um elemento em um HashSet; (b) verificar se um array tem elementos duplicados usando dois loops aninhados; (c) o mesmo problema (b), mas usando um HashSet auxiliar para rastrear valores já vistos.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 80.1 — Classificando complexidademédio Para cada trecho, identifique a complexidade Big O e justifique: (a) buscar um elemento em um HashSet; (b) verificar se um array tem elementos duplicados usando dois loops aninhados; (c) o mesmo problema (b), mas usando um HashSet auxiliar para rastrear valores já vistos. Ver solução (a) O(1) em média — HashSet usa hashing como HashMap; colisões, distribuição ruim e redimensionamento impedem tratar isso como garantia absoluta. (b) O(n²) — para cada elemento, compara com os demais. (c) O(n) esperado — percorre uma vez e usa consultas/inserções de custo médio constante, trocando memória adicional por tempo.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 80.1 — Classificando complexidade</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Para cada trecho, identifique a complexidade Big O e justifique: (a) buscar um elemento em um <code>HashSet</code>; (b) verificar se um array tem elementos duplicados usando dois loops aninhados; (c) o mesmo problema (b), mas usando um <code>HashSet</code> auxiliar para rastrear valores já vistos.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>(a) O(1) em média</strong> — <code>HashSet</code> usa hashing como <code>HashMap</code>; colisões, distribuição ruim e redimensionamento impedem tratar isso como garantia absoluta. <strong>(b) O(n²)</strong> — para cada elemento, compara com os demais. <strong>(c) O(n) esperado</strong> — percorre uma vez e usa consultas/inserções de custo médio constante, trocando memória adicional por tempo.</p>\n        </div>\n      </div>"},{"id":"big-o-table","type":"table","authorship":"authored","title":"Análise, benchmark e profiling","headers":["Ferramenta mental","Responde","Não responde"],"rows":[["Big O","crescimento com n","tempo exato"],["Benchmark","tempo em cenário controlado","causa interna sozinho"],["Profiler","onde tempo/memória são gastos","qual contrato de negócio escolher"]]},{"id":"big-o-quiz","type":"quiz","authorship":"authored","conceptId":"complexidade-assintotica","prompt":"Um algoritmo O(n²) em 30 itens sempre deve ser trocado por outro O(n log n)?","options":[{"id":"big-o-q-a","label":"Não; Big O orienta crescimento, mas decisão prática exige tamanho esperado, clareza e medição.","correct":true,"explanation":"A classe assintótica não substitui contexto e evidência."},{"id":"big-o-q-b","label":"Sim; qualquer O(n²) é bug de produção por definição.","correct":false,"explanation":"Entradas pequenas e código simples podem ser perfeitamente aceitáveis."},{"id":"big-o-q-c","label":"Não, porque Big O mede apenas uso de disco.","correct":false,"explanation":"Big O pode descrever tempo e espaço; o erro está em tratar a notação como decisão automática."}]}],"resources":[{"id":"big-o-jdk-collections","type":"reference","title":"Collections Framework Overview — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/doc-files/coll-overview.html","reinforces":"Relaciona estruturas do JDK e seus contratos de uso, base para discutir custo por operação.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"big-o-jmh","type":"reference","title":"JMH project","url":"https://openjdk.org/projects/code-tools/jmh/","reinforces":"Fonte oficial do Java Microbenchmark Harness para medição controlada.","language":"en","publisher":"OpenJDK","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A big o operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a big o operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this big o chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"// O(1) -- não importa o tamanho de \"lista\", sempre um passo:","instruction":"A big o operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this big o chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"recursao","moduleId":"algorithms-data-structures","order":1,"title":"Recursão","summary":"Um método que chama a si mesmo. Parece paradoxal na primeira vez que se vê, mas é a ferramenta natural para problemas que são, por definição, feitos de versões menores de si mesmos.","objectives":["Identificar caso base, caso recursivo e progresso","Rastrear stack frames e risco de StackOverflowError","Comparar recursão, iteração e memoization"],"whyItExists":"Algoritmos de divisão, árvores, grafos e programação dinâmica exigem entender chamadas que resolvem versões menores do mesmo problema sem perder o controle do ciclo de vida da stack.","prerequisiteChapterIds":["big-o"],"conceptIds":["os-dois-ingredientes-obrigatorios-de-toda-recursao","visualizando-a-pilha-de-chamadas","recursao-vs-iteracao-quando-cada-uma-vence"],"introducedConceptIds":["recursao-caso-base","stack-frame-recursivo","memoization-subproblemas"],"usedConceptIds":["complexidade-assintotica","areas-runtime-modelo","contrato-collection-map"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"recursao-intuition","type":"intuition","authorship":"authored","title":"Uma chamada menor precisa ser confiável","body":"Recursão funciona quando você consegue resolver diretamente um caso pequeno e transformar o caso maior em um caso menor do mesmo formato.","analogyLimit":"Bonecas russas ajudam a imaginar camadas, mas a JVM cria frames reais de chamada; entrada grande pode estourar a stack."},{"id":"recursao-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-java\">Algoritmos</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo + prática (costuma exigir mais de uma leitura)</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#big-o\">80 · Complexidade (Big O)</a>, <a class=\"prereq-tag\" href=\"#classes\">01 · Classes e objetos</a> (stack)</div>\n      </div>","fidelityText":"Algoritmos Dificuldade: Avançado ⏱ ~2h30 de estudo + prática (costuma exigir mais de uma leitura) Pré-requisitos: 80 · Complexidade (Big O), 01 · Classes e objetos (stack)"},{"id":"recursao-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Um método que chama <strong>a si mesmo</strong>. Parece paradoxal na primeira vez que se vê, mas é a ferramenta natural para problemas que são, por definição, feitos de versões menores de si mesmos.</p>","fidelityText":"Um método que chama a si mesmo. Parece paradoxal na primeira vez que se vê, mas é a ferramenta natural para problemas que são, por definição, feitos de versões menores de si mesmos."},{"id":"recursao-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em bonecas russas (matryoshka): para saber quantas bonecas existem no total, você não precisa de uma fórmula especial — só precisa saber \"abrir uma boneca e repetir o mesmo processo na boneca de dentro, até chegar na menor que não abre mais\". Recursão é exatamente esse raciocínio: resolver o problema pequeno o suficiente diretamente (o <strong>caso base</strong>), e para todo o resto, resolver \"uma camada\" e delegar o restante para uma chamada menor de si mesmo.</div>","fidelityText":"Pense em bonecas russas (matryoshka): para saber quantas bonecas existem no total, você não precisa de uma fórmula especial — só precisa saber \"abrir uma boneca e repetir o mesmo processo na boneca de dentro, até chegar na menor que não abre mais\". Recursão é exatamente esse raciocínio: resolver o problema pequeno o suficiente diretamente (o caso base), e para todo o resto, resolver \"uma camada\" e delegar o restante para uma chamada menor de si mesmo."},{"id":"recursao-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Os dois ingredientes obrigatórios de toda recursão</h2>","fidelityText":"Os dois ingredientes obrigatórios de toda recursão"},{"id":"recursao-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"int factorial(int n) {\n    if (n <= 1) return 1;              // CASO BASE -- sem isso, recursão infinita\n    return n * factorial(n - 1);         // CASO RECURSIVO -- problema menor + chamada a si mesmo\n}\n// fatorial(4) = 4 * fatorial(3)\n//             = 4 * (3 * fatorial(2))\n//             = 4 * (3 * (2 * fatorial(1)))\n//             = 4 * (3 * (2 * 1))\n//             = 24","fidelityText":"int fatorial(int n) { if (n <= 1) return 1; // CASO BASE -- sem isso, recursão infinita return n * fatorial(n - 1); // CASO RECURSIVO -- problema menor + chamada a si mesmo } // fatorial(4) = 4 * fatorial(3) // = 4 * (3 * fatorial(2)) // = 4 * (3 * (2 * fatorial(1))) // = 4 * (3 * (2 * 1)) // = 24","highlightedHtml":"<span class=\"kw\">int</span> <span class=\"fn\">factorial</span>(<span class=\"kw\">int</span> n) {\n    <span class=\"kw\">if</span> (n &lt;= 1) <span class=\"kw\">return</span> 1;              <span class=\"com\">// CASO BASE -- sem isso, recursão infinita</span>\n    <span class=\"kw\">return</span> n * factorial(n - 1);         <span class=\"com\">// CASO RECURSIVO -- problema menor + chamada a si mesmo</span>\n}\n<span class=\"com\">// fatorial(4) = 4 * fatorial(3)\n//             = 4 * (3 * fatorial(2))\n//             = 4 * (3 * (2 * fatorial(1)))\n//             = 4 * (3 * (2 * 1))\n//             = 24</span>","caption":"Exemplo executável de recursao.","explanation":["n <= 1 é o caso base que encerra a cadeia de chamadas.","fatorial(n - 1) reduz o problema e permite combinar o resultado menor com n."],"commonMistakes":["Colocar caso base depois de chamada infinita","Chamar fatorial(n) de novo sem reduzir"]},{"id":"recursao-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Todo caso recursivo precisa se aproximar do caso base.</b> Esquecer o caso base (ou escrever uma condição que nunca se torna verdadeira) causa recursão infinita — que na prática vira um <code>StackOverflowError</code>, porque cada chamada recursiva empilha um novo frame na stack (capítulo 01) até estourar a memória reservada para ela.</div>","fidelityText":"Todo caso recursivo precisa se aproximar do caso base. Esquecer o caso base (ou escrever uma condição que nunca se torna verdadeira) causa recursão infinita — que na prática vira um StackOverflowError, porque cada chamada recursiva empilha um novo frame na stack (capítulo 01) até estourar a memória reservada para ela."},{"id":"recursao-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Visualizando a pilha de chamadas</h2>","fidelityText":"Visualizando a pilha de chamadas"},{"id":"recursao-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Cada chamada recursiva empilha um novo \"andar\" na stack de execução (capítulo 01) — como empilhar pratos, um em cima do outro. <code>fatorial(4)</code> não retorna nada até que <code>fatorial(3)</code> retorne, que não retorna até <code>fatorial(2)</code> retornar, e assim por diante — só quando o caso base é alcançado é que a pilha começa a ser \"desempilhada\", de baixo para cima, cada chamada multiplicando seu resultado antes de devolver para quem a chamou.</div>","fidelityText":"Cada chamada recursiva empilha um novo \"andar\" na stack de execução (capítulo 01) — como empilhar pratos, um em cima do outro. fatorial(4) não retorna nada até que fatorial(3) retorne, que não retorna até fatorial(2) retornar, e assim por diante — só quando o caso base é alcançado é que a pilha começa a ser \"desempilhada\", de baixo para cima, cada chamada multiplicando seu resultado antes de devolver para quem a chamou."},{"id":"recursao-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Recursão vs iteração — quando cada uma vence</h2>","fidelityText":"Recursão vs iteração — quando cada uma vence"},{"id":"recursao-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"// versão iterativa do mesmo fatorial (capítulo 79, raciocínio de loop):\nint factorialIterativo(int n) {\n    int result = 1;\n    for (int i = 2; i <= n; i++) result *= i;\n    return result;\n}\n// mesmo resultado, SEM o custo de empilhar N frames na stack","fidelityText":"// versão iterativa do mesmo fatorial (capítulo 79, raciocínio de loop): int fatorialIterativo(int n) { int resultado = 1; for (int i = 2; i <= n; i++) resultado *= i; return resultado; } // mesmo resultado, SEM o custo de empilhar N frames na stack","highlightedHtml":"<span class=\"com\">// versão iterativa do mesmo fatorial (capítulo 79, raciocínio de loop):</span>\n<span class=\"kw\">int</span> <span class=\"fn\">factorialIterativo</span>(<span class=\"kw\">int</span> n) {\n    <span class=\"kw\">int</span> result = 1;\n    <span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = 2; i &lt;= n; i++) result *= i;\n    <span class=\"kw\">return</span> result;\n}\n<span class=\"com\">// mesmo resultado, SEM o custo de empilhar N frames na stack</span>","caption":"Exemplo executável de recursao.","explanation":["A versão iterativa mantém acumulador e não cria um frame por repetição.","Para contagens simples, loop costuma ser mais direto e economiza stack."],"commonMistakes":["Achar que recursão é sempre mais elegante","Ignorar overflow numérico do fatorial"]},{"id":"recursao-content-11","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Recursão</th><th>Iteração</th></tr>\n        <tr><td>Legibilidade</td><td>Frequentemente mais clara para problemas naturalmente recursivos (árvores, capítulo 82)</td><td>Mais direta para contagens simples</td></tr>\n        <tr><td>Custo de memória</td><td>Uma stack frame por chamada — pode estourar em entradas grandes</td><td>Não empilha nada extra</td></tr>\n        <tr><td>Bom para</td><td>Estruturas recursivas por natureza: árvores, backtracking, \"dividir para conquistar\"</td><td>Contagens e transformações lineares simples</td></tr>\n      </tbody></table>","fidelityText":"RecursãoIteração LegibilidadeFrequentemente mais clara para problemas naturalmente recursivos (árvores, capítulo 82)Mais direta para contagens simples Custo de memóriaUma stack frame por chamada — pode estourar em entradas grandesNão empilha nada extra Bom paraEstruturas recursivas por natureza: árvores, backtracking, \"dividir para conquistar\"Contagens e transformações lineares simples"},{"id":"recursao-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A \"recursão ingênua\" de Fibonacci é o exemplo clássico usado para ensinar por que Big O (capítulo 80) importa na prática: <code>fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)</code> parece elegante, mas recalcula os mesmos subproblemas repetidamente, chegando a <code>O(2ⁿ)</code> — para <code>n=40</code>, isso já são bilhões de chamadas. A correção (guardar resultados já calculados, técnica chamada <em>memoization</em>) transforma isso em <code>O(n)</code>, e é o primeiro contato prático que muita gente tem com programação dinâmica.</div>","fidelityText":"A \"recursão ingênua\" de Fibonacci é o exemplo clássico usado para ensinar por que Big O (capítulo 80) importa na prática: fibonacci(n) = fibonacci(n-1) + fibonacci(n-2) parece elegante, mas recalcula os mesmos subproblemas repetidamente, chegando a O(2ⁿ) — para n=40, isso já são bilhões de chamadas. A correção (guardar resultados já calculados, técnica chamada memoization) transforma isso em O(n), e é o primeiro contato prático que muita gente tem com programação dinâmica."},{"id":"recursao-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ ingênua -- O(2ⁿ), recalcula os mesmos valores repetidamente:\nint fibonacci(int n) {\n    if (n <= 1) return n;\n    return fibonacci(n - 1) + fibonacci(n - 2);\n}\n\n// ✅ com memoization -- O(n), guarda resultados já calculados:\nMap<Integer, Long> cache = new HashMap<>();\nlong fibonacciMemo(int n) {\n    if (n <= 1) return n;\n    if (cache.containsKey(n)) return cache.get(n); // já calculado, não refaz\n    long result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);\n    cache.put(n, result);\n    return result;\n}","fidelityText":"// ❌ ingênua -- O(2ⁿ), recalcula os mesmos valores repetidamente: int fibonacci(int n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); } // ✅ com memoization -- O(n), guarda resultados já calculados: Map<Integer, Long> cache = new HashMap<>(); long fibonacciMemo(int n) { if (n <= 1) return n; if (cache.containsKey(n)) return cache.get(n); // já calculado, não refaz long resultado = fibonacciMemo(n - 1) + fibonacciMemo(n - 2); cache.put(n, resultado); return resultado; }","highlightedHtml":"<span class=\"com\">// ❌ ingênua -- O(2ⁿ), recalcula os mesmos valores repetidamente:</span>\n<span class=\"kw\">int</span> <span class=\"fn\">fibonacci</span>(<span class=\"kw\">int</span> n) {\n    <span class=\"kw\">if</span> (n &lt;= 1) <span class=\"kw\">return</span> n;\n    <span class=\"kw\">return</span> fibonacci(n - 1) + fibonacci(n - 2);\n}\n\n<span class=\"com\">// ✅ com memoization -- O(n), guarda resultados já calculados:</span>\nMap&lt;<span class=\"kw\">Integer</span>, <span class=\"kw\">Long</span>&gt; cache = <span class=\"kw\">new</span> HashMap&lt;&gt;();\n<span class=\"kw\">long</span> <span class=\"fn\">fibonacciMemo</span>(<span class=\"kw\">int</span> n) {\n    <span class=\"kw\">if</span> (n &lt;= 1) <span class=\"kw\">return</span> n;\n    <span class=\"kw\">if</span> (cache.containsKey(n)) <span class=\"kw\">return</span> cache.get(n); <span class=\"com\">// já calculado, não refaz</span>\n    <span class=\"kw\">long</span> result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);\n    cache.put(n, result);\n    <span class=\"kw\">return</span> result;\n}","caption":"Exemplo executável de recursao.","explanation":["Fibonacci ingênuo recalcula os mesmos subproblemas muitas vezes.","Memoization usa Map para guardar resultados já calculados e reduzir recomputação."],"commonMistakes":["Usar cache compartilhado sem política em testes","Confundir memoization com melhoria para qualquer recursão"]},{"id":"recursao-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Ao escrever qualquer recursão, escreva o caso base <strong>primeiro</strong>, antes até de pensar no caso recursivo — é a parte mais fácil de esquecer sob pressão, e sem ela nada mais funciona. Depois, confie no \"salto de fé\": assuma que a chamada recursiva para o problema menor <em>já funciona corretamente</em> (não tente rastrear todas as chamadas mentalmente de uma vez), e escreva só como combinar esse resultado menor no resultado do problema atual.</div>","fidelityText":"Ao escrever qualquer recursão, escreva o caso base primeiro, antes até de pensar no caso recursivo — é a parte mais fácil de esquecer sob pressão, e sem ela nada mais funciona. Depois, confie no \"salto de fé\": assuma que a chamada recursiva para o problema menor já funciona corretamente (não tente rastrear todas as chamadas mentalmente de uma vez), e escreva só como combinar esse resultado menor no resultado do problema atual."},{"id":"recursao-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 81.1 — Soma recursiva de dígitos","prompt":"Escreva um método recursivo somaDigitos(int n) que soma todos os dígitos de um número (ex: somaDigitos(1234) retorna 1+2+3+4=10). Identifique claramente o caso base e o caso recursivo.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 81.1 — Soma recursiva de dígitosmédio Escreva um método recursivo somaDigitos(int n) que soma todos os dígitos de um número (ex: somaDigitos(1234) retorna 1+2+3+4=10). Identifique claramente o caso base e o caso recursivo. Ver solução int somaDigitos(int n) { if (n < 10) return n; // caso base: número de um único dígito return (n % 10) + somaDigitos(n / 10); // último dígito + soma do resto } // somaDigitos(1234) = 4 + somaDigitos(123) // = 4 + (3 + somaDigitos(12)) // = 4 + (3 + (2 + somaDigitos(1))) // = 4 + 3 + 2 + 1 = 10","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 81.1 — Soma recursiva de dígitos</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva um método recursivo <code>somaDigitos(int n)</code> que soma todos os dígitos de um número (ex: <code>somaDigitos(1234)</code> retorna <code>1+2+3+4=10</code>). Identifique claramente o caso base e o caso recursivo.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">int</span> <span class=\"fn\">sumDigitos</span>(<span class=\"kw\">int</span> n) {\n    <span class=\"kw\">if</span> (n &lt; 10) <span class=\"kw\">return</span> n;                       <span class=\"com\">// caso base: número de um único dígito</span>\n    <span class=\"kw\">return</span> (n % 10) + sumDigitos(n / 10);    <span class=\"com\">// último dígito + soma do resto</span>\n}\n<span class=\"com\">// somaDigitos(1234) = 4 + somaDigitos(123)\n//                    = 4 + (3 + somaDigitos(12))\n//                    = 4 + (3 + (2 + somaDigitos(1)))\n//                    = 4 + 3 + 2 + 1 = 10</span></pre>\n        </div>\n      </div>"},{"id":"recursao-exercise-16","type":"exercise","authorship":"legacy-preserved","title":"Exercício 81.2 — De recursivo para iterativo com memoization","prompt":"Implemente fibonacci das duas formas mostradas acima. Conte chamadas para diferentes valores de n e observe a curva de crescimento. Se medir tempo, use System.nanoTime() somente como experimento inicial e depois reproduza com JMH, pois aquecimento do JIT e otimizações distorcem medições isoladas.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 81.2 — De recursivo para iterativo com memoizationdifícil Implemente fibonacci das duas formas mostradas acima. Conte chamadas para diferentes valores de n e observe a curva de crescimento. Se medir tempo, use System.nanoTime() somente como experimento inicial e depois reproduza com JMH, pois aquecimento do JIT e otimizações distorcem medições isoladas. Ver solução A versão ingênua cresce exponencialmente em número de chamadas, enquanto memoization calcula cada subproblema uma vez. O tempo absoluto de n=35 varia com hardware, JVM e aquecimento; a evidência correta é a curva e a contagem, não prometer uma duração específica.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 81.2 — De recursivo para iterativo com memoization</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Implemente <code>fibonacci</code> das duas formas mostradas acima. Conte chamadas para diferentes valores de <code>n</code> e observe a curva de crescimento. Se medir tempo, use <code>System.nanoTime()</code> somente como experimento inicial e depois reproduza com JMH, pois aquecimento do JIT e otimizações distorcem medições isoladas.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>A versão ingênua cresce exponencialmente em número de chamadas, enquanto memoization calcula cada subproblema uma vez. O tempo absoluto de <code>n=35</code> varia com hardware, JVM e aquecimento; a evidência correta é a curva e a contagem, não prometer uma duração específica.</p>\n        </div>\n      </div>"},{"id":"recursao-error","type":"error-case","authorship":"authored","title":"Recursão que não progride","scenario":"O método chama a si mesmo com o mesmo valor de entrada.","symptom":"StackOverflowError ou execução que nunca termina normalmente.","cause":"O caso recursivo não aproxima o problema do caso base.","diagnosis":["verificar condição de parada","comparar argumento atual e próximo","testar menor entrada possível"],"correction":"Reduzir o problema a cada chamada e testar caso base antes do caso recursivo.","prevention":"Escrever caso base, medida de progresso e exemplo pequeno antes do código."},{"id":"recursao-quiz","type":"quiz","authorship":"authored","conceptId":"recursao-caso-base","prompt":"Qual prova mínima uma recursão precisa ter antes de ser considerada correta?","options":[{"id":"recursao-q-a","label":"Existe caso base e cada chamada recursiva aproxima a entrada dele.","correct":true,"explanation":"Sem parada e progresso não há garantia de término."},{"id":"recursao-q-b","label":"Ela usa menos linhas que a versão com for.","correct":false,"explanation":"Sintaxe menor não prova correção nem término."},{"id":"recursao-q-c","label":"Ela chama dois métodos em vez de um.","correct":false,"explanation":"Quantidade de métodos não define a propriedade recursiva essencial."}]}],"resources":[{"id":"recursao-jvms-frames","type":"reference","title":"JVMS 2.6: Frames","url":"https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-2.html#jvms-2.6","reinforces":"Define frames da JVM, pilha e retorno de métodos usados para explicar chamadas recursivas.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"recursao-stackoverflowerror","type":"reference","title":"StackOverflowError API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/StackOverflowError.html","reinforces":"Define o erro observado quando a aplicação recursa além da capacidade da stack.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A recursao operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a recursao operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this recursao chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"int factorial(int n) {","instruction":"A recursao operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this recursao chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"ordenacao-busca","moduleId":"algorithms-data-structures","order":2,"title":"Algoritmos de Ordenação & Busca","summary":"Collections.sort(...) e list.sort(comparator) (capítulo 11) já resolvem ordenação na prática — mas entender como por trás dos panos ajuda a reconhecer trade-offs de performance em situações que a biblioteca padrão não cobre perfeitamente.","objectives":["Implementar busca binária preservando invariante de intervalo","Entender dividir para conquistar por Merge Sort","Comparar estabilidade, memória e API padrão de ordenação"],"whyItExists":"Ordenar e buscar aparecem em bibliotecas prontas, mas a intuição de intervalo, divisão e combinação ajuda a diagnosticar desempenho e escolher comparadores corretos.","prerequisiteChapterIds":["recursao"],"conceptIds":["busca-binaria-a-aplicacao-direta-da-arvore-do-capitulo-anterior","merge-sort-dividir-para-conquistar-o-n-log-n"],"introducedConceptIds":["busca-binaria-invariante","ordenacao-estabilidade","dividir-conquistar"],"usedConceptIds":["complexidade-assintotica","recursao-caso-base","ordem-comparable-comparator"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"ordenacao-intuition","type":"intuition","authorship":"authored","title":"A pré-condição vale mais que a técnica","body":"Busca binária só é correta quando a entrada está ordenada pelo mesmo critério usado na comparação. Ordenação só é confiável quando o comparador respeita um contrato consistente.","analogyLimit":"Dividir uma lista ao meio parece simples, mas off-by-one, overflow e comparador inconsistente quebram o algoritmo silenciosamente."},{"id":"ordenacao-busca-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-java\">Algoritmos</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#big-o\">80 · Complexidade (Big O)</a>, <a class=\"prereq-tag\" href=\"#recursao\">81 · Recursão</a></div>\n      </div>","fidelityText":"Algoritmos Dificuldade: Avançado ⏱ ~2h de estudo Pré-requisitos: 80 · Complexidade (Big O), 81 · Recursão"},{"id":"ordenacao-busca-content-2","type":"html","authorship":"legacy-preserved","html":"<p><code>Collections.sort(...)</code> e <code>list.sort(comparator)</code> (capítulo 11) já resolvem ordenação na prática — mas entender <em>como</em> por trás dos panos ajuda a reconhecer trade-offs de performance em situações que a biblioteca padrão não cobre perfeitamente.</p>","fidelityText":"Collections.sort(...) e list.sort(comparator) (capítulo 11) já resolvem ordenação na prática — mas entender como por trás dos panos ajuda a reconhecer trade-offs de performance em situações que a biblioteca padrão não cobre perfeitamente."},{"id":"ordenacao-busca-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Busca binária — a aplicação direta da árvore do capítulo anterior</h2>","fidelityText":"Busca binária — a aplicação direta da árvore do capítulo anterior"},{"id":"ordenacao-busca-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"int searchBinaria(int[] sorted, int alvo) {\n    int start = 0, end = sorted.length - 1;\n    while (start <= end) {\n        int middle = start + (end - start) / 2; // evita overflow de (inicio+fim)/2 em arrays gigantes\n        if (sorted[middle] == alvo) return middle;\n        else if (sorted[middle] < alvo) start = middle + 1; // descarta metade esquerda\n        else end = middle - 1;                            // descarta metade direita\n    }\n    return -1; // não encontrado\n}\n// O(log n) -- SÓ funciona se o array já estiver ORDENADO","fidelityText":"int buscaBinaria(int[] ordenado, int alvo) { int inicio = 0, fim = ordenado.length - 1; while (inicio <= fim) { int meio = inicio + (fim - inicio) / 2; // evita overflow de (inicio+fim)/2 em arrays gigantes if (ordenado[meio] == alvo) return meio; else if (ordenado[meio] < alvo) inicio = meio + 1; // descarta metade esquerda else fim = meio - 1; // descarta metade direita } return -1; // não encontrado } // O(log n) -- SÓ funciona se o array já estiver ORDENADO","highlightedHtml":"<span class=\"kw\">int</span> <span class=\"fn\">searchBinaria</span>(<span class=\"kw\">int</span>[] sorted, <span class=\"kw\">int</span> alvo) {\n    <span class=\"kw\">int</span> start = 0, end = sorted.length - 1;\n    <span class=\"kw\">while</span> (start &lt;= end) {\n        <span class=\"kw\">int</span> middle = start + (end - start) / 2; <span class=\"com\">// evita overflow de (inicio+fim)/2 em arrays gigantes</span>\n        <span class=\"kw\">if</span> (sorted[middle] == alvo) <span class=\"kw\">return</span> middle;\n        <span class=\"kw\">else if</span> (sorted[middle] &lt; alvo) start = middle + 1; <span class=\"com\">// descarta metade esquerda</span>\n        <span class=\"kw\">else</span> end = middle - 1;                            <span class=\"com\">// descarta metade direita</span>\n    }\n    <span class=\"kw\">return</span> -1; <span class=\"com\">// não encontrado</span>\n}\n<span class=\"com\">// O(log n) -- SÓ funciona se o array já estiver ORDENADO</span>","caption":"Exemplo executável de ordenacao-busca.","explanation":["inicio e fim delimitam o intervalo ainda possível.","meio é calculado sem somar inicio + fim diretamente para evitar overflow em limites grandes.","Cada comparação descarta metade porque a entrada está ordenada."],"commonMistakes":["Usar busca binária em dados não ordenados","Não avançar inicio/fim e criar loop infinito","Errar retorno quando não encontrado"]},{"id":"ordenacao-busca-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Merge Sort — dividir para conquistar, O(n log n)</h2>","fidelityText":"Merge Sort — dividir para conquistar, O(n log n)"},{"id":"ordenacao-busca-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Merge Sort resolve o problema dividindo pela metade repetidamente (como a busca binária) até sobrar pedaços de um único elemento (trivialmente \"ordenados\"), e depois combina esses pedaços já ordenados dois a dois, de volta, mantendo a ordem — como juntar duas filas já organizadas por altura em uma única fila maior, também organizada, sem precisar reordenar do zero.</div>","fidelityText":"Merge Sort resolve o problema dividindo pela metade repetidamente (como a busca binária) até sobrar pedaços de um único elemento (trivialmente \"ordenados\"), e depois combina esses pedaços já ordenados dois a dois, de volta, mantendo a ordem — como juntar duas filas já organizadas por altura em uma única fila maior, também organizada, sem precisar reordenar do zero."},{"id":"ordenacao-busca-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"int[] mergeSort(int[] array) {\n    if (array.length <= 1) return array; // caso base -- 0 ou 1 elemento já está \"ordenado\"\n\n    int middle = array.length / 2;\n    int[] left = mergeSort(Arrays.copyOfRange(array, 0, middle));\n    int[] right = mergeSort(Arrays.copyOfRange(array, middle, array.length));\n\n    return merge(left, right); // combina os dois já ordenados\n}\n\nint[] merge(int[] left, int[] right) {\n    int[] result = new int[left.length + right.length];\n    int i = 0, j = 0, k = 0;\n    while (i < left.length && j < right.length) {\n        result[k++] = (left[i] <= right[j]) ? left[i++] : right[j++];\n    }\n    while (i < left.length) result[k++] = left[i++];\n    while (j < right.length) result[k++] = right[j++];\n    return result;\n}","fidelityText":"int[] mergeSort(int[] array) { if (array.length <= 1) return array; // caso base -- 0 ou 1 elemento já está \"ordenado\" int meio = array.length / 2; int[] esquerda = mergeSort(Arrays.copyOfRange(array, 0, meio)); int[] direita = mergeSort(Arrays.copyOfRange(array, meio, array.length)); return mesclar(esquerda, direita); // combina os dois já ordenados } int[] mesclar(int[] esq, int[] dir) { int[] resultado = new int[esq.length + dir.length]; int i = 0, j = 0, k = 0; while (i < esq.length && j < dir.length) { resultado[k++] = (esq[i] <= dir[j]) ? esq[i++] : dir[j++]; } while (i < esq.length) resultado[k++] = esq[i++]; while (j < dir.length) resultado[k++] = dir[j++]; return resultado; }","highlightedHtml":"<span class=\"kw\">int</span>[] <span class=\"fn\">mergeSort</span>(<span class=\"kw\">int</span>[] array) {\n    <span class=\"kw\">if</span> (array.length &lt;= 1) <span class=\"kw\">return</span> array; <span class=\"com\">// caso base -- 0 ou 1 elemento já está \"ordenado\"</span>\n\n    <span class=\"kw\">int</span> middle = array.length / 2;\n    <span class=\"kw\">int</span>[] left = mergeSort(Arrays.copyOfRange(array, 0, middle));\n    <span class=\"kw\">int</span>[] right = mergeSort(Arrays.copyOfRange(array, middle, array.length));\n\n    <span class=\"kw\">return</span> merge(left, right); <span class=\"com\">// combina os dois já ordenados</span>\n}\n\n<span class=\"kw\">int</span>[] <span class=\"fn\">merge</span>(<span class=\"kw\">int</span>[] left, <span class=\"kw\">int</span>[] right) {\n    <span class=\"kw\">int</span>[] result = <span class=\"kw\">new</span> <span class=\"kw\">int</span>[left.length + right.length];\n    <span class=\"kw\">int</span> i = 0, j = 0, k = 0;\n    <span class=\"kw\">while</span> (i &lt; left.length &amp;&amp; j &lt; right.length) {\n        result[k++] = (left[i] &lt;= right[j]) ? left[i++] : right[j++];\n    }\n    <span class=\"kw\">while</span> (i &lt; left.length) result[k++] = left[i++];\n    <span class=\"kw\">while</span> (j &lt; right.length) result[k++] = right[j++];\n    <span class=\"kw\">return</span> result;\n}","caption":"Exemplo executável de ordenacao-busca.","explanation":["O caso base evita dividir arrays de tamanho zero ou um.","copyOfRange divide a entrada e mesclar combina duas metades já ordenadas.","O uso de <= preserva estabilidade entre elementos equivalentes das metades."],"commonMistakes":["Esquecer de copiar o restante de uma metade","Alocar excessivamente em produção sem medir","Reimplementar sort padrão sem necessidade"]},{"id":"ordenacao-busca-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Algoritmo</th><th>Complexidade média</th><th>Observação</th></tr>\n        <tr><td>Bubble Sort</td><td><code>O(n²)</code></td><td>Simples de entender, péssimo em dados grandes — raramente usado na prática</td></tr>\n        <tr><td>Merge Sort</td><td><code>O(n log n)</code></td><td>Estável, previsível, usa memória extra</td></tr>\n        <tr><td>Quick Sort</td><td><code>O(n log n)</code> médio, <code>O(n²)</code> pior caso</td><td>Rápido na prática, in-place (sem memória extra significativa)</td></tr>\n        <tr><td><code>Collections.sort()</code> do Java</td><td><code>O(n log n)</code></td><td>Timsort (híbrido merge+insertion), a escolha certa para 99% dos casos reais</td></tr>\n      </tbody></table>","fidelityText":"AlgoritmoComplexidade médiaObservação Bubble SortO(n²)Simples de entender, péssimo em dados grandes — raramente usado na prática Merge SortO(n log n)Estável, previsível, usa memória extra Quick SortO(n log n) médio, O(n²) pior casoRápido na prática, in-place (sem memória extra significativa) Collections.sort() do JavaO(n log n)Timsort (híbrido merge+insertion), a escolha certa para 99% dos casos reais"},{"id":"ordenacao-busca-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Na prática profissional, você quase nunca implementa um algoritmo de ordenação do zero — <code>Collections.sort()</code> e <code>Arrays.sort()</code> já usam Timsort, uma implementação extremamente otimizada e testada por décadas. O valor de <strong>entender</strong> Merge Sort não é reimplementá-lo em produção, é desenvolver a intuição de \"dividir para conquistar\" — a mesma técnica reaparece resolvendo problemas completamente diferentes (busca binária, algoritmos de processamento distribuído como MapReduce).</div>","fidelityText":"Na prática profissional, você quase nunca implementa um algoritmo de ordenação do zero — Collections.sort() e Arrays.sort() já usam Timsort, uma implementação extremamente otimizada e testada por décadas. O valor de entender Merge Sort não é reimplementá-lo em produção, é desenvolver a intuição de \"dividir para conquistar\" — a mesma técnica reaparece resolvendo problemas completamente diferentes (busca binária, algoritmos de processamento distribuído como MapReduce)."},{"id":"ordenacao-busca-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Nunca implemente sua própria ordenação em código de produção</b> \"porque é mais rápido\" sem medir — <code>Collections.sort()</code> foi otimizado, testado e endurecido contra casos-limite por milhares de desenvolvedores ao longo de anos. Reimplementar isso quase sempre resulta em algo mais lento e com mais bugs do que a biblioteca padrão.</div>","fidelityText":"Nunca implemente sua própria ordenação em código de produção \"porque é mais rápido\" sem medir — Collections.sort() foi otimizado, testado e endurecido contra casos-limite por milhares de desenvolvedores ao longo de anos. Reimplementar isso quase sempre resulta em algo mais lento e com mais bugs do que a biblioteca padrão."},{"id":"ordenacao-busca-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Implemente Merge Sort uma única vez, manualmente, só para consolidar a intuição de recursão + divisão do problema (capítulo 81). Depois disso, use exclusivamente <code>Collections.sort()</code>/<code>list.sort(comparator)</code> (capítulo 11) no resto da sua carreira — o objetivo do exercício é entendimento, não reuso.</div>","fidelityText":"Implemente Merge Sort uma única vez, manualmente, só para consolidar a intuição de recursão + divisão do problema (capítulo 81). Depois disso, use exclusivamente Collections.sort()/list.sort(comparator) (capítulo 11) no resto da sua carreira — o objetivo do exercício é entendimento, não reuso."},{"id":"ordenacao-busca-exercise-12","type":"exercise","authorship":"legacy-preserved","title":"Exercício 83.1 — Implementando e comparando","prompt":"Implemente mergeSort, valide-o contra Arrays.sort() em arrays vazios, ordenados, reversos e com duplicatas. Depois crie um benchmark JMH com dados copiados a cada invocação e explique por que algoritmos de mesma classe assintótica podem ter desempenho diferente.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 83.1 — Implementando e comparandodifícil Implemente mergeSort, valide-o contra Arrays.sort() em arrays vazios, ordenados, reversos e com duplicatas. Depois crie um benchmark JMH com dados copiados a cada invocação e explique por que algoritmos de mesma classe assintótica podem ter desempenho diferente. Ver solução Arrays.sort() é altamente otimizado, mas o algoritmo depende do tipo: arrays de objetos usam uma ordenação estável baseada em TimSort, enquanto arrays primitivos usam algoritmos especializados, como dual-pivot quicksort em vários casos. Big O descreve crescimento assintótico, não constantes, alocações, localidade de cache ou adaptação à entrada.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 83.1 — Implementando e comparando</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Implemente <code>mergeSort</code>, valide-o contra <code>Arrays.sort()</code> em arrays vazios, ordenados, reversos e com duplicatas. Depois crie um benchmark JMH com dados copiados a cada invocação e explique por que algoritmos de mesma classe assintótica podem ter desempenho diferente.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><code>Arrays.sort()</code> é altamente otimizado, mas o algoritmo depende do tipo: arrays de objetos usam uma ordenação estável baseada em TimSort, enquanto arrays primitivos usam algoritmos especializados, como dual-pivot quicksort em vários casos. Big O descreve crescimento assintótico, não constantes, alocações, localidade de cache ou adaptação à entrada.</p>\n        </div>\n      </div>"},{"id":"ordenacao-comparison","type":"comparison","authorship":"authored","title":"Implementar para aprender, usar biblioteca para produzir","criteria":["objetivo","risco","evidência"],"alternatives":[{"name":"implementação didática","values":["entender invariantes","bugs de limite","testes contra Arrays.sort"],"useWhen":"aprender ou provar conceito","avoidWhen":"produção comum"},{"name":"API padrão","values":["confiabilidade e manutenção","comparador ruim ainda quebra contrato","documentação do JDK"],"useWhen":"código real","avoidWhen":"exercício de entendimento"}]},{"id":"ordenacao-quiz","type":"quiz","authorship":"authored","conceptId":"busca-binaria-invariante","prompt":"Por que busca binária falha em uma lista não ordenada?","options":[{"id":"ordenacao-q-a","label":"Porque descartar metade depende da garantia de que todos os valores antes/depois seguem a ordem.","correct":true,"explanation":"Sem ordenação, a metade descartada ainda pode conter o alvo."},{"id":"ordenacao-q-b","label":"Porque Java não permite dividir índices por dois.","correct":false,"explanation":"O cálculo é permitido; a pré-condição lógica é que falta."},{"id":"ordenacao-q-c","label":"Porque busca binária só funciona com String.","correct":false,"explanation":"Ela funciona com qualquer domínio comparável por uma ordem consistente."}]}],"resources":[{"id":"ordenacao-arrays-api","type":"reference","title":"Arrays API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Arrays.html","reinforces":"Documenta sort, binarySearch e diferenças por tipo de array.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"ordenacao-collections-api","type":"reference","title":"Collections API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collections.html","reinforces":"Documenta sort, binarySearch e contratos de listas/comparadores.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A sorting search operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a sorting search operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this sorting search chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"int searchBinaria(int[] sorted, int alvo) {","instruction":"A sorting search operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this sorting search chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"algoritmos-praticos","moduleId":"algorithms-data-structures","order":3,"title":"Estruturas e algoritmos essenciais na prática","summary":"Escolher uma estrutura é escolher quais operações serão baratas e quais custos serão aceitos. Analise tempo, memória, pior caso, custo amortizado, localidade de cache e características reais da entrada.","objectives":["Escolher estrutura pela operação dominante","Aplicar BFS, DFS, heap/top-k e Dijkstra com pré-condições","Projetar benchmarks sem autoengano"],"whyItExists":"Depois dos algoritmos base, o aluno precisa conectar estruturas do JDK a problemas reais: fila, pilha, índice por chave, prioridade, grafos e medição honesta.","prerequisiteChapterIds":["ordenacao-busca"],"conceptIds":["como-ler-custos-e-estruturas","busca-em-grafos","padroes-de-resolucao","benchmark-sem-autoengano"],"introducedConceptIds":["estrutura-operacao-dominante","grafo-bfs-dfs","heap-priorityqueue-topk"],"usedConceptIds":["complexidade-assintotica","tempo-espaco-tradeoff","busca-binaria-invariante","medicao-evidencia-performance"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"algoritmos-praticos-intuition","type":"intuition","authorship":"authored","title":"Estrutura é uma aposta explícita","body":"Ao escolher uma estrutura, você está dizendo qual operação merece ser barata e qual custo será aceito em troca.","analogyLimit":"Caixa de ferramentas ajuda como metáfora, mas estruturas têm contratos formais, custo de memória, ordem de iteração e comportamento em casos-limite."},{"id":"algoritmos-praticos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-java\">Java</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~5h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#ordenacao-busca\">Ordenação e busca</a>, <a class=\"prereq-tag\" href=\"#colecoes\">Coleções</a></div></div>","fidelityText":"JavaDificuldade: Avançado⏱ ~5h de estudo e práticaPré-requisitos: Ordenação e busca, Coleções"},{"id":"algoritmos-praticos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Escolher uma estrutura é escolher quais operações serão baratas e quais custos serão aceitos. Analise tempo, memória, pior caso, custo amortizado, localidade de cache e características reais da entrada.</p>","fidelityText":"Escolher uma estrutura é escolher quais operações serão baratas e quais custos serão aceitos. Analise tempo, memória, pior caso, custo amortizado, localidade de cache e características reais da entrada."},{"id":"algoritmos-praticos-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Como ler custos e estruturas</h2></div>\n    <p>Comece pela operação que o problema repete. A estrutura ideal para buscar pode ser ruim para inserir, ordenar ou percorrer.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>O(1), O(log n), O(n)</dt><dd>Descrevem como o trabalho cresce com a entrada: constante, logarítmico ou linear. Não são tempos em segundos.</dd></div><div class=\"concept-card\"><dt>Custo amortizado</dt><dd>Média garantida ao distribuir uma operação ocasionalmente cara por uma sequência de operações, como o crescimento interno de um ArrayList.</dd></div><div class=\"concept-card\"><dt>Localidade de cache</dt><dd>Dados próximos na memória tendem a ser lidos de forma mais eficiente pelo processador. Duas estruturas com a mesma ordem assintótica podem ter desempenho real diferente.</dd></div><div class=\"concept-card\"><dt>Grafo</dt><dd>Conjunto de vértices ligados por arestas. Cidades podem ser vértices; estradas, arestas.</dd></div><div class=\"concept-card\"><dt>Peso</dt><dd>Custo associado a uma aresta, como distância ou tempo. Grafo não ponderado trata cada travessia com o mesmo custo.</dd></div><div class=\"concept-card\"><dt>Benchmark</dt><dd>Experimento controlado para medir desempenho, com cenário, parâmetros, aquecimento e repetição documentados.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoComo ler custos e estruturas Comece pela operação que o problema repete. A estrutura ideal para buscar pode ser ruim para inserir, ordenar ou percorrer. O(1), O(log n), O(n)Descrevem como o trabalho cresce com a entrada: constante, logarítmico ou linear. Não são tempos em segundos.Custo amortizadoMédia garantida ao distribuir uma operação ocasionalmente cara por uma sequência de operações, como o crescimento interno de um ArrayList.Localidade de cacheDados próximos na memória tendem a ser lidos de forma mais eficiente pelo processador. Duas estruturas com a mesma ordem assintótica podem ter desempenho real diferente.GrafoConjunto de vértices ligados por arestas. Cidades podem ser vértices; estradas, arestas.PesoCusto associado a uma aresta, como distância ou tempo. Grafo não ponderado trata cada travessia com o mesmo custo.BenchmarkExperimento controlado para medir desempenho, com cenário, parâmetros, aquecimento e repetição documentados."},{"id":"algoritmos-praticos-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Estrutura</th><th>Operações principais</th><th>Uso típico</th></tr><tr><td><code>ArrayList</code></td><td>índice O(1), inserir no meio O(n)</td><td>sequência e leitura</td></tr><tr><td><code>HashMap</code></td><td>busca/inserção O(1) em média</td><td>índice por chave</td></tr><tr><td><code>TreeMap</code></td><td>O(log n)</td><td>chaves ordenadas e consultas por faixa</td></tr><tr><td><code>ArrayDeque</code></td><td>extremos O(1) amortizado</td><td>fila e pilha</td></tr><tr><td><code>PriorityQueue</code></td><td>consultar mínimo O(1), inserir/remover O(log n)</td><td>agendamento e top-k</td></tr></tbody></table>","fidelityText":"EstruturaOperações principaisUso típicoArrayListíndice O(1), inserir no meio O(n)sequência e leituraHashMapbusca/inserção O(1) em médiaíndice por chaveTreeMapO(log n)chaves ordenadas e consultas por faixaArrayDequeextremos O(1) amortizadofila e pilhaPriorityQueueconsultar mínimo O(1), inserir/remover O(log n)agendamento e top-k"},{"id":"algoritmos-praticos-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Busca em grafos</h2>","fidelityText":"Busca em grafos"},{"id":"algoritmos-praticos-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"static Set<String> bfs(Map<String, List<String>> graph, String source) {\n    Set<String> visited = new LinkedHashSet<>();\n    Deque<String> queue = new ArrayDeque<>();\n    queue.add(source);\n    while (!queue.isEmpty()) {\n        String current = queue.removeFirst();\n        if (!visited.add(current)) continue;\n        graph.getOrDefault(current, List.of()).stream()\n            .filter(neighbor -> !visited.contains(neighbor))\n            .forEach(queue::addLast);\n    }\n    return visited;\n}","fidelityText":"static Set<String> bfs(Map<String, List<String>> grafo, String origem) { Set<String> visitados = new LinkedHashSet<>(); Deque<String> fila = new ArrayDeque<>(); fila.add(origem); while (!fila.isEmpty()) { String atual = fila.removeFirst(); if (!visitados.add(atual)) continue; grafo.getOrDefault(atual, List.of()).stream() .filter(vizinho -> !visitados.contains(vizinho)) .forEach(fila::addLast); } return visitados; }","highlightedHtml":"<span class=\"kw\">static</span> Set&lt;String&gt; bfs(Map&lt;String, List&lt;String&gt;&gt; graph, String source) {\n    Set&lt;String&gt; visited = <span class=\"kw\">new</span> LinkedHashSet&lt;&gt;();\n    Deque&lt;String&gt; queue = <span class=\"kw\">new</span> ArrayDeque&lt;&gt;();\n    queue.add(source);\n    <span class=\"kw\">while</span> (!queue.isEmpty()) {\n        String current = queue.removeFirst();\n        <span class=\"kw\">if</span> (!visited.add(current)) <span class=\"kw\">continue</span>;\n        graph.getOrDefault(current, List.of()).stream()\n            .filter(neighbor -&gt; !visited.contains(neighbor))\n            .forEach(queue::addLast);\n    }\n    <span class=\"kw\">return</span> visited;\n}","caption":"Exemplo executável de algoritmos-praticos.","explanation":["LinkedHashSet registra visitados preservando ordem de descoberta para evidência determinística.","ArrayDeque implementa a fila da BFS com inserção no fim e remoção no início.","getOrDefault protege vértices sem lista de vizinhos declarada."],"commonMistakes":["Não controlar visitados e entrar em ciclo","Usar Stack legado sem necessidade","Esperar que stream torne BFS paralela ou mais correta"]},{"id":"algoritmos-praticos-content-7","type":"html","authorship":"legacy-preserved","html":"<p><strong>BFS</strong> (<em>breadth-first search</em>, busca em largura) explora por camadas e encontra o menor número de arestas em grafos não ponderados. <strong>DFS</strong> (<em>depth-first search</em>, busca em profundidade) segue um caminho antes de voltar e é útil para componentes, ciclos e ordenação topológica. <strong>Dijkstra</strong> escolhe repetidamente o menor custo conhecido e resolve menores caminhos com pesos não negativos usando uma fila de prioridade. Para pesos negativos, essa escolha deixa de ser segura e outro algoritmo é necessário.</p>","fidelityText":"BFS (breadth-first search, busca em largura) explora por camadas e encontra o menor número de arestas em grafos não ponderados. DFS (depth-first search, busca em profundidade) segue um caminho antes de voltar e é útil para componentes, ciclos e ordenação topológica. Dijkstra escolhe repetidamente o menor custo conhecido e resolve menores caminhos com pesos não negativos usando uma fila de prioridade. Para pesos negativos, essa escolha deixa de ser segura e outro algoritmo é necessário."},{"id":"algoritmos-praticos-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Padrões de resolução</h2>","fidelityText":"Padrões de resolução"},{"id":"algoritmos-praticos-content-9","type":"html","authorship":"legacy-preserved","html":"<ul><li><strong>Dois ponteiros:</strong> percorre uma sequência ordenada sem testar todos os pares.</li><li><strong>Janela deslizante:</strong> mantém estatísticas de um intervalo contíguo.</li><li><strong>Backtracking:</strong> explora escolhas e desfaz estado; poda caminhos impossíveis.</li><li><strong>Programação dinâmica:</strong> identifica subproblemas sobrepostos e reutiliza seus resultados.</li><li><strong>Union-find:</strong> mantém componentes disjuntos com compressão de caminho e união por rank.</li><li><strong>Top-k:</strong> usa heap de tamanho k em vez de ordenar toda a entrada.</li></ul>","fidelityText":"Dois ponteiros: percorre uma sequência ordenada sem testar todos os pares.Janela deslizante: mantém estatísticas de um intervalo contíguo.Backtracking: explora escolhas e desfaz estado; poda caminhos impossíveis.Programação dinâmica: identifica subproblemas sobrepostos e reutiliza seus resultados.Union-find: mantém componentes disjuntos com compressão de caminho e união por rank.Top-k: usa heap de tamanho k em vez de ordenar toda a entrada."},{"id":"algoritmos-praticos-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Benchmark sem autoengano</h2>","fidelityText":"Benchmark sem autoengano"},{"id":"algoritmos-praticos-content-11","type":"html","authorship":"legacy-preserved","html":"<p><code>System.currentTimeMillis()</code> não é uma metodologia de microbenchmark. O <strong>JIT</strong> compila e otimiza código durante a execução; o <strong>GC</strong> recupera memória; ambos mudam uma medição curta. <strong>JMH</strong> é o Java Microbenchmark Harness, ferramenta que organiza aquecimento, repetição e consumo do resultado. Um <strong>fork</strong> executa o benchmark em um novo processo JVM para reduzir interferência de execuções anteriores. Registre distribuição, parâmetros e ambiente.</p>","fidelityText":"System.currentTimeMillis() não é uma metodologia de microbenchmark. O JIT compila e otimiza código durante a execução; o GC recupera memória; ambos mudam uma medição curta. JMH é o Java Microbenchmark Harness, ferramenta que organiza aquecimento, repetição e consumo do resultado. Um fork executa o benchmark em um novo processo JVM para reduzir interferência de execuções anteriores. Registre distribuição, parâmetros e ambiente."},{"id":"algoritmos-praticos-exercise-12","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — rota de entregas","prompt":"Modele cidades e estradas ponderadas. Implemente BFS, DFS, detecção de ciclo, ordenação topológica para dependências e Dijkstra para menor custo. Crie testes para grafo vazio, desconectado, ciclo e múltiplos caminhos com mesmo custo.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — rota de entregasdifícilModele cidades e estradas ponderadas. Implemente BFS, DFS, detecção de ciclo, ordenação topológica para dependências e Dijkstra para menor custo. Crie testes para grafo vazio, desconectado, ciclo e múltiplos caminhos com mesmo custo.Ver critériosSepare representação do algoritmo; rejeite pesos negativos em Dijkstra; retorne caminho e custo; documente complexidade em função de vértices e arestas.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — rota de entregas</h2><span class=\"exercise-tag d\">difícil</span></div><p>Modele cidades e estradas ponderadas. Implemente BFS, DFS, detecção de ciclo, ordenação topológica para dependências e Dijkstra para menor custo. Crie testes para grafo vazio, desconectado, ciclo e múltiplos caminhos com mesmo custo.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>Separe representação do algoritmo; rejeite pesos negativos em Dijkstra; retorne caminho e custo; documente complexidade em função de vértices e arestas.</p></div></div>"},{"id":"algoritmos-praticos-error","type":"error-case","authorship":"authored","title":"Dijkstra com peso negativo","scenario":"O grafo usa uma aresta de custo negativo e o algoritmo escolhe o menor custo conhecido como definitivo.","symptom":"O caminho retornado parece válido, mas não é necessariamente o menor.","cause":"A escolha gulosa de Dijkstra depende de pesos não negativos.","diagnosis":["procurar pesos negativos","testar grafo pequeno com caminho alternativo","comparar com algoritmo apropriado"],"correction":"Rejeitar pesos negativos ou usar algoritmo que suporte esse contrato.","prevention":"Declarar pré-condições do algoritmo junto da representação do grafo."},{"id":"algoritmos-praticos-quiz","type":"quiz","authorship":"authored","conceptId":"estrutura-operacao-dominante","prompt":"Você precisa manter os 10 maiores valores de um fluxo grande. Qual estratégia evita ordenar tudo a cada item?","options":[{"id":"algoritmos-praticos-q-a","label":"Manter uma PriorityQueue limitada a k elementos.","correct":true,"explanation":"O heap mantém o candidato removível barato e evita ordenar toda a entrada."},{"id":"algoritmos-praticos-q-b","label":"Ordenar todos os valores recebidos a cada nova leitura.","correct":false,"explanation":"Isso desperdiça trabalho quando só o top-k interessa."},{"id":"algoritmos-praticos-q-c","label":"Usar HashSet porque ele itera em ordem crescente.","correct":false,"explanation":"HashSet não promete ordem de iteração."}]}],"resources":[{"id":"algoritmos-priorityqueue-api","type":"reference","title":"PriorityQueue API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/PriorityQueue.html","reinforces":"Documenta heap de prioridade, custo das operações e ausência de ordem garantida na iteração.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"algoritmos-deque-api","type":"reference","title":"Deque API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Deque.html","reinforces":"Base para filas, pilhas e BFS/DFS com ArrayDeque.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A algorithms practical operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a algorithms practical operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this algorithms practical chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"static Set<String> bfs(Map<String, List<String>> graph, String source) {","instruction":"A algorithms practical operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this algorithms practical chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"git","moduleId":"testing-engineering","order":0,"title":"Git & fluxo de trabalho","summary":"A partir daqui o curso entra em ferramentas que cercam o código Java, não a linguagem em si. Git é pré-requisito de tudo que vem depois — CI/CD, deploy, trabalho em equipe — porque é o sistema de controle de versão que registra o histórico do seu projeto.","objectives":["Entender working tree, staging area e commit como snapshots","Criar branches e integrar mudanças com merge ou rebase conscientemente","Impedir artefatos, builds e segredos no histórico"],"whyItExists":"A partir daqui, aprender Java também significa preservar histórico, experimentar com segurança e produzir evidências revisáveis. Git é a base do trabalho reproduzível.","prerequisiteChapterIds":["zenith-cli-inicial"],"conceptIds":["o-ciclo-basico-working-directory-staging-commit","branches-trabalhando-em-paralelo-sem-quebrar-o-principal","o-modelo-mental-completo-working-tree-staging-commit-branch-remote","remoto-clone-fetch-pull-e-push","desfazendo-mudancas-restore-revert-e-reset","guardando-trabalho-em-progresso-stash","reflog-a-rede-de-seguranca-contra-eu-perdi-meu-commit","tags-marcando-um-ponto-especifico-do-historico","detached-head-quando-voce-nao-esta-em-cima-de-nenhum-branch","pull-request-a-unidade-social-do-trabalho-em-equipe","merge-vs-rebase"],"introducedConceptIds":["git-snapshot-index","branch-merge-rebase","gitignore-secrets"],"usedConceptIds":["path-filesystem"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"git-intuition","type":"intuition","authorship":"authored","title":"Git registra decisões, não só arquivos","body":"Um commit bom permite voltar, comparar, revisar e explicar uma mudança lógica. O index ajuda a escolher exatamente o que entra nesse snapshot.","analogyLimit":"Snapshot ajuda a imaginar commit, mas Git também guarda grafo de histórico, pais, branches e hashes de conteúdo."},{"id":"git-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Git</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <span class=\"prereq-none\">nenhum (pode ser aprendido em paralelo ao resto do curso)</span></div>\n      </div>","fidelityText":"Git Dificuldade: Iniciante ⏱ ~2h de estudo + prática Pré-requisitos: nenhum (pode ser aprendido em paralelo ao resto do curso)"},{"id":"git-content-2","type":"html","authorship":"legacy-preserved","html":"<p>A partir daqui o curso entra em ferramentas que cercam o código Java, não a linguagem em si. <strong>Git</strong> é pré-requisito de tudo que vem depois — CI/CD, deploy, trabalho em equipe — porque é o sistema de controle de versão que registra o histórico do seu projeto.</p>","fidelityText":"A partir daqui o curso entra em ferramentas que cercam o código Java, não a linguagem em si. Git é pré-requisito de tudo que vem depois — CI/CD, deploy, trabalho em equipe — porque é o sistema de controle de versão que registra o histórico do seu projeto."},{"id":"git-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"install-box\">\n        <h2>Instalação</h2>\n        <div class=\"install-tabs\" role=\"tablist\">\n          <button type=\"button\" class=\"install-tab active\" role=\"tab\" aria-selected=\"true\">Windows</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">macOS</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">Linux</button>\n        </div>\n        <div class=\"install-panel active\"><pre class=\"code\"><span class=\"com\"># baixe o instalador em git-scm.com e siga o wizard, ou via winget:</span>\nwinget install --id Git.Git -e --source winget</pre></div>\n        <div class=\"install-panel\"><pre class=\"code\"><span class=\"com\"># via Homebrew:</span>\nbrew install git</pre></div>\n        <div class=\"install-panel\"><pre class=\"code\"><span class=\"com\"># Debian/Ubuntu:</span>\nsudo apt update &amp;&amp; sudo apt install git\n\n<span class=\"com\"># Fedora:</span>\nsudo dnf install git</pre></div>\n        <pre class=\"code\"><span class=\"com\"># confirme a instalação e configure identidade (uma vez só, por máquina):</span>\ngit --version\ngit config --global user.name \"Felipy Santos\"\ngit config --global user.email \"seu-email@exemplo.com\"</pre>\n      </div>","fidelityText":"Instalação Windows macOS Linux # baixe o instalador em git-scm.com e siga o wizard, ou via winget: winget install --id Git.Git -e --source winget # via Homebrew: brew install git # Debian/Ubuntu: sudo apt update && sudo apt install git # Fedora: sudo dnf install git # confirme a instalação e configure identidade (uma vez só, por máquina): git --version git config --global user.name \"Felipy Santos\" git config --global user.email \"seu-email@exemplo.com\""},{"id":"git-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>O ciclo básico: working directory → staging → commit</h2>","fidelityText":"O ciclo básico: working directory → staging → commit"},{"id":"git-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"git init                      # cria um repositório novo na pasta atual\ngit status                    # o que mudou desde o último commit\ngit add file.java          # move para a \"staging area\" (o que vai entrar no próximo commit)\ngit add .                     # adiciona TUDO que mudou\ngit commit -m \"Adds class Book\"  # registra um snapshot permanente\ngit log --oneline             # histórico resumido","fidelityText":"git init # cria um repositório novo na pasta atual git status # o que mudou desde o último commit git add arquivo.java # move para a \"staging area\" (o que vai entrar no próximo commit) git add . # adiciona TUDO que mudou git commit -m \"Adiciona classe Livro\" # registra um snapshot permanente git log --oneline # histórico resumido","highlightedHtml":"git init                      <span class=\"com\"># cria um repositório novo na pasta atual</span>\ngit status                    <span class=\"com\"># o que mudou desde o último commit</span>\ngit add file.java          <span class=\"com\"># move para a \"staging area\" (o que vai entrar no próximo commit)</span>\ngit add .                     <span class=\"com\"># adiciona TUDO que mudou</span>\ngit commit -m \"Adds class Book\"  <span class=\"com\"># registra um snapshot permanente</span>\ngit log --oneline             <span class=\"com\"># histórico resumido</span>","caption":"Exemplo executável de git.","explanation":["git init cria o repositório; add move para staging; commit registra o snapshot permanente."],"commonMistakes":["Confundir git add (staging) com git commit (snapshot definitivo)"]},{"id":"git-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Branches — trabalhando em paralelo sem quebrar o principal</h2>","fidelityText":"Branches — trabalhando em paralelo sem quebrar o principal"},{"id":"git-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"git branch feature/loan     # cria uma branch nova\ngit checkout feature/loan   # muda para ela\ngit checkout -b feature/loan # cria E muda, em um comando só\n\n# depois de terminar e commitar na branch:\ngit checkout main\ngit merge feature/loan      # traz as mudanças de volta para main","fidelityText":"git branch feature/emprestimo # cria uma branch nova git checkout feature/emprestimo # muda para ela git checkout -b feature/emprestimo # cria E muda, em um comando só # depois de terminar e commitar na branch: git checkout main git merge feature/emprestimo # traz as mudanças de volta para main","highlightedHtml":"git branch feature/loan     <span class=\"com\"># cria uma branch nova</span>\ngit checkout feature/loan   <span class=\"com\"># muda para ela</span>\ngit checkout -b feature/loan <span class=\"com\"># cria E muda, em um comando só</span>\n\n<span class=\"com\"># depois de terminar e commitar na branch:</span>\ngit checkout main\ngit merge feature/loan      <span class=\"com\"># traz as mudanças de volta para main</span>","caption":"Exemplo executável de git.","explanation":["Uma branch é só um ponteiro móvel para um commit -- criar uma é barato; merge traz o histórico de volta."],"commonMistakes":["Esquecer de voltar para main antes do merge"]},{"id":"git-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Commits pequenos e atômicos — uma mudança lógica por commit, não \"correções do dia inteiro\" em um commit só.</li>\n          <li>Mensagens de commit no imperativo: \"Adiciona validação de saldo\", não \"adicionado\" ou \"adicionando\".</li>\n          <li><code>.gitignore</code> sempre configurado <strong>antes</strong> do primeiro commit — <code>target/</code>, <code>.env</code>, <code>*.class</code>, credenciais nunca vão para o repositório.</li>\n          <li>Nunca dê <code>git push --force</code> em uma branch compartilhada (<code>main</code>/<code>develop</code>) — isso reescreve o histórico que outras pessoas já têm localmente.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Commits pequenos e atômicos — uma mudança lógica por commit, não \"correções do dia inteiro\" em um commit só. Mensagens de commit no imperativo: \"Adiciona validação de saldo\", não \"adicionado\" ou \"adicionando\". .gitignore sempre configurado antes do primeiro commit — target/, .env, *.class, credenciais nunca vão para o repositório. Nunca dê git push --force em uma branch compartilhada (main/develop) — isso reescreve o histórico que outras pessoas já têm localmente."},{"id":"git-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>O modelo mental completo: working tree → staging → commit → branch → remote</h2>","fidelityText":"O modelo mental completo: working tree → staging → commit → branch → remote"},{"id":"git-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"working tree            # os arquivos como você vê no disco agora\n    ↓ git add\nstaging area / index    # o que ENTRARÁ no próximo commit (uma \"prévia\" do commit)\n    ↓ git commit\ncommit local             # snapshot permanente no histórico DESTE repositório\n    ↓ git push\nremote (origin)          # cópia do histórico em outro lugar (GitHub, GitLab...) -- não sincroniza sozinho","fidelityText":"working tree # os arquivos como você vê no disco agora ↓ git add staging area / index # o que ENTRARÁ no próximo commit (uma \"prévia\" do commit) ↓ git commit commit local # snapshot permanente no histórico DESTE repositório ↓ git push remote (origin) # cópia do histórico em outro lugar (GitHub, GitLab...) -- não sincroniza sozinho","highlightedHtml":"working tree            <span class=\"com\"># os arquivos como você vê no disco agora</span>\n    ↓ git add\nstaging area / index    <span class=\"com\"># o que ENTRARÁ no próximo commit (uma \"prévia\" do commit)</span>\n    ↓ git commit\ncommit local             <span class=\"com\"># snapshot permanente no histórico DESTE repositório</span>\n    ↓ git push\nremote (origin)          <span class=\"com\"># cópia do histórico em outro lugar (GitHub, GitLab...) -- não sincroniza sozinho</span>","caption":"Exemplo executável de git.","explanation":["Cada seta do modelo (working tree -> staging -> commit -> remote) é uma ação explícita -- nenhuma acontece sozinha."]},{"id":"git-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Cada seta acima é uma ação explícita, nunca automática: editar um arquivo não move nada para staging; <code>git add</code> não cria um commit; <code>git commit</code> não envia nada para o remoto. Entender essas quatro paradas resolve a maior parte da confusão de iniciante sobre \"por que minha mudança não apareceu no GitHub\".</p>","fidelityText":"Cada seta acima é uma ação explícita, nunca automática: editar um arquivo não move nada para staging; git add não cria um commit; git commit não envia nada para o remoto. Entender essas quatro paradas resolve a maior parte da confusão de iniciante sobre \"por que minha mudança não apareceu no GitHub\"."},{"id":"git-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Remoto: clone, fetch, pull e push</h2>","fidelityText":"Remoto: clone, fetch, pull e push"},{"id":"git-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"git clone <url-of-repository-remote>   # copia um repositório remoto inteiro (histórico + branches) para sua máquina\n\ngit remote -v              # lista os remotos configurados -- \"origin\" é o nome convencional do remoto principal\ngit fetch origin           # baixa commits/branches novos do remoto SEM mesclar no seu branch atual\ngit pull origin main       # equivale a: git fetch + git merge (ou rebase, se configurado) -- baixa E mescla\ngit push origin feature/x  # envia seus commits locais da branch feature/x para o remoto","fidelityText":"git clone <url-do-repositorio-remoto> # copia um repositório remoto inteiro (histórico + branches) para sua máquina git remote -v # lista os remotos configurados -- \"origin\" é o nome convencional do remoto principal git fetch origin # baixa commits/branches novos do remoto SEM mesclar no seu branch atual git pull origin main # equivale a: git fetch + git merge (ou rebase, se configurado) -- baixa E mescla git push origin feature/x # envia seus commits locais da branch feature/x para o remoto","highlightedHtml":"git clone &lt;url-of-repository-remote&gt;   <span class=\"com\"># copia um repositório remoto inteiro (histórico + branches) para sua máquina</span>\n\ngit remote -v              <span class=\"com\"># lista os remotos configurados -- \"origin\" é o nome convencional do remoto principal</span>\ngit fetch origin           <span class=\"com\"># baixa commits/branches novos do remoto SEM mesclar no seu branch atual</span>\ngit pull origin main       <span class=\"com\"># equivale a: git fetch + git merge (ou rebase, se configurado) -- baixa E mescla</span>\ngit push origin feature/x  <span class=\"com\"># envia seus commits locais da branch feature/x para o remoto</span>","caption":"Exemplo executável de git.","explanation":["fetch só atualiza a visão do remoto sem tocar no trabalho local; pull já mescla -- prefira fetch quando quiser decidir a integração conscientemente."],"commonMistakes":["Confundir fetch (seguro, não mescla) com pull (mescla automaticamente)"]},{"id":"git-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>fetch</code> e <code>pull</code> são frequentemente confundidos: <code>fetch</code> só atualiza sua visão do remoto (branches como <code>origin/main</code>), sem tocar no seu trabalho local — é seguro rodar a qualquer momento. <code>pull</code> já tenta mesclar essa atualização no seu branch atual, o que pode gerar conflito imediatamente. Em caso de dúvida sobre o estado do remoto, prefira <code>fetch</code> primeiro e decidir conscientemente como integrar, em vez de <code>pull</code> direto.</div>","fidelityText":"fetch e pull são frequentemente confundidos: fetch só atualiza sua visão do remoto (branches como origin/main), sem tocar no seu trabalho local — é seguro rodar a qualquer momento. pull já tenta mesclar essa atualização no seu branch atual, o que pode gerar conflito imediatamente. Em caso de dúvida sobre o estado do remoto, prefira fetch primeiro e decidir conscientemente como integrar, em vez de pull direto."},{"id":"git-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Desfazendo mudanças: restore, revert e reset</h2>","fidelityText":"Desfazendo mudanças: restore, revert e reset"},{"id":"git-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"git restore file.java            # descarta mudanças NÃO commitadas no working tree (volta ao último commit)\ngit restore --staged file.java   # tira do staging sem descartar a mudança no arquivo\n\ngit revert <hash-of-commit>         # cria um NOVO commit que desfaz o efeito de um commit antigo -- preserva histórico, seguro em branch compartilhada\n\ngit reset --soft <hash>             # move o branch para outro commit, mantém staging e working tree intactos\ngit reset --mixed <hash>            # (padrão) também desfaz o staging, mas preserva o working tree\ngit reset --hard <hash>             # descarta staging E working tree -- PERDE mudanças não commitadas, sem confirmação","fidelityText":"git restore arquivo.java # descarta mudanças NÃO commitadas no working tree (volta ao último commit) git restore --staged arquivo.java # tira do staging sem descartar a mudança no arquivo git revert <hash-do-commit> # cria um NOVO commit que desfaz o efeito de um commit antigo -- preserva histórico, seguro em branch compartilhada git reset --soft <hash> # move o branch para outro commit, mantém staging e working tree intactos git reset --mixed <hash> # (padrão) também desfaz o staging, mas preserva o working tree git reset --hard <hash> # descarta staging E working tree -- PERDE mudanças não commitadas, sem confirmação","highlightedHtml":"git restore file.java            <span class=\"com\"># descarta mudanças NÃO commitadas no working tree (volta ao último commit)</span>\ngit restore --staged file.java   <span class=\"com\"># tira do staging sem descartar a mudança no arquivo</span>\n\ngit revert &lt;hash-of-commit&gt;         <span class=\"com\"># cria um NOVO commit que desfaz o efeito de um commit antigo -- preserva histórico, seguro em branch compartilhada</span>\n\ngit reset --soft &lt;hash&gt;             <span class=\"com\"># move o branch para outro commit, mantém staging e working tree intactos</span>\ngit reset --mixed &lt;hash&gt;            <span class=\"com\"># (padrão) também desfaz o staging, mas preserva o working tree</span>\ngit reset --hard &lt;hash&gt;             <span class=\"com\"># descarta staging E working tree -- PERDE mudanças não commitadas, sem confirmação</span>","caption":"Exemplo executável de git.","explanation":["restore descarta mudanças não commitadas; revert cria um novo commit desfazendo outro, preservando histórico -- por isso é seguro em branch compartilhada."],"commonMistakes":["Usar reset --hard em branch já compartilhada com outras pessoas"]},{"id":"git-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>reset --hard</code> é destrutivo e o Git não pede confirmação.</b> Ele reescreve para onde o branch aponta e descarta qualquer mudança não commitada no caminho — não há lixeira. Prefira <code>git revert</code> em qualquer branch que outras pessoas já possam ter baixado: ele preserva o histórico e nunca reescreve commits existentes, apenas adiciona um novo que desfaz o anterior. Reserve <code>reset --hard</code> para o seu próprio trabalho local ainda não compartilhado.</div>","fidelityText":"reset --hard é destrutivo e o Git não pede confirmação. Ele reescreve para onde o branch aponta e descarta qualquer mudança não commitada no caminho — não há lixeira. Prefira git revert em qualquer branch que outras pessoas já possam ter baixado: ele preserva o histórico e nunca reescreve commits existentes, apenas adiciona um novo que desfaz o anterior. Reserve reset --hard para o seu próprio trabalho local ainda não compartilhado."},{"id":"git-content-18","type":"html","authorship":"legacy-preserved","html":"<h2>Guardando trabalho em progresso: stash</h2>","fidelityText":"Guardando trabalho em progresso: stash"},{"id":"git-code-19","type":"code","authorship":"legacy-preserved","language":"java","source":"git stash                    # guarda mudanças não commitadas de lado e limpa o working tree\ngit stash list                # lista os stashes guardados\ngit stash pop                 # reaplica o stash mais recente E remove da lista\ngit stash apply                # reaplica sem remover -- útil para aplicar o mesmo stash em mais de um lugar","fidelityText":"git stash # guarda mudanças não commitadas de lado e limpa o working tree git stash list # lista os stashes guardados git stash pop # reaplica o stash mais recente E remove da lista git stash apply # reaplica sem remover -- útil para aplicar o mesmo stash em mais de um lugar","highlightedHtml":"git stash                    <span class=\"com\"># guarda mudanças não commitadas de lado e limpa o working tree</span>\ngit stash list                <span class=\"com\"># lista os stashes guardados</span>\ngit stash pop                 <span class=\"com\"># reaplica o stash mais recente E remove da lista</span>\ngit stash apply                <span class=\"com\"># reaplica sem remover -- útil para aplicar o mesmo stash em mais de um lugar</span>","caption":"Exemplo executável de git.","explanation":["stash guarda o estado do working tree de lado sem commitar -- útil para trocar de branch sem perder progresso nem sujar o histórico."]},{"id":"git-content-20","type":"html","authorship":"legacy-preserved","html":"<p><code>stash</code> resolve o caso \"preciso trocar de branch agora, mas não quero commitar isto ainda nem perder o progresso\" — guarda o estado do working tree/staging de lado, disponível para recuperar depois, em qualquer branch.</p>","fidelityText":"stash resolve o caso \"preciso trocar de branch agora, mas não quero commitar isto ainda nem perder o progresso\" — guarda o estado do working tree/staging de lado, disponível para recuperar depois, em qualquer branch."},{"id":"git-content-21","type":"html","authorship":"legacy-preserved","html":"<h2>reflog: a rede de segurança contra \"eu perdi meu commit\"</h2>","fidelityText":"reflog: a rede de segurança contra \"eu perdi meu commit\""},{"id":"git-code-22","type":"code","authorship":"legacy-preserved","language":"java","source":"git reflog   # histórico de TODOS os pontos para onde HEAD já apontou nesta máquina -- inclusive commits \"perdidos\" por reset --hard\ngit checkout <hash-of-reflog>   # volta a enxergar um commit que um reset --hard parecia ter apagado","fidelityText":"git reflog # histórico de TODOS os pontos para onde HEAD já apontou nesta máquina -- inclusive commits \"perdidos\" por reset --hard git checkout <hash-do-reflog> # volta a enxergar um commit que um reset --hard parecia ter apagado","highlightedHtml":"git reflog   <span class=\"com\"># histórico de TODOS os pontos para onde HEAD já apontou nesta máquina -- inclusive commits \"perdidos\" por reset --hard</span>\ngit checkout &lt;hash-of-reflog&gt;   <span class=\"com\"># volta a enxergar um commit que um reset --hard parecia ter apagado</span>","caption":"Exemplo executável de git.","explanation":["reflog registra todo ponto para onde HEAD já apontou nesta máquina -- inclusive commits que reset --hard parecia ter apagado."]},{"id":"git-content-23","type":"html","authorship":"legacy-preserved","html":"<p>Um <code>reset --hard</code> ou uma branch deletada por engano raramente apaga o commit de verdade imediatamente — o Git mantém um registro local (<code>reflog</code>) de para onde <code>HEAD</code> apontou, geralmente por semanas, mesmo que nenhum branch aponte mais para aquele commit. Antes de assumir que um commit se perdeu para sempre, confira o <code>reflog</code>.</p>","fidelityText":"Um reset --hard ou uma branch deletada por engano raramente apaga o commit de verdade imediatamente — o Git mantém um registro local (reflog) de para onde HEAD apontou, geralmente por semanas, mesmo que nenhum branch aponte mais para aquele commit. Antes de assumir que um commit se perdeu para sempre, confira o reflog."},{"id":"git-content-24","type":"html","authorship":"legacy-preserved","html":"<h2>Tags: marcando um ponto específico do histórico</h2>","fidelityText":"Tags: marcando um ponto específico do histórico"},{"id":"git-code-25","type":"code","authorship":"legacy-preserved","language":"java","source":"git tag v1.0.0                 # marca o commit atual com uma tag leve\ngit tag -a v1.0.0 -m \"Release 1.0.0\"  # tag anotada -- guarda autor, data e mensagem, recomendada para releases\ngit push origin v1.0.0          # tags não são enviadas ao remoto automaticamente com push comum","fidelityText":"git tag v1.0.0 # marca o commit atual com uma tag leve git tag -a v1.0.0 -m \"Release 1.0.0\" # tag anotada -- guarda autor, data e mensagem, recomendada para releases git push origin v1.0.0 # tags não são enviadas ao remoto automaticamente com push comum","highlightedHtml":"git tag v1.0.0                 <span class=\"com\"># marca o commit atual com uma tag leve</span>\ngit tag -a v1.0.0 -m \"Release 1.0.0\"  <span class=\"com\"># tag anotada -- guarda autor, data e mensagem, recomendada para releases</span>\ngit push origin v1.0.0          <span class=\"com\"># tags não são enviadas ao remoto automaticamente com push comum</span>","caption":"Exemplo executável de git.","explanation":["Tag anotada guarda autor, data e mensagem -- preferível para releases; tags não são enviadas ao remoto com push comum, precisam ser enviadas explicitamente."]},{"id":"git-content-26","type":"html","authorship":"legacy-preserved","html":"<p>Uma <strong>tag</strong> marca permanentemente um commit específico — tipicamente usada para releases (<code>v1.0.0</code>, <code>v2.1.3</code>). Diferente de uma branch, uma tag não se move conforme novos commits são adicionados.</p>","fidelityText":"Uma tag marca permanentemente um commit específico — tipicamente usada para releases (v1.0.0, v2.1.3). Diferente de uma branch, uma tag não se move conforme novos commits são adicionados."},{"id":"git-content-27","type":"html","authorship":"legacy-preserved","html":"<h2>Detached HEAD: quando você não está \"em cima\" de nenhum branch</h2>","fidelityText":"Detached HEAD: quando você não está \"em cima\" de nenhum branch"},{"id":"git-code-28","type":"code","authorship":"legacy-preserved","language":"java","source":"git checkout <hash-of-um-commit-previous>   # HEAD passa a apontar direto para um commit, não para um branch","fidelityText":"git checkout <hash-de-um-commit-antigo> # HEAD passa a apontar direto para um commit, não para um branch","highlightedHtml":"git checkout &lt;hash-of-um-commit-previous&gt;   <span class=\"com\"># HEAD passa a apontar direto para um commit, não para um branch</span>","caption":"Exemplo executável de git.","explanation":["Em detached HEAD, novos commits não pertencem a nenhum branch -- crie um branch a partir dali antes de trocar de contexto, ou dependa do reflog para recuperá-los depois."],"commonMistakes":["Trocar de branch em detached HEAD sem criar um branch novo primeiro, perdendo a referência aos commits"]},{"id":"git-content-29","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Detached HEAD não é um erro, mas exige cuidado.</b> Nesse estado, novos commits ainda funcionam, mas não pertencem a nenhum branch — se você trocar de branch sem antes criar um novo branch a partir dali (<code>git checkout -b nome-do-branch</code>), esses commits ficam sem uma referência apontando para eles e dependem do <code>reflog</code> para serem recuperados. Útil para inspecionar um estado antigo do projeto; perigoso para trabalhar de verdade sem criar um branch primeiro.</div>","fidelityText":"Detached HEAD não é um erro, mas exige cuidado. Nesse estado, novos commits ainda funcionam, mas não pertencem a nenhum branch — se você trocar de branch sem antes criar um novo branch a partir dali (git checkout -b nome-do-branch), esses commits ficam sem uma referência apontando para eles e dependem do reflog para serem recuperados. Útil para inspecionar um estado antigo do projeto; perigoso para trabalhar de verdade sem criar um branch primeiro."},{"id":"git-content-30","type":"html","authorship":"legacy-preserved","html":"<h2>Pull Request — a unidade social do trabalho em equipe</h2>","fidelityText":"Pull Request — a unidade social do trabalho em equipe"},{"id":"git-content-31","type":"html","authorship":"legacy-preserved","html":"<p>Um <strong>Pull Request</strong> (PR) é um pedido para mesclar sua branch na branch principal, geralmente com revisão de código de outra pessoa antes de aceitar. É a prática padrão em qualquer time — mesmo sozinho, vale simular esse fluxo para manter <code>main</code> sempre funcional.</p>","fidelityText":"Um Pull Request (PR) é um pedido para mesclar sua branch na branch principal, geralmente com revisão de código de outra pessoa antes de aceitar. É a prática padrão em qualquer time — mesmo sozinho, vale simular esse fluxo para manter main sempre funcional."},{"id":"git-content-32","type":"html","authorship":"legacy-preserved","html":"<h2>merge vs rebase</h2>","fidelityText":"merge vs rebase"},{"id":"git-code-33","type":"code","authorship":"legacy-preserved","language":"java","source":"git merge feature/x    # cria um commit de merge, preserva o histórico exato de cada branch\ngit rebase main         # reescreve os commits da sua branch como se tivessem partido do main atual -- histórico linear, mas reescreve commits","fidelityText":"git merge feature/x # cria um commit de merge, preserva o histórico exato de cada branch git rebase main # reescreve os commits da sua branch como se tivessem partido do main atual -- histórico linear, mas reescreve commits","highlightedHtml":"git merge feature/x    <span class=\"com\"># cria um commit de merge, preserva o histórico exato de cada branch</span>\ngit rebase main         <span class=\"com\"># reescreve os commits da sua branch como se tivessem partido do main atual -- histórico linear, mas reescreve commits</span>","caption":"Exemplo executável de git.","explanation":["merge preserva o histórico exato de cada branch com um commit de merge; rebase reescreve os commits como se tivessem partido do estado atual -- histórico linear, mas hashes novos."],"commonMistakes":["Dar rebase em uma branch que outras pessoas já baixaram"]},{"id":"git-content-34","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Nunca dê rebase em uma branch que outras pessoas já baixaram</b> — como ele reescreve o histórico (novos hashes de commit), qualquer um que já tinha a versão antiga vai ter conflitos sérios ao sincronizar.</div>","fidelityText":"Nunca dê rebase em uma branch que outras pessoas já baixaram — como ele reescreve o histórico (novos hashes de commit), qualquer um que já tinha a versão antiga vai ter conflitos sérios ao sincronizar."},{"id":"git-content-35","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O arquivo <code>.gitignore</code> é o motivo real de nunca vazar uma senha de banco de produção sem querer: sem ele, é fácil dar <code>git add .</code> distraidamente e commitar um <code>application-prod.properties</code> com credenciais reais. Isso conecta direto com o capítulo 27 (configuração externa) e com o capítulo de Secrets mais adiante — a defesa em profundidade começa aqui, no controle de versão.</div>","fidelityText":"O arquivo .gitignore é o motivo real de nunca vazar uma senha de banco de produção sem querer: sem ele, é fácil dar git add . distraidamente e commitar um application-prod.properties com credenciais reais. Isso conecta direto com o capítulo 27 (configuração externa) e com o capítulo de Secrets mais adiante — a defesa em profundidade começa aqui, no controle de versão."},{"id":"git-exercise-36","type":"exercise","authorship":"legacy-preserved","title":"Exercício 29.1 — Fluxo completo com branch","prompt":"Inicialize um repositório Git para o projeto da biblioteca (capítulos 17/18). Crie um .gitignore ignorando target/, *.class e .env. Faça o commit inicial. Crie uma branch feature/notificacao-sms, adicione (mesmo que só como esqueleto) uma classe SmsServico, commit, volte para main e faça o merge.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 29.1 — Fluxo completo com branchfácil Inicialize um repositório Git para o projeto da biblioteca (capítulos 17/18). Crie um .gitignore ignorando target/, *.class e .env. Faça o commit inicial. Crie uma branch feature/notificacao-sms, adicione (mesmo que só como esqueleto) uma classe SmsServico, commit, volte para main e faça o merge. Ver solução git init echo \"target/ *.class .env\" > .gitignore git add . git commit -m \"Commit inicial: estrutura do projeto biblioteca\" git checkout -b feature/notificacao-sms # cria SmsServico.java... git add . git commit -m \"Adiciona esqueleto de SmsServico\" git checkout main git merge feature/notificacao-sms","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 29.1 — Fluxo completo com branch</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Inicialize um repositório Git para o projeto da biblioteca (capítulos 17/18). Crie um <code>.gitignore</code> ignorando <code>target/</code>, <code>*.class</code> e <code>.env</code>. Faça o commit inicial. Crie uma branch <code>feature/notificacao-sms</code>, adicione (mesmo que só como esqueleto) uma classe <code>SmsServico</code>, commit, volte para <code>main</code> e faça o merge.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">git init\n<span class=\"kw\">echo</span> \"target/\n*.class\n.env\" &gt; .gitignore\n\ngit add .\ngit commit -m \"Commit initial: structure of the project library\"\n\ngit checkout -b feature/notification-sms\n<span class=\"com\"># cria SmsServico.java...</span>\ngit add .\ngit commit -m \"Adds esqueleto of SmsService\"\n\ngit checkout main\ngit merge feature/notification-sms</pre>\n        </div>\n      </div>"},{"id":"git-exercise-37","type":"exercise","authorship":"legacy-preserved","title":"Exercício 29.2 — Desfazer com segurança","prompt":"No mesmo repositório, faça uma mudança qualquer no arquivo e rode git stash. Confirme com git status que o working tree está limpo. Recupere a mudança com git stash pop. Em seguida, faça um commit \"errado\" de propósito e desfaça-o com git revert (não reset --hard) — explique por que revert foi a escolha certa aqui, mesmo trabalhando sozinho.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 29.2 — Desfazer com segurançamédio No mesmo repositório, faça uma mudança qualquer no arquivo e rode git stash. Confirme com git status que o working tree está limpo. Recupere a mudança com git stash pop. Em seguida, faça um commit \"errado\" de propósito e desfaça-o com git revert (não reset --hard) — explique por que revert foi a escolha certa aqui, mesmo trabalhando sozinho. Ver critérios git revert cria um novo commit que desfaz o anterior, preservando o histórico completo — útil como hábito mesmo sozinho, porque documenta a correção em vez de apagar rastro do que aconteceu. reset --hard reescreveria o histórico e descartaria o commit por completo, o que é arriscado assim que o commit já foi enviado (push) para um remoto compartilhado com outras pessoas.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 29.2 — Desfazer com segurança</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>No mesmo repositório, faça uma mudança qualquer no arquivo e rode <code>git stash</code>. Confirme com <code>git status</code> que o working tree está limpo. Recupere a mudança com <code>git stash pop</code>. Em seguida, faça um commit \"errado\" de propósito e desfaça-o com <code>git revert</code> (não <code>reset --hard</code>) — explique por que <code>revert</code> foi a escolha certa aqui, mesmo trabalhando sozinho.</p>\n        <button class=\"reveal-btn\">Ver critérios</button>\n        <div class=\"solution\">\n          <p><code>git revert</code> cria um novo commit que desfaz o anterior, preservando o histórico completo — útil como hábito mesmo sozinho, porque documenta a correção em vez de apagar rastro do que aconteceu. <code>reset --hard</code> reescreveria o histórico e descartaria o commit por completo, o que é arriscado assim que o commit já foi enviado (<code>push</code>) para um remoto compartilhado com outras pessoas.</p>\n        </div>\n      </div>"},{"id":"git-model","type":"mental-model","authorship":"authored","title":"Do arquivo editado ao histórico revisável","body":"O fluxo básico separa mudança local, seleção do próximo snapshot e registro permanente no repositório.","flow":["editar no working tree","ver diferença com status/diff","adicionar parte ao index","commitar snapshot lógico","abrir branch para próxima mudança"],"ownership":["working tree pertence ao experimento atual","index pertence ao próximo commit","histórico compartilhado exige cuidado com rebase/force push"]},{"id":"git-quiz","type":"quiz","authorship":"authored","conceptId":"gitignore-secrets","prompt":"Por que criar .gitignore antes do primeiro commit importa?","options":[{"id":"git-q-a","label":"Porque evita que artefatos e segredos entrem no histórico por acidente.","correct":true,"explanation":"Depois de versionado, remover do working tree não apaga o histórico já publicado."},{"id":"git-q-b","label":"Porque Git não funciona sem .gitignore.","correct":false,"explanation":"Git funciona, mas pode rastrear arquivos que não deveriam ser versionados."},{"id":"git-q-c","label":"Porque .gitignore criptografa senhas automaticamente.","correct":false,"explanation":"Ele só ignora padrões de arquivos ainda não rastreados; não criptografa nada."}]}],"resources":[{"id":"git-book","type":"reference","title":"Pro Git Book","url":"https://git-scm.com/book/en/v2","reinforces":"Fonte oficial do Git para snapshots, staging, branches, merge, rebase e colaboração.","language":"en","publisher":"Git SCM","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"git-ignore-doc","type":"reference","title":"gitignore documentation","url":"https://git-scm.com/docs/gitignore","reinforces":"Define padrões ignorados e limites do .gitignore.","language":"en","publisher":"Git SCM","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A git operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a git operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this git chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"# baixe o instalador em git-scm.com e siga o wizard, ou via winget:","instruction":"A git operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this git chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"build","moduleId":"testing-engineering","order":1,"title":"Maven & Gradle","summary":"Até agora, todo exemplo assumiu que você compila com javac na mão. Projetos reais dependem de bibliotecas externas (JUnit, Mockito, e mais tarde o próprio Spring) — baixar e gerenciar isso manualmente não escala. Uma build tool resolve três problemas: gerenciar dependências, compilar/testar/empacotar o projeto com um único comando, e garantir que o build seja reproduzível em qualquer máquina.","objectives":["Explicar ciclo compile/test/package/clean","Declarar dependências com escopo e versão","Comparar Maven e Gradle pelo contrato de build reproduzível"],"whyItExists":"Depois de Git, o projeto precisa deixar de depender da IDE. Build tool torna compilação, testes, dependências e artefatos reproduzíveis em qualquer máquina.","prerequisiteChapterIds":["git"],"conceptIds":["maven-configuracao-declarativa-em-xml","gradle-configuracao-em-codigo-groovy-kotlin-dsl","configurations-do-gradle-implementation-api-compileonly-runtimeonly","grafo-de-tasks-e-cache-incremental","estrutura-padrao-de-diretorios-maven-e-gradle-compartilham-a-convencao","lifecycle-phase-e-goal-maven","dependencias-transitivas-exclusoes-e-dependencymanagement","maven-wrapper-build-reproduzivel-sem-funciona-na-minha-maquina","escopos-de-dependencia-maven-e-por-que-importam"],"introducedConceptIds":["build-lifecycle","dependencia-escopo-versao","wrapper-build-reprodutivel"],"usedConceptIds":["classpath-compilacao","git-snapshot-index"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"build-intuition","type":"intuition","authorship":"authored","title":"Build é contrato de reprodução","body":"Se outra pessoa não consegue compilar, testar e empacotar com comandos documentados, o projeto ainda depende de conhecimento escondido da sua máquina.","analogyLimit":"Receita ajuda, mas build também resolve versões, classpath, plugins, ciclo de vida e cache."},{"id":"build-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#pacotes\">18 · Pacotes &amp; projeto</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: 18 · Pacotes & projeto"},{"id":"build-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Até agora, todo exemplo assumiu que você compila com <code>javac</code> na mão. Projetos reais dependem de <strong>bibliotecas externas</strong> (JUnit, Mockito, e mais tarde o próprio Spring) — baixar e gerenciar isso manualmente não escala. Uma <strong>build tool</strong> resolve três problemas: gerenciar dependências, compilar/testar/empacotar o projeto com um único comando, e garantir que o build seja reproduzível em qualquer máquina.</p>","fidelityText":"Até agora, todo exemplo assumiu que você compila com javac na mão. Projetos reais dependem de bibliotecas externas (JUnit, Mockito, e mais tarde o próprio Spring) — baixar e gerenciar isso manualmente não escala. Uma build tool resolve três problemas: gerenciar dependências, compilar/testar/empacotar o projeto com um único comando, e garantir que o build seja reproduzível em qualquer máquina."},{"id":"build-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Maven — configuração declarativa em XML</h2>","fidelityText":"Maven — configuração declarativa em XML"},{"id":"build-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"<!-- pom.xml -->\n<project>\n    <groupId>com.felipy</groupId>\n    <artifactId>library</artifactId>\n    <version>1.0.0</version>\n    <properties>\n        <maven.compiler.source>21</maven.compiler.source>\n        <maven.compiler.target>21</maven.compiler.target>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>5.10.0</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n</project>","fidelityText":"<!-- pom.xml --> <project> <groupId>com.felipy</groupId> <artifactId>biblioteca</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> </dependencies> </project>","highlightedHtml":"<span class=\"com\">&lt;!-- pom.xml --&gt;</span>\n&lt;project&gt;\n    &lt;groupId&gt;com.felipy&lt;/groupId&gt;\n    &lt;artifactId&gt;library&lt;/artifactId&gt;\n    &lt;version&gt;1.0.0&lt;/version&gt;\n    &lt;properties&gt;\n        &lt;maven.compiler.source&gt;21&lt;/maven.compiler.source&gt;\n        &lt;maven.compiler.target&gt;21&lt;/maven.compiler.target&gt;\n    &lt;/properties&gt;\n\n    &lt;dependencies&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;\n            &lt;artifactId&gt;junit-jupiter&lt;/artifactId&gt;\n            &lt;version&gt;5.10.0&lt;/version&gt;\n            &lt;scope&gt;test&lt;/scope&gt;\n        &lt;/dependency&gt;\n    &lt;/dependencies&gt;\n&lt;/project&gt;","caption":"Exemplo executável de build.","explanation":["pom.xml declara coordenadas (groupId/artifactId/version), versão do Java e dependências -- é a fonte de verdade declarativa do projeto."]},{"id":"build-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"mvn compile     # compila o código em src/main/java\nmvn test        # roda os testes de src/test/java\nmvn package     # gera um .jar (ou .war) em target/\nmvn clean       # apaga o diretório target/","fidelityText":"mvn compile # compila o código em src/main/java mvn test # roda os testes de src/test/java mvn package # gera um .jar (ou .war) em target/ mvn clean # apaga o diretório target/","highlightedHtml":"mvn compile     <span class=\"com\"># compila o código em src/main/java</span>\nmvn test        <span class=\"com\"># roda os testes de src/test/java</span>\nmvn package     <span class=\"com\"># gera um .jar (ou .war) em target/</span>\nmvn clean       <span class=\"com\"># apaga o diretório target/</span>","caption":"Exemplo executável de build.","explanation":["Cada comando roda uma phase do lifecycle -- package já inclui compile e test automaticamente antes de empacotar."],"commonMistakes":["Achar que mvn package pula a etapa de testes"]},{"id":"build-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Gradle — configuração em código (Groovy/Kotlin DSL)</h2>","fidelityText":"Gradle — configuração em código (Groovy/Kotlin DSL)"},{"id":"build-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"// build.gradle.kts (Kotlin DSL, padrão em projetos novos)\nplugins {\n    java\n}\n\ngroup = \"com.felipy\"\nversion = \"1.0.0\"\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation(\"org.junit.jupiter:junit-jupiter:5.10.0\")\n}","fidelityText":"// build.gradle.kts (Kotlin DSL, padrão em projetos novos) plugins { java } group = \"com.felipy\" version = \"1.0.0\" repositories { mavenCentral() } dependencies { testImplementation(\"org.junit.jupiter:junit-jupiter:5.10.0\") }","highlightedHtml":"<span class=\"com\">// build.gradle.kts (Kotlin DSL, padrão em projetos novos)</span>\nplugins {\n    java\n}\n\ngroup = <span class=\"str\">\"com.felipy\"</span>\nversion = <span class=\"str\">\"1.0.0\"</span>\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation(<span class=\"str\">\"org.junit.jupiter:junit-jupiter:5.10.0\"</span>)\n}","caption":"Exemplo executável de build.","explanation":["Gradle usa um script (Kotlin ou Groovy DSL) em vez de XML declarativo -- mesma informação, formato diferente."]},{"id":"build-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"./gradlew build   # compila, testa e empacota\n./gradlew test    # só roda os testes\n./gradlew run     # executa a aplicação (com o plugin \"application\")\n./gradlew tasks   # lista as tasks disponíveis no projeto","fidelityText":"./gradlew build # compila, testa e empacota ./gradlew test # só roda os testes ./gradlew run # executa a aplicação (com o plugin \"application\") ./gradlew tasks # lista as tasks disponíveis no projeto","highlightedHtml":"./gradlew build   <span class=\"com\"># compila, testa e empacota</span>\n./gradlew test    <span class=\"com\"># só roda os testes</span>\n./gradlew run     <span class=\"com\"># executa a aplicação (com o plugin \"application\")</span>\n./gradlew tasks   <span class=\"com\"># lista as tasks disponíveis no projeto</span>","caption":"Exemplo executável de build.","explanation":["gradlew usa o Gradle Wrapper -- a versão do Gradle fixada pelo projeto, não a instalada globalmente."]},{"id":"build-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Configurations do Gradle: implementation, api, compileOnly, runtimeOnly</h2>","fidelityText":"Configurations do Gradle: implementation, api, compileOnly, runtimeOnly"},{"id":"build-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"dependencies {\n    implementation(\"com.fasterxml.jackson.core:jackson-databind:2.17.0\") // disponível em compilação e runtime do PRÓPRIO módulo; NÃO vaza para quem depende dele\n    api(\"org.slf4j:slf4j-api:2.0.13\")                              // como implementation, mas VAZA para o classpath de quem depende deste módulo\n    compileOnly(\"org.projectlombok:lombok:1.18.32\")                // só em compilação -- não entra no runtime nem no artefato final\n    runtimeOnly(\"com.h2database:h2:2.2.224\")                       // só em runtime -- não precisa estar disponível para compilar\n    testImplementation(\"org.junit.jupiter:junit-jupiter:5.10.0\")     // só no classpath de teste, equivalente a scope=test do Maven\n}","fidelityText":"dependencies { implementation(\"com.fasterxml.jackson.core:jackson-databind:2.17.0\") // disponível em compilação e runtime do PRÓPRIO módulo; NÃO vaza para quem depende dele api(\"org.slf4j:slf4j-api:2.0.13\") // como implementation, mas VAZA para o classpath de quem depende deste módulo compileOnly(\"org.projectlombok:lombok:1.18.32\") // só em compilação -- não entra no runtime nem no artefato final runtimeOnly(\"com.h2database:h2:2.2.224\") // só em runtime -- não precisa estar disponível para compilar testImplementation(\"org.junit.jupiter:junit-jupiter:5.10.0\") // só no classpath de teste, equivalente a scope=test do Maven }","highlightedHtml":"dependencies {\n    implementation(<span class=\"str\">\"com.fasterxml.jackson.core:jackson-databind:2.17.0\"</span>) <span class=\"com\">// disponível em compilação e runtime do PRÓPRIO módulo; NÃO vaza para quem depende dele</span>\n    api(<span class=\"str\">\"org.slf4j:slf4j-api:2.0.13\"</span>)                              <span class=\"com\">// como implementation, mas VAZA para o classpath de quem depende deste módulo</span>\n    compileOnly(<span class=\"str\">\"org.projectlombok:lombok:1.18.32\"</span>)                <span class=\"com\">// só em compilação -- não entra no runtime nem no artefato final</span>\n    runtimeOnly(<span class=\"str\">\"com.h2database:h2:2.2.224\"</span>)                       <span class=\"com\">// só em runtime -- não precisa estar disponível para compilar</span>\n    testImplementation(<span class=\"str\">\"org.junit.jupiter:junit-jupiter:5.10.0\"</span>)     <span class=\"com\">// só no classpath de teste, equivalente a scope=test do Maven</span>\n}","caption":"Exemplo executável de build.","explanation":["implementation não vaza para quem depende do módulo; api vaza; compileOnly não entra no runtime; runtimeOnly não precisa estar disponível para compilar."],"commonMistakes":["Usar api quando implementation já resolveria, vazando classpath desnecessariamente para módulos dependentes"]},{"id":"build-content-11","type":"html","authorship":"legacy-preserved","html":"<p>A distinção <code>implementation</code> vs. <code>api</code> existe para controlar <strong>vazamento de classpath</strong> em projetos multi-módulo: se o módulo A usa <code>implementation</code> para uma biblioteca, um módulo B que depende de A não enxerga essa biblioteca automaticamente — só se A declarar como <code>api</code>. Isso deixa explícito o que faz parte do \"contrato público\" do módulo A e o que é detalhe de implementação interna, reduzindo recompilações desnecessárias quando um detalhe interno muda.</p>","fidelityText":"A distinção implementation vs. api existe para controlar vazamento de classpath em projetos multi-módulo: se o módulo A usa implementation para uma biblioteca, um módulo B que depende de A não enxerga essa biblioteca automaticamente — só se A declarar como api. Isso deixa explícito o que faz parte do \"contrato público\" do módulo A e o que é detalhe de implementação interna, reduzindo recompilações desnecessárias quando um detalhe interno muda."},{"id":"build-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Grafo de tasks e cache incremental</h2>","fidelityText":"Grafo de tasks e cache incremental"},{"id":"build-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Gradle modela o build como um <strong>grafo de tasks</strong> com dependências entre si (a task <code>test</code> depende de <code>compileJava</code>, que depende de <code>processResources</code>, etc.) — ao rodar <code>./gradlew build</code>, o Gradle calcula quais tasks realmente precisam rodar. Se as entradas de uma task (código-fonte, classpath, configuração) não mudaram desde a última execução, Gradle pode pular a task inteira (marcada <code>UP-TO-DATE</code>) ou reaproveitar resultado de um cache local/remoto — essa é a origem real de builds incrementais mais rápidas, não uma propriedade mágica da ferramenta em si.</p>","fidelityText":"Gradle modela o build como um grafo de tasks com dependências entre si (a task test depende de compileJava, que depende de processResources, etc.) — ao rodar ./gradlew build, o Gradle calcula quais tasks realmente precisam rodar. Se as entradas de uma task (código-fonte, classpath, configuração) não mudaram desde a última execução, Gradle pode pular a task inteira (marcada UP-TO-DATE) ou reaproveitar resultado de um cache local/remoto — essa é a origem real de builds incrementais mais rápidas, não uma propriedade mágica da ferramenta em si."},{"id":"build-content-14","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Maven</th><th>Gradle</th></tr>\n        <tr><td>Formato</td><td>XML declarativo (<code>pom.xml</code>)</td><td>Script/DSL (Groovy ou Kotlin)</td></tr>\n        <tr><td>Modelo de execução</td><td>Lifecycle fixo de phases predefinidas</td><td>Grafo de tasks configurável, com cache incremental explícito</td></tr>\n        <tr><td>Onde aparece mais</td><td>Projetos corporativos legados, Spring Initializr (opção padrão)</td><td>Projetos Android (oficial), projetos que precisam de lógica de build customizada</td></tr>\n      </tbody></table>","fidelityText":"MavenGradle FormatoXML declarativo (pom.xml)Script/DSL (Groovy ou Kotlin) Modelo de execuçãoLifecycle fixo de phases predefinidasGrafo de tasks configurável, com cache incremental explícito Onde aparece maisProjetos corporativos legados, Spring Initializr (opção padrão)Projetos Android (oficial), projetos que precisam de lógica de build customizada"},{"id":"build-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Evite comparar Maven e Gradle por slogans (\"Gradle é sempre mais rápido\", \"Maven é só para legado\") — a diferença real de desempenho depende de cache (frio vs. quente), tamanho do projeto, paralelismo configurado e da própria lógica de build escrita. Um projeto Gradle mal configurado (sem aproveitar cache/incrementalidade) pode ser mais lento que um Maven equivalente. O critério de escolha real costuma ser: convenção fixa e previsibilidade (Maven) vs. necessidade de lógica de build customizada e flexibilidade (Gradle) — não velocidade genérica.</div>","fidelityText":"Evite comparar Maven e Gradle por slogans (\"Gradle é sempre mais rápido\", \"Maven é só para legado\") — a diferença real de desempenho depende de cache (frio vs. quente), tamanho do projeto, paralelismo configurado e da própria lógica de build escrita. Um projeto Gradle mal configurado (sem aproveitar cache/incrementalidade) pode ser mais lento que um Maven equivalente. O critério de escolha real costuma ser: convenção fixa e previsibilidade (Maven) vs. necessidade de lógica de build customizada e flexibilidade (Gradle) — não velocidade genérica."},{"id":"build-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O <strong>Spring Initializr</strong> (start.spring.io) — a ferramenta oficial para criar um projeto Spring Boot do zero — pergunta logo na primeira tela \"Maven ou Gradle?\". Tudo que você configurar ali vira exatamente um <code>pom.xml</code> ou <code>build.gradle</code> como os de cima, com as dependências do Spring já declaradas. Entender a estrutura desses arquivos <em>antes</em> de ver o Spring Initializr transforma aquela tela de \"mágica\" em \"ah, é só isso\".</div>","fidelityText":"O Spring Initializr (start.spring.io) — a ferramenta oficial para criar um projeto Spring Boot do zero — pergunta logo na primeira tela \"Maven ou Gradle?\". Tudo que você configurar ali vira exatamente um pom.xml ou build.gradle como os de cima, com as dependências do Spring já declaradas. Entender a estrutura desses arquivos antes de ver o Spring Initializr transforma aquela tela de \"mágica\" em \"ah, é só isso\"."},{"id":"build-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Estrutura padrão de diretórios (Maven e Gradle compartilham a convenção)</h2>","fidelityText":"Estrutura padrão de diretórios (Maven e Gradle compartilham a convenção)"},{"id":"build-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"project/\n├── pom.xml (ou build.gradle.kts)\n├── src/\n│   ├── main/\n│   │   ├── java/        # código de produção\n│   │   └── resources/   # application.properties, arquivos estáticos\n│   └── test/\n│       ├── java/        # testes\n│       └── resources/   # configuração específica de teste\n└── target/ (ou build/)  # artefatos gerados -- fica de fora do controle de versão","fidelityText":"projeto/ ├── pom.xml (ou build.gradle.kts) ├── src/ │ ├── main/ │ │ ├── java/ # código de produção │ │ └── resources/ # application.properties, arquivos estáticos │ └── test/ │ ├── java/ # testes │ └── resources/ # configuração específica de teste └── target/ (ou build/) # artefatos gerados -- fica de fora do controle de versão","highlightedHtml":"project/\n├── pom.xml (ou build.gradle.kts)\n├── src/\n│   ├── main/\n│   │   ├── java/        <span class=\"com\"># código de produção</span>\n│   │   └── resources/   <span class=\"com\"># application.properties, arquivos estáticos</span>\n│   └── test/\n│       ├── java/        <span class=\"com\"># testes</span>\n│       └── resources/   <span class=\"com\"># configuração específica de teste</span>\n└── target/ (ou build/)  <span class=\"com\"># artefatos gerados -- fica de fora do controle de versão</span>","caption":"Exemplo executável de build.","explanation":["A convenção de diretórios (src/main/java, src/test/java) é o que permite rodar comandos sem configurar onde estão as fontes."]},{"id":"build-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Essa convenção (\"convention over configuration\") é o que permite rodar <code>mvn test</code> em qualquer projeto Maven sem configurar onde estão as fontes — a ferramenta já sabe procurar em <code>src/main/java</code> e <code>src/test/java</code>. Fugir dessa convenção é possível, mas exige configuração explícita e reduz a portabilidade entre projetos.</p>","fidelityText":"Essa convenção (\"convention over configuration\") é o que permite rodar mvn test em qualquer projeto Maven sem configurar onde estão as fontes — a ferramenta já sabe procurar em src/main/java e src/test/java. Fugir dessa convenção é possível, mas exige configuração explícita e reduz a portabilidade entre projetos."},{"id":"build-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Lifecycle: phase e goal (Maven)</h2>","fidelityText":"Lifecycle: phase e goal (Maven)"},{"id":"build-content-21","type":"html","authorship":"legacy-preserved","html":"<p>O Maven organiza a build em um <strong>lifecycle</strong> — uma sequência ordenada de <strong>phases</strong> (<code>validate</code> → <code>compile</code> → <code>test</code> → <code>package</code> → <code>verify</code> → <code>install</code> → <code>deploy</code>). Rodar uma phase executa <strong>todas as phases anteriores</strong> automaticamente: <code>mvn package</code> primeiro compila e testa, sem precisar rodar cada uma manualmente. Uma <strong>goal</strong> é uma tarefa concreta de um plugin (ex.: <code>compiler:compile</code>), amarrada a uma phase.</p>","fidelityText":"O Maven organiza a build em um lifecycle — uma sequência ordenada de phases (validate → compile → test → package → verify → install → deploy). Rodar uma phase executa todas as phases anteriores automaticamente: mvn package primeiro compila e testa, sem precisar rodar cada uma manualmente. Uma goal é uma tarefa concreta de um plugin (ex.: compiler:compile), amarrada a uma phase."},{"id":"build-code-22","type":"code","authorship":"legacy-preserved","language":"java","source":"mvn compile     # só compila (fase compile)\nmvn test        # compila + roda testes (fase test, que já inclui compile)\nmvn package     # compila + testa + empacota .jar (fase package)\nmvn install     # compila, testa, empacota e instala o artefato no repositório LOCAL (~/.m2), disponível a outros projetos na máquina\nmvn verify      # roda verificações adicionais (ex.: testes de integração) além do package\nmvn clean       # apaga target/ -- não faz parte do lifecycle padrão, é encadeável: mvn clean install","fidelityText":"mvn compile # só compila (fase compile) mvn test # compila + roda testes (fase test, que já inclui compile) mvn package # compila + testa + empacota .jar (fase package) mvn install # compila, testa, empacota e instala o artefato no repositório LOCAL (~/.m2), disponível a outros projetos na máquina mvn verify # roda verificações adicionais (ex.: testes de integração) além do package mvn clean # apaga target/ -- não faz parte do lifecycle padrão, é encadeável: mvn clean install","highlightedHtml":"mvn compile     <span class=\"com\"># só compila (fase compile)</span>\nmvn test        <span class=\"com\"># compila + roda testes (fase test, que já inclui compile)</span>\nmvn package     <span class=\"com\"># compila + testa + empacota .jar (fase package)</span>\nmvn install     <span class=\"com\"># compila, testa, empacota e instala o artefato no repositório LOCAL (~/.m2), disponível a outros projetos na máquina</span>\nmvn verify      <span class=\"com\"># roda verificações adicionais (ex.: testes de integração) além do package</span>\nmvn clean       <span class=\"com\"># apaga target/ -- não faz parte do lifecycle padrão, é encadeável: mvn clean install</span>","caption":"Exemplo executável de build.","explanation":["Rodar uma phase executa todas as phases anteriores do lifecycle automaticamente -- mvn package já compila e testa antes de empacotar."],"commonMistakes":["Rodar mvn test e mvn compile separadamente achando que precisa dos dois"]},{"id":"build-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>Dependências transitivas, exclusões e dependencyManagement</h2>","fidelityText":"Dependências transitivas, exclusões e dependencyManagement"},{"id":"build-content-24","type":"html","authorship":"legacy-preserved","html":"<p>Quando você declara uma dependência, o Maven também baixa <strong>as dependências dela</strong> (transitivas), recursivamente. Isso costuma ser conveniente, mas pode trazer uma versão de biblioteca que conflita com outra parte do projeto — o Maven resolve o conflito por uma regra de proximidade (<em>dependency mediation</em>), nem sempre a que você esperaria.</p>","fidelityText":"Quando você declara uma dependência, o Maven também baixa as dependências dela (transitivas), recursivamente. Isso costuma ser conveniente, mas pode trazer uma versão de biblioteca que conflita com outra parte do projeto — o Maven resolve o conflito por uma regra de proximidade (dependency mediation), nem sempre a que você esperaria."},{"id":"build-code-25","type":"code","authorship":"legacy-preserved","language":"java","source":"mvn dependency:tree  # mostra a árvore completa, incluindo transitivas -- o primeiro passo para diagnosticar conflito de versão","fidelityText":"mvn dependency:tree # mostra a árvore completa, incluindo transitivas -- o primeiro passo para diagnosticar conflito de versão","highlightedHtml":"mvn dependency:tree  <span class=\"com\"># mostra a árvore completa, incluindo transitivas -- o primeiro passo para diagnosticar conflito de versão</span>","caption":"Exemplo executável de build.","explanation":["dependency:tree revela as transitivas e ajuda a diagnosticar qual dependência trouxe uma versão conflitante."]},{"id":"build-code-26","type":"code","authorship":"legacy-preserved","language":"java","source":"<dependency>\n    <groupId>com.exemplo</groupId>\n    <artifactId>lib-x</artifactId>\n    <version>2.0</version>\n    <exclusions>\n        <exclusion> <!-- remove uma transitiva específica que conflita com outra versão já usada -->\n            <groupId>com.another</groupId>\n            <artifactId>lib-y</artifactId>\n        </exclusion>\n    </exclusions>\n</dependency>\n\n<dependencyManagement> <!-- centraliza a VERSÃO sem declarar a dependência em si -- um BOM (Bill of Materials) faz isso em escala -->\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-dependencies</artifactId>\n            <version>3.5.0</version>\n            <type>pom</type>\n            <scope>import</scope>\n        </dependency>\n    </dependencies>\n</dependencyManagement>","fidelityText":"<dependency> <groupId>com.exemplo</groupId> <artifactId>lib-x</artifactId> <version>2.0</version> <exclusions> <exclusion> <!-- remove uma transitiva específica que conflita com outra versão já usada --> <groupId>com.outra</groupId> <artifactId>lib-y</artifactId> </exclusion> </exclusions> </dependency> <dependencyManagement> <!-- centraliza a VERSÃO sem declarar a dependência em si -- um BOM (Bill of Materials) faz isso em escala --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.5.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>","highlightedHtml":"&lt;dependency&gt;\n    &lt;groupId&gt;com.exemplo&lt;/groupId&gt;\n    &lt;artifactId&gt;lib-x&lt;/artifactId&gt;\n    &lt;version&gt;2.0&lt;/version&gt;\n    &lt;exclusions&gt;\n        &lt;exclusion&gt; <span class=\"com\">&lt;!-- remove uma transitiva específica que conflita com outra versão já usada --&gt;</span>\n            &lt;groupId&gt;com.another&lt;/groupId&gt;\n            &lt;artifactId&gt;lib-y&lt;/artifactId&gt;\n        &lt;/exclusion&gt;\n    &lt;/exclusions&gt;\n&lt;/dependency&gt;\n\n&lt;dependencyManagement&gt; <span class=\"com\">&lt;!-- centraliza a VERSÃO sem declarar a dependência em si -- um BOM (Bill of Materials) faz isso em escala --&gt;</span>\n    &lt;dependencies&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;\n            &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;\n            &lt;version&gt;3.5.0&lt;/version&gt;\n            &lt;type&gt;pom&lt;/type&gt;\n            &lt;scope&gt;import&lt;/scope&gt;\n        &lt;/dependency&gt;\n    &lt;/dependencies&gt;\n&lt;/dependencyManagement&gt;","caption":"Exemplo executável de build.","explanation":["exclusions remove uma transitiva específica; dependencyManagement/BOM centraliza versões sem forçar a dependência em todo módulo."],"commonMistakes":["Declarar a mesma versão manualmente em vários módulos em vez de usar dependencyManagement/BOM"]},{"id":"build-content-27","type":"html","authorship":"legacy-preserved","html":"<p><code>dependencyManagement</code> declara <strong>versões</strong> centralizadas sem forçar a dependência em todo módulo — cada módulo ainda precisa declarar a dependência (sem versão, herdando a do management). Um <strong>BOM</strong> (Bill of Materials, como <code>spring-boot-dependencies</code>) é um <code>pom</code> especial cheio de entradas de <code>dependencyManagement</code>, garantindo que todas as bibliotecas de um ecossistema (Spring, por exemplo) usem versões compatíveis entre si sem você precisar declarar cada uma manualmente.</p>","fidelityText":"dependencyManagement declara versões centralizadas sem forçar a dependência em todo módulo — cada módulo ainda precisa declarar a dependência (sem versão, herdando a do management). Um BOM (Bill of Materials, como spring-boot-dependencies) é um pom especial cheio de entradas de dependencyManagement, garantindo que todas as bibliotecas de um ecossistema (Spring, por exemplo) usem versões compatíveis entre si sem você precisar declarar cada uma manualmente."},{"id":"build-content-28","type":"html","authorship":"legacy-preserved","html":"<h2>Maven Wrapper: build reproduzível sem \"funciona na minha máquina\"</h2>","fidelityText":"Maven Wrapper: build reproduzível sem \"funciona na minha máquina\""},{"id":"build-code-29","type":"code","authorship":"legacy-preserved","language":"java","source":"./mvnw clean install   # usa a versão do Maven declarada no projeto, baixando-a se necessário -- não depende do Maven instalado globalmente","fidelityText":"./mvnw clean install # usa a versão do Maven declarada no projeto, baixando-a se necessário -- não depende do Maven instalado globalmente","highlightedHtml":"./mvnw clean install   <span class=\"com\"># usa a versão do Maven declarada no projeto, baixando-a se necessário -- não depende do Maven instalado globalmente</span>","caption":"Exemplo executável de build.","explanation":["O Maven Wrapper fixa a versão exata do Maven usada pelo projeto, commitada no repositório -- evita divergência entre máquinas."],"commonMistakes":["Usar mvn global em vez de ./mvnw em projetos que já commitaram o wrapper"]},{"id":"build-content-30","type":"html","authorship":"legacy-preserved","html":"<p>O <strong>Maven Wrapper</strong> (<code>mvnw</code>/<code>mvnw.cmd</code>, gerados por <code>mvn wrapper:wrapper</code>) fixa a versão exata do Maven usada pelo projeto, commitada no repositório. Isso resolve o problema clássico de \"funciona aqui, não funciona no CI\" causado por duas máquinas com versões diferentes do Maven instaladas globalmente — prefira sempre <code>./mvnw</code> a um <code>mvn</code> global em projetos reais.</p>","fidelityText":"O Maven Wrapper (mvnw/mvnw.cmd, gerados por mvn wrapper:wrapper) fixa a versão exata do Maven usada pelo projeto, commitada no repositório. Isso resolve o problema clássico de \"funciona aqui, não funciona no CI\" causado por duas máquinas com versões diferentes do Maven instaladas globalmente — prefira sempre ./mvnw a um mvn global em projetos reais."},{"id":"build-content-31","type":"html","authorship":"legacy-preserved","html":"<h2>Escopos de dependência (Maven) e por que importam</h2>","fidelityText":"Escopos de dependência (Maven) e por que importam"},{"id":"build-code-32","type":"code","authorship":"legacy-preserved","language":"java","source":"<scope>compile</scope>  <!-- padrão: disponível em compilação, teste e runtime -->\n<scope>test</scope>     <!-- só disponível ao rodar testes (ex: JUnit, Mockito) -->\n<scope>provided</scope> <!-- disponível em compilação, mas fornecida pelo ambiente em runtime -->","fidelityText":"<scope>compile</scope> <!-- padrão: disponível em compilação, teste e runtime --> <scope>test</scope> <!-- só disponível ao rodar testes (ex: JUnit, Mockito) --> <scope>provided</scope> <!-- disponível em compilação, mas fornecida pelo ambiente em runtime -->","highlightedHtml":"&lt;scope&gt;compile&lt;/scope&gt;  <span class=\"com\">&lt;!-- padrão: disponível em compilação, teste e runtime --&gt;</span>\n&lt;scope&gt;test&lt;/scope&gt;     <span class=\"com\">&lt;!-- só disponível ao rodar testes (ex: JUnit, Mockito) --&gt;</span>\n&lt;scope&gt;provided&lt;/scope&gt; <span class=\"com\">&lt;!-- disponível em compilação, mas fornecida pelo ambiente em runtime --&gt;</span>","caption":"Exemplo executável de build.","explanation":["compile é o escopo padrão (disponível sempre); test só existe para testes; provided assume que o ambiente de runtime já fornece a dependência."]},{"id":"build-content-33","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Erro comum de iniciante:</b> declarar JUnit ou Mockito sem <code>&lt;scope&gt;test&lt;/scope&gt;</code>. Isso funciona, mas empacota bibliotecas de teste dentro do <code>.jar</code> de produção, aumentando o tamanho do artefato final sem necessidade.</div>","fidelityText":"Erro comum de iniciante: declarar JUnit ou Mockito sem <scope>test</scope>. Isso funciona, mas empacota bibliotecas de teste dentro do .jar de produção, aumentando o tamanho do artefato final sem necessidade."},{"id":"build-exercise-34","type":"exercise","authorship":"legacy-preserved","title":"Exercício 19.1 — Seu primeiro pom.xml","prompt":"Escreva um pom.xml mínimo e válido para o projeto da biblioteca (capítulo 17/18), com groupId, artifactId, version, compilando para Java 21, e com JUnit 5 como dependência de escopo test.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 19.1 — Seu primeiro pom.xmlfácil Escreva um pom.xml mínimo e válido para o projeto da biblioteca (capítulo 17/18), com groupId, artifactId, version, compilando para Java 21, e com JUnit 5 como dependência de escopo test. Ver solução <project> <modelVersion>4.0.0</modelVersion> <groupId>com.felipy</groupId> <artifactId>biblioteca</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> </dependencies> </project>","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 19.1 — Seu primeiro pom.xml</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Escreva um <code>pom.xml</code> mínimo e válido para o projeto da biblioteca (capítulo 17/18), com <code>groupId</code>, <code>artifactId</code>, <code>version</code>, compilando para Java 21, e com JUnit 5 como dependência de escopo <code>test</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">&lt;project&gt;\n    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;\n    &lt;groupId&gt;com.felipy&lt;/groupId&gt;\n    &lt;artifactId&gt;library&lt;/artifactId&gt;\n    &lt;version&gt;1.0.0&lt;/version&gt;\n    &lt;packaging&gt;jar&lt;/packaging&gt;\n\n    &lt;properties&gt;\n        &lt;maven.compiler.source&gt;21&lt;/maven.compiler.source&gt;\n        &lt;maven.compiler.target&gt;21&lt;/maven.compiler.target&gt;\n        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;\n    &lt;/properties&gt;\n\n    &lt;dependencies&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;\n            &lt;artifactId&gt;junit-jupiter&lt;/artifactId&gt;\n            &lt;version&gt;5.10.0&lt;/version&gt;\n            &lt;scope&gt;test&lt;/scope&gt;\n        &lt;/dependency&gt;\n    &lt;/dependencies&gt;\n&lt;/project&gt;</pre>\n        </div>\n      </div>"},{"id":"build-exercise-35","type":"exercise","authorship":"legacy-preserved","title":"Exercício 19.2 — Diagnosticando conflito de versão","prompt":"No pom.xml do exercício anterior, adicione uma dependência qualquer que você saiba que traz transitivas (por exemplo, uma lib de JSON). Rode mvn dependency:tree e identifique pelo menos uma dependência transitiva na árvore. Explique, em texto, o que você faria se essa transitiva trouxesse uma versão de uma biblioteca que conflita com uma dependência direta do seu projeto.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 19.2 — Diagnosticando conflito de versãomédio No pom.xml do exercício anterior, adicione uma dependência qualquer que você saiba que traz transitivas (por exemplo, uma lib de JSON). Rode mvn dependency:tree e identifique pelo menos uma dependência transitiva na árvore. Explique, em texto, o que você faria se essa transitiva trouxesse uma versão de uma biblioteca que conflita com uma dependência direta do seu projeto. Ver critérios mvn dependency:tree mostra a árvore completa; conflitos aparecem como a mesma groupId:artifactId em versões diferentes, resolvidos pelo Maven por proximidade (a declaração mais \"perto\" da raiz do projeto vence). Para resolver conscientemente: declare a versão desejada diretamente no seu pom.xml (que sempre vence por estar mais próxima) ou use <exclusions> na dependência que traz a versão indesejada.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 19.2 — Diagnosticando conflito de versão</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>No <code>pom.xml</code> do exercício anterior, adicione uma dependência qualquer que você saiba que traz transitivas (por exemplo, uma lib de JSON). Rode <code>mvn dependency:tree</code> e identifique pelo menos uma dependência transitiva na árvore. Explique, em texto, o que você faria se essa transitiva trouxesse uma versão de uma biblioteca que conflita com uma dependência direta do seu projeto.</p>\n        <button class=\"reveal-btn\">Ver critérios</button>\n        <div class=\"solution\">\n          <p><code>mvn dependency:tree</code> mostra a árvore completa; conflitos aparecem como a mesma <code>groupId:artifactId</code> em versões diferentes, resolvidos pelo Maven por proximidade (a declaração mais \"perto\" da raiz do projeto vence). Para resolver conscientemente: declare a versão desejada diretamente no seu <code>pom.xml</code> (que sempre vence por estar mais próxima) ou use <code>&lt;exclusions&gt;</code> na dependência que traz a versão indesejada.</p>\n        </div>\n      </div>"},{"id":"build-comparison","type":"comparison","authorship":"authored","title":"Maven e Gradle sem torcida","criteria":["força","risco","quando escolher"],"alternatives":[{"name":"Maven","values":["ciclo padronizado","XML verboso","previsibilidade corporativa"],"useWhen":"projeto Java tradicional e convenção forte","avoidWhen":"build altamente customizado sem plugin adequado"},{"name":"Gradle","values":["DSL flexível e cache","scripts podem virar programa confuso","monorepo/customização"],"useWhen":"precisa de flexibilidade e performance incremental","avoidWhen":"time quer mínima variação de build"}]},{"id":"build-quiz","type":"quiz","authorship":"authored","conceptId":"dependencia-escopo-versao","prompt":"Por que JUnit deve ficar em escopo/configuração de teste?","options":[{"id":"build-q-a","label":"Porque é necessário para compilar/rodar testes, mas não para o artefato de produção.","correct":true,"explanation":"Escopo correto reduz acoplamento e empacotamento desnecessário."},{"id":"build-q-b","label":"Porque Maven não permite dependências de produção.","correct":false,"explanation":"Maven permite vários escopos; a decisão depende do uso."},{"id":"build-q-c","label":"Porque JUnit só funciona dentro da IDE.","correct":false,"explanation":"JUnit é executado por build tools e CI de forma reproduzível."}]}],"resources":[{"id":"build-maven-guide","type":"reference","title":"Maven Getting Started Guide","url":"https://maven.apache.org/guides/getting-started/index.html","reinforces":"Explica POM, ciclo de vida, dependências e comandos fundamentais.","language":"en","publisher":"Apache Maven","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"build-gradle-basics","type":"reference","title":"Gradle User Manual: Build basics","url":"https://docs.gradle.org/current/userguide/getting_started_eng.html","reinforces":"Apresenta projetos Gradle, tarefas, plugins e execução básica.","language":"en","publisher":"Gradle","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A build operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a build operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this build chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"<!-- pom.xml -->","instruction":"A build operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this build chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"anotacoes","moduleId":"application-design","order":0,"title":"Anotações & Reflection","summary":"Você já usa anotações desde o capítulo 05 (@Override) e capítulo 15 (@Test). Este capítulo explica o que elas realmente são e como o Spring lê @Autowired, @Service ou @RestController e faz \"mágica\" com eles — a resposta é reflection.","objectives":["Entender annotation como metadado lido por ferramenta ou runtime","Criar annotation própria com retention e target adequados","Usar reflection para inspecionar classe, método e campo com limite claro","Preparar terreno para DI/framework sem chamar isso de mágica"],"whyItExists":"Antes de Spring, testes e frameworks usarem anotações, o aluno precisa saber que anotação é dado anexado ao programa e que alguém precisa lê-la. Reflection entra como ferramenta poderosa, não como padrão de regra de negócio.","prerequisiteChapterIds":["build"],"conceptIds":["criando-sua-propria-anotacao","reflection-inspecionando-classes-em-tempo-de-execucao","aprofundando-em-reflection-o-mapa-completo-da-api","anotacoes-padrao-mais-usadas-fora-do-spring"],"introducedConceptIds":["annotation-metadata-contract","reflection-runtime-introspection"],"usedConceptIds":["classe-instancia-objeto","build-lifecycle","controle-acesso"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"anotacoes-intuition","type":"intuition","authorship":"authored","title":"Anotação é etiqueta; comportamento vem de quem lê","body":"Colocar uma anotação numa classe não executa nada sozinho. Ela só adiciona metadado. Um compilador, biblioteca, framework ou código reflexivo precisa procurar essa etiqueta e decidir o que fazer.","analogyLimit":"Etiqueta ajuda a imaginar marcação, mas annotation tem retention, target, valores tipados e pode existir só no source, no bytecode ou em runtime."},{"id":"anotacoes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#build\">19 · Maven &amp; Gradle</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 19 · Maven & Gradle"},{"id":"anotacoes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Você já usa anotações desde o capítulo 05 (<code>@Override</code>) e capítulo 15 (<code>@Test</code>). Este capítulo explica <strong>o que elas realmente são</strong> e <strong>como o Spring lê <code>@Autowired</code>, <code>@Service</code> ou <code>@RestController</code> e faz \"mágica\" com eles</strong> — a resposta é <strong>reflection</strong>.</p>","fidelityText":"Você já usa anotações desde o capítulo 05 (@Override) e capítulo 15 (@Test). Este capítulo explica o que elas realmente são e como o Spring lê @Autowired, @Service ou @RestController e faz \"mágica\" com eles — a resposta é reflection."},{"id":"anotacoes-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Criando sua própria anotação</h2>","fidelityText":"Criando sua própria anotação"},{"id":"anotacoes-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"@Retention(RetentionPolicy.RUNTIME) // precisa existir em runtime para ser lida via reflection\n@Target(ElementType.METHOD)          // só pode ser usada em métodos\npublic @interface ExecutionLog {\n    String value() default \"\"; // \"atributo\" da anotação, com valor padrão\n}\n\npublic class Service {\n    @ExecutionLog(\"processOrder\")\n    public void process() { System.out.println(\"processing...\"); }\n}","fidelityText":"@Retention(RetentionPolicy.RUNTIME) // precisa existir em runtime para ser lida via reflection @Target(ElementType.METHOD) // só pode ser usada em métodos public @interface LogExecucao { String valor() default \"\"; // \"atributo\" da anotação, com valor padrão } public class Servico { @LogExecucao(\"processarPedido\") public void processar() { System.out.println(\"processando...\"); } }","highlightedHtml":"<span class=\"annotation\">@Retention</span>(RetentionPolicy.RUNTIME) <span class=\"com\">// precisa existir em runtime para ser lida via reflection</span>\n<span class=\"annotation\">@Target</span>(ElementType.METHOD)          <span class=\"com\">// só pode ser usada em métodos</span>\n<span class=\"kw\">public @interface</span> <span class=\"cls\">ExecutionLog</span> {\n    <span class=\"kw\">String</span> <span class=\"fn\">value</span>() <span class=\"kw\">default</span> <span class=\"str\">\"\"</span>; <span class=\"com\">// \"atributo\" da anotação, com valor padrão</span>\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Service</span> {\n    <span class=\"annotation\">@ExecutionLog</span>(<span class=\"str\">\"processOrder\"</span>)\n    <span class=\"kw\">public void</span> <span class=\"fn\">process</span>() { System.out.println(<span class=\"str\">\"processing...\"</span>); }\n}","caption":"Exemplo executável de anotacoes.","explanation":["@interface declara um novo tipo de anotação.","Target restringe onde ela pode ser usada; Retention decide até onde ela sobrevive."],"commonMistakes":["Criar annotation sem saber quem lerá","Usar RUNTIME sem necessidade"]},{"id":"anotacoes-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Sozinha, uma anotação <strong>não faz nada</strong> — ela é só metadado. É preciso um código separado que <em>lê</em> essa anotação via reflection e decide o que fazer com ela. É exatamente isso que frameworks como o Spring fazem por trás dos panos.</p>","fidelityText":"Sozinha, uma anotação não faz nada — ela é só metadado. É preciso um código separado que lê essa anotação via reflection e decide o que fazer com ela. É exatamente isso que frameworks como o Spring fazem por trás dos panos."},{"id":"anotacoes-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Reflection: inspecionando classes em tempo de execução</h2>","fidelityText":"Reflection: inspecionando classes em tempo de execução"},{"id":"anotacoes-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"Class<?> type = Service.class;\n\nfor (Method method : type.getDeclaredMethods()) {\n    if (method.isAnnotationPresent(ExecutionLog.class)) {\n        ExecutionLog annotation = method.getAnnotation(ExecutionLog.class);\n        System.out.println(\"Method \" + method.getName() + \" marked with value: \" + annotation.value());\n    }\n}\n\n// reflection também permite criar objetos e chamar métodos dinamicamente,\n// sem \"new\" explícito no código -- é assim que o Spring instancia seus beans:\nService instance = (Service) type.getDeclaredConstructor().newInstance();\nMethod m = type.getMethod(\"process\");\nm.invoke(instance); // chama servico.processar() dinamicamente","fidelityText":"Class<?> classe = Servico.class; for (Method metodo : classe.getDeclaredMethods()) { if (metodo.isAnnotationPresent(LogExecucao.class)) { LogExecucao anotacao = metodo.getAnnotation(LogExecucao.class); System.out.println(\"Método \" + metodo.getName() + \" marcado com valor: \" + anotacao.valor()); } } // reflection também permite criar objetos e chamar métodos dinamicamente, // sem \"new\" explícito no código -- é assim que o Spring instancia seus beans: Servico instancia = (Servico) classe.getDeclaredConstructor().newInstance(); Method m = classe.getMethod(\"processar\"); m.invoke(instancia); // chama servico.processar() dinamicamente","highlightedHtml":"Class&lt;?&gt; type = Service.<span class=\"kw\">class</span>;\n\n<span class=\"kw\">for</span> (Method method : type.getDeclaredMethods()) {\n    <span class=\"kw\">if</span> (method.isAnnotationPresent(ExecutionLog.<span class=\"kw\">class</span>)) {\n        ExecutionLog annotation = method.getAnnotation(ExecutionLog.<span class=\"kw\">class</span>);\n        System.out.println(<span class=\"str\">\"Method \"</span> + method.getName() + <span class=\"str\">\" marked with value: \"</span> + annotation.value());\n    }\n}\n\n<span class=\"com\">// reflection também permite criar objetos e chamar métodos dinamicamente,\n// sem \"new\" explícito no código -- é assim que o Spring instancia seus beans:</span>\n<span class=\"cls\">Service</span> instance = (<span class=\"cls\">Service</span>) type.getDeclaredConstructor().newInstance();\nMethod m = type.getMethod(<span class=\"str\">\"process\"</span>);\nm.invoke(instance); <span class=\"com\">// chama servico.processar() dinamicamente</span>","caption":"Exemplo executável de anotacoes.","explanation":["A anotação aplicada vira metadado no elemento anotado.","O método ou classe anotada ainda executa normalmente até alguém inspecionar o metadado."],"commonMistakes":["Esperar execução automática","Misturar regra de negócio com metadado decorativo"]},{"id":"anotacoes-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Este é literalmente o segredo por trás do Spring: quando você escreve <code>@Service</code> em uma classe e <code>@Autowired</code> em um campo, o Spring, na inicialização da aplicação, faz um <strong>scan via reflection</strong> em todos os pacotes configurados, procurando classes anotadas. Para cada uma, ele cria a instância (às vezes chamada de <em>bean</em>) usando reflection, e para cada campo <code>@Autowired</code> encontrado, ele injeta a dependência certa — também via reflection, chamando <code>Field.set(objeto, valor)</code> por baixo dos panos. Não existe mágica: é reflection, aplicada em escala, orquestrada por um container.</div>","fidelityText":"Este é literalmente o segredo por trás do Spring: quando você escreve @Service em uma classe e @Autowired em um campo, o Spring, na inicialização da aplicação, faz um scan via reflection em todos os pacotes configurados, procurando classes anotadas. Para cada uma, ele cria a instância (às vezes chamada de bean) usando reflection, e para cada campo @Autowired encontrado, ele injeta a dependência certa — também via reflection, chamando Field.set(objeto, valor) por baixo dos panos. Não existe mágica: é reflection, aplicada em escala, orquestrada por um container."},{"id":"anotacoes-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Aprofundando em Reflection: o mapa completo da API</h2>","fidelityText":"Aprofundando em Reflection: o mapa completo da API"},{"id":"anotacoes-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Tudo em reflection começa por um objeto <code>Class&lt;?&gt;</code>, que é a representação em runtime de um tipo. Existem três formas de obtê-lo:</p>","fidelityText":"Tudo em reflection começa por um objeto Class<?>, que é a representação em runtime de um tipo. Existem três formas de obtê-lo:"},{"id":"anotacoes-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"Class<?> c1 = Book.class;                 // quando você já conhece o tipo em compilação\nClass<?> c2 = myBook.getClass();          // a partir de uma instância já existente\nClass<?> c3 = Class.forName(\"com.felipy.Book\"); // pelo NOME, como String -- usado por drivers JDBC e frameworks","fidelityText":"Class<?> c1 = Livro.class; // quando você já conhece o tipo em compilação Class<?> c2 = meuLivro.getClass(); // a partir de uma instância já existente Class<?> c3 = Class.forName(\"com.felipy.Livro\"); // pelo NOME, como String -- usado por drivers JDBC e frameworks","highlightedHtml":"Class&lt;?&gt; c1 = Book.<span class=\"kw\">class</span>;                 <span class=\"com\">// quando você já conhece o tipo em compilação</span>\nClass&lt;?&gt; c2 = myBook.getClass();          <span class=\"com\">// a partir de uma instância já existente</span>\nClass&lt;?&gt; c3 = Class.forName(<span class=\"str\">\"com.felipy.Book\"</span>); <span class=\"com\">// pelo NOME, como String -- usado por drivers JDBC e frameworks</span>","caption":"Exemplo executável de anotacoes.","explanation":["Class representa metadados do tipo em runtime.","getDeclaredMethods permite inspecionar métodos declarados, inclusive fora da API pública."],"commonMistakes":["Usar reflection onde polimorfismo resolveria","Ignorar exceções e acesso"]},{"id":"anotacoes-content-12","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Inspecionando a estrutura de uma classe</h2>","fidelityText":"Inspecionando a estrutura de uma classe"},{"id":"anotacoes-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"Class<?> c = Book.class;\n\nc.getName();                 // \"com.felipy.Livro\" -- nome totalmente qualificado\nc.getSimpleName();           // \"Livro\"\nc.getSuperclass();           // a classe pai (Object, se não houver extends explícito)\nc.getInterfaces();           // array de interfaces implementadas\nc.isInterface();             // false -- e existe isEnum(), isArray(), isAnnotation()...\n\n// campos: getFields() só PUBLIC (inclusive herdados); getDeclaredFields() TODOS, só desta classe\nfor (Field f : c.getDeclaredFields()) {\n    System.out.println(f.getName() + \" : \" + f.getType());\n}\n\n// o mesmo padrão vale para métodos e construtores:\nc.getMethods();               // públicos, incluindo herdados\nc.getDeclaredMethods();       // todos os declarados NESTA classe, qualquer modificador\nc.getDeclaredConstructors();  // todos os construtores declarados","fidelityText":"Class<?> c = Livro.class; c.getName(); // \"com.felipy.Livro\" -- nome totalmente qualificado c.getSimpleName(); // \"Livro\" c.getSuperclass(); // a classe pai (Object, se não houver extends explícito) c.getInterfaces(); // array de interfaces implementadas c.isInterface(); // false -- e existe isEnum(), isArray(), isAnnotation()... // campos: getFields() só PUBLIC (inclusive herdados); getDeclaredFields() TODOS, só desta classe for (Field f : c.getDeclaredFields()) { System.out.println(f.getName() + \" : \" + f.getType()); } // o mesmo padrão vale para métodos e construtores: c.getMethods(); // públicos, incluindo herdados c.getDeclaredMethods(); // todos os declarados NESTA classe, qualquer modificador c.getDeclaredConstructors(); // todos os construtores declarados","highlightedHtml":"Class&lt;?&gt; c = Book.<span class=\"kw\">class</span>;\n\nc.getName();                 <span class=\"com\">// \"com.felipy.Livro\" -- nome totalmente qualificado</span>\nc.getSimpleName();           <span class=\"com\">// \"Livro\"</span>\nc.getSuperclass();           <span class=\"com\">// a classe pai (Object, se não houver extends explícito)</span>\nc.getInterfaces();           <span class=\"com\">// array de interfaces implementadas</span>\nc.isInterface();             <span class=\"com\">// false -- e existe isEnum(), isArray(), isAnnotation()...</span>\n\n<span class=\"com\">// campos: getFields() só PUBLIC (inclusive herdados); getDeclaredFields() TODOS, só desta classe</span>\n<span class=\"kw\">for</span> (Field f : c.getDeclaredFields()) {\n    System.out.println(f.getName() + <span class=\"str\">\" : \"</span> + f.getType());\n}\n\n<span class=\"com\">// o mesmo padrão vale para métodos e construtores:</span>\nc.getMethods();               <span class=\"com\">// públicos, incluindo herdados</span>\nc.getDeclaredMethods();       <span class=\"com\">// todos os declarados NESTA classe, qualquer modificador</span>\nc.getDeclaredConstructors();  <span class=\"com\">// todos os construtores declarados</span>","caption":"Exemplo executável de anotacoes.","explanation":["isAnnotationPresent verifica se o elemento carrega a marca esperada.","getAnnotation lê valores configurados na anotação."],"commonMistakes":["Não verificar retention RUNTIME","Assumir presença sem validar"]},{"id":"anotacoes-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A distinção <code>get*</code> vs <code>getDeclared*</code> é a pegadinha número um de quem começa em reflection: <code>getFields()</code> só enxerga membros <code>public</code> (inclusive herdados de superclasses), enquanto <code>getDeclaredFields()</code> enxerga <strong>todos</strong> os membros declarados diretamente na classe — <code>private</code> incluído — mas <strong>não</strong> os herdados. Para inspecionar uma hierarquia inteira de forma completa (como o Spring faz ao procurar campos <code>@Autowired</code> em qualquer nível), é preciso subir manualmente com <code>getSuperclass()</code> em loop.</div>","fidelityText":"A distinção get* vs getDeclared* é a pegadinha número um de quem começa em reflection: getFields() só enxerga membros public (inclusive herdados de superclasses), enquanto getDeclaredFields() enxerga todos os membros declarados diretamente na classe — private incluído — mas não os herdados. Para inspecionar uma hierarquia inteira de forma completa (como o Spring faz ao procurar campos @Autowired em qualquer nível), é preciso subir manualmente com getSuperclass() em loop."},{"id":"anotacoes-content-15","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Acessando membros privados e criando objetos dinamicamente</h2>","fidelityText":"Acessando membros privados e criando objetos dinamicamente"},{"id":"anotacoes-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"Field fieldPrivate = Book.class.getDeclaredField(\"title\");\nfieldPrivate.setAccessible(true);          // \"quebra\" o encapsulamento -- use com responsabilidade!\nString value = (String) fieldPrivate.get(myBook);\nfieldPrivate.set(myBook, \"New Title\");   // altera mesmo sendo private\n\n// criando um objeto sem \"new\" explícito no código-fonte:\nConstructor<Book> constructor = Book.class.getDeclaredConstructor(String.class, String.class, int.class);\nBook instance = constructor.newInstance(\"1984\", \"Orwell\", 328);\n\n// chamando um método privado dinamicamente:\nMethod methodPrivate = Book.class.getDeclaredMethod(\"validate\");\nmethodPrivate.setAccessible(true);\nmethodPrivate.invoke(instance);","fidelityText":"Field campoPrivado = Livro.class.getDeclaredField(\"titulo\"); campoPrivado.setAccessible(true); // \"quebra\" o encapsulamento -- use com responsabilidade! String valor = (String) campoPrivado.get(meuLivro); campoPrivado.set(meuLivro, \"Novo Título\"); // altera mesmo sendo private // criando um objeto sem \"new\" explícito no código-fonte: Constructor<Livro> construtor = Livro.class.getDeclaredConstructor(String.class, String.class, int.class); Livro instancia = construtor.newInstance(\"1984\", \"Orwell\", 328); // chamando um método privado dinamicamente: Method metodoPrivado = Livro.class.getDeclaredMethod(\"validar\"); metodoPrivado.setAccessible(true); metodoPrivado.invoke(instancia);","highlightedHtml":"Field fieldPrivate = Book.<span class=\"kw\">class</span>.getDeclaredField(<span class=\"str\">\"title\"</span>);\nfieldPrivate.setAccessible(<span class=\"kw\">true</span>);          <span class=\"com\">// \"quebra\" o encapsulamento -- use com responsabilidade!</span>\n<span class=\"kw\">String</span> value = (<span class=\"kw\">String</span>) fieldPrivate.get(myBook);\nfieldPrivate.set(myBook, <span class=\"str\">\"New Title\"</span>);   <span class=\"com\">// altera mesmo sendo private</span>\n\n<span class=\"com\">// criando um objeto sem \"new\" explícito no código-fonte:</span>\nConstructor&lt;<span class=\"cls\">Book</span>&gt; constructor = Book.<span class=\"kw\">class</span>.getDeclaredConstructor(<span class=\"kw\">String</span>.<span class=\"kw\">class</span>, <span class=\"kw\">String</span>.<span class=\"kw\">class</span>, <span class=\"kw\">int</span>.<span class=\"kw\">class</span>);\n<span class=\"cls\">Book</span> instance = constructor.newInstance(<span class=\"str\">\"1984\"</span>, <span class=\"str\">\"Orwell\"</span>, 328);\n\n<span class=\"com\">// chamando um método privado dinamicamente:</span>\nMethod methodPrivate = Book.<span class=\"kw\">class</span>.getDeclaredMethod(<span class=\"str\">\"validate\"</span>);\nmethodPrivate.setAccessible(<span class=\"kw\">true</span>);\nmethodPrivate.invoke(instance);","caption":"Exemplo executável de anotacoes.","explanation":["Reflection expõe construtores, campos e métodos como objetos manipuláveis.","Acesso reflexivo deve ser limitado porque enfraquece encapsulamento e legibilidade."],"commonMistakes":["Tornar tudo acessível por padrão","Depender de nome textual frágil"]},{"id":"anotacoes-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>setAccessible(true)</code> é poderoso e perigoso:</b> ele contorna <code>private</code>/<code>protected</code> deliberadamente, quebrando encapsulamento (capítulo 04) por completo. Frameworks usam isso de forma controlada e bem testada; código de aplicação normal quase nunca deveria precisar disso. Desde o sistema de módulos do Java 9+ (JPMS), módulos podem inclusive <em>bloquear</em> esse acesso para pacotes não explicitamente abertos — mais uma camada de proteção contra o uso indevido dessa API.</div>","fidelityText":"setAccessible(true) é poderoso e perigoso: ele contorna private/protected deliberadamente, quebrando encapsulamento (capítulo 04) por completo. Frameworks usam isso de forma controlada e bem testada; código de aplicação normal quase nunca deveria precisar disso. Desde o sistema de módulos do Java 9+ (JPMS), módulos podem inclusive bloquear esse acesso para pacotes não explicitamente abertos — mais uma camada de proteção contra o uso indevido dessa API."},{"id":"anotacoes-content-18","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Dynamic Proxy — a base do Spring AOP</h2>","fidelityText":"Dynamic Proxy — a base do Spring AOP"},{"id":"anotacoes-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Um <strong>proxy dinâmico</strong> cria, em tempo de execução, uma implementação de uma interface que intercepta toda chamada de método antes de (opcionalmente) delegar para o objeto real. É assim que o Spring implementa recursos como <code>@Transactional</code> e logging automático (AOP — Programação Orientada a Aspectos) sem exigir que você escreva código repetitivo em cada método.</p>","fidelityText":"Um proxy dinâmico cria, em tempo de execução, uma implementação de uma interface que intercepta toda chamada de método antes de (opcionalmente) delegar para o objeto real. É assim que o Spring implementa recursos como @Transactional e logging automático (AOP — Programação Orientada a Aspectos) sem exigir que você escreva código repetitivo em cada método."},{"id":"anotacoes-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"interface Service { void execute(); }\n\nclass ServiceReal implements Service {\n    @Override public void execute() { System.out.println(\"Executando logic of business\"); }\n}\n\nService real = new ServiceReal();\n\nService proxy = (Service) Proxy.newProxyInstance(\n    Service.class.getClassLoader(),\n    new Class<?>[]{ Service.class },\n    (proxyObj, method, args) -> {\n        System.out.println(\"[LOG] Before of \" + method.getName()); // código \"injetado\" ao redor da chamada\n        Object result = method.invoke(real, args);       // delega para o objeto real\n        System.out.println(\"[LOG] After of \" + method.getName());\n        return result;\n    }\n);\n\nproxy.execute();\n// [LOG] Antes de executar\n// Executando lógica de negócio\n// [LOG] Depois de executar","fidelityText":"interface Servico { void executar(); } class ServicoReal implements Servico { @Override public void executar() { System.out.println(\"Executando lógica de negócio\"); } } Servico real = new ServicoReal(); Servico proxy = (Servico) Proxy.newProxyInstance( Servico.class.getClassLoader(), new Class<?>[]{ Servico.class }, (proxyObj, metodo, args) -> { System.out.println(\"[LOG] Antes de \" + metodo.getName()); // código \"injetado\" ao redor da chamada Object resultado = metodo.invoke(real, args); // delega para o objeto real System.out.println(\"[LOG] Depois de \" + metodo.getName()); return resultado; } ); proxy.executar(); // [LOG] Antes de executar // Executando lógica de negócio // [LOG] Depois de executar","highlightedHtml":"<span class=\"kw\">interface</span> <span class=\"cls\">Service</span> { <span class=\"kw\">void</span> <span class=\"fn\">execute</span>(); }\n\n<span class=\"kw\">class</span> <span class=\"cls\">ServiceReal</span> <span class=\"kw\">implements</span> <span class=\"cls\">Service</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">execute</span>() { System.out.println(<span class=\"str\">\"Executando logic of business\"</span>); }\n}\n\n<span class=\"cls\">Service</span> real = <span class=\"kw\">new</span> <span class=\"cls\">ServiceReal</span>();\n\n<span class=\"cls\">Service</span> proxy = (<span class=\"cls\">Service</span>) Proxy.newProxyInstance(\n    <span class=\"cls\">Service</span>.<span class=\"kw\">class</span>.getClassLoader(),\n    <span class=\"kw\">new</span> Class&lt;?&gt;[]{ <span class=\"cls\">Service</span>.<span class=\"kw\">class</span> },\n    (proxyObj, method, args) -&gt; {\n        System.out.println(<span class=\"str\">\"[LOG] Before of \"</span> + method.getName()); <span class=\"com\">// código \"injetado\" ao redor da chamada</span>\n        <span class=\"kw\">Object</span> result = method.invoke(real, args);       <span class=\"com\">// delega para o objeto real</span>\n        System.out.println(<span class=\"str\">\"[LOG] After of \"</span> + method.getName());\n        <span class=\"kw\">return</span> result;\n    }\n);\n\nproxy.execute();\n<span class=\"com\">// [LOG] Antes de executar\n// Executando lógica de negócio\n// [LOG] Depois de executar</span>","caption":"Exemplo executável de anotacoes.","explanation":["Anotações padrão têm consumidores claros: compilador, runtime ou ferramentas.","A utilidade vem do contrato com quem interpreta a anotação."],"commonMistakes":["Decorar código sem efeito real","Confundir anotação de documentação com validação"]},{"id":"anotacoes-content-21","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Quando você anota um método com <code>@Transactional</code> no Spring, o objeto que você realmente recebe do container <strong>não é a sua classe original</strong> — é um proxy dinâmico exatamente como o de cima, gerado automaticamente na inicialização. Esse proxy abre a transação, chama seu método de verdade via reflection, e faz <code>commit</code> ou <code>rollback</code> dependendo se uma exceção foi lançada. É por isso que <code>@Transactional</code> não funciona quando um método chama outro método <em>da mesma classe</em> diretamente (<code>this.outroMetodo()</code>) — essa chamada nunca passa pelo proxy, então nunca é interceptada.</div>","fidelityText":"Quando você anota um método com @Transactional no Spring, o objeto que você realmente recebe do container não é a sua classe original — é um proxy dinâmico exatamente como o de cima, gerado automaticamente na inicialização. Esse proxy abre a transação, chama seu método de verdade via reflection, e faz commit ou rollback dependendo se uma exceção foi lançada. É por isso que @Transactional não funciona quando um método chama outro método da mesma classe diretamente (this.outroMetodo()) — essa chamada nunca passa pelo proxy, então nunca é interceptada."},{"id":"anotacoes-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>Anotações padrão mais usadas (fora do Spring)</h2>","fidelityText":"Anotações padrão mais usadas (fora do Spring)"},{"id":"anotacoes-content-23","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Anotação</th><th>Para que serve</th></tr>\n        <tr><td><code>@Override</code></td><td>Confirma sobrescrita de método — erro de compilação se não for uma sobrescrita de fato</td></tr>\n        <tr><td><code>@Deprecated</code></td><td>Marca um elemento como obsoleto, gera aviso ao usar</td></tr>\n        <tr><td><code>@SuppressWarnings</code></td><td>Silencia um aviso específico do compilador</td></tr>\n        <tr><td><code>@FunctionalInterface</code></td><td>Confirma que a interface tem exatamente um método abstrato (capítulo 13)</td></tr>\n      </tbody></table>","fidelityText":"AnotaçãoPara que serve @OverrideConfirma sobrescrita de método — erro de compilação se não for uma sobrescrita de fato @DeprecatedMarca um elemento como obsoleto, gera aviso ao usar @SuppressWarningsSilencia um aviso específico do compilador @FunctionalInterfaceConfirma que a interface tem exatamente um método abstrato (capítulo 13)"},{"id":"anotacoes-content-24","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Reflection tem custo:</b> é mais lento que chamadas diretas e contorna checagens de tipo em tempo de compilação. Frameworks como Spring pagam esse custo <strong>uma vez</strong>, na inicialização da aplicação (montando o container de beans), não a cada requisição — por isso a \"mágica\" não é um problema de performance no dia a dia.</div>","fidelityText":"Reflection tem custo: é mais lento que chamadas diretas e contorna checagens de tipo em tempo de compilação. Frameworks como Spring pagam esse custo uma vez, na inicialização da aplicação (montando o container de beans), não a cada requisição — por isso a \"mágica\" não é um problema de performance no dia a dia."},{"id":"anotacoes-exercise-25","type":"exercise","authorship":"legacy-preserved","title":"Exercício 20.0 — Explorando uma classe via reflection","prompt":"Escreva um método inspecionar(Object objeto) que recebe qualquer objeto e imprime: o nome completo da classe, o nome da superclasse, e o nome e tipo de cada campo declarado (mesmo os private) — usando apenas getClass(), getDeclaredFields() e um for.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 20.0 — Explorando uma classe via reflectionfácil Escreva um método inspecionar(Object objeto) que recebe qualquer objeto e imprime: o nome completo da classe, o nome da superclasse, e o nome e tipo de cada campo declarado (mesmo os private) — usando apenas getClass(), getDeclaredFields() e um for. Ver solução static void inspecionar(Object objeto) { Class<?> classe = objeto.getClass(); System.out.println(\"Classe: \" + classe.getName()); System.out.println(\"Superclasse: \" + classe.getSuperclass().getName()); for (Field f : classe.getDeclaredFields()) { System.out.println(\" campo: \" + f.getName() + \" (\" + f.getType().getSimpleName() + \")\"); } } // inspecionar(new Livro(\"1984\", \"Orwell\", 328));","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 20.0 — Explorando uma classe via reflection</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Escreva um método <code>inspecionar(Object objeto)</code> que recebe qualquer objeto e imprime: o nome completo da classe, o nome da superclasse, e o nome e tipo de cada campo declarado (mesmo os <code>private</code>) — usando apenas <code>getClass()</code>, <code>getDeclaredFields()</code> e um <code>for</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">static void</span> <span class=\"fn\">inspecionar</span>(<span class=\"kw\">Object</span> object) {\n    Class&lt;?&gt; type = object.getClass();\n    System.out.println(<span class=\"str\">\"Class: \"</span> + type.getName());\n    System.out.println(<span class=\"str\">\"Superclass: \"</span> + type.getSuperclass().getName());\n    <span class=\"kw\">for</span> (Field f : type.getDeclaredFields()) {\n        System.out.println(<span class=\"str\">\"  field: \"</span> + f.getName() + <span class=\"str\">\" (\"</span> + f.getType().getSimpleName() + <span class=\"str\">\")\"</span>);\n    }\n}\n<span class=\"com\">// inspecionar(new Livro(\"1984\", \"Orwell\", 328));</span></pre>\n        </div>\n      </div>"},{"id":"anotacoes-exercise-26","type":"exercise","authorship":"legacy-preserved","title":"Exercício 20.1 — Sua própria anotação de validação","prompt":"Crie uma anotação @NaoNulo aplicável a campos (ElementType.FIELD), com retenção em runtime. Escreva uma classe utilitária Validador com um método estático validar(Object objeto) que usa reflection para percorrer todos os campos do objeto, e lança IllegalStateException se algum campo anotado com @NaoNulo estiver null (dica: campo.setAccessible(true) antes de campo.get(objeto) para acessar campos private).","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 20.1 — Sua própria anotação de validaçãodifícil Crie uma anotação @NaoNulo aplicável a campos (ElementType.FIELD), com retenção em runtime. Escreva uma classe utilitária Validador com um método estático validar(Object objeto) que usa reflection para percorrer todos os campos do objeto, e lança IllegalStateException se algum campo anotado com @NaoNulo estiver null (dica: campo.setAccessible(true) antes de campo.get(objeto) para acessar campos private). Ver solução @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface NaoNulo {} public class Validador { public static void validar(Object objeto) throws IllegalAccessException { for (Field campo : objeto.getClass().getDeclaredFields()) { if (campo.isAnnotationPresent(NaoNulo.class)) { campo.setAccessible(true); if (campo.get(objeto) == null) { throw new IllegalStateException(\"Campo obrigatório nulo: \" + campo.getName()); } } } } } // uso: class Usuario { @NaoNulo String nome; String apelido; // não obrigatório } Usuario u = new Usuario(); Validador.validar(u); // lança IllegalStateException: \"nome\" é null Isso é essencialmente uma versão simplificada do que o Bean Validation (@NotNull do Jakarta/Spring) faz por trás dos panos.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 20.1 — Sua própria anotação de validação</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie uma anotação <code>@NaoNulo</code> aplicável a campos (<code>ElementType.FIELD</code>), com retenção em runtime. Escreva uma classe utilitária <code>Validador</code> com um método estático <code>validar(Object objeto)</code> que usa reflection para percorrer todos os campos do objeto, e lança <code>IllegalStateException</code> se algum campo anotado com <code>@NaoNulo</code> estiver <code>null</code> (dica: <code>campo.setAccessible(true)</code> antes de <code>campo.get(objeto)</code> para acessar campos <code>private</code>).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Retention</span>(RetentionPolicy.RUNTIME)\n<span class=\"annotation\">@Target</span>(ElementType.FIELD)\n<span class=\"kw\">public @interface</span> <span class=\"cls\">NotNulo</span> {}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Validador</span> {\n    <span class=\"kw\">public static void</span> <span class=\"fn\">validate</span>(<span class=\"kw\">Object</span> object) <span class=\"kw\">throws</span> <span class=\"cls\">IllegalAccessException</span> {\n        <span class=\"kw\">for</span> (Field field : object.getClass().getDeclaredFields()) {\n            <span class=\"kw\">if</span> (field.isAnnotationPresent(NotNulo.<span class=\"kw\">class</span>)) {\n                field.setAccessible(<span class=\"kw\">true</span>);\n                <span class=\"kw\">if</span> (field.get(object) == <span class=\"kw\">null</span>) {\n                    <span class=\"kw\">throw new</span> <span class=\"cls\">IllegalStateException</span>(<span class=\"str\">\"Field required nulo: \"</span> + field.getName());\n                }\n            }\n        }\n    }\n}\n\n<span class=\"com\">// uso:</span>\n<span class=\"kw\">class</span> <span class=\"cls\">User</span> {\n    <span class=\"annotation\">@NotNulo</span> <span class=\"kw\">String</span> name;\n    <span class=\"kw\">String</span> alias; <span class=\"com\">// não obrigatório</span>\n}\n<span class=\"cls\">User</span> u = <span class=\"kw\">new</span> <span class=\"cls\">User</span>();\nValidador.validate(u); <span class=\"com\">// lança IllegalStateException: \"nome\" é null</span></pre>\n          <p style=\"margin-top:12px\">Isso é essencialmente uma versão simplificada do que o Bean Validation (<code>@NotNull</code> do Jakarta/Spring) faz por trás dos panos.</p>\n        </div>\n      </div>"},{"id":"anotacoes-table","type":"table","authorship":"authored","title":"Retention decide quem ainda enxerga a anotação","headers":["Retention","Quem usa","Exemplo de decisão"],"rows":[["SOURCE","compilador/analisador","gerar aviso ou código"],["CLASS","ferramenta de bytecode","processar classe compilada"],["RUNTIME","reflection/framework","descobrir rota, teste, componente ou validação em execução"]]},{"id":"anotacoes-quiz","type":"quiz","authorship":"authored","conceptId":"annotation-metadata-contract","prompt":"Por que uma anotação personalizada não muda o comportamento do programa automaticamente?","options":[{"id":"an-q-a","label":"Porque ela é metadado; algum código ou ferramenta precisa lê-la e agir.","correct":true,"explanation":"Annotation declara informação. O comportamento vem do processador, framework ou reflection."},{"id":"an-q-b","label":"Porque Java só permite anotação em comentários.","correct":false,"explanation":"Annotations são parte da linguagem, não comentários."},{"id":"an-q-c","label":"Porque reflection só funciona com Spring instalado.","correct":false,"explanation":"Reflection é API padrão do Java."}]}],"resources":[{"id":"annotation-api-java21","type":"reference","title":"Annotation API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/annotation/Annotation.html","reinforces":"Contrato base das annotations em runtime.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"class-api-java21","type":"reference","title":"Class API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Class.html","reinforces":"Base da introspecção reflexiva de classes, métodos, campos e annotations.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A annotations operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a annotations operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this annotations chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"@Retention(RetentionPolicy.RUNTIME) // precisa existir em runtime para ser lida via reflection","instruction":"A annotations operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this annotations chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"json","moduleId":"io-cli-serialization","order":2,"title":"JSON & serialização","summary":"JSON é um formato textual para trocar dados estruturados. Ele aparece em APIs, arquivos de configuração, relatórios e integrações. Neste ponto do curso, ele entra sem Spring: você vai entender primeiro o contrato entre texto JSON, DTO Java, validação e falhas de conversão.","objectives":["Distinguir objeto Java, texto JSON e contrato externo","Serializar e desserializar DTOs sem validar domínio automaticamente","Entender Jackson por propriedades, construtores, records e falhas","Evitar vazar modelo interno ou segredo no JSON"],"whyItExists":"Depois de arquivos e relatórios, o curso precisa de um formato estruturado de troca. JSON entra como contrato textual entre fronteiras, não como mágica do Spring.","prerequisiteChapterIds":["mini-analisador-vendas"],"conceptIds":["objeto-java-dto-e-json-nao-sao-a-mesma-coisa","de-json-para-dto-java-desserializacao","jackson-com-classes-javabeans","controlando-o-contrato-com-anotacoes","records-e-json","campos-desconhecidos-o-json-de-entrada-nem-sempre-bate-100-com-o-dto","ausente-nao-e-o-mesmo-que-null","datas-java-time-precisa-do-modulo-certo","colecoes-genericas-o-problema-do-type-erasure-na-desserializacao","tratando-erros-de-des-serializacao"],"introducedConceptIds":["json-formato-contrato","serializacao-desserializacao","jackson-introspeccao-records"],"usedConceptIds":["record-invariante-copia","byte-char-charset","checked-unchecked-contrato"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"json-intuition","type":"intuition","authorship":"authored","title":"JSON é texto com forma combinada","body":"Um objeto Java vive em memória com métodos, tipos e invariantes. JSON é uma representação textual de dados. Converter entre os dois exige contrato de campos, tipos, ausências e falhas.","analogyLimit":"Envelope ajuda a pensar em transporte, mas JSON não preserva métodos, identidade de objeto, tipos Java genéricos nem regras de domínio."},{"id":"json-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#mini-analisador-vendas\">Mini-projeto com arquivos e relatórios</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: Mini-projeto com arquivos e relatórios"},{"id":"json-content-2","type":"html","authorship":"legacy-preserved","html":"<p><strong>JSON</strong> é um formato textual para trocar dados estruturados. Ele aparece em APIs, arquivos de configuração, relatórios e integrações. Neste ponto do curso, ele entra sem Spring: você vai entender primeiro o contrato entre texto JSON, DTO Java, validação e falhas de conversão.</p>","fidelityText":"JSON é um formato textual para trocar dados estruturados. Ele aparece em APIs, arquivos de configuração, relatórios e integrações. Neste ponto do curso, ele entra sem Spring: você vai entender primeiro o contrato entre texto JSON, DTO Java, validação e falhas de conversão."},{"id":"json-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Objeto Java, DTO e JSON não são a mesma coisa</h2>","fidelityText":"Objeto Java, DTO e JSON não são a mesma coisa"},{"id":"json-content-4","type":"html","authorship":"legacy-preserved","html":"<p>O objeto Java pode ter métodos, invariantes, tipos ricos e identidade em memória. JSON tem objetos, arrays, strings, números, booleanos e null. O DTO é a classe ou record que representa a forma permitida na fronteira. Não exponha o domínio inteiro só porque a biblioteca consegue serializar.</p>","fidelityText":"O objeto Java pode ter métodos, invariantes, tipos ricos e identidade em memória. JSON tem objetos, arrays, strings, números, booleanos e null. O DTO é a classe ou record que representa a forma permitida na fronteira. Não exponha o domínio inteiro só porque a biblioteca consegue serializar."},{"id":"json-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"public record BookDTO(String title, String author, int pages) {}\n\nObjectMapper mapper = new ObjectMapper();\nBookDTO dto = new BookDTO(\"1984\", \"Orwell\", 328);\n\nString json = mapper.writeValueAsString(dto);\n// {\"titulo\":\"1984\",\"autor\":\"Orwell\",\"paginas\":328}","fidelityText":"public record LivroDTO(String titulo, String autor, int paginas) {} ObjectMapper mapper = new ObjectMapper(); LivroDTO dto = new LivroDTO(\"1984\", \"Orwell\", 328); String json = mapper.writeValueAsString(dto); // {\"titulo\":\"1984\",\"autor\":\"Orwell\",\"paginas\":328}","highlightedHtml":"<span class=\"kw\">public record</span> <span class=\"cls\">BookDTO</span>(String title, String author, <span class=\"kw\">int</span> pages) {}\n\nObjectMapper mapper = <span class=\"kw\">new</span> ObjectMapper();\nBookDTO dto = <span class=\"kw\">new</span> BookDTO(<span class=\"str\">\"1984\"</span>, <span class=\"str\">\"Orwell\"</span>, 328);\n\nString json = mapper.writeValueAsString(dto);\n<span class=\"com\">// {\"titulo\":\"1984\",\"autor\":\"Orwell\",\"paginas\":328}</span>","caption":"Exemplo executável de json.","explanation":["LivroDTO é contrato de fronteira, não necessariamente o domínio inteiro.","ObjectMapper transforma o valor em texto JSON conforme propriedades do record."],"commonMistakes":["Serializar entidade interna por conveniência","Achar que JSON carrega métodos Java"]},{"id":"json-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Serializar é transformar um valor Java em representação textual. Isso não prova que o JSON publicado é uma boa API, nem que todos os campos deveriam sair. O contrato externo precisa ser desenhado.</div>","fidelityText":"Serializar é transformar um valor Java em representação textual. Isso não prova que o JSON publicado é uma boa API, nem que todos os campos deveriam sair. O contrato externo precisa ser desenhado."},{"id":"json-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>De JSON para DTO Java (desserialização)</h2>","fidelityText":"De JSON para DTO Java (desserialização)"},{"id":"json-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"String json = \"\"\"\n    {\"title\": \"Dom Casmurro\", \"author\": \"Machado de Assis\", \"pages\": 256}\n    \"\"\";\n\nBookDTO book = mapper.readValue(json, BookDTO.class);\nSystem.out.println(book.title());","fidelityText":"String json = \"\"\" {\"titulo\": \"Dom Casmurro\", \"autor\": \"Machado de Assis\", \"paginas\": 256} \"\"\"; LivroDTO livro = mapper.readValue(json, LivroDTO.class); System.out.println(livro.titulo());","highlightedHtml":"<span class=\"kw\">String</span> json = <span class=\"str\">\"\"\"\n    {\"title\": \"Dom Casmurro\", \"author\": \"Machado of Assis\", \"pages\": 256}\n    \"\"\"</span>;\n\nBookDTO book = mapper.readValue(json, BookDTO.<span class=\"kw\">class</span>);\nSystem.out.println(book.title());","caption":"Exemplo executável de json.","explanation":["readValue usa o tipo alvo para construir um DTO a partir do texto.","A conversão bem-sucedida ainda precisa de validação de domínio."],"commonMistakes":["Aceitar DTO desserializado como regra válida","Tratar erro de parsing como null"]},{"id":"json-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Desserialização não é validação de domínio.</b> O texto pode caber no DTO e ainda assim violar regras: páginas negativas, título vazio, estado impossível ou campo ausente que seu caso de uso exige.</div>","fidelityText":"Desserialização não é validação de domínio. O texto pode caber no DTO e ainda assim violar regras: páginas negativas, título vazio, estado impossível ou campo ausente que seu caso de uso exige."},{"id":"json-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Jackson com classes JavaBeans</h2>","fidelityText":"Jackson com classes JavaBeans"},{"id":"json-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Quando você usa classes comuns, Jackson costuma mapear propriedades por construtor, campos visíveis, getters, setters e configuração do <code>ObjectMapper</code>. Em muitos cenários JavaBeans, getters como <code>getTitulo()</code> expõem uma propriedade chamada <code>titulo</code>. Isso é introspecção de propriedades, não leitura mágica de regra de negócio.</p>","fidelityText":"Quando você usa classes comuns, Jackson costuma mapear propriedades por construtor, campos visíveis, getters, setters e configuração do ObjectMapper. Em muitos cenários JavaBeans, getters como getTitulo() expõem uma propriedade chamada titulo. Isso é introspecção de propriedades, não leitura mágica de regra de negócio."},{"id":"json-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"public class UserDTO {\n    private String name;\n\n    public UserDTO() {\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n}","fidelityText":"public class UsuarioDTO { private String nome; public UsuarioDTO() { } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">UserDTO</span> {\n    <span class=\"kw\">private</span> String name;\n\n    <span class=\"kw\">public</span> UserDTO() {\n    }\n\n    <span class=\"kw\">public</span> String getName() {\n        <span class=\"kw\">return</span> name;\n    }\n\n    <span class=\"kw\">public void</span> setName(String name) {\n        <span class=\"kw\">this</span>.name = name;\n    }\n}","caption":"Exemplo executável de json.","explanation":["Classe JavaBeans expõe propriedade por getter/setter e construtor acessível.","Esse modelo é útil para DTOs mutáveis, mas pode enfraquecer invariantes se usado como domínio."],"commonMistakes":["Achar que setter valida automaticamente regra de negócio","Exigir JavaBeans quando record resolve melhor o contrato"]},{"id":"json-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Controlando o contrato com anotações</h2>","fidelityText":"Controlando o contrato com anotações"},{"id":"json-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"public class User {\n    private String name;\n\n    @JsonIgnore // nunca aparece no JSON de saída (ex: senha)\n    private String password;\n\n    @JsonProperty(\"name_complete\") // renomeia o campo no JSON\n    public String getName() { return name; }\n}","fidelityText":"public class Usuario { private String nome; @JsonIgnore // nunca aparece no JSON de saída (ex: senha) private String senha; @JsonProperty(\"nome_completo\") // renomeia o campo no JSON public String getNome() { return nome; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">User</span> {\n    <span class=\"kw\">private</span> <span class=\"kw\">String</span> name;\n\n    <span class=\"annotation\">@JsonIgnore</span> <span class=\"com\">// nunca aparece no JSON de saída (ex: senha)</span>\n    <span class=\"kw\">private</span> <span class=\"kw\">String</span> password;\n\n    <span class=\"annotation\">@JsonProperty</span>(<span class=\"str\">\"name_complete\"</span>) <span class=\"com\">// renomeia o campo no JSON</span>\n    <span class=\"kw\">public String</span> <span class=\"fn\">getName</span>() { <span class=\"kw\">return</span> name; }\n}","caption":"Exemplo executável de json.","explanation":["@JsonIgnore e @JsonProperty ajustam o contrato externo.","Anotações não substituem revisão de compatibilidade nem política de dados sensíveis."],"commonMistakes":["Renomear campo publicado sem migração","Confiar em anotação para esconder segredo em qualquer contexto"]},{"id":"json-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Anotações ajudam a declarar nomes, ignorar propriedades e ajustar formato. Elas não substituem separar DTO de domínio, revisar compatibilidade e impedir que segredos apareçam em log, resposta ou arquivo.</p>","fidelityText":"Anotações ajudam a declarar nomes, ignorar propriedades e ajustar formato. Elas não substituem separar DTO de domínio, revisar compatibilidade e impedir que segredos apareçam em log, resposta ou arquivo."},{"id":"json-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>Records e JSON</h2>","fidelityText":"Records e JSON"},{"id":"json-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"public record BookDTO(String title, String author, int pages) {}\n// Jackson serializa/desserializa records automaticamente desde a versão 2.12+ --\n// sem precisar de getters \"get*\" (usa os métodos de acesso do record: titulo(), autor()...)","fidelityText":"public record LivroDTO(String titulo, String autor, int paginas) {} // Jackson serializa/desserializa records automaticamente desde a versão 2.12+ -- // sem precisar de getters \"get*\" (usa os métodos de acesso do record: titulo(), autor()...)","highlightedHtml":"<span class=\"kw\">public record</span> <span class=\"cls\">BookDTO</span>(<span class=\"kw\">String</span> title, <span class=\"kw\">String</span> author, <span class=\"kw\">int</span> pages) {}\n<span class=\"com\">// Jackson serializa/desserializa records automaticamente desde a versão 2.12+ --\n// sem precisar de getters \"get*\" (usa os métodos de acesso do record: titulo(), autor()...)</span>","caption":"Exemplo executável de json.","explanation":["Records expõem acessores com o nome do componente, e versões modernas do Jackson entendem esse modelo.","Construtor canônico ainda deve proteger invariantes quando o record representa valor de domínio."],"commonMistakes":["Chamar acessores de record de getTitulo","Achar que record torna componentes internos profundamente imutáveis"]},{"id":"json-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>DTO (Data Transfer Object):</b> em uma aplicação real, você raramente serializa entidades de domínio diretamente. Crie classes ou records específicos para representar entrada e saída, desacoplando formato externo do modelo interno.</div>","fidelityText":"DTO (Data Transfer Object): em uma aplicação real, você raramente serializa entidades de domínio diretamente. Crie classes ou records específicos para representar entrada e saída, desacoplando formato externo do modelo interno."},{"id":"json-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Campos desconhecidos: o JSON de entrada nem sempre bate 100% com o DTO</h2>","fidelityText":"Campos desconhecidos: o JSON de entrada nem sempre bate 100% com o DTO"},{"id":"json-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"String jsonWithFieldExtra = \"\"\"\n    {\"title\": \"Duna\", \"author\": \"Herbert\", \"pages\": 412, \"publisher\": \"Aleph\"}\n    \"\"\"; // \"editora\" não existe em LivroDTO\n\nBookDTO book = mapper.readValue(jsonWithFieldExtra, BookDTO.class);\n// por padrão, Jackson IGNORA campos desconhecidos silenciosamente -- não lança exceção","fidelityText":"String jsonComCampoExtra = \"\"\" {\"titulo\": \"Duna\", \"autor\": \"Herbert\", \"paginas\": 412, \"editora\": \"Aleph\"} \"\"\"; // \"editora\" não existe em LivroDTO LivroDTO livro = mapper.readValue(jsonComCampoExtra, LivroDTO.class); // por padrão, Jackson IGNORA campos desconhecidos silenciosamente -- não lança exceção","highlightedHtml":"<span class=\"kw\">String</span> jsonWithFieldExtra = <span class=\"str\">\"\"\"\n    {\"title\": \"Duna\", \"author\": \"Herbert\", \"pages\": 412, \"publisher\": \"Aleph\"}\n    \"\"\"</span>; <span class=\"com\">// \"editora\" não existe em LivroDTO</span>\n\nBookDTO book = mapper.readValue(jsonWithFieldExtra, BookDTO.<span class=\"kw\">class</span>);\n<span class=\"com\">// por padrão, Jackson IGNORA campos desconhecidos silenciosamente -- não lança exceção</span>","caption":"Exemplo executável de json.","explanation":["Por padrão, Jackson ignora campos desconhecidos do JSON de entrada silenciosamente -- não lança exceção."]},{"id":"json-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"ObjectMapper estrito = new ObjectMapper();\nestrito.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); // agora um campo extra lança UnrecognizedPropertyException","fidelityText":"ObjectMapper estrito = new ObjectMapper(); estrito.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); // agora um campo extra lança UnrecognizedPropertyException","highlightedHtml":"ObjectMapper estrito = <span class=\"kw\">new</span> ObjectMapper();\nestrito.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, <span class=\"kw\">true</span>); <span class=\"com\">// agora um campo extra lança UnrecognizedPropertyException</span>","caption":"Exemplo executável de json.","explanation":["FAIL_ON_UNKNOWN_PROPERTIES torna o mapeamento estrito -- útil para contratos internos onde um campo extra pode indicar um erro de nome no DTO."],"commonMistakes":["Assumir que o Jackson sempre valida a forma exata do JSON por padrão"]},{"id":"json-content-22","type":"html","authorship":"legacy-preserved","html":"<p>O comportamento padrão do Jackson é tolerante a campos desconhecidos — conveniente para não quebrar quando a API de origem adiciona um campo novo, mas perigoso quando um campo extra deveria ter sido mapeado e foi só um erro de nome no DTO (um <code>@JsonProperty</code> com typo, por exemplo, passaria despercebido). Decida conscientemente: APIs de terceiros que você não controla geralmente pedem tolerância; contratos internos que você controla se beneficiam de <code>FAIL_ON_UNKNOWN_PROPERTIES</code> para detectar divergência cedo.</p>","fidelityText":"O comportamento padrão do Jackson é tolerante a campos desconhecidos — conveniente para não quebrar quando a API de origem adiciona um campo novo, mas perigoso quando um campo extra deveria ter sido mapeado e foi só um erro de nome no DTO (um @JsonProperty com typo, por exemplo, passaria despercebido). Decida conscientemente: APIs de terceiros que você não controla geralmente pedem tolerância; contratos internos que você controla se beneficiam de FAIL_ON_UNKNOWN_PROPERTIES para detectar divergência cedo."},{"id":"json-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>Ausente não é o mesmo que null</h2>","fidelityText":"Ausente não é o mesmo que null"},{"id":"json-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"public record PerfilDTO(String name, String alias) {}\n\n// dois JSONs diferentes, mesmo resultado no Java puro -- a distinção se perde:\nString withAliasNulo = \"\"\"{\"name\": \"Ana\", \"alias\": null}\"\"\";      // campo PRESENTE, valor null\nString withoutAlias      = \"\"\"{\"name\": \"Ana\"}\"\"\";                    // campo AUSENTE por completo\n// PerfilDTO(nome=Ana, apelido=null) nos DOIS casos -- Java não distingue \"ausente\" de \"presente e null\"","fidelityText":"public record PerfilDTO(String nome, String apelido) {} // dois JSONs diferentes, mesmo resultado no Java puro -- a distinção se perde: String comApelidoNulo = \"\"\"{\"nome\": \"Ana\", \"apelido\": null}\"\"\"; // campo PRESENTE, valor null String semApelido = \"\"\"{\"nome\": \"Ana\"}\"\"\"; // campo AUSENTE por completo // PerfilDTO(nome=Ana, apelido=null) nos DOIS casos -- Java não distingue \"ausente\" de \"presente e null\"","highlightedHtml":"<span class=\"kw\">public record</span> <span class=\"cls\">PerfilDTO</span>(<span class=\"kw\">String</span> name, String alias) {}\n\n<span class=\"com\">// dois JSONs diferentes, mesmo resultado no Java puro -- a distinção se perde:</span>\n<span class=\"kw\">String</span> withAliasNulo = <span class=\"str\">\"\"\"{\"name\": \"Ana\", \"alias\": null}\"\"\"</span>;      <span class=\"com\">// campo PRESENTE, valor null</span>\n<span class=\"kw\">String</span> withoutAlias      = <span class=\"str\">\"\"\"{\"name\": \"Ana\"}\"\"\"</span>;                    <span class=\"com\">// campo AUSENTE por completo</span>\n<span class=\"com\">// PerfilDTO(nome=Ana, apelido=null) nos DOIS casos -- Java não distingue \"ausente\" de \"presente e null\"</span>","caption":"Exemplo executável de json.","explanation":["Um DTO Java comum não distingue campo ausente de campo presente com valor null -- os dois viram null no objeto Java."],"commonMistakes":["Tratar ausência e null como o mesmo caso quando o contrato (ex.: PATCH parcial) precisa diferenciá-los"]},{"id":"json-content-25","type":"html","authorship":"legacy-preserved","html":"<p>No JSON, um campo <strong>ausente</strong> e um campo <strong>presente com valor <code>null</code></strong> são conceitualmente diferentes (o primeiro é \"não informado\", o segundo é \"informado como vazio\") — mas um DTO Java comum não preserva essa distinção, ambos viram <code>null</code> no campo Java. Quando o contrato realmente precisa diferenciar os dois casos (ex.: um PATCH parcial, onde \"não enviei o campo\" deve significar \"não altere\", e \"enviei null\" deve significar \"apague o valor\"), a solução usual é mapear o campo como <code>JsonNode</code> bruto e checar <code>node.has(\"apelido\")</code> antes de checar <code>isNull()</code>, ou usar um wrapper como <code>JsonNullable&lt;T&gt;</code> de uma biblioteca dedicada a esse problema.</p>","fidelityText":"No JSON, um campo ausente e um campo presente com valor null são conceitualmente diferentes (o primeiro é \"não informado\", o segundo é \"informado como vazio\") — mas um DTO Java comum não preserva essa distinção, ambos viram null no campo Java. Quando o contrato realmente precisa diferenciar os dois casos (ex.: um PATCH parcial, onde \"não enviei o campo\" deve significar \"não altere\", e \"enviei null\" deve significar \"apague o valor\"), a solução usual é mapear o campo como JsonNode bruto e checar node.has(\"apelido\") antes de checar isNull(), ou usar um wrapper como JsonNullable<T> de uma biblioteca dedicada a esse problema."},{"id":"json-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>Datas: java.time precisa do módulo certo</h2>","fidelityText":"Datas: java.time precisa do módulo certo"},{"id":"json-code-27","type":"code","authorship":"legacy-preserved","language":"java","source":"public record EventDTO(String title, LocalDate date) {}\n\nObjectMapper mapper = new ObjectMapper()\n    .registerModule(new JavaTimeModule()) // sem isto, LocalDate/LocalDateTime falham ao serializar/desserializar\n    .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // serializa como \"2026-08-22\", não como número de milissegundos","fidelityText":"public record EventoDTO(String titulo, LocalDate data) {} ObjectMapper mapper = new ObjectMapper() .registerModule(new JavaTimeModule()) // sem isto, LocalDate/LocalDateTime falham ao serializar/desserializar .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // serializa como \"2026-08-22\", não como número de milissegundos","highlightedHtml":"<span class=\"kw\">public record</span> <span class=\"cls\">EventDTO</span>(<span class=\"kw\">String</span> title, LocalDate date) {}\n\nObjectMapper mapper = <span class=\"kw\">new</span> ObjectMapper()\n    .registerModule(<span class=\"kw\">new</span> JavaTimeModule()) <span class=\"com\">// sem isto, LocalDate/LocalDateTime falham ao serializar/desserializar</span>\n    .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); <span class=\"com\">// serializa como \"2026-08-22\", não como número de milissegundos</span>","caption":"Exemplo executável de json.","explanation":["Sem registrar o JavaTimeModule, tipos de java.time falham ao serializar/desserializar -- o Spring Boot já registra automaticamente, Jackson isolado não."],"commonMistakes":["Usar LocalDate/LocalDateTime sem registrar o módulo em um ObjectMapper standalone"]},{"id":"json-content-28","type":"html","authorship":"legacy-preserved","html":"<p>Por padrão, o <code>ObjectMapper</code> básico não sabe lidar com os tipos de <code>java.time</code> (<code>LocalDate</code>, <code>LocalDateTime</code>, <code>Instant</code>...) — é preciso registrar o <code>JavaTimeModule</code> (do artefato <code>jackson-datatype-jsr310</code>). O Spring Boot já faz esse registro automaticamente quando o módulo está no classpath; em Java puro com Jackson isolado, é sua responsabilidade.</p>","fidelityText":"Por padrão, o ObjectMapper básico não sabe lidar com os tipos de java.time (LocalDate, LocalDateTime, Instant...) — é preciso registrar o JavaTimeModule (do artefato jackson-datatype-jsr310). O Spring Boot já faz esse registro automaticamente quando o módulo está no classpath; em Java puro com Jackson isolado, é sua responsabilidade."},{"id":"json-content-29","type":"html","authorship":"legacy-preserved","html":"<h2>Coleções genéricas: o problema do type erasure na desserialização</h2>","fidelityText":"Coleções genéricas: o problema do type erasure na desserialização"},{"id":"json-code-30","type":"code","authorship":"legacy-preserved","language":"java","source":"// mapper.readValue(json, List<LivroDTO>.class); // ❌ NÃO compila -- generics sofrem type erasure, não existe List<LivroDTO>.class\n\nList<BookDTO> books = mapper.readValue(json, new TypeReference<List<BookDTO>>() {}); // TypeReference preserva o tipo genérico via subclasse anônima","fidelityText":"// mapper.readValue(json, List<LivroDTO>.class); // ❌ NÃO compila -- generics sofrem type erasure, não existe List<LivroDTO>.class List<LivroDTO> livros = mapper.readValue(json, new TypeReference<List<LivroDTO>>() {}); // TypeReference preserva o tipo genérico via subclasse anônima","highlightedHtml":"<span class=\"com\">// mapper.readValue(json, List&lt;LivroDTO&gt;.class); // ❌ NÃO compila -- generics sofrem type erasure, não existe List&lt;LivroDTO&gt;.class</span>\n\nList&lt;BookDTO&gt; books = mapper.readValue(json, <span class=\"kw\">new</span> TypeReference&lt;List&lt;BookDTO&gt;&gt;() {}); <span class=\"com\">// TypeReference preserva o tipo genérico via subclasse anônima</span>","caption":"Exemplo executável de json.","explanation":["List<T>.class não existe por causa do type erasure -- TypeReference preserva o tipo genérico via subclasse anônima."],"commonMistakes":["Tentar usar List<BookDTO>.class diretamente (não compila)"]},{"id":"json-content-31","type":"html","authorship":"legacy-preserved","html":"<p>Como visto no capítulo de generics, <code>List&lt;LivroDTO&gt;.class</code> não existe (type erasure) — Jackson resolve isso com <code>TypeReference&lt;T&gt;</code>, uma classe abstrata que você estende anonimamente; a subclasse anônima <em>preserva</em> a informação de tipo genérico como metadado da classe (o mesmo truque de \"super type token\"), permitindo que Jackson descubra em runtime que o tipo desejado é <code>List&lt;LivroDTO&gt;</code>, não apenas <code>List</code> bruto.</p>","fidelityText":"Como visto no capítulo de generics, List<LivroDTO>.class não existe (type erasure) — Jackson resolve isso com TypeReference<T>, uma classe abstrata que você estende anonimamente; a subclasse anônima preserva a informação de tipo genérico como metadado da classe (o mesmo truque de \"super type token\"), permitindo que Jackson descubra em runtime que o tipo desejado é List<LivroDTO>, não apenas List bruto."},{"id":"json-content-32","type":"html","authorship":"legacy-preserved","html":"<h2>Tratando erros de (des)serialização</h2>","fidelityText":"Tratando erros de (des)serialização"},{"id":"json-code-33","type":"code","authorship":"legacy-preserved","language":"java","source":"try {\n    BookDTO book = mapper.readValue(jsonPossivelmenteInvalid, BookDTO.class);\n} catch (JsonProcessingException e) {\n    // JsonMappingException (estrutura não bate com o DTO) ou\n    // JsonParseException (o texto nem é um JSON válido) -- ambas subclasses de JsonProcessingException\n    throw new IllegalArgumentException(\"JSON invalid in the boundary of the API\", e); // preserva a causa original\n}","fidelityText":"try { LivroDTO livro = mapper.readValue(jsonPossivelmenteInvalido, LivroDTO.class); } catch (JsonProcessingException e) { // JsonMappingException (estrutura não bate com o DTO) ou // JsonParseException (o texto nem é um JSON válido) -- ambas subclasses de JsonProcessingException throw new IllegalArgumentException(\"JSON inválido na fronteira da API\", e); // preserva a causa original }","highlightedHtml":"<span class=\"kw\">try</span> {\n    BookDTO book = mapper.readValue(jsonPossivelmenteInvalid, BookDTO.<span class=\"kw\">class</span>);\n} <span class=\"kw\">catch</span> (JsonProcessingException e) {\n    <span class=\"com\">// JsonMappingException (estrutura não bate com o DTO) ou\n    // JsonParseException (o texto nem é um JSON válido) -- ambas subclasses de JsonProcessingException</span>\n    <span class=\"kw\">throw new</span> IllegalArgumentException(<span class=\"str\">\"JSON invalid in the boundary of the API\"</span>, e); <span class=\"com\">// preserva a causa original</span>\n}","caption":"Exemplo executável de json.","explanation":["JsonProcessingException é a superclasse comum de JsonParseException (texto inválido) e JsonMappingException (estrutura incompatível com o DTO)."],"commonMistakes":["Deixar a stack trace do Jackson vazar direto para o cliente da API"]},{"id":"json-content-34","type":"html","authorship":"legacy-preserved","html":"<p><code>JsonProcessingException</code> é a superclasse comum de <code>JsonParseException</code> (o texto não é JSON sintaticamente válido) e <code>JsonMappingException</code> (é JSON válido, mas não bate com a estrutura esperada pelo DTO — campo obrigatório faltando em record, tipo incompatível). Capture no nível da fronteira da aplicação e traduza para uma resposta de erro do seu domínio, preservando a causa original em vez de expor a stack trace do Jackson diretamente ao cliente.</p>","fidelityText":"JsonProcessingException é a superclasse comum de JsonParseException (o texto não é JSON sintaticamente válido) e JsonMappingException (é JSON válido, mas não bate com a estrutura esperada pelo DTO — campo obrigatório faltando em record, tipo incompatível). Capture no nível da fronteira da aplicação e traduza para uma resposta de erro do seu domínio, preservando a causa original em vez de expor a stack trace do Jackson diretamente ao cliente."},{"id":"json-exercise-35","type":"exercise","authorship":"legacy-preserved","title":"Exercício 25.1 — Serializando e desserializando","prompt":"Crie um record LivroDTO(String titulo, String autor, int paginas). Usando ObjectMapper (biblioteca Jackson), serialize uma instância para JSON e imprima o resultado. Depois, desserialize uma String JSON de volta para LivroDTO e imprima os campos.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 25.1 — Serializando e desserializandomédio Crie um record LivroDTO(String titulo, String autor, int paginas). Usando ObjectMapper (biblioteca Jackson), serialize uma instância para JSON e imprima o resultado. Depois, desserialize uma String JSON de volta para LivroDTO e imprima os campos. Ver solução public record LivroDTO(String titulo, String autor, int paginas) {} ObjectMapper mapper = new ObjectMapper(); LivroDTO dto = new LivroDTO(\"O Alquimista\", \"Paulo Coelho\", 208); String json = mapper.writeValueAsString(dto); System.out.println(json); // {\"titulo\":\"O Alquimista\",\"autor\":\"Paulo Coelho\",\"paginas\":208} LivroDTO devolta = mapper.readValue(json, LivroDTO.class); System.out.println(devolta.titulo() + \" - \" + devolta.autor());","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 25.1 — Serializando e desserializando</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie um record <code>LivroDTO(String titulo, String autor, int paginas)</code>. Usando <code>ObjectMapper</code> (biblioteca Jackson), serialize uma instância para JSON e imprima o resultado. Depois, desserialize uma <code>String</code> JSON de volta para <code>LivroDTO</code> e imprima os campos.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public record</span> <span class=\"cls\">BookDTO</span>(<span class=\"kw\">String</span> title, <span class=\"kw\">String</span> author, <span class=\"kw\">int</span> pages) {}\n\nObjectMapper mapper = <span class=\"kw\">new</span> ObjectMapper();\n\n<span class=\"cls\">BookDTO</span> dto = <span class=\"kw\">new</span> <span class=\"cls\">BookDTO</span>(<span class=\"str\">\"The Alquimista\"</span>, <span class=\"str\">\"Paulo Coelho\"</span>, 208);\n<span class=\"kw\">String</span> json = mapper.writeValueAsString(dto);\nSystem.out.println(json); <span class=\"com\">// {\"titulo\":\"O Alquimista\",\"autor\":\"Paulo Coelho\",\"paginas\":208}</span>\n\n<span class=\"cls\">BookDTO</span> devolta = mapper.readValue(json, <span class=\"cls\">BookDTO</span>.<span class=\"kw\">class</span>);\nSystem.out.println(devolta.title() + <span class=\"str\">\" - \"</span> + devolta.author());</pre>\n        </div>\n      </div>"},{"id":"json-exercise-36","type":"exercise","authorship":"legacy-preserved","title":"Exercício 25.2 — Lista de DTOs e erro tratado","prompt":"Usando TypeReference, desserialize um JSON representando uma lista de LivroDTO para List<LivroDTO>. Depois, tente desserializar um JSON propositalmente malformado (uma vírgula sobrando) dentro de um try/catch (JsonProcessingException e) e imprima uma mensagem de erro do seu domínio, sem vazar a mensagem crua do Jackson.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 25.2 — Lista de DTOs e erro tratadomédio Usando TypeReference, desserialize um JSON representando uma lista de LivroDTO para List<LivroDTO>. Depois, tente desserializar um JSON propositalmente malformado (uma vírgula sobrando) dentro de um try/catch (JsonProcessingException e) e imprima uma mensagem de erro do seu domínio, sem vazar a mensagem crua do Jackson. Ver solução String jsonLista = \"\"\" [{\"titulo\":\"1984\",\"autor\":\"Orwell\",\"paginas\":328}, {\"titulo\":\"Duna\",\"autor\":\"Herbert\",\"paginas\":412}] \"\"\"; List<LivroDTO> livros = mapper.readValue(jsonLista, new TypeReference<List<LivroDTO>>() {}); try { mapper.readValue(\"{\\\"titulo\\\":,}\", LivroDTO.class); } catch (JsonProcessingException e) { System.out.println(\"Não foi possível interpretar o JSON recebido.\"); }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 25.2 — Lista de DTOs e erro tratado</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Usando <code>TypeReference</code>, desserialize um JSON representando uma lista de <code>LivroDTO</code> para <code>List&lt;LivroDTO&gt;</code>. Depois, tente desserializar um JSON propositalmente malformado (uma vírgula sobrando) dentro de um <code>try/catch (JsonProcessingException e)</code> e imprima uma mensagem de erro do seu domínio, sem vazar a mensagem crua do Jackson.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">String</span> jsonList = <span class=\"str\">\"\"\"\n    [{\"title\":\"1984\",\"author\":\"Orwell\",\"pages\":328},\n     {\"title\":\"Duna\",\"author\":\"Herbert\",\"pages\":412}]\n    \"\"\"</span>;\nList&lt;BookDTO&gt; books = mapper.readValue(jsonList, <span class=\"kw\">new</span> TypeReference&lt;List&lt;BookDTO&gt;&gt;() {});\n\n<span class=\"kw\">try</span> {\n    mapper.readValue(<span class=\"str\">\"{\\\"title\\\":,}\"</span>, BookDTO.<span class=\"kw\">class</span>);\n} <span class=\"kw\">catch</span> (JsonProcessingException e) {\n    System.out.println(<span class=\"str\">\"Not was possible interpretar the JSON recebido.\"</span>);\n}</pre>\n        </div>\n      </div>"},{"id":"json-comparison","type":"comparison","authorship":"authored","title":"Três camadas que não devem se misturar","criteria":["responsabilidade","exemplo","erro comum"],"alternatives":[{"name":"DTO externo","values":["formato publicado","PedidoJson","expor entidade JPA ou domínio interno"],"useWhen":"representar entrada/saída de fronteira","avoidWhen":"guardar regra de negócio"},{"name":"Domínio","values":["invariante e comportamento","Pedido","aceitar estado só porque JSON veio assim"],"useWhen":"decidir regras do sistema","avoidWhen":"depender de Jackson ou annotations de API"},{"name":"Mapper","values":["conversão controlada","toDomain/toDto","copiar campos sem validar ausência"],"useWhen":"atravessar fronteira","avoidWhen":"esconder falha como null"}]},{"id":"json-quiz","type":"quiz","authorship":"authored","conceptId":"serializacao-desserializacao","prompt":"Desserializar JSON para um DTO prova que o pedido é válido para o domínio?","options":[{"id":"json-q-a","label":"Não; prova apenas que o texto coube no formato esperado pelo mapper.","correct":true,"explanation":"Validação de domínio precisa ocorrer depois, com regras explícitas e mensagens úteis."},{"id":"json-q-b","label":"Sim; Jackson executa automaticamente todas as regras de negócio.","correct":false,"explanation":"Jackson converte dados; regras de domínio pertencem ao código do domínio."},{"id":"json-q-c","label":"Sim, desde que o JSON esteja em UTF-8.","correct":false,"explanation":"Charset correto evita corrupção textual, mas não valida regras como valor positivo ou estado permitido."}]}],"resources":[{"id":"json-jackson-records","type":"reference","title":"Jackson Databind README","url":"https://github.com/FasterXML/jackson-databind","reinforces":"Documenta ObjectMapper, data binding, POJOs, records e fluxo de conversão.","language":"en","publisher":"FasterXML","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"json-rfc8259","type":"reference","title":"RFC 8259: The JavaScript Object Notation Data Interchange Format","url":"https://www.rfc-editor.org/rfc/rfc8259","reinforces":"Define JSON como formato textual de intercâmbio, valores, objetos, arrays e strings.","language":"en","publisher":"IETF","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A json operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a json operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this json chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"public record BookDTO(String title, String author, int pages) {}","instruction":"A json operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this json chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"logging","moduleId":"testing-engineering","order":3,"title":"Logging & configuração externa","summary":"println não tem nível de severidade, não pode ser desligado seletivamente, não vai para arquivo automaticamente, e não tem timestamp nem contexto. Um framework de logging (SLF4J + Logback, o padrão de fato usado pelo Spring Boot) resolve tudo isso.","objectives":["Escolher níveis de log por severidade e público","Logar contexto e exceção preservando causa","Externalizar configuração e proteger segredos"],"whyItExists":"Depois de debugger e Git, o aluno precisa observar execução fora da IDE. Logs são evidência operacional e precisam ser úteis, seguros e configuráveis.","prerequisiteChapterIds":["debugging"],"conceptIds":["por-que-nao-usar-system-out-println-em-producao","niveis-de-log-do-mais-ao-menos-verboso","configuracao-externa-por-que-nada-de-senha-hardcoded"],"introducedConceptIds":["log-nivel-contexto","log-parametrizado-causa","configuracao-externa"],"usedConceptIds":["causa-excecao","gitignore-secrets","stream-resource-lifecycle"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"logging-intuition","type":"intuition","authorship":"authored","title":"Log é evidência para o futuro","body":"Um log bom ajuda alguém que não está com o debugger aberto a entender o que aconteceu, com qual entidade, em qual severidade e com qual causa.","analogyLimit":"Diário de bordo ajuda, mas logs também têm custo, formato, volume, privacidade, correlação e retenção."},{"id":"logging-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Iniciante-Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#excecoes\">10 · Exceções</a></div>\n      </div>","fidelityText":"Dificuldade: Iniciante-Intermediário Pré-requisito: 10 · Exceções"},{"id":"logging-content-2","type":"html","authorship":"legacy-preserved","html":"<h2>Por que não usar System.out.println em produção</h2>","fidelityText":"Por que não usar System.out.println em produção"},{"id":"logging-content-3","type":"html","authorship":"legacy-preserved","html":"<p><code>println</code> não tem nível de severidade, não pode ser desligado seletivamente, não vai para arquivo automaticamente, e não tem timestamp nem contexto. Um <strong>framework de logging</strong> (SLF4J + Logback, o padrão de fato usado pelo Spring Boot) resolve tudo isso.</p>","fidelityText":"println não tem nível de severidade, não pode ser desligado seletivamente, não vai para arquivo automaticamente, e não tem timestamp nem contexto. Um framework de logging (SLF4J + Logback, o padrão de fato usado pelo Spring Boot) resolve tudo isso."},{"id":"logging-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"import org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class OrderService {\n    private static final Logger log = LoggerFactory.getLogger(OrderService.class);\n\n    public void complete(Order p) {\n        log.info(\"Completing order {}\", p.getId());  // placeholder \"{}\", não concatenação\n        try {\n            // ...\n        } catch (PaymentDeclinedException e) {\n            log.error(\"Failure to the process payment of the order {}\", p.getId(), e); // exception vai no final -- imprime a stack trace\n        }\n    }\n}","fidelityText":"import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PedidoServico { private static final Logger log = LoggerFactory.getLogger(PedidoServico.class); public void finalizar(Pedido p) { log.info(\"Finalizando pedido {}\", p.getId()); // placeholder \"{}\", não concatenação try { // ... } catch (PagamentoRecusadoException e) { log.error(\"Falha ao processar pagamento do pedido {}\", p.getId(), e); // exception vai no final -- imprime a stack trace } } }","highlightedHtml":"<span class=\"kw\">import</span> org.slf4j.Logger;\n<span class=\"kw\">import</span> org.slf4j.LoggerFactory;\n\n<span class=\"kw\">public class</span> <span class=\"cls\">OrderService</span> {\n    <span class=\"kw\">private static final</span> Logger log = LoggerFactory.getLogger(<span class=\"cls\">OrderService</span>.<span class=\"kw\">class</span>);\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">complete</span>(<span class=\"cls\">Order</span> p) {\n        log.info(<span class=\"str\">\"Completing order {}\"</span>, p.getId());  <span class=\"com\">// placeholder \"{}\", não concatenação</span>\n        <span class=\"kw\">try</span> {\n            <span class=\"com\">// ...</span>\n        } <span class=\"kw\">catch</span> (<span class=\"cls\">PaymentDeclinedException</span> e) {\n            log.error(<span class=\"str\">\"Failure to the process payment of the order {}\"</span>, p.getId(), e); <span class=\"com\">// exception vai no final -- imprime a stack trace</span>\n        }\n    }\n}","caption":"Exemplo executável de logging.","explanation":["Logger é criado por classe para registrar eventos com origem identificável.","Placeholders adiam formatação e mantêm parâmetros separados.","Throwable no final preserva stack trace e causa."],"commonMistakes":["Concatenar strings em log quente","Logar exceção só com getMessage","Usar ERROR para fluxo esperado"]},{"id":"logging-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Níveis de log, do mais ao menos verboso</h2>","fidelityText":"Níveis de log, do mais ao menos verboso"},{"id":"logging-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Nível</th><th>Quando usar</th></tr>\n        <tr><td><code>TRACE</code></td><td>Detalhe extremo, só em depuração profunda</td></tr>\n        <tr><td><code>DEBUG</code></td><td>Informação útil durante desenvolvimento, desligada em produção</td></tr>\n        <tr><td><code>INFO</code></td><td>Eventos relevantes do fluxo normal (\"pedido criado\", \"aplicação iniciada\")</td></tr>\n        <tr><td><code>WARN</code></td><td>Algo inesperado, mas não quebrou o fluxo</td></tr>\n        <tr><td><code>ERROR</code></td><td>Falha real que precisa de atenção</td></tr>\n      </tbody></table>","fidelityText":"NívelQuando usar TRACEDetalhe extremo, só em depuração profunda DEBUGInformação útil durante desenvolvimento, desligada em produção INFOEventos relevantes do fluxo normal (\"pedido criado\", \"aplicação iniciada\") WARNAlgo inesperado, mas não quebrou o fluxo ERRORFalha real que precisa de atenção"},{"id":"logging-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O motivo de usar <code>log.info(\"Pedido {}\", id)</code> em vez de <code>log.info(\"Pedido \" + id)</code> não é só estilo: a concatenação de String acontece <strong>sempre</strong>, mesmo que o nível <code>INFO</code> esteja desligado, desperdiçando processamento. Com o placeholder <code>{}</code>, o framework só monta a mensagem final se aquele nível estiver realmente ativo — relevante em métodos chamados milhares de vezes por segundo, típico de uma API em produção.</div>","fidelityText":"O motivo de usar log.info(\"Pedido {}\", id) em vez de log.info(\"Pedido \" + id) não é só estilo: a concatenação de String acontece sempre, mesmo que o nível INFO esteja desligado, desperdiçando processamento. Com o placeholder {}, o framework só monta a mensagem final se aquele nível estiver realmente ativo — relevante em métodos chamados milhares de vezes por segundo, típico de uma API em produção."},{"id":"logging-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Configuração externa: por que nada de senha \"hardcoded\"</h2>","fidelityText":"Configuração externa: por que nada de senha \"hardcoded\""},{"id":"logging-content-9","type":"html","authorship":"legacy-preserved","html":"<p>URLs de banco, senhas, chaves de API e afins nunca devem estar escritos diretamente no código-fonte. O padrão é externalizar em arquivos de configuração (Spring Boot usa <code>application.properties</code> ou <code>application.yml</code>) ou variáveis de ambiente. O arquivo com <em>placeholders</em> como <code>${DB_USER}</code> pode (e deve) ser versionado no Git — ele não contém segredo nenhum, só o nome da variável esperada. O arquivo ou ambiente com os valores <strong>reais</strong> (local, <code>.env</code>, ou as variáveis do próprio servidor de deploy) é que precisa ficar de fora do repositório, listado no <code>.gitignore</code>.</p>","fidelityText":"URLs de banco, senhas, chaves de API e afins nunca devem estar escritos diretamente no código-fonte. O padrão é externalizar em arquivos de configuração (Spring Boot usa application.properties ou application.yml) ou variáveis de ambiente. O arquivo com placeholders como ${DB_USER} pode (e deve) ser versionado no Git — ele não contém segredo nenhum, só o nome da variável esperada. O arquivo ou ambiente com os valores reais (local, .env, ou as variáveis do próprio servidor de deploy) é que precisa ficar de fora do repositório, listado no .gitignore."},{"id":"logging-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties (formato chave=valor)\nserver.port=8080\nbank.url=jdbc:postgresql://localhost:5432/biblioteca\nbank.user=${DB_USER}       # ${...} lê de variável de ambiente\nbank.password=${DB_PASSWORD}","fidelityText":"# application.properties (formato chave=valor) servidor.porta=8080 banco.url=jdbc:postgresql://localhost:5432/biblioteca banco.usuario=${DB_USER} # ${...} lê de variável de ambiente banco.senha=${DB_PASSWORD}","highlightedHtml":"<span class=\"com\"># application.properties (formato chave=valor)</span>\nserver.port=8080\nbank.url=jdbc:postgresql://localhost:5432/biblioteca\nbank.user=${DB_USER}       <span class=\"com\"># ${...} lê de variável de ambiente</span>\nbank.password=${DB_PASSWORD}","caption":"Exemplo executável de logging.","explanation":["Properties usa chave=valor e pode referenciar variáveis de ambiente em frameworks que resolvem placeholders.","Segredos ficam fora do código e do Git."],"commonMistakes":["Committar senha real","Misturar configuração de produção no código","Não documentar variáveis necessárias"]},{"id":"logging-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.yml (formato hierárquico, também aceito pelo Spring Boot)\nserver:\n  port: 8080\nbank:\n  url: jdbc:postgresql://localhost:5432/biblioteca\n  user: ${DB_USER}\n  password: ${DB_PASSWORD}","fidelityText":"# application.yml (formato hierárquico, também aceito pelo Spring Boot) servidor: porta: 8080 banco: url: jdbc:postgresql://localhost:5432/biblioteca usuario: ${DB_USER} senha: ${DB_PASSWORD}","highlightedHtml":"<span class=\"com\"># application.yml (formato hierárquico, também aceito pelo Spring Boot)</span>\nserver:\n  port: 8080\nbank:\n  url: jdbc:postgresql://localhost:5432/biblioteca\n  user: <span class=\"str\">${DB_USER}</span>\n  password: <span class=\"str\">${DB_PASSWORD}</span>","caption":"Exemplo executável de logging.","explanation":["YAML representa configuração hierárquica de forma legível.","A estrutura facilita agrupar configurações por área, mas indentação faz parte do formato."],"commonMistakes":["Errar indentação","Guardar segredo no arquivo versionado","Assumir que todo Java puro lê YAML sem biblioteca"]},{"id":"logging-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Lendo um arquivo <code>.properties</code> em Java puro (o que o Spring Boot faz automaticamente por trás dos panos):</p>","fidelityText":"Lendo um arquivo .properties em Java puro (o que o Spring Boot faz automaticamente por trás dos panos):"},{"id":"logging-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"Properties props = new Properties();\ntry (InputStream input = new FileInputStream(\"config.properties\")) {\n    props.load(input);\n}\nString port = props.getProperty(\"server.port\");","fidelityText":"Properties props = new Properties(); try (InputStream input = new FileInputStream(\"config.properties\")) { props.load(input); } String porta = props.getProperty(\"servidor.porta\");","highlightedHtml":"Properties props = <span class=\"kw\">new</span> Properties();\n<span class=\"kw\">try</span> (InputStream input = <span class=\"kw\">new</span> FileInputStream(<span class=\"str\">\"config.properties\"</span>)) {\n    props.load(input);\n}\n<span class=\"kw\">String</span> port = props.getProperty(<span class=\"str\">\"server.port\"</span>);","caption":"Exemplo executável de logging.","explanation":["Properties.load lê configuração externa por InputStream.","try-with-resources fecha o arquivo mesmo em falha de leitura."],"commonMistakes":["Usar FileInputStream sem fechar","Ignorar valor ausente","Tratar config inválida como padrão silencioso"]},{"id":"logging-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>${DB_USER}</code> num <code>.properties</code> não é mágica do Java puro:</b> <code>Properties.load(...)</code> não resolve <code>${...}</code> sozinho — <code>props.getProperty(\"banco.senha\")</code> devolveria a string literal <code>\"${DB_PASSWORD}\"</code>, não o valor real da variável de ambiente. Essa interpolação é um recurso do Spring Boot (e de frameworks parecidos) por cima do <code>Properties</code>, não do <code>java.util.Properties</code> em si. Em Java puro, ler a variável de ambiente diretamente é assim:</div>","fidelityText":"${DB_USER} num .properties não é mágica do Java puro: Properties.load(...) não resolve ${...} sozinho — props.getProperty(\"banco.senha\") devolveria a string literal \"${DB_PASSWORD}\", não o valor real da variável de ambiente. Essa interpolação é um recurso do Spring Boot (e de frameworks parecidos) por cima do Properties, não do java.util.Properties em si. Em Java puro, ler a variável de ambiente diretamente é assim:"},{"id":"logging-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"String password = System.getenv(\"DB_PASSWORD\"); // reads the real environment variable, no placeholder at all\nif (password == null) {\n    throw new IllegalStateException(\"Environment variable DB_PASSWORD not configured\");\n}","fidelityText":"String senha = System.getenv(\"DB_PASSWORD\"); // lê a variável de ambiente real, sem placeholder algum if (senha == null) { throw new IllegalStateException(\"Variável de ambiente DB_PASSWORD não configurada\"); }","highlightedHtml":"<span class=\"kw\">String</span> password = System.getenv(<span class=\"str\">\"DB_PASSWORD\"</span>); <span class=\"com\">// lê a variável de ambiente real, sem placeholder algum</span>\n<span class=\"kw\">if</span> (password == <span class=\"kw\">null</span>) {\n    <span class=\"kw\">throw new</span> <span class=\"cls\">IllegalStateException</span>(<span class=\"str\">\"Environment variable DB_PASSWORD not configured\"</span>);\n}","caption":"Exemplo executável de logging.","explanation":["System.getenv lê a variável de ambiente real diretamente -- sem nenhum placeholder ${...} para resolver.","A interpolação ${DB_PASSWORD} dentro de um .properties só existe porque um framework (Spring Boot) resolve isso por cima do java.util.Properties puro."],"commonMistakes":["Achar que Properties.load resolve ${...} sozinho","Não validar variável de ambiente ausente antes de usar o valor"]},{"id":"logging-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Profiles:</b> o Spring Boot também usa esse mecanismo para separar configurações por ambiente — <code>application-dev.properties</code>, <code>application-prod.properties</code> — trocando o banco de dados, nível de log, etc. entre desenvolvimento e produção sem alterar uma linha de código Java.</div>","fidelityText":"Profiles: o Spring Boot também usa esse mecanismo para separar configurações por ambiente — application-dev.properties, application-prod.properties — trocando o banco de dados, nível de log, etc. entre desenvolvimento e produção sem alterar uma linha de código Java."},{"id":"logging-exercise-17","type":"exercise","authorship":"legacy-preserved","title":"Exercício 27.1 — Substituindo println por logging","prompt":"Pegue a classe Biblioteca (com a injeção de NotificadorEmprestimo do capítulo 22) e substitua qualquer System.out.println por chamadas a um Logger SLF4J: log.info(...) para o empréstimo bem-sucedido e log.warn(...) quando o item não estiver disponível (antes de lançar a exceção).","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 27.1 — Substituindo println por loggingfácil Pegue a classe Biblioteca (com a injeção de NotificadorEmprestimo do capítulo 22) e substitua qualquer System.out.println por chamadas a um Logger SLF4J: log.info(...) para o empréstimo bem-sucedido e log.warn(...) quando o item não estiver disponível (antes de lançar a exceção). Ver solução public class Biblioteca { private static final Logger log = LoggerFactory.getLogger(Biblioteca.class); // ... campos existentes ... public void emprestar(String codigo) throws ItemIndisponivelException { ItemAcervo item = acervo.stream() .filter(i -> i.getCodigo().equals(codigo)) .findFirst() .orElseThrow(() -> new ItemIndisponivelException(\"Item não encontrado\")); if (!item.disponivelParaEmprestimo()) { log.warn(\"Tentativa de emprestar item indisponível: {}\", codigo); throw new ItemIndisponivelException(\"Item já emprestado\"); } item.emprestar(); log.info(\"Item emprestado com sucesso: {}\", codigo); notificador.notificar(\"Empréstimo realizado: \" + item.getTitulo()); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 27.1 — Substituindo println por logging</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Pegue a classe <code>Biblioteca</code> (com a injeção de <code>NotificadorEmprestimo</code> do capítulo 22) e substitua qualquer <code>System.out.println</code> por chamadas a um <code>Logger</code> SLF4J: <code>log.info(...)</code> para o empréstimo bem-sucedido e <code>log.warn(...)</code> quando o item não estiver disponível (antes de lançar a exceção).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Library</span> {\n    <span class=\"kw\">private static final</span> Logger log = LoggerFactory.getLogger(<span class=\"cls\">Library</span>.<span class=\"kw\">class</span>);\n    <span class=\"com\">// ... campos existentes ...</span>\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">borrow</span>(<span class=\"kw\">String</span> code) <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        <span class=\"cls\">ItemCatalog</span> item = catalog.stream()\n            .filter(i -&gt; i.getCode().equals(code))\n            .findFirst()\n            .orElseThrow(() -&gt; <span class=\"kw\">new</span> <span class=\"cls\">ItemUnavailableException</span>(<span class=\"str\">\"Item not found\"</span>));\n\n        <span class=\"kw\">if</span> (!item.availableForLoan()) {\n            log.warn(<span class=\"str\">\"Attempt of borrow item unavailable: {}\"</span>, code);\n            <span class=\"kw\">throw new</span> <span class=\"cls\">ItemUnavailableException</span>(<span class=\"str\">\"Item already borrowed\"</span>);\n        }\n\n        item.borrow();\n        log.info(<span class=\"str\">\"Item borrowed with success: {}\"</span>, code);\n        notifier.notify(<span class=\"str\">\"loan realizado: \"</span> + item.getTitle());\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"logging-table","type":"table","authorship":"authored","title":"Nível pelo impacto","headers":["Nível","Use quando","Evite quando"],"rows":[["DEBUG","detalhe para investigação local","fluxo normal em produção"],["INFO","evento normal relevante","cada item de loop quente"],["WARN","situação inesperada recuperável","erro esperado de validação comum"],["ERROR","falha que exige atenção","exceção já tratada como regra de negócio"]]},{"id":"logging-quiz","type":"quiz","authorship":"authored","conceptId":"log-parametrizado-causa","prompt":"Como preservar stack trace ao logar uma exceção com SLF4J?","options":[{"id":"logging-q-a","label":"Passar o Throwable como último argumento da chamada de log.","correct":true,"explanation":"Assim o framework registra mensagem e stack trace associada."},{"id":"logging-q-b","label":"Usar apenas e.getMessage() concatenado na String.","correct":false,"explanation":"Isso perde stack trace e causa encadeada."},{"id":"logging-q-c","label":"Guardar a exceção em .gitignore.","correct":false,"explanation":".gitignore controla arquivos rastreados; não registra diagnóstico."}]}],"resources":[{"id":"logging-slf4j-manual","type":"reference","title":"SLF4J user manual","url":"https://www.slf4j.org/manual.html","reinforces":"Define API de logging, placeholders, níveis e passagem de Throwable.","language":"en","publisher":"SLF4J","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"logging-logback-manual","type":"reference","title":"Logback manual","url":"https://logback.qos.ch/manual/index.html","reinforces":"Documenta configuração, appenders, níveis e comportamento do backend comum do SLF4J.","language":"en","publisher":"QOS.ch","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A logging operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a logging operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this logging chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"import org.slf4j.Logger;","instruction":"A logging operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this logging chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mini-importador-pedidos","moduleId":"io-cli-serialization","order":4,"title":"Mini-projeto: importador de pedidos","summary":"Leia pedidos de CSV, valide cada linha, converta para objetos e produza relatório JSON. Este projeto aprofunda o analisador de vendas: agora o sucesso parcial é parte do contrato. Linhas inválidas não podem impedir o processamento das demais, mas falhas fatais do ambiente não podem ser escondidas.","objectives":["Importar CSV com sucesso parcial explícito","Validar dados antes de construir domínio","Gerar relatório JSON de aceitos e rejeitados","Distinguir erro de registro e falha fatal de ambiente"],"whyItExists":"Este projeto une I/O, CSV, JSON, exceções, coleções e Java moderno numa ferramenta profissional pequena: processar dados ruins sem corromper o resultado nem esconder evidência.","prerequisiteChapterIds":["process-api-cli","json"],"conceptIds":["contrato-de-entrada","arquitetura-minima","requisitos","formato-do-relatorio-json","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["importacao-tolerante"],"usedConceptIds":["files-nio-atomicidade","json-formato-contrato","serializacao-desserializacao","relatorio-dados-deterministico","java-time-semantica"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"importador-intuition","type":"intuition","authorship":"authored","title":"Nem todo erro tem o mesmo destino","body":"Uma linha com data inválida pode virar rejeição e permitir continuar. Um diretório de saída sem permissão impede entregar o resultado inteiro. O importador precisa separar erro de registro, erro de contrato e falha fatal.","analogyLimit":"Peneira ajuda a pensar em rejeições, mas uma importação real também precisa preservar contexto, ordem, relatório e falhas do ambiente."},{"id":"mini-importador-pedidos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>coleções, arquivos e erros</b></div><div class=\"time-est\">Tempo: <b>8–14 horas</b></div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#java-io\">Java I/O</a>, <a class=\"prereq-tag\" href=\"#json\">JSON</a></div></div>","fidelityText":"Objetivo: coleções, arquivos e errosTempo: 8–14 horasPré-requisitos: Java I/O, JSON"},{"id":"mini-importador-pedidos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Leia pedidos de CSV, valide cada linha, converta para objetos e produza relatório JSON. Este projeto aprofunda o analisador de vendas: agora o sucesso parcial é parte do contrato. Linhas inválidas não podem impedir o processamento das demais, mas falhas fatais do ambiente não podem ser escondidas.</p>","fidelityText":"Leia pedidos de CSV, valide cada linha, converta para objetos e produza relatório JSON. Este projeto aprofunda o analisador de vendas: agora o sucesso parcial é parte do contrato. Linhas inválidas não podem impedir o processamento das demais, mas falhas fatais do ambiente não podem ser escondidas."},{"id":"mini-importador-pedidos-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Contrato de entrada</h2>","fidelityText":"Contrato de entrada"},{"id":"mini-importador-pedidos-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Defina o arquivo antes de codar. Um contrato mínimo aceitável para esta fase:</p>","fidelityText":"Defina o arquivo antes de codar. Um contrato mínimo aceitável para esta fase:"},{"id":"mini-importador-pedidos-content-5","type":"html","authorship":"legacy-preserved","html":"<ul>\n        <li>UTF-8, cabeçalho obrigatório e uma linha por pedido.</li>\n        <li>Colunas: <code>id</code>, <code>data</code>, <code>cliente</code>, <code>sku</code>, <code>quantidade</code>, <code>valorUnitario</code>.</li>\n        <li><code>data</code> em ISO-8601, quantidade positiva e dinheiro como <code>BigDecimal</code>.</li>\n        <li>ID duplicado é rejeição de registro, não crash da aplicação.</li>\n      </ul>","fidelityText":"UTF-8, cabeçalho obrigatório e uma linha por pedido. Colunas: id, data, cliente, sku, quantidade, valorUnitario. data em ISO-8601, quantidade positiva e dinheiro como BigDecimal. ID duplicado é rejeição de registro, não crash da aplicação."},{"id":"mini-importador-pedidos-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Arquitetura mínima</h2>","fidelityText":"Arquitetura mínima"},{"id":"mini-importador-pedidos-content-7","type":"html","authorship":"legacy-preserved","html":"<ol>\n        <li><strong>Reader:</strong> abre arquivo, declara charset e fornece linhas numeradas.</li>\n        <li><strong>Parser:</strong> transforma texto em DTO bruto ou rejeição de formato.</li>\n        <li><strong>Validador:</strong> aplica regras de domínio e detecta duplicidade.</li>\n        <li><strong>Agregador:</strong> calcula aceitos, rejeitados, total e estatísticas.</li>\n        <li><strong>Writer:</strong> grava JSON em arquivo de saída com falha visível.</li>\n      </ol>","fidelityText":"Reader: abre arquivo, declara charset e fornece linhas numeradas. Parser: transforma texto em DTO bruto ou rejeição de formato. Validador: aplica regras de domínio e detecta duplicidade. Agregador: calcula aceitos, rejeitados, total e estatísticas. Writer: grava JSON em arquivo de saída com falha visível."},{"id":"mini-importador-pedidos-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Requisitos</h2>","fidelityText":"Requisitos"},{"id":"mini-importador-pedidos-checklist-9","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-importador-pedidos-checklist-0","label":"Separar leitura, parsing, validação, agregação e escrita."},{"id":"mini-importador-pedidos-checklist-1","label":"Identificar número da linha, campo e motivo de cada rejeição."},{"id":"mini-importador-pedidos-checklist-2","label":"Detectar IDs duplicados, datas inválidas, quantidade inválida e dinheiro malformado."},{"id":"mini-importador-pedidos-checklist-3","label":"Usar BigDecimal para dinheiro e java.time para datas."},{"id":"mini-importador-pedidos-checklist-4","label":"Gerar JSON com aceitos, rejeitados, valor total e metadados da importação."},{"id":"mini-importador-pedidos-checklist-5","label":"Abortar com causa preservada quando entrada não existe ou saída não pode ser gravada."}]},{"id":"mini-importador-pedidos-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Formato do relatório JSON</h2>","fidelityText":"Formato do relatório JSON"},{"id":"mini-importador-pedidos-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"{\n  \"file\": \"orders.csv\",\n  \"aceitos\": 42,\n  \"rejeitados\": 3,\n  \"valueTotal\": \"10591.80\",\n  \"errors\": [\n    {\"line\": 7, \"field\": \"date\", \"reason\": \"date invalid\"}\n  ]\n}","fidelityText":"{ \"arquivo\": \"pedidos.csv\", \"aceitos\": 42, \"rejeitados\": 3, \"valorTotal\": \"10591.80\", \"erros\": [ {\"linha\": 7, \"campo\": \"data\", \"motivo\": \"data inválida\"} ] }","highlightedHtml":"{\n  \"file\": \"orders.csv\",\n  \"aceitos\": 42,\n  \"rejeitados\": 3,\n  \"valueTotal\": \"10591.80\",\n  \"errors\": [\n    {\"line\": 7, \"field\": \"date\", \"reason\": \"date invalid\"}\n  ]\n}","caption":"Exemplo executável de mini-importador-pedidos.","explanation":["O relatório separa metadados, contagens, valor total e rejeições.","Valor monetário sai como texto para preservar escala e evitar ambiguidade de ponto flutuante."],"commonMistakes":["Misturar rejeições com stack trace","Emitir valor monetário como double sem contrato"]},{"id":"mini-importador-pedidos-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não engula exceções.</b> Transforme falhas esperadas em resultados de validação; deixe falhas inesperadas visíveis com contexto. Rejeição de linha e falha fatal são coisas diferentes.</div>","fidelityText":"Não engula exceções. Transforme falhas esperadas em resultados de validação; deixe falhas inesperadas visíveis com contexto. Rejeição de linha e falha fatal são coisas diferentes."},{"id":"mini-importador-pedidos-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-importador-pedidos:0","type":"quiz","authorship":"legacy-preserved","conceptId":"como-tratar-uma-linha-invalida-do-arquivo","prompt":"Como tratar uma linha inválida do arquivo?","options":[{"id":"mini-importador-pedidos:0:option:0","label":"Registrar contexto seguro, separar o erro e continuar ou abortar conforme contrato explícito.","correct":true,"explanation":"A política preserva contexto e permite sucesso parcial quando o contrato autoriza."},{"id":"mini-importador-pedidos:0:option:1","label":"Ignorar silenciosamente toda exceção.","correct":false,"explanation":"Ignorar exceção remove evidência e pode gerar relatório falso."},{"id":"mini-importador-pedidos:0:option:2","label":"Importar metade da linha para não perder dados.","correct":false,"explanation":"Importar parte de uma linha cria estado ambíguo e viola o contrato do registro."}],"sourceIndex":14},{"id":"mini-importador-pedidos:1","type":"quiz","authorship":"legacy-preserved","conceptId":"um-pedido-com-o-mesmo-id-de-um-pedido-ja-aceito-aparece-novamente-no-arq","prompt":"Um pedido com o mesmo id de um pedido já aceito aparece novamente no arquivo. O que a implementação correta faz?","options":[{"id":"mini-importador-pedidos:1:option:0","label":"Rejeita o registro duplicado, registrando linha e motivo, sem interromper o processamento do restante do arquivo.","correct":true,"explanation":"Duplicidade é um problema do registro específico, não do arquivo inteiro -- o contrato prevê rejeitá-lo e seguir."},{"id":"mini-importador-pedidos:1:option:1","label":"Substitui o pedido já aceito pelo novo, silenciosamente.","correct":false,"explanation":"Substituir silenciosamente esconde que havia dois registros conflitantes, perdendo evidência da duplicidade."},{"id":"mini-importador-pedidos:1:option:2","label":"Aborta o importador inteiro, já que duplicidade é sempre uma falha fatal.","correct":false,"explanation":"Abortar tudo por causa de uma linha duplicada penaliza pedidos válidos que nada têm a ver com o problema."}],"sourceIndex":15},{"id":"mini-importador-pedidos:2","type":"quiz","authorship":"legacy-preserved","conceptId":"o-diretorio-de-saida-nao-existe-e-a-escrita-do-json-final-falha-o-que-a-","prompt":"O diretório de saída não existe e a escrita do JSON final falha. O que a implementação correta faz?","options":[{"id":"mini-importador-pedidos:2:option:0","label":"Trata como falha fatal: aborta a importação, preserva a causa original e não finge que produziu um relatório.","correct":true,"explanation":"Sem conseguir gravar a saída, o contrato de entrega não pode ser cumprido -- esconder isso finge um resultado que não existe."},{"id":"mini-importador-pedidos:2:option:1","label":"Ignora a falha de escrita e imprime o resultado apenas no console.","correct":false,"explanation":"Imprimir só no console descarta o artefato que o contrato promete entregar, sem avisar que a promessa foi quebrada."},{"id":"mini-importador-pedidos:2:option:2","label":"Trata como uma rejeição comum, incrementando o contador de linhas rejeitadas.","correct":false,"explanation":"Tratar como rejeição de linha mistura um problema de ambiente (permissão de escrita) com um problema de dado de entrada."}],"sourceIndex":16},{"id":"mini-importador-pedidos:3","type":"quiz","authorship":"legacy-preserved","conceptId":"por-que-o-campo-valortotal-do-relatorio-json-e-emitido-como-texto-em-vez","prompt":"Por que o campo valorTotal do relatório JSON é emitido como texto em vez de número de ponto flutuante?","options":[{"id":"mini-importador-pedidos:3:option:0","label":"Para preservar a escala decimal exata do BigDecimal e evitar a imprecisão binária de double/float ao representar dinheiro.","correct":true,"explanation":"BigDecimal com escala fixa é exatamente o que o contrato exige para dinheiro; texto preserva essa escala na serialização."},{"id":"mini-importador-pedidos:3:option:1","label":"Porque JSON não suporta números com casas decimais.","correct":false,"explanation":"JSON representa números decimais nativamente -- o motivo de usar texto é preservar precisão, não uma limitação do formato."},{"id":"mini-importador-pedidos:3:option:2","label":"Porque texto é mais rápido de serializar do que número.","correct":false,"explanation":"A escolha é sobre corretude (evitar imprecisão binária), não sobre desempenho de serialização."}],"sourceIndex":17},{"id":"mini-importador-pedidos-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"importador-flow","type":"diagram","authorship":"authored","title":"Pipeline de importação seguro","description":"Cada etapa recebe uma entrada simples e devolve sucesso, rejeição ou falha com contexto.","steps":["abrir arquivo com charset declarado","ler linha com número","parsear campos conforme contrato","validar regras de domínio","agregar aceitos e rejeições","ordenar relatório","gravar JSON em saída temporária e mover quando aplicável"]},{"id":"importador-quiz","type":"quiz","authorship":"authored","conceptId":"importacao-tolerante","prompt":"Qual situação deve abortar o importador inteiro, em vez de apenas rejeitar uma linha?","options":[{"id":"importador-q-a","label":"Não conseguir gravar o relatório final no diretório de saída exigido.","correct":true,"explanation":"Sem saída confiável, o contrato de entrega não pode ser cumprido."},{"id":"importador-q-b","label":"Uma linha contém data em formato inválido previsto no contrato.","correct":false,"explanation":"Se o contrato prevê sucesso parcial, essa linha vira rejeição com contexto."},{"id":"importador-q-c","label":"Um pedido repetido aparece no arquivo.","correct":false,"explanation":"Duplicidade pode ser rejeição de registro quando a política foi definida."}]},{"id":"importador-exercise-contrato","type":"exercise","authorship":"authored","title":"Antes de codificar: contrato de falhas do importador","prompt":"Antes de implementar, classifique por escrito cada um destes casos como 'rejeição de linha' (sucesso parcial continua) ou 'falha fatal' (aborta a importação): (1) uma linha com data em formato inválido; (2) um id de pedido repetido; (3) o arquivo de entrada não existe; (4) o diretório de saída não tem permissão de escrita. Justifique cada classificação e diga o que o usuário do importador vê em cada caso.","difficulty":"intermediate","criteria":["Os casos 1 e 2 são classificados como rejeição de linha, com número, campo e motivo preservados no relatório.","Os casos 3 e 4 são classificados como falha fatal, que aborta o importador com a causa original preservada, sem fingir sucesso parcial.","Cada justificativa explica por que o caso afeta (ou não) a capacidade de entregar um resultado confiável.","A resposta descreve o que aparece para quem executa o importador em cada um dos quatro casos."]},{"id":"importador-project","type":"project","authorship":"authored","title":"Importador de pedidos com sucesso parcial","brief":"Leia pedidos de CSV, separe rejeição de registro de falha fatal de ambiente, e produza um relatório JSON reproduzível com aceitos, rejeitados e valor total.","requirements":["Reader declara charset e numera cada linha lida","Parser separa DTO bruto de rejeição de formato, sem misturar as duas responsabilidades","Validador detecta ID duplicado, data inválida, quantidade inválida e dinheiro malformado","Agregador soma aceitos, rejeitados e valor total usando BigDecimal","Writer grava JSON com metadados, aceitos, rejeitados e erros, abortando com causa preservada quando a saída falha"],"guidance":"supported","acceptanceCriteria":["Nenhuma linha rejeitada interrompe o processamento das demais","Falha fatal de ambiente (entrada ausente, saída sem permissão) aborta com causa visível, sem relatório parcial disfarçado de sucesso","valorTotal no JSON preserva a escala decimal exata do BigDecimal","Cada rejeição no relatório JSON registra linha, campo e motivo","README documenta o contrato de CSV, a distinção rejeição/falha fatal e comandos reproduzíveis"],"knowledgeMatrix":[{"requirement":"Leitura e parsing por linha","conceptIds":["files-nio-atomicidade"],"chapterIds":["java-io"],"expectedEvidence":"Reader declara charset e numera cada linha antes de o parser interpretar os campos."},{"requirement":"Rejeição de registro vs falha fatal","conceptIds":["importacao-tolerante"],"chapterIds":["mini-importador-pedidos"],"expectedEvidence":"Data inválida e ID duplicado viram rejeição registrada; entrada ausente ou saída sem permissão abortam com causa preservada."},{"requirement":"Relatório JSON reproduzível","conceptIds":["json-formato-contrato","serializacao-desserializacao"],"chapterIds":["json"],"expectedEvidence":"JSON de saída inclui metadados, contagens, valor total e lista de erros com linha, campo e motivo."},{"requirement":"Ordem determinística do relatório","conceptIds":["relatorio-dados-deterministico"],"chapterIds":["mini-analisador-vendas"],"expectedEvidence":"Duas execuções com o mesmo arquivo produzem o mesmo relatório, incluindo a ordem dos erros."},{"requirement":"Datas e valores monetários","conceptIds":["java-time-semantica"],"chapterIds":["javamoderno"],"expectedEvidence":"Datas usam java.time e valores usam BigDecimal emitido como texto no JSON, sem ponto flutuante."}]}],"resources":[{"id":"importador-files-api","type":"reference","title":"Files API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html","reinforces":"Base para leitura, escrita e movimentação segura de arquivos do importador.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"importador-jackson","type":"reference","title":"Jackson Databind README","url":"https://github.com/FasterXML/jackson-databind","reinforces":"ObjectMapper para escrever relatório JSON a partir de DTOs.","language":"en","publisher":"FasterXML","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A order importer operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a order importer operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this order importer chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"\"file\": \"orders.csv\",","instruction":"A order importer operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this order importer chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["distincao-rejeicao-vs-falha-fatal","deteccao-de-duplicidade-e-formato-invalido","pipeline-separado-em-etapas","relatorio-json-com-valor-monetario-preciso","evidencia-reproduzivel-do-projeto"],"evidenceBlocks":{"distincao-rejeicao-vs-falha-fatal":["mini-importador-pedidos-content-12","mini-importador-pedidos:2","importador-exercise-contrato"],"deteccao-de-duplicidade-e-formato-invalido":["mini-importador-pedidos-content-5","mini-importador-pedidos:1","mini-importador-pedidos-checklist-9"],"pipeline-separado-em-etapas":["importador-flow","importador-project"],"relatorio-json-com-valor-monetario-preciso":["mini-importador-pedidos:3","mini-importador-pedidos-checklist-9","importador-project"],"evidencia-reproduzivel-do-projeto":["mini-importador-pedidos-content-18","importador-project"]},"primarySources":["Files API -- Java 21 -- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html","Jackson Databind README -- https://github.com/FasterXML/jackson-databind"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"solid","moduleId":"application-design","order":1,"title":"SOLID & boas práticas","summary":"SOLID é um acrônimo de cinco princípios de design orientado a objetos, formalizados por Robert C. Martin, que resumem boa parte da sabedoria acumulada dos capítulos anteriores.","objectives":["Usar SOLID como diagnóstico de mudança, não como decoração","Preferir composição quando herança não preserva contrato","Aplicar SRP, OCP, LSP, ISP e DIP com exemplos pequenos","Evitar abstrações prematuras que não reduzem acoplamento real"],"whyItExists":"Depois de POO, testes e interfaces, o aluno já consegue discutir design sem superstição. SOLID entra como vocabulário para reduzir custo de mudança e preservar contratos, não como checklist para criar arquivos.","prerequisiteChapterIds":["interfaces","abstracao"],"conceptIds":["quando-nao-criar-uma-interface-abstracao-prematura","composicao-sobre-heranca"],"introducedConceptIds":["srp-coesao-motivo-mudanca","ocp-polimorfismo-extensao","lsp-contrato-substituicao","isp-interface-pequena","dip-dependencia-abstracao"],"usedConceptIds":["interface-contrato","polimorfismo-substituicao","encapsulamento-invariante"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"solid-intuition","type":"intuition","authorship":"authored","title":"Design bom reduz surpresa na próxima mudança","body":"SOLID não existe para deixar o código bonito em repouso. Ele ajuda quando uma regra muda, uma implementação troca, um teste isola comportamento ou uma dependência externa vira problema.","analogyLimit":"Organização de armário ajuda na ideia de separar coisas, mas software tem comportamento, contratos e mudanças futuras incertas."},{"id":"solid-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#interfaces\">08 · Interfaces</a>, <a class=\"prereq-tag\" href=\"#abstracao\">07 · Classes abstratas</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 08 · Interfaces, 07 · Classes abstratas"},{"id":"solid-content-2","type":"html","authorship":"legacy-preserved","html":"<p>SOLID é um acrônimo de cinco princípios de design orientado a objetos, formalizados por Robert C. Martin, que resumem boa parte da sabedoria acumulada dos capítulos anteriores.</p>","fidelityText":"SOLID é um acrônimo de cinco princípios de design orientado a objetos, formalizados por Robert C. Martin, que resumem boa parte da sabedoria acumulada dos capítulos anteriores."},{"id":"solid-content-3","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">S — Single Responsibility Principle</h2>","fidelityText":"S — Single Responsibility Principle"},{"id":"solid-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Uma classe deve ter <strong>um único motivo para mudar</strong>. Se <code>Funcionario</code> calcula salário <em>e</em> salva no banco <em>e</em> gera um PDF de holerite, mudanças em qualquer uma dessas três responsabilidades forçam mexer na mesma classe.</p>","fidelityText":"Uma classe deve ter um único motivo para mudar. Se Funcionario calcula salário e salva no banco e gera um PDF de holerite, mudanças em qualquer uma dessas três responsabilidades forçam mexer na mesma classe."},{"id":"solid-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ uma classe fazendo três coisas:\nclass Employee {\n    double calculateSalary() { ... }\n    void saveInBank() { ... }\n    void generatePayslipPdf() { ... }\n}\n\n// ✅ responsabilidades separadas:\nclass Employee { double calculateSalary() { ... } }\nclass EmployeeRepository { void save(Employee f) { ... } }\nclass PayslipGenerator { void generatePdf(Employee f) { ... } }","fidelityText":"// ❌ uma classe fazendo três coisas: class Funcionario { double calcularSalario() { ... } void salvarNoBanco() { ... } void gerarHoleritePdf() { ... } } // ✅ responsabilidades separadas: class Funcionario { double calcularSalario() { ... } } class FuncionarioRepositorio { void salvar(Funcionario f) { ... } } class HoleriteGenerator { void gerarPdf(Funcionario f) { ... } }","highlightedHtml":"<span class=\"com\">// ❌ uma classe fazendo três coisas:</span>\n<span class=\"kw\">class</span> <span class=\"cls\">Employee</span> {\n    <span class=\"kw\">double</span> <span class=\"fn\">calculateSalary</span>() { ... }\n    <span class=\"kw\">void</span> <span class=\"fn\">saveInBank</span>() { ... }\n    <span class=\"kw\">void</span> <span class=\"fn\">generatePayslipPdf</span>() { ... }\n}\n\n<span class=\"com\">// ✅ responsabilidades separadas:</span>\n<span class=\"kw\">class</span> <span class=\"cls\">Employee</span> { <span class=\"kw\">double</span> <span class=\"fn\">calculateSalary</span>() { ... } }\n<span class=\"kw\">class</span> <span class=\"cls\">EmployeeRepository</span> { <span class=\"kw\">void</span> <span class=\"fn\">save</span>(<span class=\"cls\">Employee</span> f) { ... } }\n<span class=\"kw\">class</span> <span class=\"cls\">PayslipGenerator</span> { <span class=\"kw\">void</span> <span class=\"fn\">generatePdf</span>(<span class=\"cls\">Employee</span> f) { ... } }","caption":"Exemplo executável de solid.","explanation":["O exemplo concentra múltiplos motivos de mudança na mesma classe.","SRP pergunta por que o código muda, não quantos métodos ele possui."],"commonMistakes":["Dividir mecanicamente por método","Criar classes anêmicas sem comportamento"]},{"id":"solid-content-6","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">O — Open/Closed Principle</h2>","fidelityText":"O — Open/Closed Principle"},{"id":"solid-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Classes devem estar <strong>abertas para extensão, fechadas para modificação</strong>. Em vez de um <code>switch</code> gigante que cresce a cada novo caso, use polimorfismo (é literalmente o que fizemos no exercício 6.1 com <code>Forma</code>).</p>","fidelityText":"Classes devem estar abertas para extensão, fechadas para modificação. Em vez de um switch gigante que cresce a cada novo caso, use polimorfismo (é literalmente o que fizemos no exercício 6.1 com Forma)."},{"id":"solid-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ toda vez que aparece uma forma nova, esse método precisa mudar:\ndouble calculateArea(Object shape) {\n    if (shape instanceof Circle) { ... }\n    else if (shape instanceof Square) { ... }\n    // ... um \"else if\" novo para cada forma futura\n}\n\n// ✅ Forma.area() já resolve isso -- adicionar uma forma nova não toca em código existente","fidelityText":"// ❌ toda vez que aparece uma forma nova, esse método precisa mudar: double calcularArea(Object forma) { if (forma instanceof Circulo) { ... } else if (forma instanceof Quadrado) { ... } // ... um \"else if\" novo para cada forma futura } // ✅ Forma.area() já resolve isso -- adicionar uma forma nova não toca em código existente","highlightedHtml":"<span class=\"com\">// ❌ toda vez que aparece uma forma nova, esse método precisa mudar:</span>\n<span class=\"kw\">double</span> <span class=\"fn\">calculateArea</span>(<span class=\"kw\">Object</span> shape) {\n    <span class=\"kw\">if</span> (shape <span class=\"kw\">instanceof</span> <span class=\"cls\">Circle</span>) { ... }\n    <span class=\"kw\">else if</span> (shape <span class=\"kw\">instanceof</span> <span class=\"cls\">Square</span>) { ... }\n    <span class=\"com\">// ... um \"else if\" novo para cada forma futura</span>\n}\n\n<span class=\"com\">// ✅ Forma.area() já resolve isso -- adicionar uma forma nova não toca em código existente</span>","caption":"Exemplo executável de solid.","explanation":["A abstração permite adicionar política sem reabrir o consumidor.","OCP aparece quando a regra está estável e a variação é prevista."],"commonMistakes":["Abstrair antes de existir variação","Manter switch central crescendo"]},{"id":"solid-content-9","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">L — Liskov Substitution Principle</h2>","fidelityText":"L — Liskov Substitution Principle"},{"id":"solid-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Uma subclasse deve poder <strong>substituir</strong> sua superclasse sem quebrar o comportamento esperado por quem usa o código. O exemplo clássico do que <em>viola</em> esse princípio:</p>","fidelityText":"Uma subclasse deve poder substituir sua superclasse sem quebrar o comportamento esperado por quem usa o código. O exemplo clássico do que viola esse princípio:"},{"id":"solid-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"class Rectangle {\n    protected double width, height;\n    void setWidth(double l) { width = l; }\n    void setHeight(double a) { height = a; }\n    double area() { return width * height; }\n}\n\n// ❌ Quadrado \"é um\" retângulo geometricamente, mas quebra o CONTRATO:\nclass Square extends Rectangle {\n    @Override void setWidth(double l) { width = height = l; } // efeito colateral inesperado!\n    @Override void setHeight(double a) { width = height = a; }\n}\n// código que confia que setLargura só muda a largura QUEBRA silenciosamente com Quadrado","fidelityText":"class Retangulo { protected double largura, altura; void setLargura(double l) { largura = l; } void setAltura(double a) { altura = a; } double area() { return largura * altura; } } // ❌ Quadrado \"é um\" retângulo geometricamente, mas quebra o CONTRATO: class Quadrado extends Retangulo { @Override void setLargura(double l) { largura = altura = l; } // efeito colateral inesperado! @Override void setAltura(double a) { largura = altura = a; } } // código que confia que setLargura só muda a largura QUEBRA silenciosamente com Quadrado","highlightedHtml":"<span class=\"kw\">class</span> <span class=\"cls\">Rectangle</span> {\n    <span class=\"kw\">protected double</span> width, height;\n    <span class=\"kw\">void</span> <span class=\"fn\">setWidth</span>(<span class=\"kw\">double</span> l) { width = l; }\n    <span class=\"kw\">void</span> <span class=\"fn\">setHeight</span>(<span class=\"kw\">double</span> a) { height = a; }\n    <span class=\"kw\">double</span> <span class=\"fn\">area</span>() { <span class=\"kw\">return</span> width * height; }\n}\n\n<span class=\"com\">// ❌ Quadrado \"é um\" retângulo geometricamente, mas quebra o CONTRATO:</span>\n<span class=\"kw\">class</span> <span class=\"cls\">Square</span> <span class=\"kw\">extends</span> <span class=\"cls\">Rectangle</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">void</span> <span class=\"fn\">setWidth</span>(<span class=\"kw\">double</span> l) { width = height = l; } <span class=\"com\">// efeito colateral inesperado!</span>\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">void</span> <span class=\"fn\">setHeight</span>(<span class=\"kw\">double</span> a) { width = height = a; }\n}\n<span class=\"com\">// código que confia que setLargura só muda a largura QUEBRA silenciosamente com Quadrado</span>","caption":"Exemplo executável de solid.","explanation":["Subtipo precisa cumprir o contrato esperado pelo consumidor.","Lançar UnsupportedOperationException onde o contrato prometia operação quebra substituição."],"commonMistakes":["Herdar só para reaproveitar código","Enfraquecer invariantes da classe base"]},{"id":"solid-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Lição:</b> \"é um\" na linguagem natural nem sempre é \"é um\" no sentido de contrato de comportamento. Se a subclasse precisa restringir ou alterar o comportamento esperado da superclasse, herança pode ser a ferramenta errada — considere composição.</div>","fidelityText":"Lição: \"é um\" na linguagem natural nem sempre é \"é um\" no sentido de contrato de comportamento. Se a subclasse precisa restringir ou alterar o comportamento esperado da superclasse, herança pode ser a ferramenta errada — considere composição."},{"id":"solid-content-13","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">I — Interface Segregation Principle</h2>","fidelityText":"I — Interface Segregation Principle"},{"id":"solid-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Prefira <strong>várias interfaces específicas</strong> a uma única interface \"gigante\" que força classes a implementar métodos que não fazem sentido para elas.</p>","fidelityText":"Prefira várias interfaces específicas a uma única interface \"gigante\" que força classes a implementar métodos que não fazem sentido para elas."},{"id":"solid-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ interface inchada:\ninterface Worker { void work(); void eat(); }\nclass Robot implements Worker {\n    public void work() { ... }\n    public void eat() { /* robô não come! método forçado e sem sentido */ }\n}\n\n// ✅ interfaces segregadas:\ninterface Worker { void work(); }\ninterface BeAlive { void eat(); }\nclass Robot implements Worker { public void work() { ... } }","fidelityText":"// ❌ interface inchada: interface Trabalhador { void trabalhar(); void comer(); } class Robo implements Trabalhador { public void trabalhar() { ... } public void comer() { /* robô não come! método forçado e sem sentido */ } } // ✅ interfaces segregadas: interface Trabalhador { void trabalhar(); } interface SerVivo { void comer(); } class Robo implements Trabalhador { public void trabalhar() { ... } }","highlightedHtml":"<span class=\"com\">// ❌ interface inchada:</span>\n<span class=\"kw\">interface</span> <span class=\"cls\">Worker</span> { <span class=\"kw\">void</span> <span class=\"fn\">work</span>(); <span class=\"kw\">void</span> <span class=\"fn\">eat</span>(); }\n<span class=\"kw\">class</span> <span class=\"cls\">Robot</span> <span class=\"kw\">implements</span> <span class=\"cls\">Worker</span> {\n    <span class=\"kw\">public void</span> <span class=\"fn\">work</span>() { ... }\n    <span class=\"kw\">public void</span> <span class=\"fn\">eat</span>() { <span class=\"com\">/* robô não come! método forçado e sem sentido */</span> }\n}\n\n<span class=\"com\">// ✅ interfaces segregadas:</span>\n<span class=\"kw\">interface</span> <span class=\"cls\">Worker</span> { <span class=\"kw\">void</span> <span class=\"fn\">work</span>(); }\n<span class=\"kw\">interface</span> <span class=\"cls\">BeAlive</span> { <span class=\"kw\">void</span> <span class=\"fn\">eat</span>(); }\n<span class=\"kw\">class</span> <span class=\"cls\">Robot</span> <span class=\"kw\">implements</span> <span class=\"cls\">Worker</span> { <span class=\"kw\">public void</span> <span class=\"fn\">work</span>() { ... } }","caption":"Exemplo executável de solid.","explanation":["Interface pequena evita obrigar cliente a depender de métodos que não usa.","Cada porta deve nascer do caso de uso consumidor."],"commonMistakes":["Interface Deus por camada","Métodos vazios para cumprir contrato ruim"]},{"id":"solid-content-16","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">D — Dependency Inversion Principle</h2>","fidelityText":"D — Dependency Inversion Principle"},{"id":"solid-content-17","type":"html","authorship":"legacy-preserved","html":"<p>Dependa de <strong>abstrações</strong> (interfaces), não de implementações concretas. Isso é o que permite trocar um <code>MySqlRepositorio</code> por um <code>MongoRepositorio</code> sem tocar na lógica de negócio.</p>","fidelityText":"Dependa de abstrações (interfaces), não de implementações concretas. Isso é o que permite trocar um MySqlRepositorio por um MongoRepositorio sem tocar na lógica de negócio."},{"id":"solid-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ acoplado à implementação concreta:\nclass OrderService {\n    private MySqlRepository repo = new MySqlRepository(); // travado nessa implementação\n}\n\n// ✅ depende da abstração, implementação é injetada de fora:\ninterface OrderRepository { void save(Order p); }\n\nclass OrderService {\n    private final OrderRepository repo;\n    OrderService(OrderRepository repo) { this.repo = repo; } // injeção de dependência\n}","fidelityText":"// ❌ acoplado à implementação concreta: class PedidoService { private MySqlRepositorio repo = new MySqlRepositorio(); // travado nessa implementação } // ✅ depende da abstração, implementação é injetada de fora: interface PedidoRepositorio { void salvar(Pedido p); } class PedidoService { private final PedidoRepositorio repo; PedidoService(PedidoRepositorio repo) { this.repo = repo; } // injeção de dependência }","highlightedHtml":"<span class=\"com\">// ❌ acoplado à implementação concreta:</span>\n<span class=\"kw\">class</span> <span class=\"cls\">OrderService</span> {\n    <span class=\"kw\">private</span> <span class=\"cls\">MySqlRepository</span> repo = <span class=\"kw\">new</span> <span class=\"cls\">MySqlRepository</span>(); <span class=\"com\">// travado nessa implementação</span>\n}\n\n<span class=\"com\">// ✅ depende da abstração, implementação é injetada de fora:</span>\n<span class=\"kw\">interface</span> <span class=\"cls\">OrderRepository</span> { <span class=\"kw\">void</span> <span class=\"fn\">save</span>(<span class=\"cls\">Order</span> p); }\n\n<span class=\"kw\">class</span> <span class=\"cls\">OrderService</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">OrderRepository</span> repo;\n    <span class=\"fn\">OrderService</span>(<span class=\"cls\">OrderRepository</span> repo) { <span class=\"kw\">this</span>.repo = repo; } <span class=\"com\">// injeção de dependência</span>\n}","caption":"Exemplo executável de solid.","explanation":["A política depende de abstração estável.","O detalhe concreto é injetado de fora, perto da composition root."],"commonMistakes":["Criar abstração com nome da implementação","Instanciar detalhe dentro da regra"]},{"id":"solid-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Note que <strong>D</strong> é exatamente o que torna testes com <strong>mocks</strong> (capítulo 15) possíveis: se <code>PedidoService</code> dependesse de <code>MySqlRepositorio</code> diretamente, você não conseguiria substituí-lo por um mock em teste. SOLID e testabilidade caminham juntos — código difícil de testar quase sempre é sintoma de algum princípio SOLID sendo violado.</div>","fidelityText":"Note que D é exatamente o que torna testes com mocks (capítulo 15) possíveis: se PedidoService dependesse de MySqlRepositorio diretamente, você não conseguiria substituí-lo por um mock em teste. SOLID e testabilidade caminham juntos — código difícil de testar quase sempre é sintoma de algum princípio SOLID sendo violado."},{"id":"solid-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Quando NÃO criar uma interface — abstração prematura</h2>","fidelityText":"Quando NÃO criar uma interface — abstração prematura"},{"id":"solid-content-21","type":"html","authorship":"legacy-preserved","html":"<p>SOLID não é uma obrigação de criar interface para toda classe. Uma abstração só paga o próprio custo quando existe <strong>pressão real de mudança</strong>: uma segunda implementação de verdade, um dublê de teste substituindo a dependência, ou uma variação já conhecida no roadmap. Sem nenhuma dessas três coisas, a interface só adiciona indireção.</p>","fidelityText":"SOLID não é uma obrigação de criar interface para toda classe. Uma abstração só paga o próprio custo quando existe pressão real de mudança: uma segunda implementação de verdade, um dublê de teste substituindo a dependência, ou uma variação já conhecida no roadmap. Sem nenhuma dessas três coisas, a interface só adiciona indireção."},{"id":"solid-code-22","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ interface created \"as a precaution\", with no real pressure to change:\ninterface CalculatorDiscount { double calculate(double value); }\n\nclass CalculatorDiscountDefault implements CalculatorDiscount {\n    @Override public double calculate(double value) { return value * 0.9; }\n}\n// CalculatorDiscountDefault is the only implementation today, tomorrow, and in every test --\n// the interface doesn't protect against any swap that a direct call wouldn't already solve.\n\n// ✅ the same rule, without premature abstraction -- still simple, still testable:\nclass CalculatorDiscount {\n    double calculate(double value) { return value * 0.9; }\n}\n// if a second real rule shows up (campaign, VIP customer), THAT is the pressure\n// of change that justifies extracting the interface -- not before it exists.","fidelityText":"// ❌ interface criada \"por precaução\", sem nenhuma pressão real de mudança: interface CalculadoraDesconto { double calcular(double valor); } class CalculadoraDescontoPadrao implements CalculadoraDesconto { @Override public double calcular(double valor) { return valor * 0.9; } } // CalculadoraDescontoPadrao é a única implementação hoje, amanhã e em todo teste -- // a interface não protege nenhuma troca que uma chamada direta já não resolveria. // ✅ a mesma regra, sem abstração prematura -- ainda simples, ainda testável: class CalculadoraDesconto { double calcular(double valor) { return valor * 0.9; } } // se surgir uma segunda regra real (campanha, cliente VIP), ESSA é a pressão // de mudança que justifica extrair a interface -- não antes dela existir.","highlightedHtml":"<span class=\"com\">// ❌ interface created \"as a precaution\", with no real pressure to change:</span>\n<span class=\"kw\">interface</span> <span class=\"cls\">CalculatorDiscount</span> { <span class=\"kw\">double</span> <span class=\"fn\">calculate</span>(<span class=\"kw\">double</span> value); }\n\n<span class=\"kw\">class</span> <span class=\"cls\">CalculatorDiscountDefault</span> <span class=\"kw\">implements</span> <span class=\"cls\">CalculatorDiscount</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public double</span> <span class=\"fn\">calculate</span>(<span class=\"kw\">double</span> value) { <span class=\"kw\">return</span> value * 0.9; }\n}\n<span class=\"com\">// CalculatorDiscountDefault is the only implementation today, tomorrow, and in every test --\n// the interface doesn't protect against any swap that a direct call wouldn't already solve.</span>\n\n<span class=\"com\">// ✅ the same rule, without premature abstraction -- still simple, still testable:</span>\n<span class=\"kw\">class</span> <span class=\"cls\">CalculatorDiscount</span> {\n    <span class=\"kw\">double</span> <span class=\"fn\">calculate</span>(<span class=\"kw\">double</span> value) { <span class=\"kw\">return</span> value * 0.9; }\n}\n<span class=\"com\">// if a second real rule shows up (campaign, VIP customer), THAT is the pressure\n// of change that justifies extracting the interface -- not before it exists.</span>","caption":"Exemplo executável de solid.","explanation":["A interface não tem segunda implementação, dublê de teste ou variação conhecida no roadmap.","Sem essa pressão real de mudança, a abstração só adiciona indireção sem reduzir acoplamento nenhum."],"commonMistakes":["Criar interface para toda classe por precaução","Confundir 'pode mudar um dia' com pressão de mudança real"]},{"id":"solid-content-23","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Abstração não é grátis:</b> cada interface extra é mais um arquivo, mais um nível de indireção para quem lê o código pela primeira vez seguir. DIP e OCP recomendam depender de abstração <em>quando ela reduz acoplamento real</em> — não recomendam abstrair tudo por segurança.</div>","fidelityText":"Abstração não é grátis: cada interface extra é mais um arquivo, mais um nível de indireção para quem lê o código pela primeira vez seguir. DIP e OCP recomendam depender de abstração quando ela reduz acoplamento real — não recomendam abstrair tudo por segurança."},{"id":"solid-content-24","type":"html","authorship":"legacy-preserved","html":"<h2>Composição sobre herança</h2>","fidelityText":"Composição sobre herança"},{"id":"solid-content-25","type":"html","authorship":"legacy-preserved","html":"<p>Uma máxima que resume boa parte do design orientado a objetos moderno: prefira <strong>\"tem um\"</strong> (composição) a <strong>\"é um\"</strong> (herança) sempre que a relação não for genuinamente uma especialização. Herança acopla fortemente subclasse e superclasse; composição é mais flexível e evita hierarquias frágeis como a de <code>Retangulo</code>/<code>Quadrado</code> vista acima.</p>","fidelityText":"Uma máxima que resume boa parte do design orientado a objetos moderno: prefira \"tem um\" (composição) a \"é um\" (herança) sempre que a relação não for genuinamente uma especialização. Herança acopla fortemente subclasse e superclasse; composição é mais flexível e evita hierarquias frágeis como a de Retangulo/Quadrado vista acima."},{"id":"solid-code-26","type":"code","authorship":"legacy-preserved","language":"java","source":"// em vez de \"Carro extends Motor\" (Carro NÃO é um tipo de Motor):\nclass Car {\n    private final Engine engine; // Carro TEM UM Motor -- composição\n    Car(Engine engine) { this.engine = engine; }\n    void accelerate() { engine.increaseRotacao(); }\n}","fidelityText":"// em vez de \"Carro extends Motor\" (Carro NÃO é um tipo de Motor): class Carro { private final Motor motor; // Carro TEM UM Motor -- composição Carro(Motor motor) { this.motor = motor; } void acelerar() { motor.aumentarRotacao(); } }","highlightedHtml":"<span class=\"com\">// em vez de \"Carro extends Motor\" (Carro NÃO é um tipo de Motor):</span>\n<span class=\"kw\">class</span> <span class=\"cls\">Car</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">Engine</span> engine; <span class=\"com\">// Carro TEM UM Motor -- composição</span>\n    <span class=\"fn\">Car</span>(<span class=\"cls\">Engine</span> engine) { <span class=\"kw\">this</span>.engine = engine; }\n    <span class=\"kw\">void</span> <span class=\"fn\">accelerate</span>() { engine.increaseRotacao(); }\n}","caption":"Exemplo executável de solid.","explanation":["Composição troca comportamento por colaboração explícita.","Ela evita hierarquia artificial quando a variação é ortogonal."],"commonMistakes":["Delegar tudo sem intenção","Confundir composição com mero repasse burocrático"]},{"id":"solid-exercise-27","type":"exercise","authorship":"legacy-preserved","title":"Exercício 16.1 — Identificando violações","prompt":"Leia esta classe e aponte por escrito quais princípios SOLID ela viola e como corrigir:","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 16.1 — Identificando violaçõesmédio Leia esta classe e aponte por escrito quais princípios SOLID ela viola e como corrigir: class RelatorioVendas { void buscarVendasNoBanco() { /* SQL direto aqui */ } void calcularTotal() { ... } void formatarComoPdf() { ... } void enviarPorEmail() { /* SMTP direto aqui */ } } Ver solução SRP violado: a classe busca dados, calcula, formata e envia e-mail — quatro motivos de mudança diferentes (mudar o banco, mudar a regra de cálculo, mudar o formato do relatório, trocar o provedor de e-mail cada um forçaria mexer na mesma classe). DIP violado: \"SQL direto\" e \"SMTP direto\" são acoplamento a implementações concretas, deveriam depender de interfaces como VendaRepositorio e ServicoEmail, injetadas de fora. Correção: separar em VendaRepositorio (busca), CalculadoraVendas (cálculo), RelatorioPdfGenerator (formatação) e ServicoEmail (envio), cada uma com uma única responsabilidade e dependendo de interfaces.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 16.1 — Identificando violações</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Leia esta classe e aponte por escrito quais princípios SOLID ela viola e como corrigir:</p>\n        <pre class=\"code\"><span class=\"kw\">class</span> <span class=\"cls\">ReportSales</span> {\n    <span class=\"kw\">void</span> <span class=\"fn\">findSalesInBank</span>() { <span class=\"com\">/* SQL direto aqui */</span> }\n    <span class=\"kw\">void</span> <span class=\"fn\">calculateTotal</span>() { ... }\n    <span class=\"kw\">void</span> <span class=\"fn\">formatarAsPdf</span>() { ... }\n    <span class=\"kw\">void</span> <span class=\"fn\">sendByEmail</span>() { <span class=\"com\">/* SMTP direto aqui */</span> }\n}</pre>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>SRP violado:</strong> a classe busca dados, calcula, formata e envia e-mail — quatro motivos de mudança diferentes (mudar o banco, mudar a regra de cálculo, mudar o formato do relatório, trocar o provedor de e-mail cada um forçaria mexer na mesma classe). <strong>DIP violado:</strong> \"SQL direto\" e \"SMTP direto\" são acoplamento a implementações concretas, deveriam depender de interfaces como <code>VendaRepositorio</code> e <code>ServicoEmail</code>, injetadas de fora. Correção: separar em <code>VendaRepositorio</code> (busca), <code>CalculadoraVendas</code> (cálculo), <code>RelatorioPdfGenerator</code> (formatação) e <code>ServicoEmail</code> (envio), cada uma com uma única responsabilidade e dependendo de interfaces.</p>\n        </div>\n      </div>"},{"id":"solid-comparison","type":"comparison","authorship":"authored","title":"Herança e composição sob pressão de mudança","criteria":["melhor quando","risco","sinal de alerta"],"alternatives":[{"name":"Herança","values":["subtipo preserva contrato","hierarquia rígida","sobrescrever método para negar comportamento"],"useWhen":"existe relação é-um e LSP é preservado","avoidWhen":"o objetivo é só reaproveitar código"},{"name":"Composição","values":["objeto delega política","mais peças explícitas","abstração sem variação real"],"useWhen":"comportamento precisa variar independentemente","avoidWhen":"a indireção não resolve nenhuma mudança concreta"}]},{"id":"solid-quiz","type":"quiz","authorship":"authored","conceptId":"dip-dependencia-abstracao","prompt":"Qual situação mostra DIP aplicado de forma útil?","options":[{"id":"solid-q-a","label":"Caso de uso depende de uma interface de repositório e o adapter JDBC implementa essa porta.","correct":true,"explanation":"A regra fica protegida do detalhe de persistência e pode ser testada com dublê."},{"id":"solid-q-b","label":"Criar interface idêntica para toda classe, mesmo sem segunda implementação ou teste.","correct":false,"explanation":"Abstração sem pressão real aumenta ruído."},{"id":"solid-q-c","label":"Fazer domínio chamar diretamente DriverManager para salvar dados.","correct":false,"explanation":"Isso prende regra de negócio ao detalhe externo."}]}],"resources":[{"id":"solid-refactoring-guru","type":"guide","title":"Refactoring.Guru: Design Patterns","url":"https://refactoring.guru/design-patterns","reinforces":"Visão prática de princípios de design orientado a objetos, padrões e trade-offs de composição.","language":"en","publisher":"Refactoring.Guru","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"jls-interfaces-solid","type":"reference","title":"JLS 9: Interfaces","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html","reinforces":"Base formal de interfaces usadas em ISP, DIP e polimorfismo.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A solid operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a solid operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this solid chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"// ❌ uma classe fazendo três coisas:","instruction":"A solid operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this solid chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"di","moduleId":"application-design","order":2,"title":"Injeção de dependência (na mão)","summary":"Este é, sem exagero, o capítulo mais importante para entender o Spring. O Spring é, na essência, um container de Inversão de Controle (IoC) que faz injeção de dependência automaticamente. Antes de ver isso \"de graça\" via anotações, vamos construir manualmente para entender o que realmente está acontecendo.","objectives":["Distinguir DI, DIP e IoC sem framework","Montar objetos por constructor injection","Centralizar criação na composition root","Implementar container didático sem transformar isso em service locator"],"whyItExists":"Antes de Spring criar objetos por você, o aluno precisa sentir o problema do acoplamento e resolver manualmente. Assim o container futuro deixa de parecer magia e passa a automatizar uma composição que ele já entende.","prerequisiteChapterIds":["javamoderno","solid","anotacoes"],"conceptIds":["o-problema-acoplamento-a-implementacoes-concretas","a-solucao-inverter-quem-cria-a-dependencia","um-container-de-di-simples-feito-a-mao","os-tres-tipos-de-injecao"],"introducedConceptIds":["di-composicao-raiz","ioc-container-registro-resolucao"],"usedConceptIds":["dip-dependencia-abstracao","annotation-metadata-contract","reflection-runtime-introspection"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"di-intuition","type":"intuition","authorship":"authored","title":"Quem usa não precisa ser quem constrói","body":"Uma classe de regra de negócio pode precisar enviar email, salvar dados ou consultar API. Se ela também escolhe e cria essas dependências concretas, fica difícil testar e trocar detalhes.","analogyLimit":"Montagem de peça ajuda na ideia, mas objeto tem ciclo de vida, estado, invariantes e dependências transitivas."},{"id":"di-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#javamoderno\">21 · Java moderno</a>, <a class=\"prereq-tag\" href=\"#solid\">16 · SOLID</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 21 · Java moderno, 16 · SOLID"},{"id":"di-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Este é, sem exagero, <strong>o capítulo mais importante para entender o Spring</strong>. O Spring é, na essência, um container de <strong>Inversão de Controle (IoC)</strong> que faz injeção de dependência automaticamente. Antes de ver isso \"de graça\" via anotações, vamos construir manualmente para entender o que realmente está acontecendo.</p>","fidelityText":"Este é, sem exagero, o capítulo mais importante para entender o Spring. O Spring é, na essência, um container de Inversão de Controle (IoC) que faz injeção de dependência automaticamente. Antes de ver isso \"de graça\" via anotações, vamos construir manualmente para entender o que realmente está acontecendo."},{"id":"di-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>O problema: acoplamento a implementações concretas</h2>","fidelityText":"O problema: acoplamento a implementações concretas"},{"id":"di-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"public class OrderService {\n    private final EmailService email = new EmailService(); // ❌ PedidoServico CRIA sua própria dependência\n\n    public void complete(Order p) {\n        // ...\n        email.send(p.getEmailCustomer(), \"Order confirmed\");\n    }\n}","fidelityText":"public class PedidoServico { private final EmailServico email = new EmailServico(); // ❌ PedidoServico CRIA sua própria dependência public void finalizar(Pedido p) { // ... email.enviar(p.getEmailCliente(), \"Pedido confirmado\"); } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">OrderService</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">EmailService</span> email = <span class=\"kw\">new</span> <span class=\"cls\">EmailService</span>(); <span class=\"com\">// ❌ PedidoServico CRIA sua própria dependência</span>\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">complete</span>(<span class=\"cls\">Order</span> p) {\n        <span class=\"com\">// ...</span>\n        email.send(p.getEmailCustomer(), <span class=\"str\">\"Order confirmed\"</span>);\n    }\n}","caption":"Exemplo executável de di.","explanation":["A classe concreta criada dentro da regra cria acoplamento rígido.","O problema aparece quando teste, ambiente ou implementação precisam variar."],"commonMistakes":["Chamar todo new de erro","Esconder dependência em singleton global"]},{"id":"di-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Problemas: <code>PedidoServico</code> está travado em <code>EmailServico</code> — impossível trocar por um <code>SmsServico</code> sem editar essa classe, e impossível testar <code>finalizar()</code> sem realmente enviar e-mails (nada de mock, capítulo 15).</p>","fidelityText":"Problemas: PedidoServico está travado em EmailServico — impossível trocar por um SmsServico sem editar essa classe, e impossível testar finalizar() sem realmente enviar e-mails (nada de mock, capítulo 15)."},{"id":"di-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>A solução: inverter quem cria a dependência</h2>","fidelityText":"A solução: inverter quem cria a dependência"},{"id":"di-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface Notifier {\n    void send(String recipient, String message);\n}\n\npublic class EmailService implements Notifier {\n    @Override public void send(String recipient, String message) { /* SMTP */ }\n}\n\npublic class OrderService {\n    private final Notifier notifier; // depende da ABSTRAÇÃO (capítulo 16, o \"D\" de SOLID)\n\n    // injeção via CONSTRUTOR -- a dependência vem de FORA, PedidoServico não cria nada\n    public OrderService(Notifier notifier) {\n        this.notifier = notifier;\n    }\n\n    public void complete(Order p) {\n        notifier.send(p.getEmailCustomer(), \"Order confirmed\");\n    }\n}\n\n// quem \"monta\" a aplicação decide a implementação real:\nNotifier notifier = new EmailService();\nOrderService service = new OrderService(notifier); // injeção manual","fidelityText":"public interface Notificador { void enviar(String destino, String mensagem); } public class EmailServico implements Notificador { @Override public void enviar(String destino, String mensagem) { /* SMTP */ } } public class PedidoServico { private final Notificador notificador; // depende da ABSTRAÇÃO (capítulo 16, o \"D\" de SOLID) // injeção via CONSTRUTOR -- a dependência vem de FORA, PedidoServico não cria nada public PedidoServico(Notificador notificador) { this.notificador = notificador; } public void finalizar(Pedido p) { notificador.enviar(p.getEmailCliente(), \"Pedido confirmado\"); } } // quem \"monta\" a aplicação decide a implementação real: Notificador notificador = new EmailServico(); PedidoServico servico = new PedidoServico(notificador); // injeção manual","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">Notifier</span> {\n    <span class=\"kw\">void</span> <span class=\"fn\">send</span>(<span class=\"kw\">String</span> recipient, <span class=\"kw\">String</span> message);\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">EmailService</span> <span class=\"kw\">implements</span> <span class=\"cls\">Notifier</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">send</span>(<span class=\"kw\">String</span> recipient, <span class=\"kw\">String</span> message) { <span class=\"com\">/* SMTP */</span> }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">OrderService</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">Notifier</span> notifier; <span class=\"com\">// depende da ABSTRAÇÃO (capítulo 16, o \"D\" de SOLID)</span>\n\n    <span class=\"com\">// injeção via CONSTRUTOR -- a dependência vem de FORA, PedidoServico não cria nada</span>\n    <span class=\"kw\">public</span> <span class=\"fn\">OrderService</span>(<span class=\"cls\">Notifier</span> notifier) {\n        <span class=\"kw\">this</span>.notifier = notifier;\n    }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">complete</span>(<span class=\"cls\">Order</span> p) {\n        notifier.send(p.getEmailCustomer(), <span class=\"str\">\"Order confirmed\"</span>);\n    }\n}\n\n<span class=\"com\">// quem \"monta\" a aplicação decide a implementação real:</span>\n<span class=\"cls\">Notifier</span> notifier = <span class=\"kw\">new</span> <span class=\"cls\">EmailService</span>();\n<span class=\"cls\">OrderService</span> service = <span class=\"kw\">new</span> <span class=\"cls\">OrderService</span>(notifier); <span class=\"com\">// injeção manual</span>","caption":"Exemplo executável de di.","explanation":["Constructor injection torna dependência obrigatória e visível.","O caso de uso depende de contrato, não de implementação concreta."],"commonMistakes":["Injeção por campo sem motivo","Construtor com dependências demais indicando responsabilidade confusa"]},{"id":"di-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Agora testar fica trivial (com um mock ou uma implementação falsa), e trocar para SMS é criar <code>SmsServico implements Notificador</code> sem tocar em <code>PedidoServico</code>.</p>","fidelityText":"Agora testar fica trivial (com um mock ou uma implementação falsa), e trocar para SMS é criar SmsServico implements Notificador sem tocar em PedidoServico."},{"id":"di-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Um container de DI simples, feito à mão</h2>","fidelityText":"Um container de DI simples, feito à mão"},{"id":"di-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Quando o número de dependências cresce, montar tudo manualmente em um <code>main</code> vira um emaranhado. Um <strong>container de IoC</strong> resolve isso: você registra \"receitas\" de como criar cada tipo, e o container resolve a árvore de dependências automaticamente.</p>","fidelityText":"Quando o número de dependências cresce, montar tudo manualmente em um main vira um emaranhado. Um container de IoC resolve isso: você registra \"receitas\" de como criar cada tipo, e o container resolve a árvore de dependências automaticamente."},{"id":"di-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"public class SimpleContainer {\n    private final Map<Class<?>, Object> instances = new HashMap<>();\n\n    public <T> void register(Class<T> type, T instance) {\n        instances.put(type, instance);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T> T resolve(Class<T> type) {\n        return (T) instances.get(type);\n    }\n}\n\n// \"montagem\" da aplicação, uma única vez:\nSimpleContainer container = new SimpleContainer();\ncontainer.register(Notifier.class, new EmailService());\ncontainer.register(OrderService.class,\n    new OrderService(container.resolve(Notifier.class)));\n\nOrderService service = container.resolve(OrderService.class);","fidelityText":"public class ContainerSimples { private final Map<Class<?>, Object> instancias = new HashMap<>(); public <T> void registrar(Class<T> tipo, T instancia) { instancias.put(tipo, instancia); } @SuppressWarnings(\"unchecked\") public <T> T resolver(Class<T> tipo) { return (T) instancias.get(tipo); } } // \"montagem\" da aplicação, uma única vez: ContainerSimples container = new ContainerSimples(); container.registrar(Notificador.class, new EmailServico()); container.registrar(PedidoServico.class, new PedidoServico(container.resolver(Notificador.class))); PedidoServico servico = container.resolver(PedidoServico.class);","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">SimpleContainer</span> {\n    <span class=\"kw\">private final</span> Map&lt;Class&lt;?&gt;, Object&gt; instances = <span class=\"kw\">new</span> HashMap&lt;&gt;();\n\n    <span class=\"kw\">public</span> &lt;T&gt; <span class=\"kw\">void</span> <span class=\"fn\">register</span>(Class&lt;T&gt; type, T instance) {\n        instances.put(type, instance);\n    }\n\n    <span class=\"annotation\">@SuppressWarnings</span>(<span class=\"str\">\"unchecked\"</span>)\n    <span class=\"kw\">public</span> &lt;T&gt; T <span class=\"fn\">resolve</span>(Class&lt;T&gt; type) {\n        <span class=\"kw\">return</span> (T) instances.get(type);\n    }\n}\n\n<span class=\"com\">// \"montagem\" da aplicação, uma única vez:</span>\n<span class=\"cls\">SimpleContainer</span> container = <span class=\"kw\">new</span> <span class=\"cls\">SimpleContainer</span>();\ncontainer.register(Notifier.<span class=\"kw\">class</span>, <span class=\"kw\">new</span> <span class=\"cls\">EmailService</span>());\ncontainer.register(<span class=\"cls\">OrderService</span>.<span class=\"kw\">class</span>,\n    <span class=\"kw\">new</span> <span class=\"cls\">OrderService</span>(container.resolve(Notifier.<span class=\"kw\">class</span>)));\n\n<span class=\"cls\">OrderService</span> service = container.resolve(<span class=\"cls\">OrderService</span>.<span class=\"kw\">class</span>);","caption":"Exemplo executável de di.","explanation":["Container didático registra tipos e resolve instâncias.","Ele demonstra IoC, mas não deve virar localizador global chamado de qualquer lugar."],"commonMistakes":["Service locator disfarçado","Resolver dependência dentro do domínio"]},{"id":"di-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Isso, em miniatura ingênua, <strong>é</strong> o que o <code>ApplicationContext</code> do Spring faz — só que em escala industrial: ele faz um scan via reflection (capítulo 20) por classes anotadas com <code>@Component</code>/<code>@Service</code>/<code>@Repository</code>, decide sozinho a ordem de criação analisando quem depende de quem, cria cada instância (chamada de <em>bean</em>) e injeta as dependências nos construtores ou campos <code>@Autowired</code> — automaticamente. Quando você aprender Spring, cada <code>@Autowired</code> vai literalmente significar \"o container, resolva essa dependência para mim\", exatamente como <code>container.resolver(...)</code> fez aqui.</div>","fidelityText":"Isso, em miniatura ingênua, é o que o ApplicationContext do Spring faz — só que em escala industrial: ele faz um scan via reflection (capítulo 20) por classes anotadas com @Component/@Service/@Repository, decide sozinho a ordem de criação analisando quem depende de quem, cria cada instância (chamada de bean) e injeta as dependências nos construtores ou campos @Autowired — automaticamente. Quando você aprender Spring, cada @Autowired vai literalmente significar \"o container, resolva essa dependência para mim\", exatamente como container.resolver(...) fez aqui."},{"id":"di-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Os três tipos de injeção</h2>","fidelityText":"Os três tipos de injeção"},{"id":"di-content-14","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Tipo</th><th>Como</th><th>Recomendação</th></tr>\n        <tr><td>Via construtor</td><td>Parâmetro do construtor, como no exemplo acima</td><td><strong>Preferida</strong> — permite campos <code>final</code>, deixa dependências obrigatórias explícitas, facilita testes</td></tr>\n        <tr><td>Via setter</td><td>Um método <code>setNotificador(Notificador n)</code></td><td>Para dependências opcionais</td></tr>\n        <tr><td>Via campo</td><td>Direto no campo (no Spring: <code>@Autowired private Notificador n;</code>)</td><td>Mais conveniente, mas dificulta testes e esconde dependências — evite quando possível</td></tr>\n      </tbody></table>","fidelityText":"TipoComoRecomendação Via construtorParâmetro do construtor, como no exemplo acimaPreferida — permite campos final, deixa dependências obrigatórias explícitas, facilita testes Via setterUm método setNotificador(Notificador n)Para dependências opcionais Via campoDireto no campo (no Spring: @Autowired private Notificador n;)Mais conveniente, mas dificulta testes e esconde dependências — evite quando possível"},{"id":"di-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 22.1 — Refatorando para injeção de dependência","prompt":"Pegue a classe Biblioteca (capítulo 17). Crie uma interface NotificadorEmprestimo com void notificar(String mensagem). Refatore Biblioteca para receber um NotificadorEmprestimo via construtor, e chame notificar(...) dentro de emprestar(...) quando o empréstimo for bem-sucedido. Escreva um teste JUnit que usa um NotificadorEmprestimo \"falso\" (uma classe simples que só guarda a última mensagem recebida em uma lista) para verificar que a notificação foi disparada, sem enviar nada de verdade.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 22.1 — Refatorando para injeção de dependênciadifícil Pegue a classe Biblioteca (capítulo 17). Crie uma interface NotificadorEmprestimo com void notificar(String mensagem). Refatore Biblioteca para receber um NotificadorEmprestimo via construtor, e chame notificar(...) dentro de emprestar(...) quando o empréstimo for bem-sucedido. Escreva um teste JUnit que usa um NotificadorEmprestimo \"falso\" (uma classe simples que só guarda a última mensagem recebida em uma lista) para verificar que a notificação foi disparada, sem enviar nada de verdade. Ver solução public interface NotificadorEmprestimo { void notificar(String mensagem); } public class Biblioteca { private final List<ItemAcervo> acervo = new ArrayList<>(); private final NotificadorEmprestimo notificador; public Biblioteca(NotificadorEmprestimo notificador) { this.notificador = notificador; } public void emprestar(String codigo) throws ItemIndisponivelException { ItemAcervo item = acervo.stream() .filter(i -> i.getCodigo().equals(codigo)) .findFirst() .orElseThrow(() -> new ItemIndisponivelException(\"Item não encontrado\")); if (!item.disponivelParaEmprestimo()) { throw new ItemIndisponivelException(\"Item já emprestado\"); } item.emprestar(); notificador.notificar(\"Empréstimo realizado: \" + item.getTitulo()); } public void adicionar(ItemAcervo i) { acervo.add(i); } } // --- classe de teste \"falsa\" (test double) --- class NotificadorFalso implements NotificadorEmprestimo { List<String> mensagens = new ArrayList<>(); @Override public void notificar(String mensagem) { mensagens.add(mensagem); } } @Test void deveNotificarAoEmprestar() throws ItemIndisponivelException { NotificadorFalso fake = new NotificadorFalso(); Biblioteca biblioteca = new Biblioteca(fake); // injeção manual do \"mock\" biblioteca.adicionar(new Livro(\"Clean Code\", \"L001\", \"Robert Martin\")); biblioteca.emprestar(\"L001\"); assertEquals(1, fake.mensagens.size()); }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 22.1 — Refatorando para injeção de dependência</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Pegue a classe <code>Biblioteca</code> (capítulo 17). Crie uma interface <code>NotificadorEmprestimo</code> com <code>void notificar(String mensagem)</code>. Refatore <code>Biblioteca</code> para receber um <code>NotificadorEmprestimo</code> via construtor, e chame <code>notificar(...)</code> dentro de <code>emprestar(...)</code> quando o empréstimo for bem-sucedido. Escreva um teste JUnit que usa um <code>NotificadorEmprestimo</code> \"falso\" (uma classe simples que só guarda a última mensagem recebida em uma lista) para verificar que a notificação foi disparada, sem enviar nada de verdade.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public interface</span> <span class=\"cls\">NotifierLoan</span> {\n    <span class=\"kw\">void</span> <span class=\"fn\">notify</span>(<span class=\"kw\">String</span> message);\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Library</span> {\n    <span class=\"kw\">private final</span> List&lt;<span class=\"cls\">ItemCatalog</span>&gt; catalog = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n    <span class=\"kw\">private final</span> <span class=\"cls\">NotifierLoan</span> notifier;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Library</span>(<span class=\"cls\">NotifierLoan</span> notifier) {\n        <span class=\"kw\">this</span>.notifier = notifier;\n    }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">borrow</span>(<span class=\"kw\">String</span> code) <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        <span class=\"cls\">ItemCatalog</span> item = catalog.stream()\n            .filter(i -&gt; i.getCode().equals(code))\n            .findFirst()\n            .orElseThrow(() -&gt; <span class=\"kw\">new</span> <span class=\"cls\">ItemUnavailableException</span>(<span class=\"str\">\"Item not found\"</span>));\n        <span class=\"kw\">if</span> (!item.availableForLoan()) {\n            <span class=\"kw\">throw new</span> <span class=\"cls\">ItemUnavailableException</span>(<span class=\"str\">\"Item already borrowed\"</span>);\n        }\n        item.borrow();\n        notifier.notify(<span class=\"str\">\"loan realizado: \"</span> + item.getTitle());\n    }\n    <span class=\"kw\">public void</span> <span class=\"fn\">add</span>(<span class=\"cls\">ItemCatalog</span> i) { catalog.add(i); }\n}\n\n<span class=\"com\">// --- classe de teste \"falsa\" (test double) ---</span>\n<span class=\"kw\">class</span> <span class=\"cls\">NotifierFake</span> <span class=\"kw\">implements</span> <span class=\"cls\">NotifierLoan</span> {\n    List&lt;<span class=\"kw\">String</span>&gt; messages = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">notify</span>(<span class=\"kw\">String</span> message) { messages.add(message); }\n}\n\n<span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldNotifyOnBorrow</span>() <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n    <span class=\"cls\">NotifierFake</span> fake = <span class=\"kw\">new</span> <span class=\"cls\">NotifierFake</span>();\n    <span class=\"cls\">Library</span> library = <span class=\"kw\">new</span> <span class=\"cls\">Library</span>(fake); <span class=\"com\">// injeção manual do \"mock\"</span>\n    library.add(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Clean Code\"</span>, <span class=\"str\">\"L001\"</span>, <span class=\"str\">\"Robert Martin\"</span>));\n\n    library.borrow(<span class=\"str\">\"L001\"</span>);\n\n    assertEquals(1, fake.messages.size());\n}</pre>\n        </div>\n      </div>"},{"id":"di-diagram","type":"diagram","authorship":"authored","title":"Fluxo de composição manual","description":"A criação fica na borda; a regra recebe contratos já prontos.","steps":["main lê configuração","composition root cria adapters concretos","composition root injeta contratos no caso de uso","caso de uso executa regra sem saber como dependências foram criadas","teste injeta dublês sem rede/banco reais"]},{"id":"di-quiz","type":"quiz","authorship":"authored","conceptId":"di-composicao-raiz","prompt":"Onde deve ficar o `new JdbcPedidoRepository(...)` em uma aplicação pequena bem separada?","options":[{"id":"di-q-a","label":"Perto da composition root, fora do caso de uso que depende da porta.","correct":true,"explanation":"A regra recebe a dependência pronta e não conhece o detalhe de criação."},{"id":"di-q-b","label":"Dentro do método de regra para garantir que sempre use JDBC real.","correct":false,"explanation":"Isso acopla a regra ao detalhe e dificulta teste."},{"id":"di-q-c","label":"Dentro de cada entidade de domínio.","correct":false,"explanation":"Entidade não deve controlar infraestrutura externa."}]}],"resources":[{"id":"fowler-di","type":"guide","title":"Inversion of Control Containers and the Dependency Injection pattern","url":"https://martinfowler.com/articles/injection.html","reinforces":"Distingue IoC, dependency injection, service locator e composição.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"method-api-reflection","type":"reference","title":"Method API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Method.html","reinforces":"Base reflexiva para entender containers didáticos que descobrem e invocam métodos.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A di operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a di operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this di chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"public class OrderService {","instruction":"A di operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this di chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"padroes","moduleId":"application-design","order":3,"title":"Padrões de projeto essenciais","summary":"O Spring é literalmente construído sobre estes padrões — reconhecê-los agora significa reconhecer o Spring depois, em vez de decorar anotações sem entender o que elas automatizam.","objectives":["Usar padrões como nomes para forças de design recorrentes","Aplicar Strategy, Factory e Adapter apenas quando resolvem variação real","Separar padrão de arquitetura inteira","Evitar pattern fever e abstração decorativa"],"whyItExists":"Depois de DI e SOLID, padrões entram como vocabulário curto para decisões recorrentes. O aluno deve reconhecer força, trade-off e mau uso, não decorar catálogo.","prerequisiteChapterIds":["di"],"conceptIds":["padroes-de-projeto-essenciais"],"introducedConceptIds":["strategy-policy-object","factory-criacao-invariante","adapter-fronteira-externa"],"usedConceptIds":["ocp-polimorfismo-extensao","di-composicao-raiz","dto-mapping-fronteira"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"padroes-intuition","type":"intuition","authorship":"authored","title":"Padrão é apelido de uma decisão que volta","body":"Um design pattern não é código para copiar. É um nome para uma tensão recorrente: variar algoritmo, criar objeto com regra, adaptar API externa, observar evento, compor comportamento.","analogyLimit":"Receita ajuda a pensar em repetição, mas padrão exige contexto; aplicar fora do problema aumenta complexidade."},{"id":"padroes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#di\">22 · Injeção de dependência</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 22 · Injeção de dependência"},{"id":"padroes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O Spring é literalmente construído sobre estes padrões — reconhecê-los agora significa reconhecer o Spring depois, em vez de decorar anotações sem entender o que elas automatizam.</p>","fidelityText":"O Spring é literalmente construído sobre estes padrões — reconhecê-los agora significa reconhecer o Spring depois, em vez de decorar anotações sem entender o que elas automatizam."},{"id":"padroes-content-3","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Singleton</h2>","fidelityText":"Singleton"},{"id":"padroes-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Garante que exista <strong>uma única instância</strong> de uma classe em toda a aplicação.</p>","fidelityText":"Garante que exista uma única instância de uma classe em toda a aplicação."},{"id":"padroes-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"public class AppConfiguration {\n    private static final AppConfiguration INSTANCE = new AppConfiguration();\n    private AppConfiguration() {} // construtor PRIVADO -- ninguém de fora cria uma nova\n    public static AppConfiguration getInstance() { return INSTANCE; }\n}","fidelityText":"public class ConfiguracaoApp { private static final ConfiguracaoApp INSTANCIA = new ConfiguracaoApp(); private ConfiguracaoApp() {} // construtor PRIVADO -- ninguém de fora cria uma nova public static ConfiguracaoApp getInstance() { return INSTANCIA; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">AppConfiguration</span> {\n    <span class=\"kw\">private static final</span> <span class=\"cls\">AppConfiguration</span> INSTANCE = <span class=\"kw\">new</span> <span class=\"cls\">AppConfiguration</span>();\n    <span class=\"kw\">private</span> <span class=\"fn\">AppConfiguration</span>() {} <span class=\"com\">// construtor PRIVADO -- ninguém de fora cria uma nova</span>\n    <span class=\"kw\">public static</span> <span class=\"cls\">AppConfiguration</span> <span class=\"fn\">getInstance</span>() { <span class=\"kw\">return</span> INSTANCE; }\n}","caption":"Exemplo executável de padroes.","explanation":["Strategy encapsula política variável atrás de interface pequena.","O consumidor não precisa conhecer cada algoritmo concreto."],"commonMistakes":["Strategy sem variação","Switch continuar crescendo ao lado das estratégias"]},{"id":"padroes-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">No Spring, todo <code>@Bean</code>/<code>@Component</code> é <strong>Singleton por padrão</strong> — o container cria uma única instância e a reutiliza em toda a aplicação, exatamente essa ideia, só que gerenciada pelo framework em vez de escrita manualmente com construtor privado.</div>","fidelityText":"No Spring, todo @Bean/@Component é Singleton por padrão — o container cria uma única instância e a reutiliza em toda a aplicação, exatamente essa ideia, só que gerenciada pelo framework em vez de escrita manualmente com construtor privado."},{"id":"padroes-content-7","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Factory Method</h2>","fidelityText":"Factory Method"},{"id":"padroes-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Centraliza a lógica de <em>como criar</em> um objeto, escondendo a decisão de qual implementação concreta usar.</p>","fidelityText":"Centraliza a lógica de como criar um objeto, escondendo a decisão de qual implementação concreta usar."},{"id":"padroes-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"public class NotifierFactory {\n    public static Notifier create(String type) {\n        return switch (type) {\n            case \"email\" -> new EmailService();\n            case \"sms\" -> new SmsService();\n            default -> throw new IllegalArgumentException(\"Type unknown: \" + type);\n        };\n    }\n}","fidelityText":"public class NotificadorFactory { public static Notificador criar(String tipo) { return switch (tipo) { case \"email\" -> new EmailServico(); case \"sms\" -> new SmsServico(); default -> throw new IllegalArgumentException(\"Tipo desconhecido: \" + tipo); }; } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">NotifierFactory</span> {\n    <span class=\"kw\">public static</span> <span class=\"cls\">Notifier</span> <span class=\"fn\">create</span>(<span class=\"kw\">String</span> type) {\n        <span class=\"kw\">return switch</span> (type) {\n            <span class=\"kw\">case</span> <span class=\"str\">\"email\"</span> -&gt; <span class=\"kw\">new</span> <span class=\"cls\">EmailService</span>();\n            <span class=\"kw\">case</span> <span class=\"str\">\"sms\"</span> -&gt; <span class=\"kw\">new</span> <span class=\"cls\">SmsService</span>();\n            <span class=\"kw\">default</span> -&gt; <span class=\"kw\">throw new</span> <span class=\"cls\">IllegalArgumentException</span>(<span class=\"str\">\"Type unknown: \"</span> + type);\n        };\n    }\n}","caption":"Exemplo executável de padroes.","explanation":["Factory dá nome à criação e concentra invariantes.","Ela é útil quando construir corretamente exige decisão, validação ou variação."],"commonMistakes":["Factory que só chama new","Esconder exceção de criação"]},{"id":"padroes-content-10","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Strategy</h2>","fidelityText":"Strategy"},{"id":"padroes-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Encapsula algoritmos intercambiáveis atrás de uma interface comum — já vimos isso na prática no exercício 8.1 (<code>MetodoPagamento</code>).</p>","fidelityText":"Encapsula algoritmos intercambiáveis atrás de uma interface comum — já vimos isso na prática no exercício 8.1 (MetodoPagamento)."},{"id":"padroes-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface StrategyDiscount { double apply(double value); }\n\nclass DiscountBlackFriday implements StrategyDiscount {\n    public double apply(double v) { return v * 0.5; }\n}\nclass WithoutDiscount implements StrategyDiscount {\n    public double apply(double v) { return v; }\n}\n// o \"cliente\" escolhe a estratégia em runtime, sem if/else espalhado","fidelityText":"public interface EstrategiaDesconto { double aplicar(double valor); } class DescontoBlackFriday implements EstrategiaDesconto { public double aplicar(double v) { return v * 0.5; } } class SemDesconto implements EstrategiaDesconto { public double aplicar(double v) { return v; } } // o \"cliente\" escolhe a estratégia em runtime, sem if/else espalhado","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">StrategyDiscount</span> { <span class=\"kw\">double</span> <span class=\"fn\">apply</span>(<span class=\"kw\">double</span> value); }\n\n<span class=\"kw\">class</span> <span class=\"cls\">DiscountBlackFriday</span> <span class=\"kw\">implements</span> <span class=\"cls\">StrategyDiscount</span> {\n    <span class=\"kw\">public double</span> <span class=\"fn\">apply</span>(<span class=\"kw\">double</span> v) { <span class=\"kw\">return</span> v * 0.5; }\n}\n<span class=\"kw\">class</span> <span class=\"cls\">WithoutDiscount</span> <span class=\"kw\">implements</span> <span class=\"cls\">StrategyDiscount</span> {\n    <span class=\"kw\">public double</span> <span class=\"fn\">apply</span>(<span class=\"kw\">double</span> v) { <span class=\"kw\">return</span> v; }\n}\n<span class=\"com\">// o \"cliente\" escolhe a estratégia em runtime, sem if/else espalhado</span>","caption":"Exemplo executável de padroes.","explanation":["Adapter traduz contrato externo para contrato interno.","A direção correta protege domínio do detalhe externo."],"commonMistakes":["Adapter vazar DTO externo","Colocar regra de negócio no adapter"]},{"id":"padroes-content-13","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Repository (e DAO)</h2>","fidelityText":"Repository (e DAO)"},{"id":"padroes-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Isola a lógica de acesso a dados atrás de uma interface, como se fosse uma coleção em memória. É o padrão que o Spring Data JPA leva ao extremo — você declara uma interface e o framework <em>gera a implementação inteira sozinho</em>.</p>","fidelityText":"Isola a lógica de acesso a dados atrás de uma interface, como se fosse uma coleção em memória. É o padrão que o Spring Data JPA leva ao extremo — você declara uma interface e o framework gera a implementação inteira sozinho."},{"id":"padroes-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface BookRepository {\n    void save(Book book);\n    Optional<Book> findById(long id);\n    List<Book> listarAll();\n}\n\n// implementação hoje: em memória. Amanhã: JDBC (capítulo 24). Depois: Spring Data JPA.\n// o código que USA LivroRepositorio não muda em NENHUM desses casos.\npublic class BookRepositoryInMemory implements BookRepository {\n    private final Map<Long, Book> data = new HashMap<>();\n    @Override public void save(Book l) { data.put(l.getId(), l); }\n    @Override public Optional<Book> findById(long id) { return Optional.ofNullable(data.get(id)); }\n    @Override public List<Book> listarAll() { return List.copyOf(data.values()); }\n}","fidelityText":"public interface LivroRepositorio { void salvar(Livro livro); Optional<Livro> buscarPorId(long id); List<Livro> listarTodos(); } // implementação hoje: em memória. Amanhã: JDBC (capítulo 24). Depois: Spring Data JPA. // o código que USA LivroRepositorio não muda em NENHUM desses casos. public class LivroRepositorioEmMemoria implements LivroRepositorio { private final Map<Long, Livro> dados = new HashMap<>(); @Override public void salvar(Livro l) { dados.put(l.getId(), l); } @Override public Optional<Livro> buscarPorId(long id) { return Optional.ofNullable(dados.get(id)); } @Override public List<Livro> listarTodos() { return List.copyOf(dados.values()); } }","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">BookRepository</span> {\n    <span class=\"kw\">void</span> <span class=\"fn\">save</span>(<span class=\"cls\">Book</span> book);\n    Optional&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findById</span>(<span class=\"kw\">long</span> id);\n    List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">listarAll</span>();\n}\n\n<span class=\"com\">// implementação hoje: em memória. Amanhã: JDBC (capítulo 24). Depois: Spring Data JPA.\n// o código que USA LivroRepositorio não muda em NENHUM desses casos.</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">BookRepositoryInMemory</span> <span class=\"kw\">implements</span> <span class=\"cls\">BookRepository</span> {\n    <span class=\"kw\">private final</span> Map&lt;<span class=\"kw\">Long</span>, <span class=\"cls\">Book</span>&gt; data = <span class=\"kw\">new</span> HashMap&lt;&gt;();\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">save</span>(<span class=\"cls\">Book</span> l) { data.put(l.getId(), l); }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public</span> Optional&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findById</span>(<span class=\"kw\">long</span> id) { <span class=\"kw\">return</span> Optional.ofNullable(data.get(id)); }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public</span> List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">listarAll</span>() { <span class=\"kw\">return</span> List.copyOf(data.values()); }\n}","caption":"Exemplo executável de padroes.","explanation":["Observer/evento desacopla emissor de reações imediatas.","Ainda exige política de erro, ordem e assincronia quando sai de memória."],"commonMistakes":["Usar evento para fluxo que precisa resposta imediata","Ignorar falha do listener"]},{"id":"padroes-content-16","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Observer</h2>","fidelityText":"Observer"},{"id":"padroes-content-17","type":"html","authorship":"legacy-preserved","html":"<p>Um objeto (\"sujeito\") notifica múltiplos \"observadores\" quando seu estado muda — a base conceitual de eventos (e do <code>ApplicationEventPublisher</code> do Spring).</p>","fidelityText":"Um objeto (\"sujeito\") notifica múltiplos \"observadores\" quando seu estado muda — a base conceitual de eventos (e do ApplicationEventPublisher do Spring)."},{"id":"padroes-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface ObserverOrder { void onCompleteOrder(Order p); }\n\npublic class OrderService {\n    private final List<ObserverOrder> observers = new ArrayList<>();\n    public void addObserver(ObserverOrder o) { observers.add(o); }\n\n    public void complete(Order p) {\n        // ... lógica de finalização ...\n        for (ObserverOrder o : observers) o.onCompleteOrder(p); // notifica todos, sem saber quem são\n    }\n}","fidelityText":"public interface ObservadorPedido { void aoFinalizarPedido(Pedido p); } public class PedidoServico { private final List<ObservadorPedido> observadores = new ArrayList<>(); public void adicionarObservador(ObservadorPedido o) { observadores.add(o); } public void finalizar(Pedido p) { // ... lógica de finalização ... for (ObservadorPedido o : observadores) o.aoFinalizarPedido(p); // notifica todos, sem saber quem são } }","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">ObserverOrder</span> { <span class=\"kw\">void</span> <span class=\"fn\">onCompleteOrder</span>(<span class=\"cls\">Order</span> p); }\n\n<span class=\"kw\">public class</span> <span class=\"cls\">OrderService</span> {\n    <span class=\"kw\">private final</span> List&lt;<span class=\"cls\">ObserverOrder</span>&gt; observers = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n    <span class=\"kw\">public void</span> <span class=\"fn\">addObserver</span>(<span class=\"cls\">ObserverOrder</span> o) { observers.add(o); }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">complete</span>(<span class=\"cls\">Order</span> p) {\n        <span class=\"com\">// ... lógica de finalização ...</span>\n        <span class=\"kw\">for</span> (<span class=\"cls\">ObserverOrder</span> o : observers) o.onCompleteOrder(p); <span class=\"com\">// notifica todos, sem saber quem são</span>\n    }\n}","caption":"Exemplo executável de padroes.","explanation":["Decorator compõe comportamento adicional sem alterar o objeto base.","A ordem dos decorators pode alterar resultado e deve ser explícita."],"commonMistakes":["Criar cadeia invisível demais","Usar herança para todas as combinações"]},{"id":"padroes-content-19","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Adapter</h2>","fidelityText":"Adapter"},{"id":"padroes-content-20","type":"html","authorship":"legacy-preserved","html":"<p>Traduz o contrato de uma biblioteca ou API externa — que você não controla — para o contrato que o seu próprio domínio já usa. A tradução acontece numa classe isolada; o domínio nunca deveria enxergar o vocabulário do fornecedor externo diretamente.</p>","fidelityText":"Traduz o contrato de uma biblioteca ou API externa — que você não controla — para o contrato que o seu próprio domínio já usa. A tradução acontece numa classe isolada; o domínio nunca deveria enxergar o vocabulário do fornecedor externo diretamente."},{"id":"padroes-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface ProcessadorPayment { ResultPayment process(Payment payment); }\n\n// third-party library -- names, format, and unit outside your control:\nclass GatewayPaymentExternal {\n    ResponseGateway charge(String token, long valueInCentavos) { ... }\n}\nclass ResponseGateway { String status; String idTransaction; }\n\n// Adapter: sits at the boundary, knows both vocabularies\npublic class AdapterGatewayPayment implements ProcessadorPayment {\n    private final GatewayPaymentExternal gateway;\n    AdapterGatewayPayment(GatewayPaymentExternal gateway) { this.gateway = gateway; }\n\n    @Override\n    public ResultPayment process(Payment payment) {\n        long centavos = payment.getValue().multiply(BigDecimal.valueOf(100)).longValue();\n        ResponseGateway response = gateway.charge(payment.getToken(), centavos);\n        boolean approved = \"OK\".equals(response.status);\n        return new ResultPayment(approved, response.idTransaction);\n    }\n}","fidelityText":"public interface ProcessadorPagamento { ResultadoPagamento processar(Pagamento pagamento); } // biblioteca de terceiros -- nomes, formato e unidade fora do seu controle: class GatewayPagamentoExterno { RespostaGateway cobrar(String token, long valorEmCentavos) { ... } } class RespostaGateway { String status; String idTransacao; } // Adapter: fica na fronteira, conhece os dois vocabulários public class AdaptadorGatewayPagamento implements ProcessadorPagamento { private final GatewayPagamentoExterno gateway; AdaptadorGatewayPagamento(GatewayPagamentoExterno gateway) { this.gateway = gateway; } @Override public ResultadoPagamento processar(Pagamento pagamento) { long centavos = pagamento.getValor().multiply(BigDecimal.valueOf(100)).longValue(); RespostaGateway resposta = gateway.cobrar(pagamento.getToken(), centavos); boolean aprovado = \"OK\".equals(resposta.status); return new ResultadoPagamento(aprovado, resposta.idTransacao); } }","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">ProcessadorPayment</span> { <span class=\"cls\">ResultPayment</span> <span class=\"fn\">process</span>(<span class=\"cls\">Payment</span> payment); }\n\n<span class=\"com\">// third-party library -- names, format, and unit outside your control:</span>\n<span class=\"kw\">class</span> <span class=\"cls\">GatewayPaymentExternal</span> {\n    <span class=\"cls\">ResponseGateway</span> <span class=\"fn\">charge</span>(<span class=\"kw\">String</span> token, <span class=\"kw\">long</span> valueInCentavos) { ... }\n}\n<span class=\"kw\">class</span> <span class=\"cls\">ResponseGateway</span> { <span class=\"kw\">String</span> status; <span class=\"kw\">String</span> idTransaction; }\n\n<span class=\"com\">// Adapter: sits at the boundary, knows both vocabularies</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">AdapterGatewayPayment</span> <span class=\"kw\">implements</span> <span class=\"cls\">ProcessadorPayment</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">GatewayPaymentExternal</span> gateway;\n    <span class=\"fn\">AdapterGatewayPayment</span>(<span class=\"cls\">GatewayPaymentExternal</span> gateway) { <span class=\"kw\">this</span>.gateway = gateway; }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public</span> <span class=\"cls\">ResultPayment</span> <span class=\"fn\">process</span>(<span class=\"cls\">Payment</span> payment) {\n        <span class=\"kw\">long</span> centavos = payment.getValue().multiply(BigDecimal.valueOf(100)).longValue();\n        <span class=\"cls\">ResponseGateway</span> response = gateway.charge(payment.getToken(), centavos);\n        <span class=\"kw\">boolean</span> approved = <span class=\"str\">\"OK\"</span>.equals(response.status);\n        <span class=\"kw\">return new</span> <span class=\"cls\">ResultPayment</span>(approved, response.idTransaction);\n    }\n}","caption":"Exemplo executável de padroes.","explanation":["O Adapter conhece os dois vocabulários (externo e interno) e faz a tradução num único lugar.","O domínio depende só de ProcessadorPagamento; nunca de RespostaGateway."],"commonMistakes":["Deixar campo do gateway vazar para dentro do contrato interno","Colocar regra de negócio dentro do adapter em vez de só tradução"]},{"id":"padroes-content-22","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>A direção importa:</b> se <code>status</code> ou <code>idTransacao</code> (vocabulário do gateway) vazar para dentro de <code>ProcessadorPagamento</code> ou do domínio, o Adapter deixou de proteger a fronteira — a tradução tem que ficar contida nele.</div>","fidelityText":"A direção importa: se status ou idTransacao (vocabulário do gateway) vazar para dentro de ProcessadorPagamento ou do domínio, o Adapter deixou de proteger a fronteira — a tradução tem que ficar contida nele."},{"id":"padroes-content-23","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Padrão</th><th>No Spring, aparece como</th></tr>\n        <tr><td>Singleton</td><td>Escopo padrão de todo <code>@Bean</code>/<code>@Component</code></td></tr>\n        <tr><td>Factory</td><td>Métodos <code>@Bean</code> dentro de classes <code>@Configuration</code></td></tr>\n        <tr><td>Strategy</td><td>Múltiplas implementações de uma interface, injetadas conforme perfil/qualificador</td></tr>\n        <tr><td>Repository</td><td>Interfaces <code>JpaRepository</code>/<code>CrudRepository</code> do Spring Data</td></tr>\n        <tr><td>Observer</td><td><code>ApplicationEvent</code> + <code>@EventListener</code></td></tr>\n        <tr><td>Adapter</td><td><code>RestTemplate</code>/<code>WebClient</code> e clientes de SDK isolando a API externa real</td></tr>\n        <tr><td>Dependency Injection</td><td>O próprio núcleo do container (<code>@Autowired</code>)</td></tr>\n      </tbody></table>","fidelityText":"PadrãoNo Spring, aparece como SingletonEscopo padrão de todo @Bean/@Component FactoryMétodos @Bean dentro de classes @Configuration StrategyMúltiplas implementações de uma interface, injetadas conforme perfil/qualificador RepositoryInterfaces JpaRepository/CrudRepository do Spring Data ObserverApplicationEvent + @EventListener AdapterRestTemplate/WebClient e clientes de SDK isolando a API externa real Dependency InjectionO próprio núcleo do container (@Autowired)"},{"id":"padroes-exercise-24","type":"exercise","authorship":"legacy-preserved","title":"Exercício 23.1 — Repository em memória","prompt":"Defina a interface LivroRepositorio mostrada acima. Implemente LivroRepositorioEmMemoria. Escreva um teste JUnit garantindo que salvar seguido de buscarPorId retorna o livro correto, e que buscarPorId para um id inexistente retorna Optional.empty().","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 23.1 — Repository em memóriamédio Defina a interface LivroRepositorio mostrada acima. Implemente LivroRepositorioEmMemoria. Escreva um teste JUnit garantindo que salvar seguido de buscarPorId retorna o livro correto, e que buscarPorId para um id inexistente retorna Optional.empty(). Ver solução @Test void deveSalvarEBuscarLivro() { LivroRepositorio repo = new LivroRepositorioEmMemoria(); Livro livro = new Livro(1L, \"Clean Code\", \"Robert Martin\"); repo.salvar(livro); assertTrue(repo.buscarPorId(1L).isPresent()); assertEquals(\"Clean Code\", repo.buscarPorId(1L).get().getTitulo()); assertTrue(repo.buscarPorId(999L).isEmpty()); }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 23.1 — Repository em memória</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Defina a interface <code>LivroRepositorio</code> mostrada acima. Implemente <code>LivroRepositorioEmMemoria</code>. Escreva um teste JUnit garantindo que <code>salvar</code> seguido de <code>buscarPorId</code> retorna o livro correto, e que <code>buscarPorId</code> para um id inexistente retorna <code>Optional.empty()</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldSaveAndFindBook</span>() {\n    <span class=\"cls\">BookRepository</span> repo = <span class=\"kw\">new</span> <span class=\"cls\">BookRepositoryInMemory</span>();\n    <span class=\"cls\">Book</span> book = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(1L, <span class=\"str\">\"Clean Code\"</span>, <span class=\"str\">\"Robert Martin\"</span>);\n\n    repo.save(book);\n\n    assertTrue(repo.findById(1L).isPresent());\n    assertEquals(<span class=\"str\">\"Clean Code\"</span>, repo.findById(1L).get().getTitle());\n    assertTrue(repo.findById(999L).isEmpty());\n}</pre>\n        </div>\n      </div>"},{"id":"padroes-table","type":"table","authorship":"authored","title":"Quando o padrão paga o aluguel","headers":["Padrão","Força real","Cheiro de mau uso"],"rows":[["Strategy","algoritmos/políticas variam","uma única implementação eterna"],["Factory","criação tem regra, nome ou variação","método que só repassa construtor"],["Adapter","API externa não combina com o domínio","vazar DTO externo com outro nome"]]},{"id":"padroes-quiz","type":"quiz","authorship":"authored","conceptId":"adapter-fronteira-externa","prompt":"Quando um Adapter é uma boa escolha?","options":[{"id":"pad-q-a","label":"Quando uma API externa tem formato/contrato diferente do contrato que o domínio quer usar.","correct":true,"explanation":"O adapter traduz a fronteira e protege o núcleo."},{"id":"pad-q-b","label":"Quando queremos renomear toda classe para terminar com Adapter.","correct":false,"explanation":"Nome não isola fronteira nem traduz contrato."},{"id":"pad-q-c","label":"Quando a regra de negócio precisa conhecer JSON externo diretamente.","correct":false,"explanation":"Isso é justamente o vazamento que o adapter deveria evitar."}]}],"resources":[{"id":"patterns-catalog-refactoring","type":"guide","title":"Design Patterns Catalog","url":"https://refactoring.guru/design-patterns/catalog","reinforces":"Catálogo com forças, estrutura e intenção de padrões comuns.","language":"en","publisher":"Refactoring.Guru","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"strategy-refactoring","type":"guide","title":"Strategy pattern","url":"https://refactoring.guru/design-patterns/strategy","reinforces":"Exemplo específico de política substituível por composição.","language":"en","publisher":"Refactoring.Guru","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A patterns operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a patterns operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this patterns chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"public class AppConfiguration {","instruction":"A patterns operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this patterns chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"clean-code","moduleId":"application-design","order":4,"title":"Clean Code & Refatoração","summary":"SOLID (capítulo 16) organiza classes; Clean Code organiza código dentro de cada método e classe — nomes, tamanho de função, comentários, estrutura. É a diferença entre um sistema bem arquitetado que ainda assim é doloroso de ler linha a linha.","objectives":["Nomear intenção com vocabulário do problema","Separar funções por nível de abstração","Refatorar com teste de segurança","Distinguir limpeza real de gosto pessoal"],"whyItExists":"Depois de padrões e testes, o aluno precisa melhorar código sem apagar comportamento. Clean code entra como técnica de comunicação e manutenção, não como moralismo estético.","prerequisiteChapterIds":["solid","padroes"],"conceptIds":["nomes-que-contam-a-historia-sozinhos","funcoes-pequenas-fazendo-uma-coisa-so","refatoracao-mudando-a-estrutura-sem-mudar-o-comportamento"],"introducedConceptIds":["nome-intencao-codigo","funcao-pequena-nivel-abstracao","refatoracao-rede-seguranca"],"usedConceptIds":["srp-coesao-motivo-mudanca","teste-aaa-first","debug-hipotese-breakpoint"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"clean-intuition","type":"intuition","authorship":"authored","title":"Código limpo é código que deixa a próxima alteração menos perigosa","body":"Um nome bom reduz explicação externa. Uma função com um nível de abstração reduz salto mental. Um teste antes da refatoração impede que limpeza vire mudança de comportamento disfarçada.","analogyLimit":"Texto bem escrito ajuda como analogia, mas código também executa, falha, muda estado e precisa de evidência automatizada."},{"id":"clean-code-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Engenharia</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#solid\">16 · SOLID</a>, <a class=\"prereq-tag\" href=\"#padroes\">23 · Padrões de projeto</a></div>\n      </div>","fidelityText":"Engenharia Dificuldade: Intermediário ⏱ ~2h30 de estudo Pré-requisitos: 16 · SOLID, 23 · Padrões de projeto"},{"id":"clean-code-content-2","type":"html","authorship":"legacy-preserved","html":"<p>SOLID (capítulo 16) organiza classes; Clean Code organiza <strong>código dentro</strong> de cada método e classe — nomes, tamanho de função, comentários, estrutura. É a diferença entre um sistema bem arquitetado que ainda assim é doloroso de ler linha a linha.</p>","fidelityText":"SOLID (capítulo 16) organiza classes; Clean Code organiza código dentro de cada método e classe — nomes, tamanho de função, comentários, estrutura. É a diferença entre um sistema bem arquitetado que ainda assim é doloroso de ler linha a linha."},{"id":"clean-code-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Nomes que contam a história sozinhos</h2>","fidelityText":"Nomes que contam a história sozinhos"},{"id":"clean-code-comparison-4","type":"html","html":"<div class=\"code-comparison\"><div class=\"code-toggle-bar\" role=\"tablist\">\n        <button class=\"ct-btn bad active\" role=\"tab\" aria-selected=\"true\" type=\"button\">❌ Nomes ruins</button>\n        <button class=\"ct-btn good\" role=\"tab\" aria-selected=\"false\" type=\"button\" tabindex=\"-1\">✅ Nomes que explicam</button>\n      </div><div class=\"ct-panel active\">\n<pre class=\"code\"><span class=\"kw\">int</span> d; <span class=\"com\">// dias desde a última atualização? </span>\nList&lt;<span class=\"kw\">int</span>[]&gt; l = <span class=\"fn\">getList</span>();\n<span class=\"kw\">if</span> (x.getStatus() == 1) { <span class=\"com\">// o que é \"1\"?</span>\n    process(x);\n}</pre>\n      </div><div class=\"ct-panel\">\n<pre class=\"code\"><span class=\"kw\">int</span> daysDesdeLastUpdate;\nList&lt;<span class=\"cls\">Cell</span>&gt; cellsVivas = board.<span class=\"fn\">getCellsVivas</span>();\n<span class=\"kw\">if</span> (order.getStatus() == <span class=\"cls\">StatusOrder</span>.APPROVED) { <span class=\"com\">// enum, não número mágico</span>\n    processOrderApproved(order);\n}</pre>\n      </div></div>","sourceIndexes":[4,5,6],"fidelityText":"❌ Nomes ruins ✅ Nomes que explicam int d; // dias desde a última atualização? List<int[]> l = getList(); if (x.getStatus() == 1) { // o que é \"1\"? processar(x); } int diasDesdeUltimaAtualizacao; List<Celula> celulasVivas = tabuleiro.getCelulasVivas(); if (pedido.getStatus() == StatusPedido.APROVADO) { // enum, não número mágico processarPedidoAprovado(pedido); }"},{"id":"clean-code-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Funções pequenas, fazendo uma coisa só</h2>","fidelityText":"Funções pequenas, fazendo uma coisa só"},{"id":"clean-code-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Isso é o SRP do capítulo 16 aplicado no nível de <em>método</em>, não de classe. Um método que precisa de comentários para separar \"seções\" internas geralmente deveria ser quebrado em métodos menores, cada um nomeado pela intenção daquela seção.</p>","fidelityText":"Isso é o SRP do capítulo 16 aplicado no nível de método, não de classe. Um método que precisa de comentários para separar \"seções\" internas geralmente deveria ser quebrado em métodos menores, cada um nomeado pela intenção daquela seção."},{"id":"clean-code-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ um método fazendo três coisas, precisando de comentários pra separar:\npublic void processOrder(Order p) {\n    // validar\n    if (p.getItems().isEmpty()) throw new InvalidOrderException();\n    // calcular total\n    double total = 0;\n    for (var item : p.getItems()) total += item.getPrice() * item.getQuantity();\n    // notificar\n    notifier.send(\"Order of R$\" + total);\n}\n\n// ✅ cada intenção é um método nomeado -- os comentários viram desnecessários:\npublic void processOrder(Order p) {\n    validate(p);\n    double total = calculateTotal(p);\n    notifyCustomer(total);\n}","fidelityText":"// ❌ um método fazendo três coisas, precisando de comentários pra separar: public void processarPedido(Pedido p) { // validar if (p.getItens().isEmpty()) throw new PedidoInvalidoException(); // calcular total double total = 0; for (var item : p.getItens()) total += item.getPreco() * item.getQtd(); // notificar notificador.enviar(\"Pedido de R$\" + total); } // ✅ cada intenção é um método nomeado -- os comentários viram desnecessários: public void processarPedido(Pedido p) { validar(p); double total = calcularTotal(p); notificarCliente(total); }","highlightedHtml":"<span class=\"com\">// ❌ um método fazendo três coisas, precisando de comentários pra separar:</span>\n<span class=\"kw\">public void</span> <span class=\"fn\">processOrder</span>(<span class=\"cls\">Order</span> p) {\n    <span class=\"com\">// validar</span>\n    <span class=\"kw\">if</span> (p.getItems().isEmpty()) <span class=\"kw\">throw new</span> <span class=\"cls\">InvalidOrderException</span>();\n    <span class=\"com\">// calcular total</span>\n    <span class=\"kw\">double</span> total = 0;\n    <span class=\"kw\">for</span> (<span class=\"kw\">var</span> item : p.getItems()) total += item.getPrice() * item.getQuantity();\n    <span class=\"com\">// notificar</span>\n    notifier.send(<span class=\"str\">\"Order of R$\"</span> + total);\n}\n\n<span class=\"com\">// ✅ cada intenção é um método nomeado -- os comentários viram desnecessários:</span>\n<span class=\"kw\">public void</span> <span class=\"fn\">processOrder</span>(<span class=\"cls\">Order</span> p) {\n    validate(p);\n    <span class=\"kw\">double</span> total = calculateTotal(p);\n    notifyCustomer(total);\n}","caption":"Exemplo executável de clean-code.","explanation":["A refatoração separa intenção em passos nomeados.","Cada extração deve manter o mesmo comportamento observável."],"commonMistakes":["Extrair método com nome genérico","Mudar regra junto com limpeza"]},{"id":"clean-code-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Comentários que descrevem <em>o que</em> o código faz costumam ser um sintoma, não uma virtude — se você precisa explicar \"isso calcula o total\", o próprio nome do método deveria dizer isso (<code>calcularTotal</code>), sem exigir tradução extra. Comentários bons explicam o <strong>porquê</strong> de uma decisão não óbvia (por que esse algoritmo específico, por que essa exceção é ignorada de propósito), nunca o <em>o que</em> — isso o código já diz por si.</div>","fidelityText":"Comentários que descrevem o que o código faz costumam ser um sintoma, não uma virtude — se você precisa explicar \"isso calcula o total\", o próprio nome do método deveria dizer isso (calcularTotal), sem exigir tradução extra. Comentários bons explicam o porquê de uma decisão não óbvia (por que esse algoritmo específico, por que essa exceção é ignorada de propósito), nunca o o que — isso o código já diz por si."},{"id":"clean-code-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Refatoração: mudando a estrutura sem mudar o comportamento</h2>","fidelityText":"Refatoração: mudando a estrutura sem mudar o comportamento"},{"id":"clean-code-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Refatorar só é seguro com uma rede de segurança — é por isso que o capítulo 15 (testes) e o TDD vieram antes deste capítulo, não depois. Sem testes, \"refatorar\" é só \"reescrever e torcer\".</p>","fidelityText":"Refatorar só é seguro com uma rede de segurança — é por isso que o capítulo 15 (testes) e o TDD vieram antes deste capítulo, não depois. Sem testes, \"refatorar\" é só \"reescrever e torcer\"."},{"id":"clean-code-content-13","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Refatoração</th><th>Quando aplicar</th></tr>\n        <tr><td><strong>Extract Method</strong></td><td>Um bloco de código dentro de um método merece seu próprio nome</td></tr>\n        <tr><td><strong>Rename Variable/Method</strong></td><td>O nome atual não comunica a intenção real</td></tr>\n        <tr><td><strong>Replace Conditional with Polymorphism</strong></td><td>Um <code>if/else</code> ou <code>switch</code> gigante decidindo comportamento por tipo — vira o padrão do exercício 6.1 (capítulo 06)</td></tr>\n        <tr><td><strong>Introduce Parameter Object</strong></td><td>Um método com 5+ parâmetros — agrupe em um objeto com nome próprio</td></tr>\n      </tbody></table>","fidelityText":"RefatoraçãoQuando aplicar Extract MethodUm bloco de código dentro de um método merece seu próprio nome Rename Variable/MethodO nome atual não comunica a intenção real Replace Conditional with PolymorphismUm if/else ou switch gigante decidindo comportamento por tipo — vira o padrão do exercício 6.1 (capítulo 06) Introduce Parameter ObjectUm método com 5+ parâmetros — agrupe em um objeto com nome próprio"},{"id":"clean-code-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">\"Código limpo\" não é sobre estética ou preferência pessoal — é sobre o <strong>custo real de manutenção</strong>. Estudos e experiência de mercado convergem na mesma observação: código é lido muito mais vezes do que é escrito. Um método que economiza 30 segundos para escrever hoje, mas exige 10 minutos para entender daqui a 6 meses (por você mesmo ou por outra pessoa), foi um mau negócio, mesmo que tenha \"funcionado\" no primeiro momento.</div>","fidelityText":"\"Código limpo\" não é sobre estética ou preferência pessoal — é sobre o custo real de manutenção. Estudos e experiência de mercado convergem na mesma observação: código é lido muito mais vezes do que é escrito. Um método que economiza 30 segundos para escrever hoje, mas exige 10 minutos para entender daqui a 6 meses (por você mesmo ou por outra pessoa), foi um mau negócio, mesmo que tenha \"funcionado\" no primeiro momento."},{"id":"clean-code-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Cuidado com o perfeccionismo paralisante:</b> Clean Code é uma bússola, não uma lei absoluta. Código \"bom o suficiente\" que resolve o problema e vai para produção vale mais que código \"perfeito\" que nunca sai do rascunho. A regra prática do Boy Scout — \"deixe o código um pouco melhor do que você encontrou\" — é mais sustentável do que tentar reescrever tudo perfeitamente de uma vez.</div>","fidelityText":"Cuidado com o perfeccionismo paralisante: Clean Code é uma bússola, não uma lei absoluta. Código \"bom o suficiente\" que resolve o problema e vai para produção vale mais que código \"perfeito\" que nunca sai do rascunho. A regra prática do Boy Scout — \"deixe o código um pouco melhor do que você encontrou\" — é mais sustentável do que tentar reescrever tudo perfeitamente de uma vez."},{"id":"clean-code-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Pratique refatoração em código que <strong>você mesmo já escreveu</strong> antes de tentar em código de terceiros ou de produção real. Pegue qualquer exercício resolvido nos capítulos anteriores deste curso e pergunte: \"se eu tivesse que explicar este método para alguém só pelo nome, sem ler o corpo, ele contaria a história certa?\"</div>","fidelityText":"Pratique refatoração em código que você mesmo já escreveu antes de tentar em código de terceiros ou de produção real. Pegue qualquer exercício resolvido nos capítulos anteriores deste curso e pergunte: \"se eu tivesse que explicar este método para alguém só pelo nome, sem ler o corpo, ele contaria a história certa?\""},{"id":"clean-code-exercise-17","type":"exercise","authorship":"legacy-preserved","title":"Exercício 74.1 — Refatorando um método confuso","prompt":"Refatore o método abaixo aplicando Extract Method e nomes melhores, sem mudar o comportamento:","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 74.1 — Refatorando um método confusomédio Refatore o método abaixo aplicando Extract Method e nomes melhores, sem mudar o comportamento: public double calc(List<Item> l) { double t = 0; for (Item i : l) { double p = i.getPreco(); if (i.getQtd() > 10) p = p * 0.9; // desconto t += p * i.getQtd(); } if (t > 500) t = t * 0.95; // desconto adicional return t; } Ver solução public double calcularTotalComDescontos(List<Item> itens) { double subtotal = calcularSubtotal(itens); return aplicarDescontoPorVolumeTotal(subtotal); } private double calcularSubtotal(List<Item> itens) { double subtotal = 0; for (Item item : itens) { subtotal += precoComDescontoPorQuantidade(item); } return subtotal; } private double precoComDescontoPorQuantidade(Item item) { double preco = item.getPreco(); boolean qualificaDescontoVolume = item.getQtd() > 10; if (qualificaDescontoVolume) preco *= 0.9; return preco * item.getQtd(); } private double aplicarDescontoPorVolumeTotal(double subtotal) { boolean qualificaDescontoTotal = subtotal > 500; return qualificaDescontoTotal ? subtotal * 0.95 : subtotal; }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 74.1 — Refatorando um método confuso</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Refatore o método abaixo aplicando Extract Method e nomes melhores, sem mudar o comportamento:</p>\n        <pre class=\"code\"><span class=\"kw\">public double</span> <span class=\"fn\">calc</span>(List&lt;<span class=\"cls\">Item</span>&gt; l) {\n    <span class=\"kw\">double</span> t = 0;\n    <span class=\"kw\">for</span> (<span class=\"cls\">Item</span> i : l) {\n        <span class=\"kw\">double</span> p = i.getPrice();\n        <span class=\"kw\">if</span> (i.getQuantity() &gt; 10) p = p * 0.9; <span class=\"com\">// desconto</span>\n        t += p * i.getQuantity();\n    }\n    <span class=\"kw\">if</span> (t &gt; 500) t = t * 0.95; <span class=\"com\">// desconto adicional</span>\n    <span class=\"kw\">return</span> t;\n}</pre>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public double</span> <span class=\"fn\">calculateTotalWithDiscounts</span>(List&lt;<span class=\"cls\">Item</span>&gt; items) {\n    <span class=\"kw\">double</span> subtotal = calculateSubtotal(items);\n    <span class=\"kw\">return</span> applyDiscountByVolumeTotal(subtotal);\n}\n\n<span class=\"kw\">private double</span> <span class=\"fn\">calculateSubtotal</span>(List&lt;<span class=\"cls\">Item</span>&gt; items) {\n    <span class=\"kw\">double</span> subtotal = 0;\n    <span class=\"kw\">for</span> (<span class=\"cls\">Item</span> item : items) {\n        subtotal += priceWithDiscountByQuantity(item);\n    }\n    <span class=\"kw\">return</span> subtotal;\n}\n\n<span class=\"kw\">private double</span> <span class=\"fn\">priceWithDiscountByQuantity</span>(<span class=\"cls\">Item</span> item) {\n    <span class=\"kw\">double</span> price = item.getPrice();\n    <span class=\"kw\">boolean</span> qualificaDiscountVolume = item.getQuantity() &gt; 10;\n    <span class=\"kw\">if</span> (qualificaDiscountVolume) price *= 0.9;\n    <span class=\"kw\">return</span> price * item.getQuantity();\n}\n\n<span class=\"kw\">private double</span> <span class=\"fn\">applyDiscountByVolumeTotal</span>(<span class=\"kw\">double</span> subtotal) {\n    <span class=\"kw\">boolean</span> qualificaDiscountTotal = subtotal &gt; 500;\n    <span class=\"kw\">return</span> qualificaDiscountTotal ? subtotal * 0.95 : subtotal;\n}</pre>\n        </div>\n      </div>"},{"id":"clean-error","type":"error-case","authorship":"authored","title":"Refatoração que mudou regra sem perceber","scenario":"Você extrai métodos e simplifica condicionais em cálculo de desconto.","symptom":"O teste de caso limite passa a dar valor diferente para cliente bloqueado ou pedido vazio.","cause":"A mudança alterou ordem/condição de uma regra, então não era apenas refatoração.","diagnosis":["Crie teste de caracterização antes de mexer","Compare saídas antes/depois","Separe renomeação/extração de mudança de regra","Faça commit pequeno por transformação"],"correction":"Restaure comportamento e aplique uma transformação estrutural por vez com testes verdes.","prevention":"Toda refatoração relevante precisa de rede de segurança e escopo pequeno."},{"id":"clean-quiz","type":"quiz","authorship":"authored","conceptId":"refatoracao-rede-seguranca","prompt":"O que diferencia refatoração de reescrita funcional?","options":[{"id":"clean-q-a","label":"Refatoração muda estrutura preservando comportamento observado.","correct":true,"explanation":"Por isso testes/caracterização são a rede de segurança."},{"id":"clean-q-b","label":"Refatoração sempre remove testes antigos.","correct":false,"explanation":"Testes são proteção, não lixo."},{"id":"clean-q-c","label":"Refatoração é trocar Java por framework.","correct":false,"explanation":"Framework muda tecnologia; não define preservação de comportamento."}]}],"resources":[{"id":"refactoring-catalog","type":"guide","title":"Refactoring Catalog","url":"https://refactoring.com/catalog/","reinforces":"Catálogo clássico de transformações estruturais pequenas.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"google-java-style","type":"reference","title":"Google Java Style Guide","url":"https://google.github.io/styleguide/javaguide.html","reinforces":"Convenções explícitas de legibilidade e consistência para Java.","language":"en","publisher":"Google","official":false,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A clean code operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a clean code operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this clean code chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"int d; // dias desde a última atualização?","instruction":"A clean code operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this clean code chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"projetospring","moduleId":"application-design","order":5,"title":"Projeto final — mini-framework pré-Spring","summary":"O projeto que fecha a trilha pré-Spring: um container de injeção de dependência baseado em anotações, construído do zero. Depois deste projeto, quando você abrir seu primeiro projeto Spring Boot, @Component e @Autowired vão parecer familiares — porque você já construiu uma versão simplificada deles com as próprias mãos.","objectives":["Construir um mini-framework didático antes do Spring real","Combinar annotations, reflection, DI manual e dispatcher","Separar roteamento, criação de dependências e regra de negócio","Documentar limites do experimento sem prometer produção"],"whyItExists":"O projeto final pré-Spring consolida design de aplicação mostrando o mínimo que um framework automatiza. Depois disso, Spring deixa de ser magia e vira uma implementação industrial de ideias já praticadas.","prerequisiteChapterIds":["anotacoes","di","padroes","clean-code"],"conceptIds":["projeto-final-mini-framework-pre-spring"],"introducedConceptIds":["mini-framework-dispatcher"],"usedConceptIds":["annotation-metadata-contract","reflection-runtime-introspection","di-composicao-raiz","strategy-policy-object","refatoracao-rede-seguranca"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"projetospring-intuition","type":"intuition","authorship":"authored","title":"Mini-framework é microscópio, não produto","body":"Você cria algo pequeno parecido com framework para enxergar as peças: metadado, descoberta, criação, despacho e chamada. O objetivo é estudar mecanismo, não competir com Spring.","analogyLimit":"Microscópio ajuda porque amplia uma parte pequena; não significa que a amostra é o organismo inteiro."},{"id":"projetospring-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: todos os capítulos 18-27</div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: todos os capítulos 18-27"},{"id":"projetospring-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O projeto que fecha a trilha pré-Spring: um <strong>container de injeção de dependência baseado em anotações</strong>, construído do zero. Depois deste projeto, quando você abrir seu primeiro projeto Spring Boot, <code>@Component</code> e <code>@Autowired</code> vão parecer familiares — porque você já construiu uma versão simplificada deles com as próprias mãos.</p>","fidelityText":"O projeto que fecha a trilha pré-Spring: um container de injeção de dependência baseado em anotações, construído do zero. Depois deste projeto, quando você abrir seu primeiro projeto Spring Boot, @Component e @Autowired vão parecer familiares — porque você já construiu uma versão simplificada deles com as próprias mãos."},{"id":"projetospring-checklist-3","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"projetospring-checklist-0","label":"Crie sua própria anotação @Componente (@Retention(RUNTIME), @Target(TYPE)), marcando classes que o container deve gerenciar."},{"id":"projetospring-checklist-1","label":"Crie sua própria anotação @Injetar (@Target(FIELD)), marcando campos que o container deve preencher automaticamente."},{"id":"projetospring-checklist-2","label":"Implemente uma classe MiniContainer com um método registrar(Class<?> tipo) que guarda os tipos anotados com @Componente em um Map<Class<?>, Object> (as instâncias criadas — reaproveitando a ideia de Singleton do capítulo 23)."},{"id":"projetospring-checklist-3","label":"Implemente iniciar(): para cada tipo registrado, cria a instância via reflection (construtor sem argumentos) e guarda no map."},{"id":"projetospring-checklist-4","label":"Implemente a injeção: depois de todas as instâncias criadas, percorra os campos de cada uma via getDeclaredFields(); para cada campo com @Injetar, busque a instância do tipo certo no map e injete com Field.set(...) (setAccessible(true) antes)."},{"id":"projetospring-checklist-5","label":"Implemente <T> T resolver(Class<T> tipo), devolvendo a instância já criada e com as dependências injetadas."},{"id":"projetospring-checklist-6","label":"Aplique o container nas classes Biblioteca e NotificadorEmprestimo/EmailServico dos capítulos anteriores: anote-as com @Componente, o campo de dependência com @Injetar, registre ambas no container, chame iniciar() e resolva Biblioteca pronta para uso, sem nenhum new manual no código cliente."}]},{"id":"projetospring-exercise-4","type":"exercise","authorship":"legacy-preserved","title":"Gabarito completo do mini-container","prompt":"Uma possível solução, reunindo anotações, reflection, generics, coleções e o padrão de injeção de dependência vistos na trilha inteira:","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Gabarito completo do mini-containerprojeto Uma possível solução, reunindo anotações, reflection, generics, coleções e o padrão de injeção de dependência vistos na trilha inteira: Ver solução completa @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Componente {} @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Injetar {} public class MiniContainer { private final List<Class<?>> tiposRegistrados = new ArrayList<>(); private final Map<Class<?>, Object> instancias = new HashMap<>(); public void registrar(Class<?> tipo) { if (!tipo.isAnnotationPresent(Componente.class)) { throw new IllegalArgumentException(tipo.getName() + \" não é @Componente\"); } tiposRegistrados.add(tipo); } public void iniciar() throws ReflectiveOperationException { // 1ª passada: cria todas as instâncias (construtor sem argumentos) for (Class<?> tipo : tiposRegistrados) { Object instancia = tipo.getDeclaredConstructor().newInstance(); instancias.put(tipo, instancia); } // 2ª passada: injeta dependências em cada campo @Injetar for (Object instancia : instancias.values()) { for (Field campo : instancia.getClass().getDeclaredFields()) { if (campo.isAnnotationPresent(Injetar.class)) { Object dependencia = resolverPorTipoCompativel(campo.getType()); campo.setAccessible(true); campo.set(instancia, dependencia); } } } } private Object resolverPorTipoCompativel(Class<?> tipoAlvo) { return instancias.values().stream() .filter(i -> tipoAlvo.isAssignableFrom(i.getClass())) // aceita interface OU implementação exata .findFirst() .orElseThrow(() -> new IllegalStateException(\"Nenhum componente compatível com \" + tipoAlvo.getName())); } @SuppressWarnings(\"unchecked\") public <T> T resolver(Class<T> tipo) { return (T) instancias.get(tipo); } } // --- usando o mini-container --- @Componente public class EmailServico implements NotificadorEmprestimo { @Override public void notificar(String msg) { System.out.println(\"[email] \" + msg); } } @Componente public class Biblioteca { @Injetar private NotificadorEmprestimo notificador; // preenchido automaticamente pelo container! private final List<ItemAcervo> acervo = new ArrayList<>(); public void adicionar(ItemAcervo i) { acervo.add(i); } public void emprestar(String codigo) { /* ... usa notificador normalmente ... */ } } // main: MiniContainer container = new MiniContainer(); container.registrar(EmailServico.class); container.registrar(Biblioteca.class); container.iniciar(); Biblioteca biblioteca = container.resolver(Biblioteca.class); // biblioteca já está pronta, com \"notificador\" injetado -- ZERO \"new\" manual! Compare este MiniContainer com @SpringBootApplication: a ideia central — descobrir definições, instanciar, resolver por tipo e coordenar ciclo de vida — é semelhante. O Spring acrescenta escopos, qualificadores, callbacks, proxies, post-processors e scan de pacotes. Dependências circulares não são um recurso a ser “resolvido”: ciclos por construtor falham e normalmente revelam responsabilidades mal separadas.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Gabarito completo do mini-container</h2><span class=\"exercise-tag d\">projeto</span></div>\n        <p>Uma possível solução, reunindo anotações, reflection, generics, coleções e o padrão de injeção de dependência vistos na trilha inteira:</p>\n        <button class=\"reveal-btn\">Ver solução completa</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Retention</span>(RetentionPolicy.RUNTIME)\n<span class=\"annotation\">@Target</span>(ElementType.TYPE)\n<span class=\"kw\">public @interface</span> <span class=\"cls\">Componente</span> {}\n\n<span class=\"annotation\">@Retention</span>(RetentionPolicy.RUNTIME)\n<span class=\"annotation\">@Target</span>(ElementType.FIELD)\n<span class=\"kw\">public @interface</span> <span class=\"cls\">Injetar</span> {}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">MiniContainer</span> {\n    <span class=\"kw\">private final</span> List&lt;Class&lt;?&gt;&gt; typesRegistrados = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n    <span class=\"kw\">private final</span> Map&lt;Class&lt;?&gt;, <span class=\"kw\">Object</span>&gt; instances = <span class=\"kw\">new</span> HashMap&lt;&gt;();\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">register</span>(Class&lt;?&gt; type) {\n        <span class=\"kw\">if</span> (!type.isAnnotationPresent(Componente.<span class=\"kw\">class</span>)) {\n            <span class=\"kw\">throw new</span> <span class=\"cls\">IllegalArgumentException</span>(type.getName() + <span class=\"str\">\" not and @Componente\"</span>);\n        }\n        typesRegistrados.add(type);\n    }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">start</span>() <span class=\"kw\">throws</span> <span class=\"cls\">ReflectiveOperationException</span> {\n        <span class=\"com\">// 1ª passada: cria todas as instâncias (construtor sem argumentos)</span>\n        <span class=\"kw\">for</span> (Class&lt;?&gt; type : typesRegistrados) {\n            <span class=\"kw\">Object</span> instance = type.getDeclaredConstructor().newInstance();\n            instances.put(type, instance);\n        }\n\n        <span class=\"com\">// 2ª passada: injeta dependências em cada campo @Injetar</span>\n        <span class=\"kw\">for</span> (<span class=\"kw\">Object</span> instance : instances.values()) {\n            <span class=\"kw\">for</span> (Field field : instance.getClass().getDeclaredFields()) {\n                <span class=\"kw\">if</span> (field.isAnnotationPresent(Injetar.<span class=\"kw\">class</span>)) {\n                    <span class=\"kw\">Object</span> dependencia = resolveByTypeCompativel(field.getType());\n                    field.setAccessible(<span class=\"kw\">true</span>);\n                    field.set(instance, dependencia);\n                }\n            }\n        }\n    }\n\n    <span class=\"kw\">private</span> <span class=\"kw\">Object</span> <span class=\"fn\">resolveByTypeCompativel</span>(Class&lt;?&gt; typeAlvo) {\n        <span class=\"kw\">return</span> instances.values().stream()\n            .filter(i -&gt; typeAlvo.isAssignableFrom(i.getClass())) <span class=\"com\">// aceita interface OU implementação exata</span>\n            .findFirst()\n            .orElseThrow(() -&gt; <span class=\"kw\">new</span> <span class=\"cls\">IllegalStateException</span>(<span class=\"str\">\"No componente compatible with \"</span> + typeAlvo.getName()));\n    }\n\n    <span class=\"annotation\">@SuppressWarnings</span>(<span class=\"str\">\"unchecked\"</span>)\n    <span class=\"kw\">public</span> &lt;T&gt; T <span class=\"fn\">resolve</span>(Class&lt;T&gt; type) {\n        <span class=\"kw\">return</span> (T) instances.get(type);\n    }\n}\n\n<span class=\"com\">// --- usando o mini-container ---</span>\n<span class=\"annotation\">@Componente</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">EmailService</span> <span class=\"kw\">implements</span> <span class=\"cls\">NotifierLoan</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">notify</span>(<span class=\"kw\">String</span> msg) { System.out.println(<span class=\"str\">\"[email] \"</span> + msg); }\n}\n\n<span class=\"annotation\">@Componente</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Library</span> {\n    <span class=\"annotation\">@Injetar</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">NotifierLoan</span> notifier; <span class=\"com\">// preenchido automaticamente pelo container!</span>\n\n    <span class=\"kw\">private final</span> List&lt;<span class=\"cls\">ItemCatalog</span>&gt; catalog = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n    <span class=\"kw\">public void</span> <span class=\"fn\">add</span>(<span class=\"cls\">ItemCatalog</span> i) { catalog.add(i); }\n    <span class=\"kw\">public void</span> <span class=\"fn\">borrow</span>(<span class=\"kw\">String</span> code) { <span class=\"com\">/* ... usa notificador normalmente ... */</span> }\n}\n\n<span class=\"com\">// main:</span>\n<span class=\"cls\">MiniContainer</span> container = <span class=\"kw\">new</span> <span class=\"cls\">MiniContainer</span>();\ncontainer.register(<span class=\"cls\">EmailService</span>.<span class=\"kw\">class</span>);\ncontainer.register(<span class=\"cls\">Library</span>.<span class=\"kw\">class</span>);\ncontainer.start();\n\n<span class=\"cls\">Library</span> library = container.resolve(<span class=\"cls\">Library</span>.<span class=\"kw\">class</span>);\n<span class=\"com\">// biblioteca já está pronta, com \"notificador\" injetado -- ZERO \"new\" manual!</span></pre>\n          <p style=\"margin-top:12px\">Compare este <code>MiniContainer</code> com <code>@SpringBootApplication</code>: a ideia central — descobrir definições, instanciar, resolver por tipo e coordenar ciclo de vida — é semelhante. O Spring acrescenta escopos, qualificadores, callbacks, proxies, post-processors e scan de pacotes. Dependências circulares não são um recurso a ser “resolvido”: ciclos por construtor falham e normalmente revelam responsabilidades mal separadas.</p>\n        </div>\n      </div>"},{"id":"projetospring-project","type":"project","authorship":"authored","title":"Mini-framework pré-Spring","brief":"Implemente um dispatcher mínimo que descobre handlers anotados, instancia dependências explícitas e executa comandos de exemplo sem servidor HTTP real.","requirements":["Annotation própria marca handler e ação","Reflection descobre métodos anotados em classes registradas","Composition root registra implementações concretas","Dispatcher separa parsing da chamada do handler","Erros de rota, método inválido e dependência ausente têm mensagem segura","README explica o que isso ensina e o que não deve ir para produção"],"guidance":"bounded","acceptanceCriteria":["Não há regra de negócio dentro do mecanismo de reflection","O domínio é testável sem o dispatcher","Dependências não são resolvidas por global service locator","Existe teste para rota inexistente e dependência ausente","Limitações são documentadas explicitamente"],"knowledgeMatrix":[{"requirement":"Metadado lido em runtime","conceptIds":["annotation-metadata-contract","reflection-runtime-introspection"],"chapterIds":["anotacoes"],"expectedEvidence":"Teste demonstra annotation descoberta e método invocado pelo dispatcher."},{"requirement":"Composição explícita","conceptIds":["di-composicao-raiz","ioc-container-registro-resolucao"],"chapterIds":["di"],"expectedEvidence":"Composition root registra adapters e casos de uso sem new espalhado no domínio."},{"requirement":"Extensão controlada","conceptIds":["strategy-policy-object","ocp-polimorfismo-extensao"],"chapterIds":["solid","padroes"],"expectedEvidence":"Nova ação entra sem alterar o núcleo do dispatcher."},{"requirement":"Refatoração segura","conceptIds":["refatoracao-rede-seguranca","teste-aaa-first"],"chapterIds":["clean-code","testes"],"expectedEvidence":"Testes protegem dispatcher antes de extrair métodos/classes."}]},{"id":"projetospring-quiz","type":"quiz","authorship":"authored","conceptId":"mini-framework-dispatcher","prompt":"Qual é o principal objetivo do mini-framework pré-Spring?","options":[{"id":"ps-q-a","label":"Entender os mecanismos que frameworks reais automatizam em escala maior.","correct":true,"explanation":"O projeto é didático: annotations, reflection, DI e dispatch."},{"id":"ps-q-b","label":"Substituir Spring Boot em produção.","correct":false,"explanation":"Ele não cobre segurança, performance, HTTP real, lifecycle completo nem ecossistema."},{"id":"ps-q-c","label":"Evitar aprender DI e reflection separadamente.","correct":false,"explanation":"O projeto depende exatamente desses conceitos."}]}],"resources":[{"id":"front-controller-pattern","type":"guide","title":"Front Controller pattern","url":"https://martinfowler.com/eaaCatalog/frontController.html","reinforces":"Ideia de dispatcher central que recebe entrada e delega processamento.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"constructor-api-java21","type":"reference","title":"Constructor API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Constructor.html","reinforces":"Base reflexiva para criação controlada de objetos em experimento didático.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A pre-Spring mini framework operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a pre-Spring mini framework operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this pre-Spring mini framework chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"@Retention(RetentionPolicy.RUNTIME)","instruction":"A pre-Spring mini framework operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this pre-Spring mini framework chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"http","moduleId":"http-api-clients","order":0,"title":"HTTP & APIs REST","summary":"O Spring MVC / Spring Web (o que você vai usar para criar APIs) é uma camada de conveniência em cima do protocolo HTTP e do estilo arquitetural REST. Entender os conceitos puros evita que anotações como @GetMapping pareçam arbitrárias.","objectives":["Separar request, response, método, target, headers e body","Escolher método e status por semântica, não por anotação futura","Entender idempotência, Content-Type, Accept e Authorization","Desenhar recursos HTTP antes de framework"],"whyItExists":"Depois de JSON e testes, o aluno pode estudar HTTP como protocolo de integração síncrona antes de qualquer controller Spring. Isso evita decorar anotações sem saber o que atravessa a rede.","prerequisiteChapterIds":["testes","json"],"conceptIds":["metodos-http-e-o-que-cada-um-significa","codigos-de-status-o-vocabulario-de-uma-resposta-http","anatomia-de-uma-requisicao-e-resposta","rest-recursos-nao-acoes","cliente-http-em-java-puro"],"introducedConceptIds":["http-mensagem-recurso","http-metodo-semantica","http-status-classe","http-idempotencia-seguranca","http-header-body-negociacao"],"usedConceptIds":["json-formato-contrato","serializacao-desserializacao","teste-aaa-first"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"http-intuition","type":"intuition","authorship":"authored","title":"Antes do framework existe uma mensagem","body":"HTTP é uma troca de mensagens entre cliente e servidor. O método expressa intenção, o alvo identifica o recurso, headers carregam metadados e o body carrega uma representação opcional.","analogyLimit":"Carta ajuda a imaginar envelope e conteúdo, mas HTTP tem cache, negociação, autenticação, idempotência, intermediários e status padronizados."},{"id":"http-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#json\">25 · JSON &amp; serialização</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: 25 · JSON & serialização"},{"id":"http-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O Spring MVC / Spring Web (o que você vai usar para criar APIs) é uma camada de conveniência em cima do protocolo <strong>HTTP</strong> e do estilo arquitetural <strong>REST</strong>. Entender os conceitos puros evita que anotações como <code>@GetMapping</code> pareçam arbitrárias.</p>","fidelityText":"O Spring MVC / Spring Web (o que você vai usar para criar APIs) é uma camada de conveniência em cima do protocolo HTTP e do estilo arquitetural REST. Entender os conceitos puros evita que anotações como @GetMapping pareçam arbitrárias."},{"id":"http-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Métodos HTTP e o que cada um significa</h2>","fidelityText":"Métodos HTTP e o que cada um significa"},{"id":"http-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Método</th><th>Uso convencional (REST)</th><th>Idempotente?</th></tr>\n        <tr><td><code>GET</code></td><td>Buscar um recurso, sem efeitos colaterais</td><td>Sim</td></tr>\n        <tr><td><code>POST</code></td><td>Criar um novo recurso</td><td>Não</td></tr>\n        <tr><td><code>PUT</code></td><td>Substituir um recurso inteiro por completo</td><td>Sim</td></tr>\n        <tr><td><code>PATCH</code></td><td>Atualizar parcialmente um recurso</td><td>Não, em geral</td></tr>\n        <tr><td><code>DELETE</code></td><td>Remover um recurso</td><td>Sim</td></tr>\n      </tbody></table>","fidelityText":"MétodoUso convencional (REST)Idempotente? GETBuscar um recurso, sem efeitos colateraisSim POSTCriar um novo recursoNão PUTSubstituir um recurso inteiro por completoSim PATCHAtualizar parcialmente um recursoNão, em geral DELETERemover um recursoSim"},{"id":"http-content-5","type":"html","authorship":"legacy-preserved","html":"<p><strong>Idempotente</strong> significa: repetir a mesma requisição várias vezes tem o mesmo efeito de fazê-la uma única vez. <code>DELETE /livros/5</code> chamado duas vezes deixa o sistema no mesmo estado (livro removido); <code>POST /livros</code> chamado duas vezes cria dois livros.</p>","fidelityText":"Idempotente significa: repetir a mesma requisição várias vezes tem o mesmo efeito de fazê-la uma única vez. DELETE /livros/5 chamado duas vezes deixa o sistema no mesmo estado (livro removido); POST /livros chamado duas vezes cria dois livros."},{"id":"http-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Códigos de status — o vocabulário de uma resposta HTTP</h2>","fidelityText":"Códigos de status — o vocabulário de uma resposta HTTP"},{"id":"http-content-7","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Faixa</th><th>Significado</th><th>Exemplos comuns</th></tr>\n        <tr><td>2xx</td><td>Sucesso</td><td><code>200 OK</code>, <code>201 Created</code>, <code>204 No Content</code></td></tr>\n        <tr><td>3xx</td><td>Redirecionamento</td><td><code>301 Moved Permanently</code></td></tr>\n        <tr><td>4xx</td><td>Erro do cliente</td><td><code>400 Bad Request</code>, <code>401 Unauthorized</code>, <code>403 Forbidden</code>, <code>404 Not Found</code></td></tr>\n        <tr><td>5xx</td><td>Erro do servidor</td><td><code>500 Internal Server Error</code></td></tr>\n      </tbody></table>","fidelityText":"FaixaSignificadoExemplos comuns 2xxSucesso200 OK, 201 Created, 204 No Content 3xxRedirecionamento301 Moved Permanently 4xxErro do cliente400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found 5xxErro do servidor500 Internal Server Error"},{"id":"http-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Anatomia de uma requisição e resposta</h2>","fidelityText":"Anatomia de uma requisição e resposta"},{"id":"http-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"--- REQUISIÇÃO ---\nPOST /books HTTP/1.1\nHost: api.library.com\nContent-Type: application/json\nAuthorization: Bearer eyJhbGc...\n\n{\"title\": \"1984\", \"author\": \"Orwell\", \"pages\": 328}\n\n--- RESPOSTA ---\nHTTP/1.1 201 Created\nContent-Type: application/json\nLocation: /books/42\n\n{\"id\": 42, \"title\": \"1984\", \"author\": \"Orwell\", \"pages\": 328}","fidelityText":"--- REQUISIÇÃO --- POST /livros HTTP/1.1 Host: api.biblioteca.com Content-Type: application/json Authorization: Bearer eyJhbGc... {\"titulo\": \"1984\", \"autor\": \"Orwell\", \"paginas\": 328} --- RESPOSTA --- HTTP/1.1 201 Created Content-Type: application/json Location: /livros/42 {\"id\": 42, \"titulo\": \"1984\", \"autor\": \"Orwell\", \"paginas\": 328}","highlightedHtml":"<span class=\"com\">--- REQUISIÇÃO ---</span>\nPOST /books HTTP/1.1\nHost: api.library.com\nContent-Type: application/json\nAuthorization: Bearer eyJhbGc...\n\n{\"title\": \"1984\", \"author\": \"Orwell\", \"pages\": 328}\n\n<span class=\"com\">--- RESPOSTA ---</span>\nHTTP/1.1 201 Created\nContent-Type: application/json\nLocation: /books/42\n\n{\"id\": 42, \"title\": \"1984\", \"author\": \"Orwell\", \"pages\": 328}","caption":"Exemplo executável de http.","explanation":["A mensagem separa linha inicial, headers e body.","Content-Type informa como interpretar o JSON enviado ou recebido.","Location em 201 aponta a URI do recurso criado."],"commonMistakes":["Misturar header e body","Achar que Authorization é seguro para log","Retornar 201 sem recurso identificável"]},{"id":"http-content-10","type":"html","authorship":"legacy-preserved","html":"<p>O <strong>header</strong> <code>Content-Type: application/json</code> avisa ao servidor (e ao cliente, na resposta) que o corpo (<em>body</em>) da mensagem está em JSON — informação que o Jackson (capítulo 25) usa para decidir como interpretar os bytes.</p>","fidelityText":"O header Content-Type: application/json avisa ao servidor (e ao cliente, na resposta) que o corpo (body) da mensagem está em JSON — informação que o Jackson (capítulo 25) usa para decidir como interpretar os bytes."},{"id":"http-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>REST: recursos, não ações</h2>","fidelityText":"REST: recursos, não ações"},{"id":"http-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Uma API REST bem desenhada nomeia URLs como <strong>substantivos</strong> (recursos), e usa o <em>método HTTP</em> para expressar a ação — nunca verbos na URL.</p>","fidelityText":"Uma API REST bem desenhada nomeia URLs como substantivos (recursos), e usa o método HTTP para expressar a ação — nunca verbos na URL."},{"id":"http-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ estilo RPC, verbos na URL:\nGET /findBookById?id=5\nPOST /createNewBook\nPOST /deleteBook?id=5\n\n// ✅ estilo REST, recursos + métodos HTTP:\nGET    /books          // listar todos\nGET    /books/5        // buscar um específico\nPOST   /books          // criar\nPUT    /books/5        // substituir por completo\nDELETE /books/5        // remover","fidelityText":"// ❌ estilo RPC, verbos na URL: GET /buscarLivroPorId?id=5 POST /criarNovoLivro POST /deletarLivro?id=5 // ✅ estilo REST, recursos + métodos HTTP: GET /livros // listar todos GET /livros/5 // buscar um específico POST /livros // criar PUT /livros/5 // substituir por completo DELETE /livros/5 // remover","highlightedHtml":"<span class=\"com\">// ❌ estilo RPC, verbos na URL:</span>\nGET /findBookById?id=5\nPOST /createNewBook\nPOST /deleteBook?id=5\n\n<span class=\"com\">// ✅ estilo REST, recursos + métodos HTTP:</span>\nGET    /books          <span class=\"com\">// listar todos</span>\nGET    /books/5        <span class=\"com\">// buscar um específico</span>\nPOST   /books          <span class=\"com\">// criar</span>\nPUT    /books/5        <span class=\"com\">// substituir por completo</span>\nDELETE /books/5        <span class=\"com\">// remover</span>","caption":"Exemplo executável de http.","explanation":["URLs nomeiam recursos e métodos expressam intenção.","Sub-recursos podem representar transições quando CRUD simples não cobre a regra."],"commonMistakes":["Criar /criarAlgo e /deletarAlgo por hábito","Usar GET para mudar estado","Ignorar 409 em conflito de estado"]},{"id":"http-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Cada uma dessas linhas vai virar, quase literalmente, uma anotação Spring: <code>@GetMapping(\"/livros/{id}\")</code>, <code>@PostMapping(\"/livros\")</code>, <code>@PutMapping(\"/livros/{id}\")</code>, <code>@DeleteMapping(\"/livros/{id}\")</code>. Um <em>controller</em> Spring nada mais é do que uma classe cujos métodos são mapeados para combinações de \"verbo HTTP + caminho de URL\" — o mapeamento que você acabou de desenhar aqui em pseudo-REST.</div>","fidelityText":"Cada uma dessas linhas vai virar, quase literalmente, uma anotação Spring: @GetMapping(\"/livros/{id}\"), @PostMapping(\"/livros\"), @PutMapping(\"/livros/{id}\"), @DeleteMapping(\"/livros/{id}\"). Um controller Spring nada mais é do que uma classe cujos métodos são mapeados para combinações de \"verbo HTTP + caminho de URL\" — o mapeamento que você acabou de desenhar aqui em pseudo-REST."},{"id":"http-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Cliente HTTP em Java puro</h2>","fidelityText":"Cliente HTTP em Java puro"},{"id":"http-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"HttpClient client = HttpClient.newHttpClient();\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.library.com/books\"))\n    .header(\"Content-Type\", \"application/json\")\n    .POST(HttpRequest.BodyPublishers.ofString(json))\n    .build();\n\nHttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\nSystem.out.println(response.statusCode()); // 201\nSystem.out.println(response.body());       // JSON de resposta","fidelityText":"HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(\"https://api.biblioteca.com/livros\")) .header(\"Content-Type\", \"application/json\") .POST(HttpRequest.BodyPublishers.ofString(json)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); // 201 System.out.println(response.body()); // JSON de resposta","highlightedHtml":"HttpClient client = HttpClient.newHttpClient();\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(<span class=\"str\">\"https://api.library.com/books\"</span>))\n    .header(<span class=\"str\">\"Content-Type\"</span>, <span class=\"str\">\"application/json\"</span>)\n    .POST(HttpRequest.BodyPublishers.ofString(json))\n    .build();\n\nHttpResponse&lt;<span class=\"kw\">String</span>&gt; response = client.send(request, HttpResponse.BodyHandlers.ofString());\nSystem.out.println(response.statusCode()); <span class=\"com\">// 201</span>\nSystem.out.println(response.body());       <span class=\"com\">// JSON de resposta</span>","caption":"Exemplo executável de http.","explanation":["HttpRequest declara URI, headers e body publisher.","HttpClient envia e devolve status/body separadamente.","O exemplo é primeiro contato; a fase aprofunda timeout, reuso e falhas em capítulos estruturados."],"commonMistakes":["Criar cliente sem política de timeout","Tratar todo status como sucesso","Misturar DTO externo com domínio"]},{"id":"http-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>REST vs RPC vs GraphQL:</b> este curso foca em REST por ser o padrão histórico do Spring Web/Spring MVC (e o mais comum no mercado). Vale saber que existem alternativas (gRPC, GraphQL) que resolvem os mesmos problemas com trade-offs diferentes — mas dominar REST é o pré-requisito universal.</div>","fidelityText":"REST vs RPC vs GraphQL: este curso foca em REST por ser o padrão histórico do Spring Web/Spring MVC (e o mais comum no mercado). Vale saber que existem alternativas (gRPC, GraphQL) que resolvem os mesmos problemas com trade-offs diferentes — mas dominar REST é o pré-requisito universal."},{"id":"http-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Exercício 26.1 — Desenhando uma API REST","prompt":"Para o sistema de biblioteca dos capítulos anteriores, desenhe (no papel, sem implementar) os endpoints REST para: listar todos os itens, buscar um item por código, emprestar um item, devolver um item, e cadastrar um novo livro. Para cada um, defina o verbo HTTP, o caminho da URL, e o código de status esperado em caso de sucesso.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 26.1 — Desenhando uma API RESTmédio Para o sistema de biblioteca dos capítulos anteriores, desenhe (no papel, sem implementar) os endpoints REST para: listar todos os itens, buscar um item por código, emprestar um item, devolver um item, e cadastrar um novo livro. Para cada um, defina o verbo HTTP, o caminho da URL, e o código de status esperado em caso de sucesso. Ver solução GET /itens → 200 OK (lista todos) GET /itens/{codigo} → 200 OK ou 404 Not Found POST /itens → 201 Created (cadastra um novo livro) POST /itens/{codigo}/emprestimo → 200 OK ou 409 Conflict (já emprestado) POST /itens/{codigo}/devolucao → 200 OK Repare que \"emprestar\" e \"devolver\" não são recursos no sentido estrito, mas ações sobre um recurso — uma convenção comum é tratá-los como sub-recursos (POST /itens/{codigo}/emprestimo) em vez de verbos soltos na URL principal, mantendo o estilo REST mesmo quando a ação não é um CRUD simples.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 26.1 — Desenhando uma API REST</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Para o sistema de biblioteca dos capítulos anteriores, desenhe (no papel, sem implementar) os endpoints REST para: listar todos os itens, buscar um item por código, emprestar um item, devolver um item, e cadastrar um novo livro. Para cada um, defina o verbo HTTP, o caminho da URL, e o código de status esperado em caso de sucesso.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">GET    /items                 → 200 OK (list all)\nGET    /items/{code}        → 200 OK ou 404 Not Found\nPOST   /items                 → 201 Created (cadastra um new book)\nPOST   /items/{code}/loan → 200 OK ou 409 Conflict (already borrowed)\nPOST   /items/{code}/return  → 200 OK</pre>\n          <p style=\"margin-top:12px\">Repare que \"emprestar\" e \"devolver\" não são recursos no sentido estrito, mas <em>ações sobre um recurso</em> — uma convenção comum é tratá-los como sub-recursos (<code>POST /itens/{codigo}/emprestimo</code>) em vez de verbos soltos na URL principal, mantendo o estilo REST mesmo quando a ação não é um CRUD simples.</p>\n        </div>\n      </div>"},{"id":"http-comparison","type":"comparison","authorship":"authored","title":"Accept e Content-Type em direções diferentes","criteria":["pergunta","exemplo","erro comum"],"alternatives":[{"name":"Content-Type","values":["que formato estou enviando?","application/json no request com body","usar em GET sem body como se pedisse JSON"],"useWhen":"o corpo existe e precisa ser interpretado","avoidWhen":"declarar formato aceito pelo cliente"},{"name":"Accept","values":["que formato aceito receber?","Accept: application/json","confundir com formato do body enviado"],"useWhen":"cliente negocia representação da resposta","avoidWhen":"substituir validação do body"}]},{"id":"http-quiz","type":"quiz","authorship":"authored","conceptId":"http-idempotencia-seguranca","prompt":"Um POST de pagamento deu timeout. Por que repetir automaticamente pode ser perigoso?","options":[{"id":"http-q-a","label":"Porque o efeito remoto pode ter ocorrido e só a resposta ter se perdido.","correct":true,"explanation":"Timeout não é rollback. Repetição segura exige contrato como idempotency key ou consulta de estado."},{"id":"http-q-b","label":"Porque POST nunca pode enviar JSON.","correct":false,"explanation":"POST pode enviar JSON; o problema é repetir efeito não idempotente sem proteção."},{"id":"http-q-c","label":"Porque todo timeout significa que o servidor apagou a operação.","correct":false,"explanation":"Timeout diz que o cliente não recebeu resposta no prazo, não o que aconteceu no servidor."}]}],"resources":[{"id":"http-rfc9110","type":"reference","title":"RFC 9110: HTTP Semantics","url":"https://www.rfc-editor.org/rfc/rfc9110","reinforces":"Define métodos, status, representações, headers, idempotência e semântica HTTP.","language":"en","publisher":"IETF","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"http-mdn-overview","type":"guide","title":"MDN: Overview of HTTP","url":"https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Overview","reinforces":"Explicação complementar de mensagens, cliente/servidor, headers e fluxo HTTP.","language":"en","publisher":"MDN","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A http operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a http operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this http chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"--- REQUISIÇÃO ---","instruction":"A http operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this http chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"sql","moduleId":"relational-data-jdbc","order":0,"title":"SQL — fundamentos de banco relacional","summary":"Até aqui, todo \"banco de dados\" do curso foi um List ou Map em memória — os dados somem quando o programa fecha. SQL é a linguagem para conversar com um banco de dados relacional, que guarda dados permanentemente, organizados em tabelas.","objectives":["Modelar tabela, chave primária, chave estrangeira e cardinalidade","Separar DDL de DML","Usar SELECT, JOIN, agregação e transação com intenção explícita","Ler índice como estrutura a serviço de consulta real"],"whyItExists":"Depois de coleções, algoritmos e testes, o aluno já entende estrutura e evidência. SQL entra como linguagem declarativa para dados persistentes e compartilhados, não como planilha nem como detalhe do Spring.","prerequisiteChapterIds":["colecoes"],"conceptIds":["criando-tabelas-ddl-data-definition-language","crud-em-sql-insert-select-update-delete","join-combinando-tabelas-relacionadas","agregacoes-group-by-count-sum-avg","transacoes-tudo-ou-nada","indices-por-que-consultas-ficam-rapidas-ou-lentas"],"introducedConceptIds":["modelo-relacional-tabela-chave","ddl-schema-constraint","dml-crud-sql","join-cardinalidade-sql","agregacao-groupby-sql","sql-transacao-atomicidade","indice-plano-custo"],"usedConceptIds":["contrato-collection-map","estrutura-operacao-dominante","teste-aaa-first"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"sql-intuition","type":"intuition","authorship":"authored","title":"Banco relacional é contrato compartilhado","body":"Uma tabela não é só uma lista gravada em disco. Ela declara colunas, tipos, chaves e restrições que vários programas podem respeitar ao mesmo tempo.","analogyLimit":"Planilha ajuda a visualizar linhas e colunas, mas banco tem transações, isolamento, índices, constraints, concorrência e plano de execução."},{"id":"sql-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-db\">SQL</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~4h</b> de estudo + prática (é um capítulo denso — não tenha pressa)</div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#colecoes\">Coleções</a></div>\n      </div>","fidelityText":"SQL Dificuldade: Intermediário ⏱ ~4h de estudo + prática (é um capítulo denso — não tenha pressa) Pré-requisito: Coleções"},{"id":"sql-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Até aqui, todo \"banco de dados\" do curso foi um <code>List</code> ou <code>Map</code> em memória — os dados somem quando o programa fecha. SQL é a linguagem para conversar com um banco de dados <strong>relacional</strong>, que guarda dados permanentemente, organizados em tabelas.</p>","fidelityText":"Até aqui, todo \"banco de dados\" do curso foi um List ou Map em memória — os dados somem quando o programa fecha. SQL é a linguagem para conversar com um banco de dados relacional, que guarda dados permanentemente, organizados em tabelas."},{"id":"sql-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Como primeiro empurrão de intuição, pense em cada <strong>tabela</strong> como uma classe Java, e cada <strong>linha</strong> como um objeto daquela classe: colunas parecem atributos, e o tipo de cada coluna (INTEGER, VARCHAR, BOOLEAN) parece o tipo de cada atributo. A diferença mais visível é que, ao contrário de um <code>ArrayList</code>, os dados de uma tabela sobrevivem ao fechar o programa, porque vivem no disco, gerenciados pelo banco.</div>","fidelityText":"Como primeiro empurrão de intuição, pense em cada tabela como uma classe Java, e cada linha como um objeto daquela classe: colunas parecem atributos, e o tipo de cada coluna (INTEGER, VARCHAR, BOOLEAN) parece o tipo de cada atributo. A diferença mais visível é que, ao contrário de um ArrayList, os dados de uma tabela sobrevivem ao fechar o programa, porque vivem no disco, gerenciados pelo banco."},{"id":"sql-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Onde essa analogia para de valer:</b> uma tabela não é uma classe e uma linha não é um objeto Java. Uma coluna aceita <code>NULL</code> — \"ausência de valor\" — algo que um <code>int</code> Java não representa (só tipos referência podem ser <code>null</code>, e mesmo assim é um conceito diferente de <code>NULL</code> em SQL, que participa de uma lógica de três valores: verdadeiro, falso e <em>desconhecido</em>). Uma linha não tem identidade de objeto nem métodos — ela é só um conjunto de valores, e duas linhas com os mesmos valores em todas as colunas (sem chave primária que as diferencie) são indistinguíveis para o banco, o que nunca acontece com dois objetos Java distintos (que têm identidade mesmo sendo <code>equals</code>). E o banco não conhece relacionamentos \"navegáveis\" como <code>livro.getAutor()</code> — toda relação entre tabelas precisa ser reconstruída explicitamente a cada consulta, via <code>JOIN</code>. Trate a analogia como ponto de partida, não como modelo mental definitivo — o resto deste capítulo ensina o modelo relacional por si mesmo.</div>","fidelityText":"Onde essa analogia para de valer: uma tabela não é uma classe e uma linha não é um objeto Java. Uma coluna aceita NULL — \"ausência de valor\" — algo que um int Java não representa (só tipos referência podem ser null, e mesmo assim é um conceito diferente de NULL em SQL, que participa de uma lógica de três valores: verdadeiro, falso e desconhecido). Uma linha não tem identidade de objeto nem métodos — ela é só um conjunto de valores, e duas linhas com os mesmos valores em todas as colunas (sem chave primária que as diferencie) são indistinguíveis para o banco, o que nunca acontece com dois objetos Java distintos (que têm identidade mesmo sendo equals). E o banco não conhece relacionamentos \"navegáveis\" como livro.getAutor() — toda relação entre tabelas precisa ser reconstruída explicitamente a cada consulta, via JOIN. Trate a analogia como ponto de partida, não como modelo mental definitivo — o resto deste capítulo ensina o modelo relacional por si mesmo."},{"id":"sql-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Criando tabelas: DDL (Data Definition Language)</h2>","fidelityText":"Criando tabelas: DDL (Data Definition Language)"},{"id":"sql-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"CREATE TABLE authors (\n    id SERIAL PRIMARY KEY,             -- SERIAL: número que se auto-incrementa\n    name VARCHAR(150) NOT NULL,\n    birthDate DATE\n);\n\nCREATE TABLE books (\n    id SERIAL PRIMARY KEY,\n    title VARCHAR(200) NOT NULL,\n    pages INTEGER CHECK (pages > 0),   -- restrição de valor\n    author_id INTEGER REFERENCES authors(id), -- CHAVE ESTRANGEIRA -- liga as duas tabelas\n    available BOOLEAN DEFAULT true\n);","fidelityText":"CREATE TABLE autores ( id SERIAL PRIMARY KEY, -- SERIAL: número que se auto-incrementa nome VARCHAR(150) NOT NULL, nascimento DATE ); CREATE TABLE livros ( id SERIAL PRIMARY KEY, titulo VARCHAR(200) NOT NULL, paginas INTEGER CHECK (paginas > 0), -- restrição de valor autor_id INTEGER REFERENCES autores(id), -- CHAVE ESTRANGEIRA -- liga as duas tabelas disponivel BOOLEAN DEFAULT true );","highlightedHtml":"CREATE TABLE authors (\n    id SERIAL PRIMARY KEY,             <span class=\"com\">-- SERIAL: número que se auto-incrementa</span>\n    name VARCHAR(150) NOT NULL,\n    birthDate DATE\n);\n\nCREATE TABLE books (\n    id SERIAL PRIMARY KEY,\n    title VARCHAR(200) NOT NULL,\n    pages INTEGER CHECK (pages &gt; 0),   <span class=\"com\">-- restrição de valor</span>\n    author_id INTEGER REFERENCES authors(id), <span class=\"com\">-- CHAVE ESTRANGEIRA -- liga as duas tabelas</span>\n    available BOOLEAN DEFAULT true\n);","caption":"Exemplo executável de sql.","explanation":["CREATE TABLE declara forma e restrições antes dos dados.","PRIMARY KEY identifica linha; FOREIGN KEY preserva relação entre tabelas."],"commonMistakes":["Usar id sem constraint","Deixar regra importante só no Java"]},{"id":"sql-content-7","type":"html","authorship":"legacy-preserved","html":"<p>A <code>autor_id REFERENCES autores(id)</code> é uma <strong>chave estrangeira</strong> (foreign key) — ela garante, no próprio banco, que todo livro aponte para um autor que realmente existe. Essa é a diferença central entre \"vários <code>ArrayList</code> soltos\" e um banco relacional de verdade: as relações são <em>impostas</em>, não apenas uma convenção que seu código Java promete respeitar.</p>","fidelityText":"A autor_id REFERENCES autores(id) é uma chave estrangeira (foreign key) — ela garante, no próprio banco, que todo livro aponte para um autor que realmente existe. Essa é a diferença central entre \"vários ArrayList soltos\" e um banco relacional de verdade: as relações são impostas, não apenas uma convenção que seu código Java promete respeitar."},{"id":"sql-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>CRUD em SQL: INSERT, SELECT, UPDATE, DELETE</h2>","fidelityText":"CRUD em SQL: INSERT, SELECT, UPDATE, DELETE"},{"id":"sql-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"INSERT INTO authors (name, birthDate) VALUES ('Machado de Assis', '1839-06-21');\n\nINSERT INTO books (title, pages, author_id) VALUES ('Dom Casmurro', 256, 1);\n\nSELECT * FROM books;                              -- todas as colunas, todas as linhas\nSELECT title, pages FROM books WHERE pages > 200;\nSELECT * FROM books ORDER BY pages DESC LIMIT 5;\n\nUPDATE books SET available = false WHERE id = 1;\n\nDELETE FROM books WHERE id = 1;","fidelityText":"INSERT INTO autores (nome, nascimento) VALUES ('Machado de Assis', '1839-06-21'); INSERT INTO livros (titulo, paginas, autor_id) VALUES ('Dom Casmurro', 256, 1); SELECT * FROM livros; -- todas as colunas, todas as linhas SELECT titulo, paginas FROM livros WHERE paginas > 200; SELECT * FROM livros ORDER BY paginas DESC LIMIT 5; UPDATE livros SET disponivel = false WHERE id = 1; DELETE FROM livros WHERE id = 1;","highlightedHtml":"INSERT INTO authors (name, birthDate) VALUES ('Machado de Assis', '1839-06-21');\n\nINSERT INTO books (title, pages, author_id) VALUES ('Dom Casmurro', 256, 1);\n\nSELECT * FROM books;                              <span class=\"com\">-- todas as colunas, todas as linhas</span>\nSELECT title, pages FROM books WHERE pages &gt; 200;\nSELECT * FROM books ORDER BY pages DESC LIMIT 5;\n\nUPDATE books SET available = false WHERE id = 1;\n\nDELETE FROM books WHERE id = 1;","caption":"Exemplo executável de sql.","explanation":["INSERT cria linha, SELECT observa linha, UPDATE altera linha e DELETE remove linha.","WHERE delimita o alvo da alteração."],"commonMistakes":["UPDATE/DELETE sem WHERE","Assumir ordem sem ORDER BY"]},{"id":"sql-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Se você já entendeu bem <code>Stream</code> (capítulo 13), a <strong>mentalidade</strong> de pipeline de transformação ajuda a entender a intenção de cada cláusula: <code>WHERE</code> lembra <code>.filter(...)</code>, <code>ORDER BY</code> lembra <code>.sorted(...)</code>, <code>SELECT titulo, paginas</code> lembra um <code>.map(...)</code> que projeta só os campos que você quer. Mas essa transferência não é 1:1: SQL é uma linguagem <strong>declarativa</strong> — você descreve <em>o que</em> quer, e o otimizador do banco decide <em>como</em> buscar (em que ordem, usando qual índice), algo que não existe em uma cadeia de Stream, onde a ordem das chamadas define literalmente a ordem de execução. Além disso, um <code>Stream</code> processa objetos Java já carregados em memória; uma consulta SQL processa dados em disco, sob controle de transações e concorrência com outras consultas simultâneas — uma dimensão inteira (consistência, isolamento, planos de execução) que a analogia com Stream não cobre e que este capítulo trata mais adiante.</div>","fidelityText":"Se você já entendeu bem Stream (capítulo 13), a mentalidade de pipeline de transformação ajuda a entender a intenção de cada cláusula: WHERE lembra .filter(...), ORDER BY lembra .sorted(...), SELECT titulo, paginas lembra um .map(...) que projeta só os campos que você quer. Mas essa transferência não é 1:1: SQL é uma linguagem declarativa — você descreve o que quer, e o otimizador do banco decide como buscar (em que ordem, usando qual índice), algo que não existe em uma cadeia de Stream, onde a ordem das chamadas define literalmente a ordem de execução. Além disso, um Stream processa objetos Java já carregados em memória; uma consulta SQL processa dados em disco, sob controle de transações e concorrência com outras consultas simultâneas — uma dimensão inteira (consistência, isolamento, planos de execução) que a analogia com Stream não cobre e que este capítulo trata mais adiante."},{"id":"sql-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>JOIN — combinando tabelas relacionadas</h2>","fidelityText":"JOIN — combinando tabelas relacionadas"},{"id":"sql-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Sem <code>JOIN</code>, uma consulta em <code>livros</code> te dá só o <code>autor_id</code> (um número) — não o nome do autor. <code>JOIN</code> combina linhas de duas tabelas com base em uma condição de igualdade.</p>","fidelityText":"Sem JOIN, uma consulta em livros te dá só o autor_id (um número) — não o nome do autor. JOIN combina linhas de duas tabelas com base em uma condição de igualdade."},{"id":"sql-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"SELECT books.title, authors.name\nFROM books\nINNER JOIN authors ON books.author_id = authors.id;\n-- só retorna livros que TÊM autor correspondente\n\nSELECT books.title, authors.name\nFROM books\nLEFT JOIN authors ON books.author_id = authors.id;\n-- retorna TODOS os livros, mesmo os sem autor (nome viria NULL)","fidelityText":"SELECT livros.titulo, autores.nome FROM livros INNER JOIN autores ON livros.autor_id = autores.id; -- só retorna livros que TÊM autor correspondente SELECT livros.titulo, autores.nome FROM livros LEFT JOIN autores ON livros.autor_id = autores.id; -- retorna TODOS os livros, mesmo os sem autor (nome viria NULL)","highlightedHtml":"SELECT books.title, authors.name\nFROM books\nINNER JOIN authors ON books.author_id = authors.id;\n<span class=\"com\">-- só retorna livros que TÊM autor correspondente</span>\n\nSELECT books.title, authors.name\nFROM books\nLEFT JOIN authors ON books.author_id = authors.id;\n<span class=\"com\">-- retorna TODOS os livros, mesmo os sem autor (nome viria NULL)</span>","caption":"Exemplo executável de sql.","explanation":["JOIN combina linhas por relação declarada entre chaves.","A cardinalidade da relação decide quantas linhas aparecem no resultado."],"commonMistakes":["Confundir JOIN com filtro invisível","Não perceber duplicação por 1:N"]},{"id":"sql-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Como intuição inicial, <code>INNER JOIN</code> lembra o resultado de um <code>Set.retainAll(...)</code> — só o que existe nos dois lados sobrevive — e <code>LEFT JOIN</code> lembra manter <strong>tudo</strong> do lado esquerdo, preenchendo com <code>null</code> o que não tiver correspondência, algo parecido com <code>getOrDefault(chave, null)</code> de um <code>Map</code> (capítulo 11), aplicado linha a linha.</div>","fidelityText":"Como intuição inicial, INNER JOIN lembra o resultado de um Set.retainAll(...) — só o que existe nos dois lados sobrevive — e LEFT JOIN lembra manter tudo do lado esquerdo, preenchendo com null o que não tiver correspondência, algo parecido com getOrDefault(chave, null) de um Map (capítulo 11), aplicado linha a linha."},{"id":"sql-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Limite da analogia:</b> <code>Set.retainAll</code> compara elementos inteiros por igualdade (<code>equals</code>); um <code>JOIN</code> compara apenas os valores das colunas indicadas na condição <code>ON</code>, não a linha inteira — e essa condição não precisa ser igualdade de chave (dá para fazer <code>JOIN</code> por faixa de datas, por exemplo, algo sem equivalente natural em <code>Set.retainAll</code>). Mais importante: um <code>JOIN</code> tem <strong>cardinalidade</strong> — se um autor tiver 3 livros, o <code>JOIN</code> produz 3 linhas para esse autor (uma por livro), nunca um \"encolhimento\" para um único elemento como aconteceria em uma operação de conjuntos. <code>Set.retainAll</code> nunca duplica elementos; <code>JOIN</code> frequentemente multiplica linhas. Pense nessas analogias como pontes de intuição para o primeiro contato, não como regras de tradução — a semântica real de cada <code>JOIN</code> está definida pela condição <code>ON</code> e pela cardinalidade da relação entre as tabelas.</div>","fidelityText":"Limite da analogia: Set.retainAll compara elementos inteiros por igualdade (equals); um JOIN compara apenas os valores das colunas indicadas na condição ON, não a linha inteira — e essa condição não precisa ser igualdade de chave (dá para fazer JOIN por faixa de datas, por exemplo, algo sem equivalente natural em Set.retainAll). Mais importante: um JOIN tem cardinalidade — se um autor tiver 3 livros, o JOIN produz 3 linhas para esse autor (uma por livro), nunca um \"encolhimento\" para um único elemento como aconteceria em uma operação de conjuntos. Set.retainAll nunca duplica elementos; JOIN frequentemente multiplica linhas. Pense nessas analogias como pontes de intuição para o primeiro contato, não como regras de tradução — a semântica real de cada JOIN está definida pela condição ON e pela cardinalidade da relação entre as tabelas."},{"id":"sql-content-16","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Tipo de JOIN</th><th>O que retorna</th></tr>\n        <tr><td><code>INNER JOIN</code></td><td>Só as linhas que têm correspondência nos dois lados</td></tr>\n        <tr><td><code>LEFT JOIN</code></td><td>Todas as linhas da esquerda + correspondências (ou NULL) da direita</td></tr>\n        <tr><td><code>RIGHT JOIN</code></td><td>O espelho do LEFT JOIN — todas da direita</td></tr>\n        <tr><td><code>FULL JOIN</code></td><td>Tudo dos dois lados, casando onde possível</td></tr>\n      </tbody></table>","fidelityText":"Tipo de JOINO que retorna INNER JOINSó as linhas que têm correspondência nos dois lados LEFT JOINTodas as linhas da esquerda + correspondências (ou NULL) da direita RIGHT JOINO espelho do LEFT JOIN — todas da direita FULL JOINTudo dos dois lados, casando onde possível"},{"id":"sql-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Agregações: GROUP BY, COUNT, SUM, AVG</h2>","fidelityText":"Agregações: GROUP BY, COUNT, SUM, AVG"},{"id":"sql-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"SELECT authors.name, COUNT(books.id) AS total_books\nFROM authors\nLEFT JOIN books ON books.author_id = authors.id\nGROUP BY authors.name\nORDER BY total_books DESC;\n-- equivalente ao Collectors.groupingBy(...) + counting() do capítulo 13!","fidelityText":"SELECT autores.nome, COUNT(livros.id) AS total_livros FROM autores LEFT JOIN livros ON livros.autor_id = autores.id GROUP BY autores.nome ORDER BY total_livros DESC; -- equivalente ao Collectors.groupingBy(...) + counting() do capítulo 13!","highlightedHtml":"SELECT authors.name, COUNT(books.id) AS total_books\nFROM authors\nLEFT JOIN books ON books.author_id = authors.id\nGROUP BY authors.name\nORDER BY total_books DESC;\n<span class=\"com\">-- equivalente ao Collectors.groupingBy(...) + counting() do capítulo 13!</span>","caption":"Exemplo executável de sql.","explanation":["GROUP BY transforma várias linhas em grupos.","Funções como COUNT e SUM resumem cada grupo."],"commonMistakes":["Selecionar coluna fora do GROUP BY sem agregação","Confundir total geral com total por grupo"]},{"id":"sql-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Transações — tudo ou nada</h2>","fidelityText":"Transações — tudo ou nada"},{"id":"sql-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"BEGIN;\nUPDATE accounts SET balance = balance - 100 WHERE id = 1; -- débito\nUPDATE accounts SET balance = balance + 100 WHERE id = 2; -- crédito\nCOMMIT; -- só agora as duas mudanças ficam permanentes, JUNTAS\n\n-- se algo desse errado no meio, \"ROLLBACK;\" desfaz TUDO desde o BEGIN","fidelityText":"BEGIN; UPDATE contas SET saldo = saldo - 100 WHERE id = 1; -- débito UPDATE contas SET saldo = saldo + 100 WHERE id = 2; -- crédito COMMIT; -- só agora as duas mudanças ficam permanentes, JUNTAS -- se algo desse errado no meio, \"ROLLBACK;\" desfaz TUDO desde o BEGIN","highlightedHtml":"BEGIN;\nUPDATE accounts SET balance = balance - 100 WHERE id = 1; <span class=\"com\">-- débito</span>\nUPDATE accounts SET balance = balance + 100 WHERE id = 2; <span class=\"com\">-- crédito</span>\nCOMMIT; <span class=\"com\">-- só agora as duas mudanças ficam permanentes, JUNTAS</span>\n\n<span class=\"com\">-- se algo desse errado no meio, \"ROLLBACK;\" desfaz TUDO desde o BEGIN</span>","caption":"Exemplo executável de sql.","explanation":["BEGIN abre a unidade, COMMIT confirma, ROLLBACK desfaz o que ainda não foi confirmado.","A transação protege mudanças que pertencem à mesma regra."],"commonMistakes":["Esperar rollback depois de commit","Abrir transação longa sem necessidade"]},{"id":"sql-content-21","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Uma transferência bancária que debita de uma conta e credita em outra <strong>precisa</strong> ser atômica — se o servidor cair exatamente entre os dois <code>UPDATE</code>, sem transação o dinheiro simplesmente desaparece. Esse é o conceito por trás de <code>@Transactional</code> no Spring (que você vai ver adiante): ele abre um <code>BEGIN</code>, executa seu método, e decide <code>COMMIT</code> ou <code>ROLLBACK</code> automaticamente dependendo se uma exceção foi lançada — exatamente o dynamic proxy que você já estudou no capítulo 20.</div>","fidelityText":"Uma transferência bancária que debita de uma conta e credita em outra precisa ser atômica — se o servidor cair exatamente entre os dois UPDATE, sem transação o dinheiro simplesmente desaparece. Esse é o conceito por trás de @Transactional no Spring (que você vai ver adiante): ele abre um BEGIN, executa seu método, e decide COMMIT ou ROLLBACK automaticamente dependendo se uma exceção foi lançada — exatamente o dynamic proxy que você já estudou no capítulo 20."},{"id":"sql-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>Índices — por que consultas ficam rápidas (ou lentas)</h2>","fidelityText":"Índices — por que consultas ficam rápidas (ou lentas)"},{"id":"sql-content-23","type":"html","authorship":"legacy-preserved","html":"<p>Sem índice, o banco varre a tabela inteira linha por linha para achar o que você pediu (<em>full table scan</em>) — ok para 100 linhas, catastrófico para 10 milhões. Um <strong>índice</strong> é uma estrutura extra (geralmente uma árvore B) que permite pular direto para os dados relevantes.</p>","fidelityText":"Sem índice, o banco varre a tabela inteira linha por linha para achar o que você pediu (full table scan) — ok para 100 linhas, catastrófico para 10 milhões. Um índice é uma estrutura extra (geralmente uma árvore B) que permite pular direto para os dados relevantes."},{"id":"sql-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"CREATE INDEX idx_books_author_id ON books(author_id);\n-- toda coluna usada com frequência em WHERE ou JOIN é candidata a índice","fidelityText":"CREATE INDEX idx_livros_autor_id ON livros(autor_id); -- toda coluna usada com frequência em WHERE ou JOIN é candidata a índice","highlightedHtml":"CREATE INDEX idx_books_author_id ON books(author_id);\n<span class=\"com\">-- toda coluna usada com frequência em WHERE ou JOIN é candidata a índice</span>","caption":"Exemplo executável de sql.","explanation":["Índice muda a estrutura disponível para busca.","Ele acelera consultas compatíveis, mas custa escrita e armazenamento."],"commonMistakes":["Indexar tudo","Criar índice sem olhar consulta e plano"]},{"id":"sql-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um índice é o índice remissivo no final de um livro técnico. Sem ele, achar todas as páginas que mencionam \"polimorfismo\" significa ler o livro inteiro. Com o índice, você vai direto às páginas certas — só que, como o índice ocupa espaço e precisa ser atualizado a cada novo capítulo adicionado, ele tem um custo (mais lento em <code>INSERT</code>/<code>UPDATE</code>, mais rápido em leitura). É exatamente esse trade-off que existe em índices de banco.</div>","fidelityText":"Um índice é o índice remissivo no final de um livro técnico. Sem ele, achar todas as páginas que mencionam \"polimorfismo\" significa ler o livro inteiro. Com o índice, você vai direto às páginas certas — só que, como o índice ocupa espaço e precisa ser atualizado a cada novo capítulo adicionado, ele tem um custo (mais lento em INSERT/UPDATE, mais rápido em leitura). É exatamente esse trade-off que existe em índices de banco."},{"id":"sql-content-26","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Índice em excesso também é problema:</b> toda escrita (<code>INSERT</code>/<code>UPDATE</code>/<code>DELETE</code>) precisa atualizar <strong>todos</strong> os índices da tabela afetada. Criar índice em toda coluna \"por garantia\" deixa escritas lentas sem necessidade — crie índices baseados em consultas reais e lentas, não por precaução.</div>","fidelityText":"Índice em excesso também é problema: toda escrita (INSERT/UPDATE/DELETE) precisa atualizar todos os índices da tabela afetada. Criar índice em toda coluna \"por garantia\" deixa escritas lentas sem necessidade — crie índices baseados em consultas reais e lentas, não por precaução."},{"id":"sql-exercise-27","type":"exercise","authorship":"legacy-preserved","title":"Exercício 33.1 — Modelando e consultando","prompt":"Crie as tabelas autores e livros como no exemplo acima. Insira 3 autores e 5 livros (alguns autores com mais de um livro). Escreva uma consulta que lista o título de cada livro junto com o nome do autor, ordenado por título. Depois, escreva uma consulta que mostra quantos livros cada autor tem, incluindo autores com zero livros.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 33.1 — Modelando e consultandomédio Crie as tabelas autores e livros como no exemplo acima. Insira 3 autores e 5 livros (alguns autores com mais de um livro). Escreva uma consulta que lista o título de cada livro junto com o nome do autor, ordenado por título. Depois, escreva uma consulta que mostra quantos livros cada autor tem, incluindo autores com zero livros. Ver solução SELECT livros.titulo, autores.nome FROM livros INNER JOIN autores ON livros.autor_id = autores.id ORDER BY livros.titulo; SELECT autores.nome, COUNT(livros.id) AS total FROM autores LEFT JOIN livros ON livros.autor_id = autores.id GROUP BY autores.nome ORDER BY total DESC; -- LEFT JOIN é essencial aqui: com INNER JOIN, autores sem livro nenhum -- simplesmente desapareceriam do resultado","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 33.1 — Modelando e consultando</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie as tabelas <code>autores</code> e <code>livros</code> como no exemplo acima. Insira 3 autores e 5 livros (alguns autores com mais de um livro). Escreva uma consulta que lista o título de cada livro junto com o nome do autor, ordenado por título. Depois, escreva uma consulta que mostra quantos livros cada autor tem, incluindo autores com zero livros.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">SELECT books.title, authors.name\nFROM books\nINNER JOIN authors ON books.author_id = authors.id\nORDER BY books.title;\n\nSELECT authors.name, COUNT(books.id) AS total\nFROM authors\nLEFT JOIN books ON books.author_id = authors.id\nGROUP BY authors.name\nORDER BY total DESC;\n<span class=\"com\">-- LEFT JOIN é essencial aqui: com INNER JOIN, autores sem livro nenhum\n-- simplesmente desapareceriam do resultado</span></pre>\n        </div>\n      </div>"},{"id":"sql-exercise-28","type":"exercise","authorship":"legacy-preserved","title":"Exercício 33.2 — Transação simulando empréstimo","prompt":"Crie uma tabela emprestimos (id SERIAL, livro_id INTEGER, data_emprestimo DATE). Escreva uma transação que: (1) marca disponivel = false no livro, (2) insere um registro em emprestimos. Envolva as duas operações em BEGIN/COMMIT, e explique em texto por que rodar essas duas operações sem transação poderia deixar o sistema em um estado inconsistente se o processo falhasse entre as duas.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 33.2 — Transação simulando empréstimodifícil Crie uma tabela emprestimos (id SERIAL, livro_id INTEGER, data_emprestimo DATE). Escreva uma transação que: (1) marca disponivel = false no livro, (2) insere um registro em emprestimos. Envolva as duas operações em BEGIN/COMMIT, e explique em texto por que rodar essas duas operações sem transação poderia deixar o sistema em um estado inconsistente se o processo falhasse entre as duas. Ver solução BEGIN; UPDATE livros SET disponivel = false WHERE id = 3; INSERT INTO emprestimos (livro_id, data_emprestimo) VALUES (3, CURRENT_DATE); COMMIT; Sem transação, se o processo caísse depois do UPDATE mas antes do INSERT, o livro ficaria marcado como indisponível para sempre, sem nenhum registro de empréstimo explicando por quê — um estado inconsistente impossível de auditar. Com transação, ou as duas operações acontecem juntas, ou nenhuma acontece.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 33.2 — Transação simulando empréstimo</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie uma tabela <code>emprestimos (id SERIAL, livro_id INTEGER, data_emprestimo DATE)</code>. Escreva uma transação que: (1) marca <code>disponivel = false</code> no livro, (2) insere um registro em <code>emprestimos</code>. Envolva as duas operações em <code>BEGIN</code>/<code>COMMIT</code>, e explique em texto por que rodar essas duas operações <strong>sem</strong> transação poderia deixar o sistema em um estado inconsistente se o processo falhasse entre as duas.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">BEGIN;\nUPDATE books SET available = false WHERE id = 3;\nINSERT INTO loans (book_id, date_loan) VALUES (3, CURRENT_DATE);\nCOMMIT;</pre>\n          <p style=\"margin-top:12px\">Sem transação, se o processo caísse depois do <code>UPDATE</code> mas antes do <code>INSERT</code>, o livro ficaria marcado como indisponível <strong>para sempre</strong>, sem nenhum registro de empréstimo explicando por quê — um estado inconsistente impossível de auditar. Com transação, ou as duas operações acontecem juntas, ou nenhuma acontece.</p>\n        </div>\n      </div>"},{"id":"sql-comparison","type":"comparison","authorship":"authored","title":"DDL, DML e transação não fazem o mesmo papel","criteria":["pergunta","exemplo","erro comum"],"alternatives":[{"name":"DDL","values":["que estrutura existe?","CREATE TABLE, ALTER TABLE","alterar schema sem migration"],"useWhen":"criar ou evoluir forma dos dados","avoidWhen":"modificar linhas de negócio diretamente"},{"name":"DML","values":["quais dados entram/saem/mudam?","INSERT, SELECT, UPDATE, DELETE","UPDATE sem WHERE"],"useWhen":"manipular linhas sob um contrato","avoidWhen":"substituir constraint do banco"},{"name":"Transação","values":["qual unidade precisa confirmar junta?","BEGIN, COMMIT, ROLLBACK","achar que todo comando já é uma unidade de negócio"],"useWhen":"duas ou mais mudanças precisam ser atômicas","avoidWhen":"esconder erro já confirmado"}]},{"id":"sql-quiz","type":"quiz","authorship":"authored","conceptId":"sql-transacao-atomicidade","prompt":"Uma transferência debitou a origem e falhou antes de creditar o destino. Qual era a proteção correta?","options":[{"id":"sql-q-a","label":"Executar débito e crédito dentro da mesma transação e confirmar só no final.","correct":true,"explanation":"A unidade de negócio é a transferência inteira; rollback preserva atomicidade antes do commit."},{"id":"sql-q-b","label":"Executar dois UPDATEs separados porque cada comando é rápido.","correct":false,"explanation":"Rapidez não garante atomicidade entre comandos."},{"id":"sql-q-c","label":"Criar um índice no saldo para impedir falha parcial.","correct":false,"explanation":"Índice ajuda consulta/constraint específica, não substitui transação."}]}],"resources":[{"id":"sql-postgres-ddl","type":"reference","title":"PostgreSQL: Data Definition","url":"https://www.postgresql.org/docs/current/ddl.html","reinforces":"Documenta tabelas, constraints, chaves e estrutura de schema em PostgreSQL.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"sql-postgres-queries","type":"reference","title":"PostgreSQL: Queries","url":"https://www.postgresql.org/docs/current/queries.html","reinforces":"Fundamenta SELECT, joins, agregações e ordem lógica de consultas.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A sql operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a sql operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this sql chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"CREATE TABLE authors (","instruction":"A sql operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this sql chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"postgres","moduleId":"relational-data-jdbc","order":1,"title":"PostgreSQL na prática","summary":"SQL é o idioma; PostgreSQL é o \"sotaque\" específico que você vai usar no dia a dia — o banco relacional open-source mais popular do ecossistema Spring, com recursos avançados (JSON nativo, full-text search, extensões) que muitos concorrentes não têm.","objectives":["Escolher tipos PostgreSQL por semântica de domínio","Usar psql/PostgreSQL como ambiente observável","Ler EXPLAIN antes de otimizar","Preparar ponte futura para JDBC sem ainda esconder SQL"],"whyItExists":"Depois de SQL conceitual, o aluno precisa tocar um banco real. PostgreSQL mostra tipos, planos, constraints e comportamento concreto antes da aplicação Java depender dele.","prerequisiteChapterIds":["sql"],"conceptIds":["tipos-de-dados-que-voce-vai-usar-o-tempo-todo","explain-enxergando-o-que-o-banco-realmente-faz","conectando-do-java-via-jdbc-revisao-aplicada"],"introducedConceptIds":["postgres-tipo-dado-dominio","postgres-explain-analyze"],"usedConceptIds":["ddl-schema-constraint","indice-plano-custo","dml-crud-sql"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"postgres-intuition","type":"intuition","authorship":"authored","title":"Um banco real tem opinião","body":"PostgreSQL não é apenas onde o SQL roda. Ele tem tipos, planner, estatísticas, extensões, logs, permissões e decisões próprias que aparecem quando os dados crescem.","analogyLimit":"Pensar no banco como arquivo grande esconde concorrência, cache, planner, locks e garantias de transação."},{"id":"postgres-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-db\">PostgreSQL</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#sql\">SQL fundamentos</a></div>\n      </div>","fidelityText":"PostgreSQL Dificuldade: Intermediário ⏱ ~2h30 de estudo + prática Pré-requisito: SQL fundamentos"},{"id":"postgres-content-2","type":"html","authorship":"legacy-preserved","html":"<p>SQL é o idioma; <strong>PostgreSQL</strong> é o \"sotaque\" específico que você vai usar no dia a dia — o banco relacional open-source mais popular do ecossistema Spring, com recursos avançados (JSON nativo, full-text search, extensões) que muitos concorrentes não têm.</p>","fidelityText":"SQL é o idioma; PostgreSQL é o \"sotaque\" específico que você vai usar no dia a dia — o banco relacional open-source mais popular do ecossistema Spring, com recursos avançados (JSON nativo, full-text search, extensões) que muitos concorrentes não têm."},{"id":"postgres-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"install-box\">\n        <h2>Instalação</h2>\n        <div class=\"install-tabs\" role=\"tablist\">\n          <button type=\"button\" class=\"install-tab active\" role=\"tab\" aria-selected=\"true\">Via Docker (recomendado)</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">Windows</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">macOS</button>\n        </div>\n        <div class=\"install-panel active\"><pre class=\"code\"><span class=\"com\"># já vimos isso no capítulo 30 -- é assim que praticamente todo mundo\n# roda Postgres em desenvolvimento hoje em dia:</span>\ndocker run -d --name pg -e POSTGRES_PASSWORD=password123 -p 5432:5432 -v pgdata:/var/lib/postgresql/date postgres:16</pre></div>\n        <div class=\"install-panel\"><pre class=\"code\"><span class=\"com\"># instalador oficial em postgresql.org/download/windows\n# ou via winget:</span>\nwinget install PostgreSQL.PostgreSQL</pre></div>\n        <div class=\"install-panel\"><pre class=\"code\">brew install postgresql@16\nbrew services start postgresql@16</pre></div>\n        <pre class=\"code\"><span class=\"com\"># cliente de linha de comando -- funciona igual, instalado localmente ou via Docker:</span>\npsql -U postgres -h localhost\n<span class=\"com\">\\l          -- listar bancos\n\\c library  -- conectar a um banco\n\\dt         -- listar tabelas\n\\d livros   -- descrever a estrutura da tabela livros\n\\q          -- sair</span></pre>\n      </div>","fidelityText":"Instalação Via Docker (recomendado) Windows macOS # já vimos isso no capítulo 30 -- é assim que praticamente todo mundo # roda Postgres em desenvolvimento hoje em dia: docker run -d --name pg -e POSTGRES_PASSWORD=senha123 -p 5432:5432 -v pgdata:/var/lib/postgresql/data postgres:16 # instalador oficial em postgresql.org/download/windows # ou via winget: winget install PostgreSQL.PostgreSQL brew install postgresql@16 brew services start postgresql@16 # cliente de linha de comando -- funciona igual, instalado localmente ou via Docker: psql -U postgres -h localhost \\l -- listar bancos \\c biblioteca -- conectar a um banco \\dt -- listar tabelas \\d livros -- descrever a estrutura da tabela livros \\q -- sair"},{"id":"postgres-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não decore os comandos do <code>psql</code> de uma vez. Deixe uma aba com <code>\\?</code> (lista todos os comandos) aberta enquanto pratica os primeiros exercícios do capítulo anterior — o hábito vem do uso repetido, não da memorização.</div>","fidelityText":"Não decore os comandos do psql de uma vez. Deixe uma aba com \\? (lista todos os comandos) aberta enquanto pratica os primeiros exercícios do capítulo anterior — o hábito vem do uso repetido, não da memorização."},{"id":"postgres-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Tipos de dados que você vai usar o tempo todo</h2>","fidelityText":"Tipos de dados que você vai usar o tempo todo"},{"id":"postgres-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Tipo Postgres</th><th>Equivalente Java</th><th>Observação</th></tr>\n        <tr><td><code>SERIAL</code> / <code>BIGSERIAL</code></td><td><code>int</code>/<code>long</code> auto-incrementado</td><td>Ideal para chaves primárias simples</td></tr>\n        <tr><td><code>VARCHAR(n)</code> / <code>TEXT</code></td><td><code>String</code></td><td><code>TEXT</code> sem limite de tamanho, <code>VARCHAR(n)</code> com limite</td></tr>\n        <tr><td><code>NUMERIC(p,s)</code></td><td><code>BigDecimal</code></td><td>Use para <strong>dinheiro</strong>, nunca <code>double</code> (capítulo 15 já avisou sobre imprecisão)</td></tr>\n        <tr><td><code>TIMESTAMP</code> / <code>DATE</code></td><td><code>LocalDateTime</code> / <code>LocalDate</code></td><td>Conecta direto com o capítulo 21 (java.time)</td></tr>\n        <tr><td><code>BOOLEAN</code></td><td><code>boolean</code></td><td></td></tr>\n        <tr><td><code>JSONB</code></td><td>String/objeto serializado</td><td>Postgres consegue indexar e consultar <em>dentro</em> do JSON — recurso raro em bancos relacionais</td></tr>\n      </tbody></table>","fidelityText":"Tipo PostgresEquivalente JavaObservação SERIAL / BIGSERIALint/long auto-incrementadoIdeal para chaves primárias simples VARCHAR(n) / TEXTStringTEXT sem limite de tamanho, VARCHAR(n) com limite NUMERIC(p,s)BigDecimalUse para dinheiro, nunca double (capítulo 15 já avisou sobre imprecisão) TIMESTAMP / DATELocalDateTime / LocalDateConecta direto com o capítulo 21 (java.time) BOOLEANboolean JSONBString/objeto serializadoPostgres consegue indexar e consultar dentro do JSON — recurso raro em bancos relacionais"},{"id":"postgres-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>EXPLAIN — enxergando o que o banco realmente faz</h2>","fidelityText":"EXPLAIN — enxergando o que o banco realmente faz"},{"id":"postgres-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"EXPLAIN ANALYZE\nSELECT * FROM books WHERE author_id = 5;","fidelityText":"EXPLAIN ANALYZE SELECT * FROM livros WHERE autor_id = 5;","highlightedHtml":"EXPLAIN ANALYZE\nSELECT * FROM books WHERE author_id = 5;","caption":"Exemplo executável de postgres.","explanation":["Tipos como numeric, date/timestamp e boolean carregam semântica que text não carrega.","Constraint no banco protege dados mesmo se outro cliente escrever."],"commonMistakes":["Usar text para tudo","Guardar dinheiro em double/float"]},{"id":"postgres-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Isso mostra o <strong>plano de execução</strong> real da consulta: se o banco está usando um índice (rápido) ou fazendo um <em>Seq Scan</em> — varredura sequencial da tabela inteira (lento em tabelas grandes). É a ferramenta que separa \"acho que está lento\" de \"sei exatamente por quê\".</p>","fidelityText":"Isso mostra o plano de execução real da consulta: se o banco está usando um índice (rápido) ou fazendo um Seq Scan — varredura sequencial da tabela inteira (lento em tabelas grandes). É a ferramenta que separa \"acho que está lento\" de \"sei exatamente por quê\"."},{"id":"postgres-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"-- sem índice em autor_id:\nSeq Scan on books  (cost=0.00..18.50 rows=5 width=64) (actual time=0.02..0.15 rows=5 loops=1)\n  Filter: (author_id = 5)\n\n-- depois de \"CREATE INDEX idx_livros_autor ON livros(autor_id);\":\nIndex Scan using idx_books_author on books  (cost=0.15..8.30 rows=5 width=64) (actual time=0.01..0.02 rows=5 loops=1)\n  Index Cond: (author_id = 5)","fidelityText":"-- sem índice em autor_id: Seq Scan on livros (cost=0.00..18.50 rows=5 width=64) (actual time=0.02..0.15 rows=5 loops=1) Filter: (autor_id = 5) -- depois de \"CREATE INDEX idx_livros_autor ON livros(autor_id);\": Index Scan using idx_livros_autor on livros (cost=0.15..8.30 rows=5 width=64) (actual time=0.01..0.02 rows=5 loops=1) Index Cond: (autor_id = 5)","highlightedHtml":"<span class=\"com\">-- sem índice em autor_id:</span>\nSeq Scan on books  (cost=0.00..18.50 rows=5 width=64) (actual time=0.02..0.15 rows=5 loops=1)\n  Filter: (author_id = 5)\n\n<span class=\"com\">-- depois de \"CREATE INDEX idx_livros_autor ON livros(autor_id);\":</span>\nIndex Scan using idx_books_author on books  (cost=0.15..8.30 rows=5 width=64) (actual time=0.01..0.02 rows=5 loops=1)\n  Index Cond: (author_id = 5)","caption":"Exemplo executável de postgres.","explanation":["EXPLAIN revela o plano estimado para a consulta.","A leitura começa por tipo de scan, filtros, joins e custo estimado."],"commonMistakes":["Otimizar sem medir","Comparar plano em tabela vazia com produção"]},{"id":"postgres-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Rodar <code>EXPLAIN</code> é como pedir ao banco \"me mostre seu raciocínio\" — parecido com pedir para alguém explicar passo a passo como resolveu um problema, em vez de só aceitar a resposta final. <code>Seq Scan</code> é \"eu li linha por linha até achar\"; <code>Index Scan</code> é \"eu fui direto no índice remissivo e pulei pra página certa\" (lembra da analogia do capítulo anterior).</div>","fidelityText":"Rodar EXPLAIN é como pedir ao banco \"me mostre seu raciocínio\" — parecido com pedir para alguém explicar passo a passo como resolveu um problema, em vez de só aceitar a resposta final. Seq Scan é \"eu li linha por linha até achar\"; Index Scan é \"eu fui direto no índice remissivo e pulei pra página certa\" (lembra da analogia do capítulo anterior)."},{"id":"postgres-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Conectando do Java via JDBC (revisão aplicada)</h2>","fidelityText":"Conectando do Java via JDBC (revisão aplicada)"},{"id":"postgres-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"String url = \"jdbc:postgresql://localhost:5432/library\";\n// exatamente o mesmo padrão do capítulo 24 -- agora com um Postgres real rodando\nConnection connection = DriverManager.getConnection(url, \"postgres\", \"password123\");","fidelityText":"String url = \"jdbc:postgresql://localhost:5432/biblioteca\"; // exatamente o mesmo padrão do capítulo 24 -- agora com um Postgres real rodando Connection conexao = DriverManager.getConnection(url, \"postgres\", \"senha123\");","highlightedHtml":"<span class=\"kw\">String</span> url = <span class=\"str\">\"jdbc:postgresql://localhost:5432/library\"</span>;\n<span class=\"com\">// exatamente o mesmo padrão do capítulo 24 -- agora com um Postgres real rodando</span>\nConnection connection = DriverManager.getConnection(url, <span class=\"str\">\"postgres\"</span>, <span class=\"str\">\"password123\"</span>);","caption":"Exemplo executável de postgres.","explanation":["A URL JDBC aponta protocolo, host, porta e banco.","Usuário/senha são configuração, não código fixo no repositório."],"commonMistakes":["Commmitar credenciais","Confundir conexão aberta com transação de negócio"]},{"id":"postgres-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Nunca use <code>double</code>/<code>float</code> para dinheiro — use <code>NUMERIC</code> no banco e <code>BigDecimal</code> no Java.</li>\n          <li>Toda tabela deveria ter uma chave primária explícita — nunca confie na \"ordem de inserção\" para identificar uma linha.</li>\n          <li>Rode <code>EXPLAIN ANALYZE</code> antes de \"otimizar\" qualquer coisa — otimização sem medir é só um palpite caro.</li>\n          <li>Faça backup <strong>antes</strong> de rodar qualquer <code>DELETE</code> ou <code>UPDATE</code> sem <code>WHERE</code> em produção — sim, isso já aconteceu com todo mundo pelo menos uma vez.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Nunca use double/float para dinheiro — use NUMERIC no banco e BigDecimal no Java. Toda tabela deveria ter uma chave primária explícita — nunca confie na \"ordem de inserção\" para identificar uma linha. Rode EXPLAIN ANALYZE antes de \"otimizar\" qualquer coisa — otimização sem medir é só um palpite caro. Faça backup antes de rodar qualquer DELETE ou UPDATE sem WHERE em produção — sim, isso já aconteceu com todo mundo pelo menos uma vez."},{"id":"postgres-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 34.1 — Medindo o efeito de um índice","prompt":"Usando o Postgres rodando via Docker (capítulo 30), crie uma tabela livros com pelo menos 3 colunas, e insira 1000 linhas de teste (dica: INSERT INTO livros (titulo, paginas) SELECT 'Livro ' || i, (random()*500)::int FROM generate_series(1,1000) AS i;). Rode EXPLAIN ANALYZE em uma busca por paginas > 400 sem índice, depois crie um índice em paginas e rode de novo, comparando o plano de execução.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 34.1 — Medindo o efeito de um índicemédio Usando o Postgres rodando via Docker (capítulo 30), crie uma tabela livros com pelo menos 3 colunas, e insira 1000 linhas de teste (dica: INSERT INTO livros (titulo, paginas) SELECT 'Livro ' || i, (random()*500)::int FROM generate_series(1,1000) AS i;). Rode EXPLAIN ANALYZE em uma busca por paginas > 400 sem índice, depois crie um índice em paginas e rode de novo, comparando o plano de execução. Ver solução CREATE TABLE livros (id SERIAL PRIMARY KEY, titulo TEXT, paginas INTEGER); INSERT INTO livros (titulo, paginas) SELECT 'Livro ' || i, (random()*500)::int FROM generate_series(1,1000) AS i; EXPLAIN ANALYZE SELECT * FROM livros WHERE paginas > 400; -- provavelmente Seq Scan, já que a tabela é pequena o suficiente para o -- planner decidir que vale mais a pena ler tudo do que usar índice CREATE INDEX idx_livros_paginas ON livros(paginas); EXPLAIN ANALYZE SELECT * FROM livros WHERE paginas > 400; -- com uma tabela pequena, o Postgres pode CONTINUAR escolhendo Seq Scan -- -- e essa é a lição real: índice não é garantia automática de velocidade, -- o \"planner\" decide com base em estatísticas reais da tabela. O efeito -- fica claro em tabelas de centenas de milhares de linhas ou mais.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 34.1 — Medindo o efeito de um índice</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Usando o Postgres rodando via Docker (capítulo 30), crie uma tabela <code>livros</code> com pelo menos 3 colunas, e insira 1000 linhas de teste (dica: <code>INSERT INTO livros (titulo, paginas) SELECT 'Livro ' || i, (random()*500)::int FROM generate_series(1,1000) AS i;</code>). Rode <code>EXPLAIN ANALYZE</code> em uma busca por <code>paginas &gt; 400</code> sem índice, depois crie um índice em <code>paginas</code> e rode de novo, comparando o plano de execução.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">CREATE TABLE books (id SERIAL PRIMARY KEY, title TEXT, pages INTEGER);\n\nINSERT INTO books (title, pages)\nSELECT 'Book ' || i, (random()*500)::int\nFROM generate_series(1,1000) AS i;\n\nEXPLAIN ANALYZE SELECT * FROM books WHERE pages &gt; 400;\n<span class=\"com\">-- provavelmente Seq Scan, já que a tabela é pequena o suficiente para o\n-- planner decidir que vale mais a pena ler tudo do que usar índice</span>\n\nCREATE INDEX idx_books_pages ON books(pages);\nEXPLAIN ANALYZE SELECT * FROM books WHERE pages &gt; 400;\n<span class=\"com\">-- com uma tabela pequena, o Postgres pode CONTINUAR escolhendo Seq Scan --\n-- e essa é a lição real: índice não é garantia automática de velocidade,\n-- o \"planner\" decide com base em estatísticas reais da tabela. O efeito\n-- fica claro em tabelas de centenas de milhares de linhas ou mais.</span></pre>\n        </div>\n      </div>"},{"id":"postgres-table","type":"table","authorship":"authored","title":"Tipo como decisão de domínio","headers":["Dado","Tipo comum","Por quê"],"rows":[["dinheiro","numeric(12,2)","preserva escala e precisão"],["instante","timestamptz","representa ponto no tempo com fuso interpretável"],["identidade pública","uuid","reduz previsibilidade e acoplamento a sequência"],["texto categórico pequeno","text + CHECK ou tabela de domínio","declara valores permitidos"]]},{"id":"postgres-quiz","type":"quiz","authorship":"authored","conceptId":"postgres-explain-analyze","prompt":"Por que olhar EXPLAIN antes de criar índice?","options":[{"id":"postgres-q-a","label":"Porque o plano mostra como o PostgreSQL pretende acessar e combinar os dados.","correct":true,"explanation":"Índice só faz sentido diante de consulta, seletividade e plano observável."},{"id":"postgres-q-b","label":"Porque EXPLAIN cria automaticamente o índice ideal.","correct":false,"explanation":"EXPLAIN observa/estima plano; não cria estrutura."},{"id":"postgres-q-c","label":"Porque índice sempre piora SELECT e melhora INSERT.","correct":false,"explanation":"Normalmente índice ajuda algumas leituras e custa escrita/manutenção, mas depende do caso."}]}],"resources":[{"id":"postgres-datatypes","type":"reference","title":"PostgreSQL: Data Types","url":"https://www.postgresql.org/docs/current/datatype.html","reinforces":"Tipos numéricos, texto, data/hora, booleanos e UUID por contrato de dados.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"postgres-explain-doc","type":"reference","title":"PostgreSQL: Using EXPLAIN","url":"https://www.postgresql.org/docs/current/using-explain.html","reinforces":"Leitura de planos, custos, scans e evidência para otimização.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A postgres operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a postgres operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this postgres chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"# já vimos isso no capítulo 30 -- é assim que praticamente todo mundo","instruction":"A postgres operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this postgres chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"postgres-concorrencia","moduleId":"relational-data-jdbc","order":2,"title":"PostgreSQL avançado: planos, MVCC, isolamento e locks","summary":"Um banco relacional não é apenas armazenamento: ele coordena concorrência, aplica invariantes e escolhe planos físicos para consultas declarativas. Dominar PostgreSQL exige observar o que ocorre quando muitas transações disputam as mesmas linhas.","objectives":["Explicar MVCC como visões de dados no tempo","Distinguir isolamento, lock e constraint","Usar EXPLAIN/índices sem superstição","Projetar teste simples de disputa entre transações"],"whyItExists":"Antes de JPA e produção, o aluno precisa saber que banco relacional também é sistema concorrente. Falhas de isolamento e locks não aparecem em CRUD feliz de um usuário só.","prerequisiteChapterIds":["postgres","sql"],"conceptIds":["vocabulario-antes-da-disputa","modelagem-e-constraints-primeiro","mvcc-e-niveis-de-isolamento","planos-de-execucao"],"introducedConceptIds":["mvcc-isolamento-lock"],"usedConceptIds":["sql-transacao-atomicidade","postgres-explain-analyze","indice-plano-custo","ddl-schema-constraint"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"postgres-concorrencia-intuition","type":"intuition","authorship":"authored","title":"Concorrência é duas verdades tentando caber no mesmo tempo","body":"Duas transações podem ler, esperar, bloquear ou confirmar em ordens diferentes. O banco precisa equilibrar visibilidade, consistência e paralelismo.","analogyLimit":"Fila de banco ajuda para lock exclusivo, mas MVCC permite várias leituras simultâneas por versões, não apenas uma fila única."},{"id":"postgres-concorrencia-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-db\">Banco de dados</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~5h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#postgres\">PostgreSQL</a>, <a class=\"prereq-tag\" href=\"#sql\">SQL</a></div></div>","fidelityText":"Banco de dadosDificuldade: Avançado⏱ ~5h de estudo e práticaPré-requisitos: PostgreSQL, SQL"},{"id":"postgres-concorrencia-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Um banco relacional não é apenas armazenamento: ele coordena concorrência, aplica invariantes e escolhe planos físicos para consultas declarativas. Dominar PostgreSQL exige observar o que ocorre quando muitas transações disputam as mesmas linhas.</p>","fidelityText":"Um banco relacional não é apenas armazenamento: ele coordena concorrência, aplica invariantes e escolhe planos físicos para consultas declarativas. Dominar PostgreSQL exige observar o que ocorre quando muitas transações disputam as mesmas linhas."},{"id":"postgres-concorrencia-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Vocabulário antes da disputa</h2></div>\n    <p>A consulta SQL declara o resultado; o banco decide como obtê-lo e como isolar operações concorrentes.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Invariante</dt><dd>Regra que precisa permanecer verdadeira, como saldo nunca negativo. Uma constraint permite ao próprio banco protegê-la.</dd></div><div class=\"concept-card\"><dt>Plano de execução</dt><dd>Estratégia física escolhida pelo banco: quais índices ler, em que ordem unir tabelas e onde ordenar.</dd></div><div class=\"concept-card\"><dt>MVCC</dt><dd><em>Multi-Version Concurrency Control</em>: mantém versões de linhas para criar visões consistentes e reduzir bloqueio entre leitura e escrita.</dd></div><div class=\"concept-card\"><dt>Snapshot</dt><dd>Visão das versões de dados que uma operação pode enxergar em determinado momento lógico.</dd></div><div class=\"concept-card\"><dt>Lock</dt><dd>Reserva que restringe operações concorrentes sobre um recurso.</dd></div><div class=\"concept-card\"><dt>Deadlock</dt><dd>Ciclo de espera: cada transação segura algo de que a outra precisa; o banco aborta uma para romper o ciclo.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoVocabulário antes da disputa A consulta SQL declara o resultado; o banco decide como obtê-lo e como isolar operações concorrentes. InvarianteRegra que precisa permanecer verdadeira, como saldo nunca negativo. Uma constraint permite ao próprio banco protegê-la.Plano de execuçãoEstratégia física escolhida pelo banco: quais índices ler, em que ordem unir tabelas e onde ordenar.MVCCMulti-Version Concurrency Control: mantém versões de linhas para criar visões consistentes e reduzir bloqueio entre leitura e escrita.SnapshotVisão das versões de dados que uma operação pode enxergar em determinado momento lógico.LockReserva que restringe operações concorrentes sobre um recurso.DeadlockCiclo de espera: cada transação segura algo de que a outra precisa; o banco aborta uma para romper o ciclo."},{"id":"postgres-concorrencia-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Modelagem e constraints primeiro</h2>","fidelityText":"Modelagem e constraints primeiro"},{"id":"postgres-concorrencia-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"CREATE TABLE account (\n    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n    balance numeric(19,2) NOT NULL CHECK (balance >= 0),\n    version bigint NOT NULL DEFAULT 0\n);\n\nCREATE UNIQUE INDEX uq_user_email_normalizado\n    ON user (lower(email));","fidelityText":"CREATE TABLE conta ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, saldo numeric(19,2) NOT NULL CHECK (saldo >= 0), versao bigint NOT NULL DEFAULT 0 ); CREATE UNIQUE INDEX uq_usuario_email_normalizado ON usuario (lower(email));","highlightedHtml":"<span class=\"kw\">CREATE TABLE</span> account (\n    id <span class=\"kw\">bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY</span>,\n    balance <span class=\"kw\">numeric</span>(19,2) <span class=\"kw\">NOT NULL CHECK</span> (balance &gt;= 0),\n    version <span class=\"kw\">bigint NOT NULL DEFAULT</span> 0\n);\n\n<span class=\"kw\">CREATE UNIQUE INDEX</span> uq_user_email_normalizado\n    <span class=\"kw\">ON</span> user (lower(email));","caption":"Exemplo executável de postgres-concorrencia.","explanation":["O vocabulário separa transação, isolamento, lock e deadlock.","Nomear o fenômeno evita tratar toda espera como bug igual."],"commonMistakes":["Chamar qualquer lentidão de deadlock","Achar que isolamento serializable é grátis"]},{"id":"postgres-concorrencia-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Constraints protegem o dado contra qualquer cliente, não somente contra a aplicação atual. Normalize para evitar dependências e anomalias; desnormalize quando uma medição justificar e documente como a duplicação ficará consistente.</p>","fidelityText":"Constraints protegem o dado contra qualquer cliente, não somente contra a aplicação atual. Normalize para evitar dependências e anomalias; desnormalize quando uma medição justificar e documente como a duplicação ficará consistente."},{"id":"postgres-concorrencia-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>MVCC e níveis de isolamento</h2>","fidelityText":"MVCC e níveis de isolamento"},{"id":"postgres-concorrencia-content-8","type":"html","authorship":"legacy-preserved","html":"<p>O PostgreSQL mantém versões de linhas para que leitores e escritores interfiram menos. <code>READ COMMITTED</code> cria um novo snapshot por comando; <code>REPEATABLE READ</code> mantém uma visão estável na transação; <code>SERIALIZABLE</code> detecta execuções que não podem equivaler a alguma ordem serial e pode abortar uma delas. Uma aplicação correta precisa saber repetir a transação inteira quando recebe uma falha de serialização.</p>","fidelityText":"O PostgreSQL mantém versões de linhas para que leitores e escritores interfiram menos. READ COMMITTED cria um novo snapshot por comando; REPEATABLE READ mantém uma visão estável na transação; SERIALIZABLE detecta execuções que não podem equivaler a alguma ordem serial e pode abortar uma delas. Uma aplicação correta precisa saber repetir a transação inteira quando recebe uma falha de serialização."},{"id":"postgres-concorrencia-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;\nSELECT balance FROM account WHERE id = 10 FOR UPDATE;\nUPDATE account SET balance = balance - 50 WHERE id = 10;\nUPDATE account SET balance = balance + 50 WHERE id = 20;\nCOMMIT;","fidelityText":"BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; SELECT saldo FROM conta WHERE id = 10 FOR UPDATE; UPDATE conta SET saldo = saldo - 50 WHERE id = 10; UPDATE conta SET saldo = saldo + 50 WHERE id = 20; COMMIT;","highlightedHtml":"<span class=\"kw\">BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE</span>;\n<span class=\"kw\">SELECT</span> balance <span class=\"kw\">FROM</span> account <span class=\"kw\">WHERE</span> id = 10 <span class=\"kw\">FOR UPDATE</span>;\n<span class=\"kw\">UPDATE</span> account <span class=\"kw\">SET</span> balance = balance - 50 <span class=\"kw\">WHERE</span> id = 10;\n<span class=\"kw\">UPDATE</span> account <span class=\"kw\">SET</span> balance = balance + 50 <span class=\"kw\">WHERE</span> id = 20;\n<span class=\"kw\">COMMIT</span>;","caption":"Exemplo executável de postgres-concorrencia.","explanation":["Constraints reduzem dependência de validação apenas na aplicação.","Modelagem correta diminui estados impossíveis antes da concorrência começar."],"commonMistakes":["Validar unicidade só com SELECT antes do INSERT","Ignorar CHECK em invariantes simples"]},{"id":"postgres-concorrencia-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Adquira locks de múltiplas contas sempre na mesma ordem para reduzir deadlocks. Um deadlock ainda pode ocorrer; trate a exceção e repita com limite. <strong>Jitter</strong> é uma pequena variação aleatória no tempo de espera, usada para evitar que todos repitam juntos. Não mantenha uma transação aberta durante chamadas HTTP ou interação humana.</p>","fidelityText":"Adquira locks de múltiplas contas sempre na mesma ordem para reduzir deadlocks. Um deadlock ainda pode ocorrer; trate a exceção e repita com limite. Jitter é uma pequena variação aleatória no tempo de espera, usada para evitar que todos repitam juntos. Não mantenha uma transação aberta durante chamadas HTTP ou interação humana."},{"id":"postgres-concorrencia-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Planos de execução</h2>","fidelityText":"Planos de execução"},{"id":"postgres-concorrencia-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"EXPLAIN (ANALYZE, BUFFERS, VERBOSE)\nSELECT p.id, sum(i.quantity * i.price)\nFROM order p JOIN item_order i ON i.order_id = p.id\nWHERE p.created_in >= current_date - interval '30 days'\nGROUP BY p.id;","fidelityText":"EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT p.id, sum(i.quantidade * i.preco) FROM pedido p JOIN item_pedido i ON i.pedido_id = p.id WHERE p.criado_em >= current_date - interval '30 days' GROUP BY p.id;","highlightedHtml":"<span class=\"kw\">EXPLAIN (ANALYZE, BUFFERS, VERBOSE)</span>\n<span class=\"kw\">SELECT</span> p.id, <span class=\"kw\">sum</span>(i.quantity * i.price)\n<span class=\"kw\">FROM</span> order p <span class=\"kw\">JOIN</span> item_order i <span class=\"kw\">ON</span> i.order_id = p.id\n<span class=\"kw\">WHERE</span> p.created_in &gt;= current_date - interval <span class=\"str\">'30 days'</span>\n<span class=\"kw\">GROUP BY</span> p.id;","caption":"Exemplo executável de postgres-concorrencia.","explanation":["EXPLAIN mostra como o planner executará a consulta.","Índice e estatística influenciam plano, mas não garantem uma única escolha para sempre."],"commonMistakes":["Criar índice sem consulta-alvo","Comparar plano sem volume de dados representativo"]},{"id":"postgres-concorrencia-content-13","type":"html","authorship":"legacy-preserved","html":"<p><code>ANALYZE</code> executa a consulta: não o use despreocupadamente em comandos mutáveis. Compare linhas estimadas e reais, buffers lidos, tipo de join e ordenações em disco. Um índice tem custo de escrita e só ajuda quando combina com filtros, ordem, seletividade e estatísticas.</p>","fidelityText":"ANALYZE executa a consulta: não o use despreocupadamente em comandos mutáveis. Compare linhas estimadas e reais, buffers lidos, tipo de join e ordenações em disco. Um índice tem custo de escrita e só ajuda quando combina com filtros, ordem, seletividade e estatísticas."},{"id":"postgres-concorrencia-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — reserva sem venda dupla","prompt":"Implemente reserva concorrente de estoque por atualização condicional: decremente somente se a quantidade for suficiente e valide o número de linhas alteradas. Compare com SELECT FOR UPDATE e optimistic locking.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — reserva sem venda dupladifícilImplemente reserva concorrente de estoque por atualização condicional: decremente somente se a quantidade for suficiente e valide o número de linhas alteradas. Compare com SELECT FOR UPDATE e optimistic locking.Ver solução-baseUPDATE produto SET estoque = estoque - :qtd, versao = versao + 1 WHERE id = :id AND estoque >= :qtd;Zero linhas alteradas significa estoque insuficiente ou disputa perdida. Não faça primeiro um SELECT desprotegido e depois um UPDATE.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — reserva sem venda dupla</h2><span class=\"exercise-tag d\">difícil</span></div><p>Implemente reserva concorrente de estoque por atualização condicional: decremente somente se a quantidade for suficiente e valide o número de linhas alteradas. Compare com <code>SELECT FOR UPDATE</code> e optimistic locking.</p><button class=\"reveal-btn\">Ver solução-base</button><div class=\"solution\"><pre class=\"code\"><span class=\"kw\">UPDATE</span> product\n<span class=\"kw\">SET</span> inventory = inventory - :quantity, version = version + 1\n<span class=\"kw\">WHERE</span> id = :id <span class=\"kw\">AND</span> inventory &gt;= :quantity;</pre><p>Zero linhas alteradas significa estoque insuficiente ou disputa perdida. Não faça primeiro um <code>SELECT</code> desprotegido e depois um <code>UPDATE</code>.</p></div></div>"},{"id":"postgres-concorrencia-error","type":"error-case","authorship":"authored","title":"Saldo negativo por leitura desatualizada","scenario":"Duas transações leem saldo 100 e ambas aprovam saque de 80 antes de atualizar.","symptom":"Depois dos commits, a regra de saldo mínimo foi violada ou uma atualização sobrescreveu a outra.","cause":"A regra foi validada fora de uma política transacional/constraint adequada para concorrência.","diagnosis":["Reproduza com duas sessões SQL","Registre nível de isolamento","Observe locks e ordem dos commits","Transforme a regra em UPDATE condicional ou constraint quando possível"],"correction":"Use UPDATE condicional dentro da transação e trate zero linhas afetadas como saldo insuficiente.","prevention":"Teste concorrente mínimo para operações financeiras e regra crítica."},{"id":"postgres-concorrencia-quiz","type":"quiz","authorship":"authored","conceptId":"mvcc-isolamento-lock","prompt":"O que MVCC permite em PostgreSQL?","options":[{"id":"pgc-q-a","label":"Leitores enxergarem versões consistentes sem bloquear toda escrita por padrão.","correct":true,"explanation":"MVCC mantém versões para visibilidade transacional, reduzindo bloqueios entre leitura e escrita."},{"id":"pgc-q-b","label":"Toda transação enxergar automaticamente o futuro commit das outras.","correct":false,"explanation":"Visibilidade depende do nível de isolamento e do momento da leitura."},{"id":"pgc-q-c","label":"Eliminar a necessidade de constraints e testes concorrentes.","correct":false,"explanation":"MVCC não substitui regra declarada nem política de conflito."}]}],"resources":[{"id":"postgres-mvcc-doc","type":"reference","title":"PostgreSQL: MVCC","url":"https://www.postgresql.org/docs/current/mvcc.html","reinforces":"Modelo de controle de concorrência por múltiplas versões e visibilidade.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"postgres-transaction-iso","type":"reference","title":"PostgreSQL: Transaction Isolation","url":"https://www.postgresql.org/docs/current/transaction-iso.html","reinforces":"Níveis de isolamento, fenômenos de concorrência e comportamento do PostgreSQL.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A postgres concurrency operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a postgres concurrency operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this postgres concurrency chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"CREATE TABLE account (","instruction":"A postgres concurrency operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this postgres concurrency chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"migrations","moduleId":"relational-data-jdbc","order":3,"title":"Migrations — versionando o schema","summary":"Você versiona código com Git (capítulo 29). Mas quem versiona o schema do banco? Sem um processo formal, o schema vira um segredo que só existe na cabeça de quem mexeu por último — e sincronizar isso entre sua máquina, a de um colega e o servidor de produção vira um pesadelo manual.","objectives":["Versionar schema como histórico revisável","Entender ordem, checksum e imutabilidade de migrations aplicadas","Separar evolução compatível de mudança destrutiva","Evitar ddl-auto update como mecanismo de produção"],"whyItExists":"Quando código e banco evoluem separados, deploy vira aposta. Migrations ensinam a transformar mudança de schema em artefato versionado, revisável e reproduzível.","prerequisiteChapterIds":["postgres","git"],"conceptIds":["flyway-convencao-de-nomes-que-vira-ordem-de-execucao","flyway-com-spring-boot-quase-zero-configuracao"],"introducedConceptIds":["migration-versionada-checksum","migration-expand-contract"],"usedConceptIds":["ddl-schema-constraint","git-snapshot-index","build-lifecycle"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"migrations-intuition","type":"intuition","authorship":"authored","title":"Schema também tem histórico","body":"Se o Git guarda a evolução do código, migrations guardam a evolução do banco. A aplicação precisa saber quais mudanças já chegaram antes de confiar no schema.","analogyLimit":"Commit ajuda a intuição, mas migration executa contra estado externo, pode travar dados reais e precisa considerar compatibilidade entre versões."},{"id":"migrations-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-db\">Flyway</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#postgres\">34 · PostgreSQL</a>, <a class=\"prereq-tag\" href=\"#git\">29 · Git</a></div>\n      </div>","fidelityText":"Flyway Dificuldade: Intermediário ⏱ ~1h30 de estudo + prática Pré-requisitos: 34 · PostgreSQL, 29 · Git"},{"id":"migrations-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Você versiona código com Git (capítulo 29). Mas quem versiona o <strong>schema do banco</strong>? Sem um processo formal, o schema vira um segredo que só existe na cabeça de quem mexeu por último — e sincronizar isso entre sua máquina, a de um colega e o servidor de produção vira um pesadelo manual.</p>","fidelityText":"Você versiona código com Git (capítulo 29). Mas quem versiona o schema do banco? Sem um processo formal, o schema vira um segredo que só existe na cabeça de quem mexeu por último — e sincronizar isso entre sua máquina, a de um colega e o servidor de produção vira um pesadelo manual."},{"id":"migrations-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Uma migration está para o banco de dados assim como um commit está para o código: uma mudança pequena, versionada, com uma mensagem descrevendo o que mudou, aplicada <strong>em ordem</strong>. Se Git é o histórico do seu código, o histórico de migrations é o histórico do seu schema — e os dois deveriam viver juntos no mesmo repositório.</div>","fidelityText":"Uma migration está para o banco de dados assim como um commit está para o código: uma mudança pequena, versionada, com uma mensagem descrevendo o que mudou, aplicada em ordem. Se Git é o histórico do seu código, o histórico de migrations é o histórico do seu schema — e os dois deveriam viver juntos no mesmo repositório."},{"id":"migrations-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Flyway: convenção de nomes que vira ordem de execução</h2>","fidelityText":"Flyway: convenção de nomes que vira ordem de execução"},{"id":"migrations-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"src/main/resources/db/migration/\n├── v1__create_table_authors.sql\n├── v2__create_table_books.sql\n├── v3__add_column_isbn.sql\n└── v4__create_indice_pages.sql","fidelityText":"src/main/resources/db/migration/ ├── V1__criar_tabela_autores.sql ├── V2__criar_tabela_livros.sql ├── V3__adicionar_coluna_isbn.sql └── V4__criar_indice_paginas.sql","highlightedHtml":"src/main/resources/db/migration/\n├── v1__create_table_authors.sql\n├── v2__create_table_books.sql\n├── v3__add_column_isbn.sql\n└── v4__create_indice_pages.sql","caption":"Exemplo executável de migrations.","explanation":["A convenção V1, V2, V3 define ordem de execução.","O nome descreve intenção humana; o número resolve sequência."],"commonMistakes":["Criar duas migrations com mesmo número","Usar nome sem intenção revisável"]},{"id":"migrations-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"-- V1__criar_tabela_autores.sql\nCREATE TABLE authors (\n    id SERIAL PRIMARY KEY,\n    name VARCHAR(150) NOT NULL\n);","fidelityText":"-- V1__criar_tabela_autores.sql CREATE TABLE autores ( id SERIAL PRIMARY KEY, nome VARCHAR(150) NOT NULL );","highlightedHtml":"<span class=\"com\">-- V1__criar_tabela_autores.sql</span>\nCREATE TABLE authors (\n    id SERIAL PRIMARY KEY,\n    name VARCHAR(150) NOT NULL\n);","caption":"Exemplo executável de migrations.","explanation":["A primeira migration cria uma estrutura inicial pequena.","Cada arquivo deve ter uma responsabilidade fácil de revisar."],"commonMistakes":["Misturar dezenas de mudanças sem necessidade","Depender de ordem não declarada"]},{"id":"migrations-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"-- V3__adicionar_coluna_isbn.sql (aplicada DEPOIS que V1 e V2 já rodaram)\nALTER TABLE books ADD COLUMN isbn VARCHAR(20);","fidelityText":"-- V3__adicionar_coluna_isbn.sql (aplicada DEPOIS que V1 e V2 já rodaram) ALTER TABLE livros ADD COLUMN isbn VARCHAR(20);","highlightedHtml":"<span class=\"com\">-- V3__adicionar_coluna_isbn.sql (aplicada DEPOIS que V1 e V2 já rodaram)</span>\nALTER TABLE books ADD COLUMN isbn VARCHAR(20);","caption":"Exemplo executável de migrations.","explanation":["Nova coluna entra em nova migration, não alterando V1.","Ambientes antigos e novos convergem pelo histórico."],"commonMistakes":["Editar migration já aplicada","Não pensar em backfill/default"]},{"id":"migrations-content-8","type":"html","authorship":"legacy-preserved","html":"<p>O Flyway mantém uma tabela de controle (<code>flyway_schema_history</code>) registrando quais versões já foram aplicadas em <em>cada</em> banco. Ao iniciar a aplicação, ele compara essa tabela com os arquivos disponíveis e aplica só o que ainda falta — automaticamente, na ordem certa, em qualquer ambiente (sua máquina, CI, produção).</p>","fidelityText":"O Flyway mantém uma tabela de controle (flyway_schema_history) registrando quais versões já foram aplicadas em cada banco. Ao iniciar a aplicação, ele compara essa tabela com os arquivos disponíveis e aplica só o que ainda falta — automaticamente, na ordem certa, em qualquer ambiente (sua máquina, CI, produção)."},{"id":"migrations-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Em produção, não use <code>ddl-auto=update</code> como mecanismo de evolução do schema. Alterações inferidas do modelo não passam por revisão SQL, não documentam a intenção, variam entre bancos e não oferecem uma estratégia confiável de implantação ou reversão. Use <code>ddl-auto=validate</code> para detectar incompatibilidade e mantenha mudanças estruturais em migrations versionadas, revisadas e testadas.</div>","fidelityText":"Em produção, não use ddl-auto=update como mecanismo de evolução do schema. Alterações inferidas do modelo não passam por revisão SQL, não documentam a intenção, variam entre bancos e não oferecem uma estratégia confiável de implantação ou reversão. Use ddl-auto=validate para detectar incompatibilidade e mantenha mudanças estruturais em migrations versionadas, revisadas e testadas."},{"id":"migrations-content-10","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Prefixo</th><th>Significado</th></tr>\n        <tr><td><code>V</code></td><td>Versionada — roda uma única vez, em ordem, e nunca deveria ser editada depois de aplicada em algum ambiente</td></tr>\n        <tr><td><code>R</code></td><td>Repetível — roda toda vez que o conteúdo do arquivo muda (útil para views, procedures)</td></tr>\n      </tbody></table>","fidelityText":"PrefixoSignificado VVersionada — roda uma única vez, em ordem, e nunca deveria ser editada depois de aplicada em algum ambiente RRepetível — roda toda vez que o conteúdo do arquivo muda (útil para views, procedures)"},{"id":"migrations-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Nunca edite uma migration que já foi aplicada em produção.</b> O Flyway detecta alteração no conteúdo de um arquivo já registrado (via checksum) e recusa continuar — de propósito, para evitar inconsistência entre ambientes. Se precisar corrigir algo, crie uma <strong>nova</strong> migration (<code>V5__corrige_isbn.sql</code>), nunca reescreva a antiga.</div>","fidelityText":"Nunca edite uma migration que já foi aplicada em produção. O Flyway detecta alteração no conteúdo de um arquivo já registrado (via checksum) e recusa continuar — de propósito, para evitar inconsistência entre ambientes. Se precisar corrigir algo, crie uma nova migration (V5__corrige_isbn.sql), nunca reescreva a antiga."},{"id":"migrations-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Flyway com Spring Boot: quase zero configuração</h2>","fidelityText":"Flyway com Spring Boot: quase zero configuração"},{"id":"migrations-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties\nspring.flyway.enabled=true\nspring.jpa.hibernate.ddl-auto=validate\n# o Spring Boot detecta o Flyway no classpath e roda as migrations\n# AUTOMATICAMENTE antes da aplicação subir, toda vez","fidelityText":"# application.properties spring.flyway.enabled=true spring.jpa.hibernate.ddl-auto=validate # o Spring Boot detecta o Flyway no classpath e roda as migrations # AUTOMATICAMENTE antes da aplicação subir, toda vez","highlightedHtml":"<span class=\"com\"># application.properties</span>\nspring.flyway.enabled=true\nspring.jpa.hibernate.ddl-auto=validate\n<span class=\"com\"># o Spring Boot detecta o Flyway no classpath e roda as migrations\n# AUTOMATICAMENTE antes da aplicação subir, toda vez</span>","caption":"Exemplo executável de migrations.","explanation":["Flyway integra migrations ao ciclo de inicialização da aplicação.","Em produção, validação e aplicação precisam de política controlada."],"commonMistakes":["Usar ddl-auto update como substituto","Aplicar migration sem backup/observabilidade"]},{"id":"migrations-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Trate cada migration como um commit Git: pequena, com nome descritivo, e nunca \"uma migration gigante fazendo 10 coisas\". Isso facilita revisar mudanças de schema em Pull Requests exatamente como você revisa código — e reduz o risco de uma migration ruim quebrar produção inteira de uma vez.</div>","fidelityText":"Trate cada migration como um commit Git: pequena, com nome descritivo, e nunca \"uma migration gigante fazendo 10 coisas\". Isso facilita revisar mudanças de schema em Pull Requests exatamente como você revisa código — e reduz o risco de uma migration ruim quebrar produção inteira de uma vez."},{"id":"migrations-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 35.1 — Sequência de migrations","prompt":"Escreva três arquivos de migration Flyway para o projeto da biblioteca: V1 criando a tabela autores, V2 criando livros com chave estrangeira para autores, e V3 adicionando uma coluna ano_publicacao INTEGER em livros. Nomeie os arquivos seguindo exatamente a convenção do Flyway.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 35.1 — Sequência de migrationsmédio Escreva três arquivos de migration Flyway para o projeto da biblioteca: V1 criando a tabela autores, V2 criando livros com chave estrangeira para autores, e V3 adicionando uma coluna ano_publicacao INTEGER em livros. Nomeie os arquivos seguindo exatamente a convenção do Flyway. Ver solução -- V1__criar_tabela_autores.sql CREATE TABLE autores ( id SERIAL PRIMARY KEY, nome VARCHAR(150) NOT NULL ); -- V2__criar_tabela_livros.sql CREATE TABLE livros ( id SERIAL PRIMARY KEY, titulo VARCHAR(200) NOT NULL, autor_id INTEGER REFERENCES autores(id) ); -- V3__adicionar_ano_publicacao.sql ALTER TABLE livros ADD COLUMN ano_publicacao INTEGER;","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 35.1 — Sequência de migrations</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva três arquivos de migration Flyway para o projeto da biblioteca: <code>V1</code> criando a tabela <code>autores</code>, <code>V2</code> criando <code>livros</code> com chave estrangeira para <code>autores</code>, e <code>V3</code> adicionando uma coluna <code>ano_publicacao INTEGER</code> em <code>livros</code>. Nomeie os arquivos seguindo exatamente a convenção do Flyway.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"com\">-- V1__criar_tabela_autores.sql</span>\nCREATE TABLE authors (\n    id SERIAL PRIMARY KEY,\n    name VARCHAR(150) NOT NULL\n);\n\n<span class=\"com\">-- V2__criar_tabela_livros.sql</span>\nCREATE TABLE books (\n    id SERIAL PRIMARY KEY,\n    title VARCHAR(200) NOT NULL,\n    author_id INTEGER REFERENCES authors(id)\n);\n\n<span class=\"com\">-- V3__adicionar_ano_publicacao.sql</span>\nALTER TABLE books ADD COLUMN year_publicacao INTEGER;</pre>\n        </div>\n      </div>"},{"id":"migrations-diagram","type":"diagram","authorship":"authored","title":"Fluxo seguro de uma mudança de schema","description":"A mudança sai de uma necessidade do domínio e vira uma sequência rastreável antes de produção.","steps":["escrever migration pequena","revisar SQL e impacto","testar em banco descartável","aplicar em staging","implantar código compatível","monitorar aplicação","remover compatibilidade antiga apenas em nova migration quando seguro"]},{"id":"migrations-quiz","type":"quiz","authorship":"authored","conceptId":"migration-versionada-checksum","prompt":"Por que não editar uma migration já aplicada em produção?","options":[{"id":"mig-q-a","label":"Porque ambientes já registraram aquele conteúdo; mudar o arquivo quebra o histórico e o checksum.","correct":true,"explanation":"A correção deve vir em nova migration para preservar rastreabilidade."},{"id":"mig-q-b","label":"Porque SQL não pode ser versionado em Git.","correct":false,"explanation":"SQL deve ser versionado; o problema é reescrever passado aplicado."},{"id":"mig-q-c","label":"Porque migrations só funcionam com MongoDB.","correct":false,"explanation":"Migrations são comuns em bancos relacionais; aqui o foco é schema PostgreSQL/Flyway."}]}],"resources":[{"id":"flyway-migrations","type":"reference","title":"Flyway: Migrations","url":"https://documentation.red-gate.com/flyway/flyway-concepts/migrations","reinforces":"Conceito de migrations versionadas, repetíveis e ordem de aplicação.","language":"en","publisher":"Redgate","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"flyway-versioned-migrations","type":"reference","title":"Flyway: Versioned migrations","url":"https://documentation.red-gate.com/fd/versioned-migrations-273973333.html","reinforces":"Convenção de nomes, versionamento e checksums para migrations aplicadas uma vez.","language":"en","publisher":"Redgate","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A migrations operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a migrations operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this migrations chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"src/main/resources/db/migration/","instruction":"A migrations operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this migrations chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"jdbc","moduleId":"relational-data-jdbc","order":4,"title":"JDBC & banco de dados","summary":"JDBC (Java Database Connectivity) é a API padrão do Java para conversar com bancos relacionais. O Spring Data JPA (que você vai usar no módulo Spring) é construído em cima de JDBC — entender esta camada evita que o JPA pareça mágica.","objectives":["Abrir conexão JDBC sem esconder ciclo de vida","Usar PreparedStatement para valores externos","Mapear ResultSet para objetos sem vazar cursor","Controlar transação, commit, rollback e pool como recursos limitados"],"whyItExists":"JDBC mostra a fronteira real entre Java e banco antes de ORM. O aluno precisa ver conexão, statement, result set, erro SQL e transação para depois entender o que frameworks automatizam.","prerequisiteChapterIds":["sql","excecoes"],"conceptIds":["conexao-statement-e-resultset","insert-update-delete","transacoes-commit-rollback-e-por-que-autocommit-true-engana","batch-agrupando-varias-operacoes-em-uma-unica-viagem-de-rede","chaves-geradas-recuperando-o-id-gerado-pelo-banco-apos-um-insert","wasnull-distinguindo-zero-de-null","sqlexception-sqlstate-codigo-do-fornecedor-e-traducao","connection-pool-por-que-ninguem-abre-conexao-crua-em-producao","mapeando-resultset-para-objetos-o-embriao-do-orm","testando-jdbc-de-verdade-banco-real-nao-simulado"],"introducedConceptIds":["jdbc-driver-connection","preparedstatement-parametro","resultset-mapeamento","pool-conexao-backpressure","jdbc-transacao-rollback"],"usedConceptIds":["dml-crud-sql","sql-transacao-atomicidade","try-resource-lifecycle","checked-unchecked-contrato","configuracao-externa"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"jdbc-intuition","type":"intuition","authorship":"authored","title":"JDBC é encanamento visível","body":"O Java não conversa com tabela diretamente. Ele usa um driver, abre uma sessão, envia SQL, recebe cursor/contagem e precisa fechar tudo mesmo em erro.","analogyLimit":"Encanamento ajuda para recurso limitado, mas conexão tem transação, isolamento, protocolo, exceções e pool."},{"id":"jdbc-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#sql\">SQL</a>, <a class=\"prereq-tag\" href=\"#padroes\">Padrões de projeto</a>, <a class=\"prereq-tag\" href=\"#excecoes\">Exceções</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisitos: SQL, Padrões de projeto, Exceções"},{"id":"jdbc-content-2","type":"html","authorship":"legacy-preserved","html":"<p><strong>JDBC</strong> (Java Database Connectivity) é a API padrão do Java para conversar com bancos relacionais. O Spring Data JPA (que você vai usar no módulo Spring) é construído <em>em cima</em> de JDBC — entender esta camada evita que o JPA pareça mágica.</p>","fidelityText":"JDBC (Java Database Connectivity) é a API padrão do Java para conversar com bancos relacionais. O Spring Data JPA (que você vai usar no módulo Spring) é construído em cima de JDBC — entender esta camada evita que o JPA pareça mágica."},{"id":"jdbc-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Conexão, Statement e ResultSet</h2>","fidelityText":"Conexão, Statement e ResultSet"},{"id":"jdbc-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"String url = \"jdbc:postgresql://localhost:5432/library\";\n\ntry (Connection connection = DriverManager.getConnection(url, \"user\", \"password\");\n     PreparedStatement stmt = connection.prepareStatement(\n         \"SELECT title, author FROM books WHERE pages > ?\")) {\n\n    stmt.setInt(1, 300); // preenche o \"?\" -- posição 1\n\n    try (ResultSet rs = stmt.executeQuery()) {\n        while (rs.next()) {\n            System.out.println(rs.getString(\"title\") + \" - \" + rs.getString(\"author\"));\n        }\n    }\n} catch (SQLException e) {\n    throw new RuntimeException(\"Error to the query books\", e); // exception chaining (capítulo 10)\n}","fidelityText":"String url = \"jdbc:postgresql://localhost:5432/biblioteca\"; try (Connection conexao = DriverManager.getConnection(url, \"usuario\", \"senha\"); PreparedStatement stmt = conexao.prepareStatement( \"SELECT titulo, autor FROM livros WHERE paginas > ?\")) { stmt.setInt(1, 300); // preenche o \"?\" -- posição 1 try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { System.out.println(rs.getString(\"titulo\") + \" - \" + rs.getString(\"autor\")); } } } catch (SQLException e) { throw new RuntimeException(\"Erro ao consultar livros\", e); // exception chaining (capítulo 10) }","highlightedHtml":"<span class=\"kw\">String</span> url = <span class=\"str\">\"jdbc:postgresql://localhost:5432/library\"</span>;\n\n<span class=\"kw\">try</span> (Connection connection = DriverManager.getConnection(url, <span class=\"str\">\"user\"</span>, <span class=\"str\">\"password\"</span>);\n     PreparedStatement stmt = connection.prepareStatement(\n         <span class=\"str\">\"SELECT title, author FROM books WHERE pages &gt; ?\"</span>)) {\n\n    stmt.setInt(1, 300); <span class=\"com\">// preenche o \"?\" -- posição 1</span>\n\n    <span class=\"kw\">try</span> (ResultSet rs = stmt.executeQuery()) {\n        <span class=\"kw\">while</span> (rs.next()) {\n            System.out.println(rs.getString(<span class=\"str\">\"title\"</span>) + <span class=\"str\">\" - \"</span> + rs.getString(<span class=\"str\">\"author\"</span>));\n        }\n    }\n} <span class=\"kw\">catch</span> (SQLException e) {\n    <span class=\"kw\">throw new</span> <span class=\"cls\">RuntimeException</span>(<span class=\"str\">\"Error to the query books\"</span>, e); <span class=\"com\">// exception chaining (capítulo 10)</span>\n}","caption":"Exemplo executável de jdbc.","explanation":["A URL JDBC identifica protocolo, host, porta e banco.","Connection representa sessão externa e deve ser fechada."],"commonMistakes":["Fixar senha no código","Não fechar Connection"]},{"id":"jdbc-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Repare o uso de <strong>try-with-resources</strong> (capítulo 10) duplo — <code>Connection</code>, <code>PreparedStatement</code> e <code>ResultSet</code> implementam <code>AutoCloseable</code> e precisam ser fechados sempre, mesmo em caso de erro.</p>","fidelityText":"Repare o uso de try-with-resources (capítulo 10) duplo — Connection, PreparedStatement e ResultSet implementam AutoCloseable e precisam ser fechados sempre, mesmo em caso de erro."},{"id":"jdbc-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>NUNCA concatene SQL com dados do usuário:</b></div>","fidelityText":"NUNCA concatene SQL com dados do usuário:"},{"id":"jdbc-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ SQL Injection -- um usuário malicioso pode injetar \"'; DROP TABLE livros; --\"\nString sql = \"SELECT * FROM books WHERE title = '\" + titleDigitadoPeloUser + \"'\";\n\n// ✅ PreparedStatement separa o valor da estrutura SQL\nPreparedStatement stmt = connection.prepareStatement(\"SELECT * FROM books WHERE title = ?\");\nstmt.setString(1, titleDigitadoPeloUser);","fidelityText":"// ❌ SQL Injection -- um usuário malicioso pode injetar \"'; DROP TABLE livros; --\" String sql = \"SELECT * FROM livros WHERE titulo = '\" + tituloDigitadoPeloUsuario + \"'\"; // ✅ PreparedStatement separa o valor da estrutura SQL PreparedStatement stmt = conexao.prepareStatement(\"SELECT * FROM livros WHERE titulo = ?\"); stmt.setString(1, tituloDigitadoPeloUsuario);","highlightedHtml":"<span class=\"com\">// ❌ SQL Injection -- um usuário malicioso pode injetar \"'; DROP TABLE livros; --\"</span>\n<span class=\"kw\">String</span> sql = <span class=\"str\">\"SELECT * FROM books WHERE title = '\"</span> + titleDigitadoPeloUser + <span class=\"str\">\"'\"</span>;\n\n<span class=\"com\">// ✅ PreparedStatement separa o valor da estrutura SQL</span>\nPreparedStatement stmt = connection.prepareStatement(<span class=\"str\">\"SELECT * FROM books WHERE title = ?\"</span>);\nstmt.setString(1, titleDigitadoPeloUser);","caption":"Exemplo executável de jdbc.","explanation":["try-with-resources fecha Connection, Statement e ResultSet em ordem segura.","SELECT devolve cursor que depende dos recursos abertos."],"commonMistakes":["Retornar ResultSet para fora do método","Ignorar SQLException sem contexto"]},{"id":"jdbc-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Limite da proteção:</b> parâmetros protegem valores. Nomes de tabela, coluna, direção de ordenação e fragmentos SQL não podem ser recebidos do usuário e passados como parâmetro; selecione-os a partir de uma allowlist controlada pela aplicação.</div>","fidelityText":"Limite da proteção: parâmetros protegem valores. Nomes de tabela, coluna, direção de ordenação e fragmentos SQL não podem ser recebidos do usuário e passados como parâmetro; selecione-os a partir de uma allowlist controlada pela aplicação."},{"id":"jdbc-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>INSERT, UPDATE, DELETE</h2>","fidelityText":"INSERT, UPDATE, DELETE"},{"id":"jdbc-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"try (PreparedStatement stmt = connection.prepareStatement(\n        \"INSERT INTO books (title, author, pages) VALUES (?, ?, ?)\")) {\n    stmt.setString(1, \"Dom Casmurro\");\n    stmt.setString(2, \"Machado de Assis\");\n    stmt.setInt(3, 256);\n    int linesAffected = stmt.executeUpdate(); // INSERT/UPDATE/DELETE usam executeUpdate(), não executeQuery()\n}","fidelityText":"try (PreparedStatement stmt = conexao.prepareStatement( \"INSERT INTO livros (titulo, autor, paginas) VALUES (?, ?, ?)\")) { stmt.setString(1, \"Dom Casmurro\"); stmt.setString(2, \"Machado de Assis\"); stmt.setInt(3, 256); int linhasAfetadas = stmt.executeUpdate(); // INSERT/UPDATE/DELETE usam executeUpdate(), não executeQuery() }","highlightedHtml":"<span class=\"kw\">try</span> (PreparedStatement stmt = connection.prepareStatement(\n        <span class=\"str\">\"INSERT INTO books (title, author, pages) VALUES (?, ?, ?)\"</span>)) {\n    stmt.setString(1, <span class=\"str\">\"Dom Casmurro\"</span>);\n    stmt.setString(2, <span class=\"str\">\"Machado de Assis\"</span>);\n    stmt.setInt(3, 256);\n    <span class=\"kw\">int</span> linesAffected = stmt.executeUpdate(); <span class=\"com\">// INSERT/UPDATE/DELETE usam executeUpdate(), não executeQuery()</span>\n}","caption":"Exemplo executável de jdbc.","explanation":["Concatenar entrada do usuário cria SQL novo e perigoso.","PreparedStatement separa comando e valores por placeholders."],"commonMistakes":["Achar que replace de aspas resolve injeção","Tentar parametrizar nome de coluna"]},{"id":"jdbc-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Transações: commit, rollback e por que autoCommit=true engana</h2>","fidelityText":"Transações: commit, rollback e por que autoCommit=true engana"},{"id":"jdbc-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Por padrão, uma <code>Connection</code> nasce com <code>autoCommit=true</code> — cada instrução SQL confirma sozinha, como se fosse sua própria transação. Isso é conveniente para uma única operação isolada, mas perigoso quando duas ou mais operações precisam ser atômicas (tudo ou nada).</p>","fidelityText":"Por padrão, uma Connection nasce com autoCommit=true — cada instrução SQL confirma sozinha, como se fosse sua própria transação. Isso é conveniente para uma única operação isolada, mas perigoso quando duas ou mais operações precisam ser atômicas (tudo ou nada)."},{"id":"jdbc-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"public void transfer(Connection connection, long accountSource, long accountRecipient, double value) throws SQLException {\n    connection.setAutoCommit(false); // a partir daqui, nada é confirmado até commit() explícito\n    try {\n        try (PreparedStatement debit = connection.prepareStatement(\n                \"UPDATE accounts SET balance = balance - ? WHERE id = ?\")) {\n            debit.setDouble(1, value);\n            debit.setLong(2, accountSource);\n            debit.executeUpdate();\n        }\n        try (PreparedStatement credit = connection.prepareStatement(\n                \"UPDATE accounts SET balance = balance + ? WHERE id = ?\")) {\n            credit.setDouble(1, value);\n            credit.setLong(2, accountRecipient);\n            credit.executeUpdate();\n        }\n        connection.commit(); // só agora as duas mudanças ficam permanentes, JUNTAS\n    } catch (SQLException e) {\n        connection.rollback(); // desfaz o débito e o crédito juntos -- nada fica confirmado pela metade\n        throw e;\n    } finally {\n        connection.setAutoCommit(true); // devolve a conexão ao estado padrão antes de voltar ao pool\n    }\n}","fidelityText":"public void transferir(Connection conexao, long contaOrigem, long contaDestino, double valor) throws SQLException { conexao.setAutoCommit(false); // a partir daqui, nada é confirmado até commit() explícito try { try (PreparedStatement debito = conexao.prepareStatement( \"UPDATE contas SET saldo = saldo - ? WHERE id = ?\")) { debito.setDouble(1, valor); debito.setLong(2, contaOrigem); debito.executeUpdate(); } try (PreparedStatement credito = conexao.prepareStatement( \"UPDATE contas SET saldo = saldo + ? WHERE id = ?\")) { credito.setDouble(1, valor); credito.setLong(2, contaDestino); credito.executeUpdate(); } conexao.commit(); // só agora as duas mudanças ficam permanentes, JUNTAS } catch (SQLException e) { conexao.rollback(); // desfaz o débito e o crédito juntos -- nada fica confirmado pela metade throw e; } finally { conexao.setAutoCommit(true); // devolve a conexão ao estado padrão antes de voltar ao pool } }","highlightedHtml":"<span class=\"kw\">public void</span> <span class=\"fn\">transfer</span>(Connection connection, <span class=\"kw\">long</span> accountSource, <span class=\"kw\">long</span> accountRecipient, <span class=\"kw\">double</span> value) <span class=\"kw\">throws</span> SQLException {\n    connection.setAutoCommit(<span class=\"kw\">false</span>); <span class=\"com\">// a partir daqui, nada é confirmado até commit() explícito</span>\n    <span class=\"kw\">try</span> {\n        <span class=\"kw\">try</span> (PreparedStatement debit = connection.prepareStatement(\n                <span class=\"str\">\"UPDATE accounts SET balance = balance - ? WHERE id = ?\"</span>)) {\n            debit.setDouble(1, value);\n            debit.setLong(2, accountSource);\n            debit.executeUpdate();\n        }\n        <span class=\"kw\">try</span> (PreparedStatement credit = connection.prepareStatement(\n                <span class=\"str\">\"UPDATE accounts SET balance = balance + ? WHERE id = ?\"</span>)) {\n            credit.setDouble(1, value);\n            credit.setLong(2, accountRecipient);\n            credit.executeUpdate();\n        }\n        connection.commit(); <span class=\"com\">// só agora as duas mudanças ficam permanentes, JUNTAS</span>\n    } <span class=\"kw\">catch</span> (SQLException e) {\n        connection.rollback(); <span class=\"com\">// desfaz o débito e o crédito juntos -- nada fica confirmado pela metade</span>\n        <span class=\"kw\">throw</span> e;\n    } <span class=\"kw\">finally</span> {\n        connection.setAutoCommit(<span class=\"kw\">true</span>); <span class=\"com\">// devolve a conexão ao estado padrão antes de voltar ao pool</span>\n    }\n}","caption":"Exemplo executável de jdbc.","explanation":["Débito e crédito rodam na MESMA Connection, com autoCommit desabilitado -- só viram permanentes juntos, no commit()."],"commonMistakes":["Usar duas conexões diferentes do pool para débito e crédito, tornando o rollback de uma inútil para a outra"]},{"id":"jdbc-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Uma transação é uma conexão, não um método.</b> Todas as operações de uma mesma transação precisam usar exatamente a mesma <code>Connection</code> — se cada método pegar sua própria conexão do pool (como no <code>LivroRepositorioJdbc</code> mostrado adiante), débito e crédito rodariam em transações diferentes, e um <code>rollback</code> não desfaria a outra operação. Uma camada de serviço geralmente precisa <em>fornecer</em> a mesma conexão para várias chamadas do repositório dentro de uma transação.</div>","fidelityText":"Uma transação é uma conexão, não um método. Todas as operações de uma mesma transação precisam usar exatamente a mesma Connection — se cada método pegar sua própria conexão do pool (como no LivroRepositorioJdbc mostrado adiante), débito e crédito rodariam em transações diferentes, e um rollback não desfaria a outra operação. Uma camada de serviço geralmente precisa fornecer a mesma conexão para várias chamadas do repositório dentro de uma transação."},{"id":"jdbc-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Para unidades de trabalho mais complexas, um <strong>savepoint</strong> permite desfazer só uma parte da transação, sem descartar tudo:</p>","fidelityText":"Para unidades de trabalho mais complexas, um savepoint permite desfazer só uma parte da transação, sem descartar tudo:"},{"id":"jdbc-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"Savepoint beforeOfCredit = connection.setSavepoint();\ntry {\n    // tenta o crédito...\n} catch (SQLException e) {\n    connection.rollback(beforeOfCredit); // desfaz só o que veio depois do savepoint, mantém o débito\n}","fidelityText":"Savepoint antesDoCredito = conexao.setSavepoint(); try { // tenta o crédito... } catch (SQLException e) { conexao.rollback(antesDoCredito); // desfaz só o que veio depois do savepoint, mantém o débito }","highlightedHtml":"Savepoint beforeOfCredit = connection.setSavepoint();\n<span class=\"kw\">try</span> {\n    <span class=\"com\">// tenta o crédito...</span>\n} <span class=\"kw\">catch</span> (SQLException e) {\n    connection.rollback(beforeOfCredit); <span class=\"com\">// desfaz só o que veio depois do savepoint, mantém o débito</span>\n}","caption":"Exemplo executável de jdbc.","explanation":["Um savepoint permite desfazer só uma parte da transação (o que veio depois dele), preservando o trabalho já confirmado antes."]},{"id":"jdbc-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Batch: agrupando várias operações em uma única viagem de rede</h2>","fidelityText":"Batch: agrupando várias operações em uma única viagem de rede"},{"id":"jdbc-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"try (PreparedStatement stmt = connection.prepareStatement(\n        \"INSERT INTO books (title, author, pages) VALUES (?, ?, ?)\")) {\n    for (Book book : books) {\n        stmt.setString(1, book.getTitle());\n        stmt.setString(2, book.getAuthor());\n        stmt.setInt(3, book.getPages());\n        stmt.addBatch(); // acumula esta combinação de parâmetros, ainda não executa\n    }\n    int[] linesByOperation = stmt.executeBatch(); // envia TODAS as inserções em uma única viagem de rede\n}","fidelityText":"try (PreparedStatement stmt = conexao.prepareStatement( \"INSERT INTO livros (titulo, autor, paginas) VALUES (?, ?, ?)\")) { for (Livro livro : livros) { stmt.setString(1, livro.getTitulo()); stmt.setString(2, livro.getAutor()); stmt.setInt(3, livro.getPaginas()); stmt.addBatch(); // acumula esta combinação de parâmetros, ainda não executa } int[] linhasPorOperacao = stmt.executeBatch(); // envia TODAS as inserções em uma única viagem de rede }","highlightedHtml":"<span class=\"kw\">try</span> (PreparedStatement stmt = connection.prepareStatement(\n        <span class=\"str\">\"INSERT INTO books (title, author, pages) VALUES (?, ?, ?)\"</span>)) {\n    <span class=\"kw\">for</span> (<span class=\"cls\">Book</span> book : books) {\n        stmt.setString(1, book.getTitle());\n        stmt.setString(2, book.getAuthor());\n        stmt.setInt(3, book.getPages());\n        stmt.addBatch(); <span class=\"com\">// acumula esta combinação de parâmetros, ainda não executa</span>\n    }\n    <span class=\"kw\">int</span>[] linesByOperation = stmt.executeBatch(); <span class=\"com\">// envia TODAS as inserções em uma única viagem de rede</span>\n}","caption":"Exemplo executável de jdbc.","explanation":["addBatch acumula parâmetros sem executar; executeBatch envia todas as inserções em uma única viagem de rede, reduzindo overhead em cargas grandes."],"commonMistakes":["Chamar executeUpdate() em loop em vez de addBatch()/executeBatch() para inserções em massa"]},{"id":"jdbc-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Sem batch, inserir 1000 linhas significa 1000 viagens de rede separadas (cada uma com sua latência) — <code>addBatch()</code>/<code>executeBatch()</code> agrupa tudo em uma única troca com o banco, reduzindo drasticamente o overhead de rede em cargas de escrita grandes.</p>","fidelityText":"Sem batch, inserir 1000 linhas significa 1000 viagens de rede separadas (cada uma com sua latência) — addBatch()/executeBatch() agrupa tudo em uma única troca com o banco, reduzindo drasticamente o overhead de rede em cargas de escrita grandes."},{"id":"jdbc-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Chaves geradas: recuperando o ID gerado pelo banco após um INSERT</h2>","fidelityText":"Chaves geradas: recuperando o ID gerado pelo banco após um INSERT"},{"id":"jdbc-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"try (PreparedStatement stmt = connection.prepareStatement(\n        \"INSERT INTO books (title, author, pages) VALUES (?, ?, ?)\",\n        Statement.RETURN_GENERATED_KEYS)) { // avisa o driver que você quer a chave gerada de volta\n    stmt.setString(1, \"Duna\");\n    stmt.setString(2, \"Herbert\");\n    stmt.setInt(3, 412);\n    stmt.executeUpdate();\n\n    try (ResultSet keys = stmt.getGeneratedKeys()) {\n        if (keys.next()) {\n            long idGerado = keys.getLong(1); // o SERIAL/IDENTITY que o Postgres gerou para esta linha\n        }\n    }\n}","fidelityText":"try (PreparedStatement stmt = conexao.prepareStatement( \"INSERT INTO livros (titulo, autor, paginas) VALUES (?, ?, ?)\", Statement.RETURN_GENERATED_KEYS)) { // avisa o driver que você quer a chave gerada de volta stmt.setString(1, \"Duna\"); stmt.setString(2, \"Herbert\"); stmt.setInt(3, 412); stmt.executeUpdate(); try (ResultSet chaves = stmt.getGeneratedKeys()) { if (chaves.next()) { long idGerado = chaves.getLong(1); // o SERIAL/IDENTITY que o Postgres gerou para esta linha } } }","highlightedHtml":"<span class=\"kw\">try</span> (PreparedStatement stmt = connection.prepareStatement(\n        <span class=\"str\">\"INSERT INTO books (title, author, pages) VALUES (?, ?, ?)\"</span>,\n        Statement.RETURN_GENERATED_KEYS)) { <span class=\"com\">// avisa o driver que você quer a chave gerada de volta</span>\n    stmt.setString(1, <span class=\"str\">\"Duna\"</span>);\n    stmt.setString(2, <span class=\"str\">\"Herbert\"</span>);\n    stmt.setInt(3, 412);\n    stmt.executeUpdate();\n\n    <span class=\"kw\">try</span> (ResultSet keys = stmt.getGeneratedKeys()) {\n        <span class=\"kw\">if</span> (keys.next()) {\n            <span class=\"kw\">long</span> idGerado = keys.getLong(1); <span class=\"com\">// o SERIAL/IDENTITY que o Postgres gerou para esta linha</span>\n        }\n    }\n}","caption":"Exemplo executável de jdbc.","explanation":["RETURN_GENERATED_KEYS avisa o driver para devolver a chave gerada pelo banco (SERIAL/IDENTITY) através de getGeneratedKeys()."]},{"id":"jdbc-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>wasNull(): distinguindo zero de NULL</h2>","fidelityText":"wasNull(): distinguindo zero de NULL"},{"id":"jdbc-code-23","type":"code","authorship":"legacy-preserved","language":"java","source":"int pages = rs.getInt(\"pages\"); // se a coluna for SQL NULL, getInt devolve 0 -- indistinguível de \"realmente zero\"!\nif (rs.wasNull()) {\n    // a última coluna lida era NULL -- \"paginas\" não é 0, é ausente\n}","fidelityText":"int paginas = rs.getInt(\"paginas\"); // se a coluna for SQL NULL, getInt devolve 0 -- indistinguível de \"realmente zero\"! if (rs.wasNull()) { // a última coluna lida era NULL -- \"paginas\" não é 0, é ausente }","highlightedHtml":"<span class=\"kw\">int</span> pages = rs.getInt(<span class=\"str\">\"pages\"</span>); <span class=\"com\">// se a coluna for SQL NULL, getInt devolve 0 -- indistinguível de \"realmente zero\"!</span>\n<span class=\"kw\">if</span> (rs.wasNull()) {\n    <span class=\"com\">// a última coluna lida era NULL -- \"paginas\" não é 0, é ausente</span>\n}","caption":"Exemplo executável de jdbc.","explanation":["getInt devolve 0 tanto para uma coluna genuinamente zero quanto para NULL -- wasNull() é a única forma de distinguir os dois casos para tipos primitivos."],"commonMistakes":["Tratar 0 vindo de getInt como garantia de que a coluna não é NULL"]},{"id":"jdbc-content-24","type":"html","authorship":"legacy-preserved","html":"<p>Para tipos primitivos (<code>getInt</code>, <code>getDouble</code>, <code>getBoolean</code>...), o <code>ResultSet</code> não tem como devolver <code>null</code> — ele devolve o valor \"zero\" do tipo (0, 0.0, false) tanto para uma coluna genuinamente zero quanto para uma coluna <code>NULL</code>. <code>wasNull()</code>, chamado logo após o <code>get</code>, é a única forma de saber qual dos dois casos realmente aconteceu. Para colunas que podem ser <code>NULL</code> e você quer tratar isso como ausência real, prefira <code>getObject(\"paginas\", Integer.class)</code>, que devolve <code>null</code> diretamente.</p>","fidelityText":"Para tipos primitivos (getInt, getDouble, getBoolean...), o ResultSet não tem como devolver null — ele devolve o valor \"zero\" do tipo (0, 0.0, false) tanto para uma coluna genuinamente zero quanto para uma coluna NULL. wasNull(), chamado logo após o get, é a única forma de saber qual dos dois casos realmente aconteceu. Para colunas que podem ser NULL e você quer tratar isso como ausência real, prefira getObject(\"paginas\", Integer.class), que devolve null diretamente."},{"id":"jdbc-content-25","type":"html","authorship":"legacy-preserved","html":"<h2>SQLException: SQLState, código do fornecedor e tradução</h2>","fidelityText":"SQLException: SQLState, código do fornecedor e tradução"},{"id":"jdbc-code-26","type":"code","authorship":"legacy-preserved","language":"java","source":"try {\n    stmt.executeUpdate();\n} catch (SQLException e) {\n    System.out.println(e.getSQLState());   // código padronizado ANSI/ISO, ex: \"23505\" = violação de unicidade\n    System.out.println(e.getErrorCode());  // código específico do PostgreSQL para o mesmo erro\n\n    if (\"23505\".equals(e.getSQLState())) {\n        throw new TitleAlreadyCadastradoException(\"already exists a book with this title\", e); // causa original preservada\n    }\n    throw new RuntimeException(\"Error unexpected to the save book\", e);\n}","fidelityText":"try { stmt.executeUpdate(); } catch (SQLException e) { System.out.println(e.getSQLState()); // código padronizado ANSI/ISO, ex: \"23505\" = violação de unicidade System.out.println(e.getErrorCode()); // código específico do PostgreSQL para o mesmo erro if (\"23505\".equals(e.getSQLState())) { throw new TituloJaCadastradoException(\"Já existe um livro com este título\", e); // causa original preservada } throw new RuntimeException(\"Erro inesperado ao salvar livro\", e); }","highlightedHtml":"<span class=\"kw\">try</span> {\n    stmt.executeUpdate();\n} <span class=\"kw\">catch</span> (SQLException e) {\n    System.out.println(e.getSQLState());   <span class=\"com\">// código padronizado ANSI/ISO, ex: \"23505\" = violação de unicidade</span>\n    System.out.println(e.getErrorCode());  <span class=\"com\">// código específico do PostgreSQL para o mesmo erro</span>\n\n    <span class=\"kw\">if</span> (<span class=\"str\">\"23505\"</span>.equals(e.getSQLState())) {\n        <span class=\"kw\">throw new</span> <span class=\"cls\">TitleAlreadyCadastradoException</span>(<span class=\"str\">\"already exists a book with this title\"</span>, e); <span class=\"com\">// causa original preservada</span>\n    }\n    <span class=\"kw\">throw new</span> <span class=\"cls\">RuntimeException</span>(<span class=\"str\">\"Error unexpected to the save book\"</span>, e);\n}","caption":"Exemplo executável de jdbc.","explanation":["SQLState é um código de 5 caracteres padronizado entre bancos; getErrorCode() é específico do driver/fornecedor e não é portável."],"commonMistakes":["Expor SQLException genérica direto ao invés de traduzir para uma exceção de domínio significativa"]},{"id":"jdbc-content-27","type":"html","authorship":"legacy-preserved","html":"<p><code>SQLState</code> é um código de 5 caracteres <strong>padronizado entre bancos</strong> (definido pelo padrão SQL/ANSI) — <code>23505</code> significa violação de restrição de unicidade em praticamente qualquer banco compatível, incluindo PostgreSQL. O código de erro específico do driver (<code>getErrorCode()</code>) é próprio de cada fornecedor e não é portável. <strong>Tradução de exceção</strong> é o processo de converter uma <code>SQLException</code> genérica (com esses códigos técnicos) em uma exceção de domínio significativa — <code>TituloJaCadastradoException</code> comunica a intenção para quem chama, sem expor detalhes de SQLState pela aplicação inteira. É exatamente esse mecanismo que o Spring automatiza com <code>@Repository</code> e sua hierarquia de <code>DataAccessException</code>.</p>","fidelityText":"SQLState é um código de 5 caracteres padronizado entre bancos (definido pelo padrão SQL/ANSI) — 23505 significa violação de restrição de unicidade em praticamente qualquer banco compatível, incluindo PostgreSQL. O código de erro específico do driver (getErrorCode()) é próprio de cada fornecedor e não é portável. Tradução de exceção é o processo de converter uma SQLException genérica (com esses códigos técnicos) em uma exceção de domínio significativa — TituloJaCadastradoException comunica a intenção para quem chama, sem expor detalhes de SQLState pela aplicação inteira. É exatamente esse mecanismo que o Spring automatiza com @Repository e sua hierarquia de DataAccessException."},{"id":"jdbc-content-28","type":"html","authorship":"legacy-preserved","html":"<h2>Connection Pool — por que ninguém abre conexão \"crua\" em produção</h2>","fidelityText":"Connection Pool — por que ninguém abre conexão \"crua\" em produção"},{"id":"jdbc-content-29","type":"html","authorship":"legacy-preserved","html":"<p>Abrir uma <code>Connection</code> nova a cada operação é caro (handshake de rede, autenticação). Um <strong>pool de conexões</strong> (ex: HikariCP, que é o padrão usado pelo Spring Boot) mantém um conjunto de conexões já abertas e as reutiliza entre requisições.</p>","fidelityText":"Abrir uma Connection nova a cada operação é caro (handshake de rede, autenticação). Um pool de conexões (ex: HikariCP, que é o padrão usado pelo Spring Boot) mantém um conjunto de conexões já abertas e as reutiliza entre requisições."},{"id":"jdbc-content-30","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Quando você configurar um <code>DataSource</code> no Spring Boot (geralmente só com algumas linhas em <code>application.properties</code>), o framework vai montar automaticamente um <code>Connection</code> pool HikariCP por trás — e o <code>JdbcTemplate</code>/Spring Data JPA vão usar exatamente os mesmos conceitos de <code>PreparedStatement</code> e mapeamento de <code>ResultSet</code> que você acabou de ver, só que com boilerplate reduzido drasticamente. Entender o JDBC puro é entender <em>o que</em> essas camadas de conveniência estão economizando do seu código.</div>","fidelityText":"Quando você configurar um DataSource no Spring Boot (geralmente só com algumas linhas em application.properties), o framework vai montar automaticamente um Connection pool HikariCP por trás — e o JdbcTemplate/Spring Data JPA vão usar exatamente os mesmos conceitos de PreparedStatement e mapeamento de ResultSet que você acabou de ver, só que com boilerplate reduzido drasticamente. Entender o JDBC puro é entender o que essas camadas de conveniência estão economizando do seu código."},{"id":"jdbc-content-31","type":"html","authorship":"legacy-preserved","html":"<h2>Mapeando ResultSet para objetos — o embrião do ORM</h2>","fidelityText":"Mapeando ResultSet para objetos — o embrião do ORM"},{"id":"jdbc-code-32","type":"code","authorship":"legacy-preserved","language":"java","source":"public List<Book> findAll(Connection connection) throws SQLException {\n    List<Book> books = new ArrayList<>();\n    try (Statement stmt = connection.createStatement();\n         ResultSet rs = stmt.executeQuery(\"SELECT * FROM books\")) {\n        while (rs.next()) {\n            Book book = new Book(\n                rs.getLong(\"id\"),\n                rs.getString(\"title\"),\n                rs.getString(\"author\"),\n                rs.getInt(\"pages\")\n            );\n            books.add(book);\n        }\n    }\n    return books;\n}","fidelityText":"public List<Livro> buscarTodos(Connection conexao) throws SQLException { List<Livro> livros = new ArrayList<>(); try (Statement stmt = conexao.createStatement(); ResultSet rs = stmt.executeQuery(\"SELECT * FROM livros\")) { while (rs.next()) { Livro livro = new Livro( rs.getLong(\"id\"), rs.getString(\"titulo\"), rs.getString(\"autor\"), rs.getInt(\"paginas\") ); livros.add(livro); } } return livros; }","highlightedHtml":"<span class=\"kw\">public</span> List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findAll</span>(Connection connection) <span class=\"kw\">throws</span> SQLException {\n    List&lt;<span class=\"cls\">Book</span>&gt; books = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n    <span class=\"kw\">try</span> (Statement stmt = connection.createStatement();\n         ResultSet rs = stmt.executeQuery(<span class=\"str\">\"SELECT * FROM books\"</span>)) {\n        <span class=\"kw\">while</span> (rs.next()) {\n            <span class=\"cls\">Book</span> book = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(\n                rs.getLong(<span class=\"str\">\"id\"</span>),\n                rs.getString(<span class=\"str\">\"title\"</span>),\n                rs.getString(<span class=\"str\">\"author\"</span>),\n                rs.getInt(<span class=\"str\">\"pages\"</span>)\n            );\n            books.add(book);\n        }\n    }\n    <span class=\"kw\">return</span> books;\n}","caption":"Exemplo executável de jdbc.","explanation":["Mapear cada linha do ResultSet para um objeto Java manualmente é o que um ORM (Hibernate/JPA) automatiza depois, usando anotações e reflection em vez de código explícito."]},{"id":"jdbc-content-33","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Isso é literalmente o que um ORM (Object-Relational Mapper) automatiza.</b> Hibernate/JPA — usados pelo Spring Data JPA — fazem exatamente esse \"linha de tabela vira objeto Java\" automaticamente, usando anotações (<code>@Entity</code>, <code>@Column</code>) e reflection (capítulo 20) para descobrir os mapeamentos, no lugar de você escrever esse <code>while (rs.next())</code> manualmente para cada tabela.</div>","fidelityText":"Isso é literalmente o que um ORM (Object-Relational Mapper) automatiza. Hibernate/JPA — usados pelo Spring Data JPA — fazem exatamente esse \"linha de tabela vira objeto Java\" automaticamente, usando anotações (@Entity, @Column) e reflection (capítulo 20) para descobrir os mapeamentos, no lugar de você escrever esse while (rs.next()) manualmente para cada tabela."},{"id":"jdbc-content-34","type":"html","authorship":"legacy-preserved","html":"<h2>Testando JDBC de verdade: banco real, não simulado</h2>","fidelityText":"Testando JDBC de verdade: banco real, não simulado"},{"id":"jdbc-content-35","type":"html","authorship":"legacy-preserved","html":"<p>Testar contra um Postgres real (via <strong>Testcontainers</strong>, que sobe um container descartável só para a execução dos testes) é mais confiável do que mockar <code>Connection</code>/<code>ResultSet</code> ou depender do banco de desenvolvimento da sua máquina: um mock não valida se o SQL é sintaticamente correto, se os tipos batem com o schema real, nem se uma constraint do banco (como a violação de unicidade do exemplo anterior) realmente dispara. O capítulo de Testcontainers, mais adiante, aprofunda o ciclo de vida do container e a integração com testes JUnit.</p>","fidelityText":"Testar contra um Postgres real (via Testcontainers, que sobe um container descartável só para a execução dos testes) é mais confiável do que mockar Connection/ResultSet ou depender do banco de desenvolvimento da sua máquina: um mock não valida se o SQL é sintaticamente correto, se os tipos batem com o schema real, nem se uma constraint do banco (como a violação de unicidade do exemplo anterior) realmente dispara. O capítulo de Testcontainers, mais adiante, aprofunda o ciclo de vida do container e a integração com testes JUnit."},{"id":"jdbc-exercise-36","type":"exercise","authorship":"legacy-preserved","title":"Exercício 24.1 — Repository JDBC","prompt":"Implemente LivroRepositorioJdbc recebendo um DataSource. Cada operação adquire e devolve uma conexão pelo try-with-resources. Implemente salvar, buscarPorId e listarTodos; depois explique como uma camada de serviço forneceria a mesma conexão para várias operações dentro de uma transação.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 24.1 — Repository JDBCdifícil Implemente LivroRepositorioJdbc recebendo um DataSource. Cada operação adquire e devolve uma conexão pelo try-with-resources. Implemente salvar, buscarPorId e listarTodos; depois explique como uma camada de serviço forneceria a mesma conexão para várias operações dentro de uma transação. Ver solução public class LivroRepositorioJdbc implements LivroRepositorio { private final DataSource dataSource; public LivroRepositorioJdbc(DataSource dataSource) { this.dataSource = dataSource; } @Override public void salvar(Livro livro) { try (Connection conexao = dataSource.getConnection(); PreparedStatement stmt = conexao.prepareStatement( \"INSERT INTO livros (titulo, autor, paginas) VALUES (?, ?, ?)\")) { stmt.setString(1, livro.getTitulo()); stmt.setString(2, livro.getAutor()); stmt.setInt(3, livro.getPaginas()); stmt.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(\"Erro ao salvar livro\", e); } } @Override public Optional<Livro> buscarPorId(long id) { try (Connection conexao = dataSource.getConnection(); PreparedStatement stmt = conexao.prepareStatement( \"SELECT * FROM livros WHERE id = ?\")) { stmt.setLong(1, id); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { return Optional.of(new Livro(rs.getLong(\"id\"), rs.getString(\"titulo\"), rs.getString(\"autor\"), rs.getInt(\"paginas\"))); } return Optional.empty(); } } catch (SQLException e) { throw new RuntimeException(\"Erro ao buscar livro\", e); } } @Override public List<Livro> listarTodos() { List<Livro> livros = new ArrayList<>(); try (Connection conexao = dataSource.getConnection(); Statement stmt = conexao.createStatement(); ResultSet rs = stmt.executeQuery(\"SELECT * FROM livros\")) { while (rs.next()) { livros.add(new Livro(rs.getLong(\"id\"), rs.getString(\"titulo\"), rs.getString(\"autor\"), rs.getInt(\"paginas\"))); } } catch (SQLException e) { throw new RuntimeException(\"Erro ao listar livros\", e); } return livros; } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 24.1 — Repository JDBC</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Implemente <code>LivroRepositorioJdbc</code> recebendo um <code>DataSource</code>. Cada operação adquire e devolve uma conexão pelo try-with-resources. Implemente <code>salvar</code>, <code>buscarPorId</code> e <code>listarTodos</code>; depois explique como uma camada de serviço forneceria a mesma conexão para várias operações dentro de uma transação.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">BookRepositoryJdbc</span> <span class=\"kw\">implements</span> <span class=\"cls\">BookRepository</span> {\n    <span class=\"kw\">private final</span> DataSource dataSource;\n    <span class=\"kw\">public</span> <span class=\"fn\">BookRepositoryJdbc</span>(DataSource dataSource) { <span class=\"kw\">this</span>.dataSource = dataSource; }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">save</span>(<span class=\"cls\">Book</span> book) {\n        <span class=\"kw\">try</span> (Connection connection = dataSource.getConnection();\n             PreparedStatement stmt = connection.prepareStatement(\n                <span class=\"str\">\"INSERT INTO books (title, author, pages) VALUES (?, ?, ?)\"</span>)) {\n            stmt.setString(1, book.getTitle());\n            stmt.setString(2, book.getAuthor());\n            stmt.setInt(3, book.getPages());\n            stmt.executeUpdate();\n        } <span class=\"kw\">catch</span> (SQLException e) {\n            <span class=\"kw\">throw new</span> <span class=\"cls\">RuntimeException</span>(<span class=\"str\">\"Error to the save book\"</span>, e);\n        }\n    }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public</span> Optional&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findById</span>(<span class=\"kw\">long</span> id) {\n        <span class=\"kw\">try</span> (Connection connection = dataSource.getConnection();\n             PreparedStatement stmt = connection.prepareStatement(\n                <span class=\"str\">\"SELECT * FROM books WHERE id = ?\"</span>)) {\n            stmt.setLong(1, id);\n            <span class=\"kw\">try</span> (ResultSet rs = stmt.executeQuery()) {\n                <span class=\"kw\">if</span> (rs.next()) {\n                    <span class=\"kw\">return</span> Optional.of(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(rs.getLong(<span class=\"str\">\"id\"</span>), rs.getString(<span class=\"str\">\"title\"</span>),\n                        rs.getString(<span class=\"str\">\"author\"</span>), rs.getInt(<span class=\"str\">\"pages\"</span>)));\n                }\n                <span class=\"kw\">return</span> Optional.empty();\n            }\n        } <span class=\"kw\">catch</span> (SQLException e) {\n            <span class=\"kw\">throw new</span> <span class=\"cls\">RuntimeException</span>(<span class=\"str\">\"Error to the find book\"</span>, e);\n        }\n    }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public</span> List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">listarAll</span>() {\n        List&lt;<span class=\"cls\">Book</span>&gt; books = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n        <span class=\"kw\">try</span> (Connection connection = dataSource.getConnection();\n             Statement stmt = connection.createStatement();\n             ResultSet rs = stmt.executeQuery(<span class=\"str\">\"SELECT * FROM books\"</span>)) {\n            <span class=\"kw\">while</span> (rs.next()) {\n                books.add(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(rs.getLong(<span class=\"str\">\"id\"</span>), rs.getString(<span class=\"str\">\"title\"</span>),\n                    rs.getString(<span class=\"str\">\"author\"</span>), rs.getInt(<span class=\"str\">\"pages\"</span>)));\n            }\n        } <span class=\"kw\">catch</span> (SQLException e) {\n            <span class=\"kw\">throw new</span> <span class=\"cls\">RuntimeException</span>(<span class=\"str\">\"Error to the list books\"</span>, e);\n        }\n        <span class=\"kw\">return</span> books;\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"jdbc-exercise-37","type":"exercise","authorship":"legacy-preserved","title":"Exercício 24.2 — Transferência atômica com rollback","prompt":"Implemente um método transferir(Connection conexao, long contaOrigem, long contaDestino, double valor) que debita e credita dentro da mesma transação (autoCommit=false, commit/rollback). Escreva um teste que force uma falha no crédito (ex.: contaDestino inexistente) e prove — consultando o saldo depois — que o débito também foi revertido, não só o crédito.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 24.2 — Transferência atômica com rollbackdifícil Implemente um método transferir(Connection conexao, long contaOrigem, long contaDestino, double valor) que debita e credita dentro da mesma transação (autoCommit=false, commit/rollback). Escreva um teste que force uma falha no crédito (ex.: contaDestino inexistente) e prove — consultando o saldo depois — que o débito também foi revertido, não só o crédito. Ver solução public void transferir(Connection conexao, long origem, long destino, double valor) throws SQLException { conexao.setAutoCommit(false); try { try (PreparedStatement debito = conexao.prepareStatement( \"UPDATE contas SET saldo = saldo - ? WHERE id = ?\")) { debito.setDouble(1, valor); debito.setLong(2, origem); debito.executeUpdate(); } try (PreparedStatement credito = conexao.prepareStatement( \"UPDATE contas SET saldo = saldo + ? WHERE id = ?\")) { credito.setDouble(1, valor); credito.setLong(2, destino); int linhas = credito.executeUpdate(); if (linhas == 0) throw new SQLException(\"Conta destino inexistente: \" + destino); } conexao.commit(); } catch (SQLException e) { conexao.rollback(); throw e; } finally { conexao.setAutoCommit(true); } } // teste: chamar transferir com contaDestino inexistente, capturar a SQLException, // e então consultar o saldo de contaOrigem -- deve estar INALTERADO, provando o rollback","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 24.2 — Transferência atômica com rollback</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Implemente um método <code>transferir(Connection conexao, long contaOrigem, long contaDestino, double valor)</code> que debita e credita dentro da mesma transação (<code>autoCommit=false</code>, <code>commit</code>/<code>rollback</code>). Escreva um teste que force uma falha no crédito (ex.: <code>contaDestino</code> inexistente) e prove — consultando o saldo depois — que o débito também foi revertido, não só o crédito.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public void</span> <span class=\"fn\">transfer</span>(Connection connection, <span class=\"kw\">long</span> source, <span class=\"kw\">long</span> recipient, <span class=\"kw\">double</span> value) <span class=\"kw\">throws</span> SQLException {\n    connection.setAutoCommit(<span class=\"kw\">false</span>);\n    <span class=\"kw\">try</span> {\n        <span class=\"kw\">try</span> (PreparedStatement debit = connection.prepareStatement(\n                <span class=\"str\">\"UPDATE accounts SET balance = balance - ? WHERE id = ?\"</span>)) {\n            debit.setDouble(1, value); debit.setLong(2, source);\n            debit.executeUpdate();\n        }\n        <span class=\"kw\">try</span> (PreparedStatement credit = connection.prepareStatement(\n                <span class=\"str\">\"UPDATE accounts SET balance = balance + ? WHERE id = ?\"</span>)) {\n            credit.setDouble(1, value); credit.setLong(2, recipient);\n            <span class=\"kw\">int</span> lines = credit.executeUpdate();\n            <span class=\"kw\">if</span> (lines == 0) <span class=\"kw\">throw new</span> SQLException(<span class=\"str\">\"Account recipient nonexistent: \"</span> + recipient);\n        }\n        connection.commit();\n    } <span class=\"kw\">catch</span> (SQLException e) {\n        connection.rollback();\n        <span class=\"kw\">throw</span> e;\n    } <span class=\"kw\">finally</span> {\n        connection.setAutoCommit(<span class=\"kw\">true</span>);\n    }\n}\n<span class=\"com\">// teste: chamar transferir com contaDestino inexistente, capturar a SQLException,\n// e então consultar o saldo de contaOrigem -- deve estar INALTERADO, provando o rollback</span></pre>\n        </div>\n      </div>"},{"id":"jdbc-error","type":"error-case","authorship":"authored","title":"SQL injection começa como concatenação inocente","scenario":"O código monta SELECT concatenando o título digitado pelo usuário.","symptom":"Entrada maliciosa muda a consulta, retorna dados indevidos ou quebra o comando.","cause":"SQL e valor externo foram misturados no mesmo texto.","diagnosis":["Localize concatenação com entrada de usuário","Troque por PreparedStatement","Teste aspas, comentários e caracteres especiais","Registre SQL parametrizado sem expor dados sensíveis"],"correction":"Use placeholders para valores e allowlist para nomes estruturais como coluna/tabela.","prevention":"Revisão bloqueia SQL montado por concatenação de input."},{"id":"jdbc-quiz","type":"quiz","authorship":"authored","conceptId":"preparedstatement-parametro","prompt":"O que PreparedStatement protege melhor?","options":[{"id":"jdbc-q-a","label":"Separar o texto SQL dos valores fornecidos externamente.","correct":true,"explanation":"Parâmetros são enviados como valores, reduzindo injeção e melhorando conversão de tipos."},{"id":"jdbc-q-b","label":"Permitir que usuário escolha qualquer nome de tabela com segurança automática.","correct":false,"explanation":"Identificadores estruturais exigem allowlist; placeholders protegem valores."},{"id":"jdbc-q-c","label":"Eliminar a necessidade de transação e fechamento de recursos.","correct":false,"explanation":"Statement parametrizado não substitui lifecycle nem commit/rollback."}]}],"resources":[{"id":"jdbc-basics-oracle","type":"reference","title":"Oracle JDBC Basics","url":"https://docs.oracle.com/javase/tutorial/jdbc/basics/","reinforces":"Conexões, statements, ResultSet e transações no modelo JDBC.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"preparedstatement-api","type":"reference","title":"PreparedStatement API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/PreparedStatement.html","reinforces":"Contrato de parâmetros, execução e SQL pré-compilado/parametrizado.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A jdbc operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a jdbc operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this jdbc chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"String url = \"jdbc:postgresql://localhost:5432/library\";","instruction":"A jdbc operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this jdbc chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mini-financas-jdbc","moduleId":"relational-data-jdbc","order":5,"title":"Mini-projeto: finanças pessoais com PostgreSQL","summary":"Construa um sistema de contas, categorias e lançamentos recorrentes. Comece pelo esquema e pelas invariantes; a interface pode continuar no terminal.","objectives":["Construir mini-ledger financeiro com PostgreSQL e JDBC","Persistir lançamentos com tipos e constraints adequados","Executar transferências em transação explícita","Provar rollback, consulta agregada e evidências de execução"],"whyItExists":"O projeto força SQL, PostgreSQL, JDBC, transação e teste manual/automatizado a trabalharem juntos. O objetivo não é tela bonita: é provar que dinheiro, falha e persistência têm contrato.","prerequisiteChapterIds":["jdbc","postgres","migrations"],"conceptIds":["requisitos","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["financas-ledger-invariante"],"usedConceptIds":["postgres-tipo-dado-dominio","migration-versionada-checksum","jdbc-transacao-rollback","preparedstatement-parametro","agregacao-groupby-sql","teste-aaa-first"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"financas-intuition","type":"intuition","authorship":"authored","title":"Saldo é consequência, não chute","body":"Em finanças, cada mudança precisa deixar rastro. O saldo pode ser derivado dos lançamentos; atualizar um número solto sem histórico torna auditoria e rollback frágeis.","analogyLimit":"Extrato bancário ajuda a pensar em lançamentos, mas o sistema precisa de constraints, transação e teste de falha, não apenas lista visual."},{"id":"mini-financas-jdbc-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>SQL, JDBC e transações</b></div><div class=\"time-est\">Tempo: <b>12–20 horas</b></div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#jdbc\">JDBC</a>, <a class=\"prereq-tag\" href=\"#postgres\">PostgreSQL</a></div></div>","fidelityText":"Objetivo: SQL, JDBC e transaçõesTempo: 12–20 horasPré-requisitos: JDBC, PostgreSQL"},{"id":"mini-financas-jdbc-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Construa um sistema de contas, categorias e lançamentos recorrentes. Comece pelo esquema e pelas invariantes; a interface pode continuar no terminal.</p>","fidelityText":"Construa um sistema de contas, categorias e lançamentos recorrentes. Comece pelo esquema e pelas invariantes; a interface pode continuar no terminal."},{"id":"mini-financas-jdbc-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Requisitos</h2>","fidelityText":"Requisitos"},{"id":"mini-financas-jdbc-checklist-4","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-financas-jdbc-checklist-0","label":"Migrations versionadas com chaves, restrições e índices justificados."},{"id":"mini-financas-jdbc-checklist-1","label":"DAO usando parâmetros, nunca SQL concatenado com entrada do usuário."},{"id":"mini-financas-jdbc-checklist-2","label":"Transferência entre contas atômica: debitar e creditar na mesma transação."},{"id":"mini-financas-jdbc-checklist-3","label":"Relatório mensal agregado no banco e comparado com cálculo em Java."},{"id":"mini-financas-jdbc-checklist-4","label":"Teste que prova rollback quando a segunda operação falha."}]},{"id":"mini-financas-jdbc-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não use <code>double</code> para saldo.</b> Defina precisão no Java e no banco e prove como o arredondamento funciona.</div>","fidelityText":"Não use double para saldo. Defina precisão no Java e no banco e prove como o arredondamento funciona."},{"id":"mini-financas-jdbc-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-financas-jdbc:0","type":"quiz","authorship":"legacy-preserved","conceptId":"o-que-torna-uma-transferencia-atomica","prompt":"O que torna uma transferência atômica?","options":[{"id":"mini-financas-jdbc:0:option:0","label":"Débito e crédito na mesma transação, com rollback e controle de concorrência.","correct":true,"explanation":"Transação garante que as duas alterações da transferência confirmem juntas ou sejam desfeitas juntas."},{"id":"mini-financas-jdbc:0:option:1","label":"Dois UPDATEs em conexões independentes.","correct":false,"explanation":"try/catch não torna duas escritas atômicas por si só."},{"id":"mini-financas-jdbc:0:option:2","label":"Executar o crédito alguns segundos depois.","correct":false,"explanation":"Ordem visual não substitui commit/rollback no banco."}],"sourceIndex":7},{"id":"mini-financas-jdbc:1","type":"quiz","authorship":"legacy-preserved","conceptId":"um-dao-monta-a-query-concatenando-o-nome-da-categoria-digitado-pelo-usua","prompt":"Um DAO monta a query concatenando o nome da categoria digitado pelo usuário diretamente na string SQL. Qual é o risco e a correção?","options":[{"id":"mini-financas-jdbc:1:option:0","label":"SQL Injection: a entrada do usuário deve ir como parâmetro de um PreparedStatement, nunca concatenada na string SQL.","correct":true,"explanation":"PreparedStatement separa o texto SQL fixo dos valores de entrada, eliminando a classe inteira de SQL Injection."},{"id":"mini-financas-jdbc:1:option:1","label":"Nenhum risco, desde que o campo seja validado como texto no Java antes de montar a query.","correct":false,"explanation":"Validação de formato no Java não impede que caracteres de controle SQL cheguem à query concatenada."},{"id":"mini-financas-jdbc:1:option:2","label":"O risco existe só se o banco de dados não tiver senha configurada.","correct":false,"explanation":"SQL Injection é sobre como a query é montada, independente de a conexão ter senha ou não."}],"sourceIndex":8},{"id":"mini-financas-jdbc:2","type":"quiz","authorship":"legacy-preserved","conceptId":"uma-transferencia-tenta-debitar-mais-do-que-o-saldo-disponivel-na-conta-","prompt":"Uma transferência tenta debitar mais do que o saldo disponível na conta de origem. Em que ponto isso deve ser detectado?","options":[{"id":"mini-financas-jdbc:2:option:0","label":"Antes de confirmar a transação, impedindo o commit do débito -- saldo suficiente é parte da invariante do lançamento, não uma checagem \"para depois\".","correct":true,"explanation":"Detectar saldo insuficiente antes do commit é o que impede o banco de ficar com um débito sem o crédito correspondente."},{"id":"mini-financas-jdbc:2:option:1","label":"Depois do commit, corrigindo o saldo com um lançamento de estorno automático.","correct":false,"explanation":"Um estorno automático depois do commit é um lançamento adicional que reconhece que a operação original nunca deveria ter sido confirmada."},{"id":"mini-financas-jdbc:2:option:2","label":"No relatório mensal, quando o saldo negativo aparece agregado.","correct":false,"explanation":"Descobrir o problema só no relatório mensal significa que o banco já ficou em um estado inválido por todo esse tempo."}],"sourceIndex":9},{"id":"mini-financas-jdbc:3","type":"quiz","authorship":"legacy-preserved","conceptId":"por-que-a-coluna-de-valor-monetario-no-postgresql-deve-usar-numeric-ou-d","prompt":"Por que a coluna de valor monetário no PostgreSQL deve usar NUMERIC (ou DECIMAL) em vez de FLOAT/DOUBLE PRECISION?","options":[{"id":"mini-financas-jdbc:3:option:0","label":"NUMERIC representa valores decimais exatos com escala fixa; FLOAT/DOUBLE usam representação binária que introduz erro de arredondamento em valores monetários.","correct":true,"explanation":"NUMERIC guarda dígitos decimais exatos com escala declarada, sem a aproximação binária de FLOAT/DOUBLE."},{"id":"mini-financas-jdbc:3:option:1","label":"FLOAT é mais lento para o PostgreSQL indexar, então NUMERIC é só uma otimização de performance.","correct":false,"explanation":"A diferença entre NUMERIC e FLOAT é de precisão de representação, não de velocidade de indexação."},{"id":"mini-financas-jdbc:3:option:2","label":"Não há diferença real de comportamento; a escolha é apenas estilística.","correct":false,"explanation":"Para dinheiro, a escolha do tipo é uma questão de corretude do valor armazenado, não de estilo."}],"sourceIndex":10},{"id":"mini-financas-jdbc-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"financas-exercise-transacao","type":"exercise","authorship":"authored","title":"Antes de codificar: contrato da transferência atômica","prompt":"Antes de implementar o método de transferência, responda por escrito: (1) quais operações (débito, crédito, validação de saldo) precisam estar dentro da mesma transação e por quê; (2) o que acontece quando o saldo da conta de origem é insuficiente -- em que ponto exato isso é detectado e o que a transação faz; (3) que tipo SQL e que tipo Java representam o valor monetário, e por que double está fora de cogitação; (4) como você provaria, num teste ou roteiro manual, que uma falha no meio da transferência não deixa o banco com débito sem o crédito correspondente.","difficulty":"advanced","criteria":["A resposta 1 inclui débito, crédito e a validação de saldo suficiente dentro da mesma transação, não apenas as duas escritas.","A resposta 2 recusa a transferência antes de qualquer commit, sem deixar débito parcial confirmado no banco.","A resposta 3 aponta NUMERIC/DECIMAL no banco e BigDecimal no Java, com escala explícita.","A resposta 4 descreve um teste ou roteiro que força falha no meio da transação e verifica que o rollback desfez as duas operações."]},{"id":"financas-project","type":"project","authorship":"authored","title":"Ledger pessoal com JDBC","brief":"Implemente contas, lançamentos, categorias e transferências em PostgreSQL usando JDBC direto, migrations versionadas e transações explícitas.","requirements":["Migrations criam tabelas, constraints, índices mínimos e dados de exemplo","Conta possui identidade, nome e moeda","Lançamento guarda valor decimal positivo/negativo por regra declarada, categoria, data e descrição","Transferência registra débito e crédito na mesma transação","Relatórios mostram saldo por conta e gasto por categoria usando SQL agregado","Falhas de validação, saldo insuficiente e erro SQL têm mensagens seguras"],"guidance":"guided","acceptanceCriteria":["Nenhum SQL concatena entrada do usuário como valor","Rollback é demonstrado em teste ou roteiro reproduzível","Dinheiro não usa double/float","README local mostra comandos para criar banco, aplicar migrations e executar consultas","Logs não exibem senha ou string de conexão completa"],"knowledgeMatrix":[{"requirement":"Schema versionado","conceptIds":["migration-versionada-checksum","ddl-schema-constraint"],"chapterIds":["migrations","sql"],"expectedEvidence":"Arquivos V1/V2 criam tabelas, constraints e índices sem editar migration aplicada."},{"requirement":"Dinheiro auditável","conceptIds":["financas-ledger-invariante","postgres-tipo-dado-dominio"],"chapterIds":["postgres","mini-financas-jdbc"],"expectedEvidence":"Saldo é calculado por lançamentos em numeric, não armazenado como double solto."},{"requirement":"Transação de transferência","conceptIds":["jdbc-transacao-rollback","sql-transacao-atomicidade"],"chapterIds":["sql","jdbc"],"expectedEvidence":"Débito e crédito confirmam juntos ou fazem rollback juntos."},{"requirement":"Consulta segura","conceptIds":["preparedstatement-parametro","agregacao-groupby-sql"],"chapterIds":["sql","jdbc"],"expectedEvidence":"Filtros usam PreparedStatement e relatórios usam GROUP BY."},{"requirement":"Evidência de falha","conceptIds":["teste-aaa-first","checked-unchecked-contrato"],"chapterIds":["testes","excecoes"],"expectedEvidence":"Teste ou roteiro cobre saldo insuficiente, constraint violada e banco indisponível."}]},{"id":"financas-quiz","type":"quiz","authorship":"authored","conceptId":"financas-ledger-invariante","prompt":"Por que um ledger é melhor do que apenas atualizar uma coluna saldo?","options":[{"id":"fin-q-a","label":"Porque cada mudança fica rastreável e o saldo pode ser recalculado por eventos persistidos.","correct":true,"explanation":"O histórico permite auditoria, correção e diagnóstico de falha."},{"id":"fin-q-b","label":"Porque ledger elimina a necessidade de transação.","correct":false,"explanation":"Lançamentos relacionados ainda precisam de transação."},{"id":"fin-q-c","label":"Porque ledger permite usar double sem perda.","correct":false,"explanation":"Dinheiro ainda exige tipo decimal/escala adequada."}]}],"resources":[{"id":"financas-postgres-transactions","type":"reference","title":"PostgreSQL: Transactions","url":"https://www.postgresql.org/docs/current/tutorial-transactions.html","reinforces":"BEGIN, COMMIT, ROLLBACK e unidades de trabalho no banco usado pelo projeto.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"connection-api","type":"reference","title":"Connection API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/Connection.html","reinforces":"Controle JDBC de auto-commit, commit, rollback e fechamento de conexão.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A personal finance with JDBC operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a personal finance with JDBC operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this personal finance with JDBC chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"// Observe how names reveal the personal finance with JDBC contract.","instruction":"A personal finance with JDBC operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this personal finance with JDBC chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["transferencia-atomica-com-rollback","sql-parametrizado-contra-injecao","dinheiro-em-tipo-decimal-exato","consulta-agregada-segura","evidencia-de-rollback-reproduzivel"],"evidenceBlocks":{"transferencia-atomica-com-rollback":["mini-financas-jdbc:0","mini-financas-jdbc:2","financas-exercise-transacao"],"sql-parametrizado-contra-injecao":["mini-financas-jdbc-checklist-4","mini-financas-jdbc:1","financas-project"],"dinheiro-em-tipo-decimal-exato":["mini-financas-jdbc-content-5","mini-financas-jdbc:3","financas-exercise-transacao"],"consulta-agregada-segura":["mini-financas-jdbc-checklist-4","financas-project"],"evidencia-de-rollback-reproduzivel":["mini-financas-jdbc-checklist-4","mini-financas-jdbc-content-11","financas-project"]},"primarySources":["PostgreSQL: Transactions -- https://www.postgresql.org/docs/current/tutorial-transactions.html","Connection API -- Java 21 -- https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/Connection.html"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"spring-core","moduleId":"spring-api","order":0,"title":"Spring Core — IoC/DI oficial","summary":"Este capítulo deveria parecer fácil — você já construiu, com as próprias mãos, uma versão simplificada de tudo que vem aqui, no capítulo 28. Agora é só trocar seu MiniContainer pelo ApplicationContext real do Spring.","objectives":["Mapear DI manual para IoC oficial do Spring","Entender component scan, stereotypes, escopos e @Bean","Separar componente, configuração e regra de negócio","Evitar campo estático/global como atalho"],"whyItExists":"Depois do mini-framework pré-Spring, o aluno já conhece annotations, reflection, DI e IoC. Spring Core entra como implementação industrial dessas ideias, não como primeira explicação mágica.","prerequisiteChapterIds":["projetospring","build"],"conceptIds":["de-componente-injetar-para-as-anotacoes-reais-do-spring","as-especializacoes-de-component-e-por-que-existem","escopos-de-bean","configuration-e-bean-quando-voce-nao-controla-a-classe","como-o-container-realmente-resolve-e-cria-beans"],"introducedConceptIds":["spring-component-scan-bean","spring-bean-scope-configuration"],"usedConceptIds":["mini-framework-dispatcher","ioc-lifecycle-bean","annotation-metadata-contract"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"spring-core-intuition","type":"intuition","authorship":"authored","title":"Spring automatiza uma composição que você já fez à mão","body":"O container descobre componentes, cria objetos, injeta dependências e controla parte do ciclo de vida. O objetivo é reduzir montagem repetitiva, não esconder onde a regra deve viver.","analogyLimit":"Oficina ajuda a imaginar montagem, mas Spring também usa classpath, condições, proxies, escopos e lifecycle."},{"id":"spring-core-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#projetospring\">28 · Mini-container pré-Spring</a>, <a class=\"prereq-tag\" href=\"#build\">19 · Maven &amp; Gradle</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~2h de estudo Pré-requisitos: 28 · Mini-container pré-Spring, 19 · Maven & Gradle"},{"id":"spring-core-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Este capítulo deveria parecer <strong>fácil</strong> — você já construiu, com as próprias mãos, uma versão simplificada de tudo que vem aqui, no capítulo 28. Agora é só trocar seu <code>MiniContainer</code> pelo <code>ApplicationContext</code> real do Spring.</p>","fidelityText":"Este capítulo deveria parecer fácil — você já construiu, com as próprias mãos, uma versão simplificada de tudo que vem aqui, no capítulo 28. Agora é só trocar seu MiniContainer pelo ApplicationContext real do Spring."},{"id":"spring-core-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Se o mini-container foi uma bicicleta montada para entender as engrenagens, Spring Core é um sistema industrial com definições de bean, escopos, ciclo de vida, eventos e extensões. Ele diagnostica ciclos impossíveis; não use dependência circular como mecanismo de design.</div>","fidelityText":"Se o mini-container foi uma bicicleta montada para entender as engrenagens, Spring Core é um sistema industrial com definições de bean, escopos, ciclo de vida, eventos e extensões. Ele diagnostica ciclos impossíveis; não use dependência circular como mecanismo de design."},{"id":"spring-core-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>De @Componente/@Injetar para as anotações reais do Spring</h2>","fidelityText":"De @Componente/@Injetar para as anotações reais do Spring"},{"id":"spring-core-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"// seu capítulo 28:              →  Spring real:\n// @Componente                   →  @Component (ou especializações abaixo)\n// @Injetar                      →  @Autowired\n// MiniContainer.registrar()     →  @ComponentScan (automático, via reflection)\n// MiniContainer.resolver()      →  context.getBean(Tipo.class)\n\n@Component // genérico -- \"isso é um bean gerenciado pelo Spring\"\npublic class EmailService implements NotifierLoan { ... }\n\n@Service  // especialização -- comunica INTENÇÃO: \"isso é lógica de negócio\"\npublic class Library {\n    private final NotifierLoan notifier;\n\n    @Autowired // injeção via construtor -- a MELHOR forma (capítulo 22)\n    public Library(NotifierLoan notifier) {\n        this.notifier = notifier;\n    }\n}","fidelityText":"// seu capítulo 28: → Spring real: // @Componente → @Component (ou especializações abaixo) // @Injetar → @Autowired // MiniContainer.registrar() → @ComponentScan (automático, via reflection) // MiniContainer.resolver() → context.getBean(Tipo.class) @Component // genérico -- \"isso é um bean gerenciado pelo Spring\" public class EmailServico implements NotificadorEmprestimo { ... } @Service // especialização -- comunica INTENÇÃO: \"isso é lógica de negócio\" public class Biblioteca { private final NotificadorEmprestimo notificador; @Autowired // injeção via construtor -- a MELHOR forma (capítulo 22) public Biblioteca(NotificadorEmprestimo notificador) { this.notificador = notificador; } }","highlightedHtml":"<span class=\"com\">// seu capítulo 28:              →  Spring real:\n// @Componente                   →  @Component (ou especializações abaixo)\n// @Injetar                      →  @Autowired\n// MiniContainer.registrar()     →  @ComponentScan (automático, via reflection)\n// MiniContainer.resolver()      →  context.getBean(Tipo.class)</span>\n\n<span class=\"annotation\">@Component</span> <span class=\"com\">// genérico -- \"isso é um bean gerenciado pelo Spring\"</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">EmailService</span> <span class=\"kw\">implements</span> <span class=\"cls\">NotifierLoan</span> { ... }\n\n<span class=\"annotation\">@Service</span>  <span class=\"com\">// especialização -- comunica INTENÇÃO: \"isso é lógica de negócio\"</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Library</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">NotifierLoan</span> notifier;\n\n    <span class=\"annotation\">@Autowired</span> <span class=\"com\">// injeção via construtor -- a MELHOR forma (capítulo 22)</span>\n    <span class=\"kw\">public</span> <span class=\"fn\">Library</span>(<span class=\"cls\">NotifierLoan</span> notifier) {\n        <span class=\"kw\">this</span>.notifier = notifier;\n    }\n}","caption":"Exemplo executável de spring-core.","explanation":["@Service é especialização de @Component para semântica de serviço.","Constructor injection torna dependências obrigatórias e testáveis."],"commonMistakes":["Usar field injection por conveniência","Colocar regra de negócio na configuração"]},{"id":"spring-core-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Desde o Spring 4.3, <code>@Autowired</code> é <strong>opcional</strong> quando a classe tem um único construtor — o Spring injeta automaticamente. Isso não é mágica nova: é o mesmo princípio de reflection do capítulo 20, aplicado por convenção.</p>","fidelityText":"Desde o Spring 4.3, @Autowired é opcional quando a classe tem um único construtor — o Spring injeta automaticamente. Isso não é mágica nova: é o mesmo princípio de reflection do capítulo 20, aplicado por convenção."},{"id":"spring-core-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>As especializações de @Component e por que existem</h2>","fidelityText":"As especializações de @Component e por que existem"},{"id":"spring-core-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Anotação</th><th>Camada</th><th>Por quê usar em vez de @Component puro</th></tr>\n        <tr><td><code>@Component</code></td><td>Genérico</td><td>Qualquer bean que não se encaixa nas categorias abaixo</td></tr>\n        <tr><td><code>@Service</code></td><td>Lógica de negócio</td><td>Comunica intenção — e algumas ferramentas de análise de código usam isso para identificar a camada de serviço</td></tr>\n        <tr><td><code>@Repository</code></td><td>Acesso a dados</td><td>Além de marcar, o Spring converte exceções de banco específicas em exceções unificadas do Spring (<code>DataAccessException</code>)</td></tr>\n        <tr><td><code>@Controller</code>/<code>@RestController</code></td><td>Camada web</td><td>Ativa o mapeamento de requisições HTTP (próximo capítulo)</td></tr>\n      </tbody></table>","fidelityText":"AnotaçãoCamadaPor quê usar em vez de @Component puro @ComponentGenéricoQualquer bean que não se encaixa nas categorias abaixo @ServiceLógica de negócioComunica intenção — e algumas ferramentas de análise de código usam isso para identificar a camada de serviço @RepositoryAcesso a dadosAlém de marcar, o Spring converte exceções de banco específicas em exceções unificadas do Spring (DataAccessException) @Controller/@RestControllerCamada webAtiva o mapeamento de requisições HTTP (próximo capítulo)"},{"id":"spring-core-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>@Service</code>, <code>@Repository</code> e <code>@Controller</code> são especializações de <code>@Component</code> e comunicam papéis. A diferença não é sempre puramente semântica: <code>@Repository</code> participa da tradução de exceções de persistência e <code>@Controller</code>/<code>@RestController</code> são interpretados pela infraestrutura web.</div>","fidelityText":"@Service, @Repository e @Controller são especializações de @Component e comunicam papéis. A diferença não é sempre puramente semântica: @Repository participa da tradução de exceções de persistência e @Controller/@RestController são interpretados pela infraestrutura web."},{"id":"spring-core-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Escopos de bean</h2>","fidelityText":"Escopos de bean"},{"id":"spring-core-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"@Component\n@Scope(\"singleton\") // PADRÃO -- uma única instância para toda a aplicação (capítulo 23!)\npublic class AppConfiguration { ... }\n\n@Component\n@Scope(\"prototype\") // uma instância NOVA a cada vez que é injetada/solicitada\npublic class TemporaryReport { ... }","fidelityText":"@Component @Scope(\"singleton\") // PADRÃO -- uma única instância para toda a aplicação (capítulo 23!) public class ConfiguracaoApp { ... } @Component @Scope(\"prototype\") // uma instância NOVA a cada vez que é injetada/solicitada public class RelatorioTemporario { ... }","highlightedHtml":"<span class=\"annotation\">@Component</span>\n<span class=\"annotation\">@Scope</span>(<span class=\"str\">\"singleton\"</span>) <span class=\"com\">// PADRÃO -- uma única instância para toda a aplicação (capítulo 23!)</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">AppConfiguration</span> { ... }\n\n<span class=\"annotation\">@Component</span>\n<span class=\"annotation\">@Scope</span>(<span class=\"str\">\"prototype\"</span>) <span class=\"com\">// uma instância NOVA a cada vez que é injetada/solicitada</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">TemporaryReport</span> { ... }","caption":"Exemplo executável de spring-core.","explanation":["Stereotypes comunicam papel arquitetural e participam do component scan.","A diferença é semântica e de integração futura, não superpoder da classe."],"commonMistakes":["Escolher annotation aleatoriamente","Criar componente para entidade de domínio"]},{"id":"spring-core-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Cuidado com estado mutável em beans Singleton:</b> como existe uma única instância compartilhada por toda a aplicação (inclusive entre requisições HTTP concorrentes), um campo mutável não sincronizado em um <code>@Service</code> é a mesma race condition do capítulo 14, só que ainda mais fácil de introduzir sem perceber. Beans deveriam, na maioria dos casos, ser <strong>stateless</strong> (sem estado próprio entre chamadas) — toda a \"memória\" do sistema deveria estar no banco, não em campos de instância do bean.</div>","fidelityText":"Cuidado com estado mutável em beans Singleton: como existe uma única instância compartilhada por toda a aplicação (inclusive entre requisições HTTP concorrentes), um campo mutável não sincronizado em um @Service é a mesma race condition do capítulo 14, só que ainda mais fácil de introduzir sem perceber. Beans deveriam, na maioria dos casos, ser stateless (sem estado próprio entre chamadas) — toda a \"memória\" do sistema deveria estar no banco, não em campos de instância do bean."},{"id":"spring-core-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>@Configuration e @Bean — quando você não controla a classe</h2>","fidelityText":"@Configuration e @Bean — quando você não controla a classe"},{"id":"spring-core-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Nem toda dependência é sua — bibliotecas externas não têm como carregar <code>@Component</code>. Para essas, você declara manualmente como criar o bean:</p>","fidelityText":"Nem toda dependência é sua — bibliotecas externas não têm como carregar @Component. Para essas, você declara manualmente como criar o bean:"},{"id":"spring-core-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"@Configuration\npublic class AppConfig {\n    @Bean\n    public ObjectMapper objectMapper() { // capítulo 25 -- Jackson não é sua classe, não pode ter @Component\n        return new ObjectMapper();\n    }\n}","fidelityText":"@Configuration public class AppConfig { @Bean public ObjectMapper objectMapper() { // capítulo 25 -- Jackson não é sua classe, não pode ter @Component return new ObjectMapper(); } }","highlightedHtml":"<span class=\"annotation\">@Configuration</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">AppConfig</span> {\n    <span class=\"annotation\">@Bean</span>\n    <span class=\"kw\">public</span> ObjectMapper <span class=\"fn\">objectMapper</span>() { <span class=\"com\">// capítulo 25 -- Jackson não é sua classe, não pode ter @Component</span>\n        <span class=\"kw\">return new</span> ObjectMapper();\n    }\n}","caption":"Exemplo executável de spring-core.","explanation":["@Configuration e @Bean declaram objetos que o Spring não descobre sozinho ou cuja criação precisa de fábrica.","Isso pertence à composition root/configuração, não ao domínio."],"commonMistakes":["Usar @Bean para todo objeto simples","Ler secret direto em classe de regra"]},{"id":"spring-core-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>Como o container realmente resolve e cria beans</h2>","fidelityText":"Como o container realmente resolve e cria beans"},{"id":"spring-core-content-17","type":"html","authorship":"legacy-preserved","html":"<p>É tentador simplificar o processo como \"primeiro cria todos os beans, depois injeta as dependências entre eles\" — mas isso não é o que acontece, e a diferença importa quando você for depurar um erro de inicialização. O processo real tem duas etapas bem distintas:</p>","fidelityText":"É tentador simplificar o processo como \"primeiro cria todos os beans, depois injeta as dependências entre eles\" — mas isso não é o que acontece, e a diferença importa quando você for depurar um erro de inicialização. O processo real tem duas etapas bem distintas:"},{"id":"spring-core-content-18","type":"html","authorship":"legacy-preserved","html":"<ol>\n        <li><strong>Registro de metadata (bean definitions):</strong> ao escanear <code>@Component</code>/<code>@Service</code>/<code>@Repository</code>/<code>@Bean</code>, o Spring não cria objeto nenhum ainda — ele só registra, para cada bean, <em>como</em> criá-lo (qual construtor, quais dependências declaradas) em um registro interno de <code>BeanDefinition</code>. Isso é análogo ao seu <code>MiniContainer.registrar(...)</code> do capítulo 28: você registrava a \"receita\", não a instância.</li>\n        <li><strong>Criação sob demanda, recursiva:</strong> quando o container precisa de fato instanciar um singleton (por padrão, todos os singletons são pré-instanciados na inicialização do <code>ApplicationContext</code>, na ordem em que são requisitados), ele resolve as dependências desse bean primeiro — e se uma dependência ainda não foi criada, o container a cria <em>agora</em>, recursivamente, antes de terminar de construir o bean original. Criar um bean pode, portanto, disparar em cascata a criação de todo o seu grafo de dependências ainda não resolvido.</li>\n      </ol>","fidelityText":"Registro de metadata (bean definitions): ao escanear @Component/@Service/@Repository/@Bean, o Spring não cria objeto nenhum ainda — ele só registra, para cada bean, como criá-lo (qual construtor, quais dependências declaradas) em um registro interno de BeanDefinition. Isso é análogo ao seu MiniContainer.registrar(...) do capítulo 28: você registrava a \"receita\", não a instância. Criação sob demanda, recursiva: quando o container precisa de fato instanciar um singleton (por padrão, todos os singletons são pré-instanciados na inicialização do ApplicationContext, na ordem em que são requisitados), ele resolve as dependências desse bean primeiro — e se uma dependência ainda não foi criada, o container a cria agora, recursivamente, antes de terminar de construir o bean original. Criar um bean pode, portanto, disparar em cascata a criação de todo o seu grafo de dependências ainda não resolvido."},{"id":"spring-core-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Não existe um momento único em que \"todos os beans já existem\" e só então a injeção acontece — criação e injeção estão entrelaçadas, bean a bean, seguindo o grafo de dependências. É exatamente por isso que uma dependência circular entre construtores (A precisa de B, B precisa de A) trava o Spring em tempo de inicialização: para criar A ele precisa de B já pronto, mas para criar B ele precisa de A já pronto, e nenhum dos dois pode terminar de existir primeiro (para setter/field injection o Spring consegue contornar isso com uma referência antecipada ao objeto ainda incompleto, mas para injeção via construtor — a que este capítulo recomenda — não há como, e o Spring falha explicitamente em vez de arriscar um objeto meio-construído).</p>","fidelityText":"Não existe um momento único em que \"todos os beans já existem\" e só então a injeção acontece — criação e injeção estão entrelaçadas, bean a bean, seguindo o grafo de dependências. É exatamente por isso que uma dependência circular entre construtores (A precisa de B, B precisa de A) trava o Spring em tempo de inicialização: para criar A ele precisa de B já pronto, mas para criar B ele precisa de A já pronto, e nenhum dos dois pode terminar de existir primeiro (para setter/field injection o Spring consegue contornar isso com uma referência antecipada ao objeto ainda incompleto, mas para injeção via construtor — a que este capítulo recomenda — não há como, e o Spring falha explicitamente em vez de arriscar um objeto meio-construído)."},{"id":"spring-core-content-20","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Se um bean não é encontrado, quase sempre é porque ele está fora do pacote escaneado pelo <code>@ComponentScan</code> (implícito em <code>@SpringBootApplication</code>) — o mesmo problema de \"esquecer de chamar <code>registrar(...)</code>\" no seu container manual. Se a falha for por dependência circular, o erro (<code>BeanCurrentlyInCreationException</code>) é uma consequência direta do modelo de criação recursiva descrito acima, não um bug do framework.</div>","fidelityText":"Se um bean não é encontrado, quase sempre é porque ele está fora do pacote escaneado pelo @ComponentScan (implícito em @SpringBootApplication) — o mesmo problema de \"esquecer de chamar registrar(...)\" no seu container manual. Se a falha for por dependência circular, o erro (BeanCurrentlyInCreationException) é uma consequência direta do modelo de criação recursiva descrito acima, não um bug do framework."},{"id":"spring-core-exercise-21","type":"exercise","authorship":"legacy-preserved","title":"Exercício 43.1 — Convertendo o mini-container real","prompt":"Pegue as classes EmailServico e Biblioteca do capítulo 28 (com @Componente/@Injetar) e reescreva usando as anotações reais do Spring: @Service em ambas, injeção via construtor. Escreva também uma classe @Configuration com um @Bean para um ObjectMapper, simulando uma dependência externa que você não controla.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 43.1 — Convertendo o mini-container realmédio Pegue as classes EmailServico e Biblioteca do capítulo 28 (com @Componente/@Injetar) e reescreva usando as anotações reais do Spring: @Service em ambas, injeção via construtor. Escreva também uma classe @Configuration com um @Bean para um ObjectMapper, simulando uma dependência externa que você não controla. Ver solução @Service public class EmailServico implements NotificadorEmprestimo { @Override public void notificar(String msg) { System.out.println(\"[email] \" + msg); } } @Service public class Biblioteca { private final NotificadorEmprestimo notificador; public Biblioteca(NotificadorEmprestimo notificador) { this.notificador = notificador; } // @Autowired implícito } @Configuration public class AppConfig { @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 43.1 — Convertendo o mini-container real</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Pegue as classes <code>EmailServico</code> e <code>Biblioteca</code> do capítulo 28 (com <code>@Componente</code>/<code>@Injetar</code>) e reescreva usando as anotações reais do Spring: <code>@Service</code> em ambas, injeção via construtor. Escreva também uma classe <code>@Configuration</code> com um <code>@Bean</code> para um <code>ObjectMapper</code>, simulando uma dependência externa que você não controla.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">EmailService</span> <span class=\"kw\">implements</span> <span class=\"cls\">NotifierLoan</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">notify</span>(<span class=\"kw\">String</span> msg) { System.out.println(<span class=\"str\">\"[email] \"</span> + msg); }\n}\n\n<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Library</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">NotifierLoan</span> notifier;\n    <span class=\"kw\">public</span> <span class=\"fn\">Library</span>(<span class=\"cls\">NotifierLoan</span> notifier) { <span class=\"kw\">this</span>.notifier = notifier; } <span class=\"com\">// @Autowired implícito</span>\n}\n\n<span class=\"annotation\">@Configuration</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">AppConfig</span> {\n    <span class=\"annotation\">@Bean</span>\n    <span class=\"kw\">public</span> ObjectMapper <span class=\"fn\">objectMapper</span>() { <span class=\"kw\">return new</span> ObjectMapper(); }\n}</pre>\n        </div>\n      </div>"},{"id":"spring-core-quiz","type":"quiz","authorship":"authored","conceptId":"spring-component-scan-bean","prompt":"O que faz uma classe anotada com @Service virar dependência injetável?","options":[{"id":"sc-a","label":"O container Spring escaneia, registra e instancia o componente conforme sua configuração.","correct":true,"explanation":"A anotação é metadado; o container é quem lê e age."},{"id":"sc-b","label":"A JVM injeta automaticamente qualquer classe com esse nome.","correct":false,"explanation":"A JVM não conhece @Service como regra especial."},{"id":"sc-c","label":"O Maven transforma a classe em singleton global estático.","correct":false,"explanation":"Build empacota; quem gerencia bean é o container."}]}],"resources":[{"id":"spring-core-beans","type":"reference","title":"Spring Framework: IoC Container","url":"https://docs.spring.io/spring-framework/reference/core/beans.html","reinforces":"Beans, container, injeção, lifecycle e configuração.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-component-scanning","type":"reference","title":"Spring: Classpath Scanning and Managed Components","url":"https://docs.spring.io/spring-framework/reference/core/beans/classpath-scanning.html","reinforces":"Component scan, stereotypes e detecção de beans.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The spring core component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to spring core. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible spring core failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// seu capítulo 28:              →  Spring real:","instruction":"The spring core component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible spring core failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"spring-boot-fundamentos","moduleId":"spring-api","order":1,"title":"Spring Boot por dentro: bootstrap, auto-configuração e configuração tipada","summary":"Spring Framework fornece o contêiner e os módulos; Spring Boot fornece convenções, auto-configuração, starters, servidor embutido, configuração externa e ferramentas operacionais para iniciar uma aplicação coerente rapidamente.","objectives":["Entender bootstrap de uma aplicação Boot","Ler auto-configuração como conjunto de condições","Usar starters e versões com responsabilidade","Modelar configuração tipada sem secrets no código"],"whyItExists":"Spring Core explica o container; Spring Boot explica como a aplicação sobe com convenções, starters e auto-configuração. Isso evita tratar Boot como template pronto sem saber por que ele funciona.","prerequisiteChapterIds":["spring-core","build"],"conceptIds":["o-que-acontece-quando-a-aplicacao-inicia","auto-configuracao-nao-e-magica-e-uma-classe-configuration-com-condicoes","laboratorio-descobrindo-auto-configuracao-de-verdade-nao-so-ouvindo-fala","configuracao-tipada","property-sources-e-a-ordem-de-precedencia","logging-inicial-e-encerramento-gracioso","starters-e-versoes"],"introducedConceptIds":["boot-bootstrap-autoconfig","boot-config-properties"],"usedConceptIds":["spring-component-scan-bean","build-lifecycle","configuracao-externa"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"boot-intuition","type":"intuition","authorship":"authored","title":"Boot escolhe bons padrões, mas eles são condicionais","body":"Auto-configuração não é mágica universal. Ela observa classpath, propriedades e beans existentes para decidir o que criar. Quando você entende as condições, sabe explicar por que algo apareceu ou não.","analogyLimit":"Piloto automático ajuda, mas Boot não decide objetivo de negócio nem substitui leitura de logs e configuração."},{"id":"spring-boot-fundamentos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-spring\">Spring</span><div class=\"meta-item\">Dificuldade: <b>Intermediário</b></div><div class=\"time-est\">⏱ <b>~3h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-core\">Spring Core</a>, <a class=\"prereq-tag\" href=\"#build\">Maven e Gradle</a></div></div>","fidelityText":"SpringDificuldade: Intermediário⏱ ~3h de estudo e práticaPré-requisitos: Spring Core, Maven e Gradle"},{"id":"spring-boot-fundamentos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Spring Framework fornece o contêiner e os módulos; Spring Boot fornece convenções, auto-configuração, starters, servidor embutido, configuração externa e ferramentas operacionais para iniciar uma aplicação coerente rapidamente.</p>","fidelityText":"Spring Framework fornece o contêiner e os módulos; Spring Boot fornece convenções, auto-configuração, starters, servidor embutido, configuração externa e ferramentas operacionais para iniciar uma aplicação coerente rapidamente."},{"id":"spring-boot-fundamentos-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>O que acontece quando a aplicação inicia</h2></div>\n    <p>Antes das anotações, conecte cada termo a uma etapa simples do início da aplicação.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Bootstrap</dt><dd>Sequência que cria e prepara a aplicação: lê configuração, monta o contexto e inicia o servidor.</dd></div><div class=\"concept-card\"><dt>Bean</dt><dd>Objeto criado e administrado pelo contêiner Spring.</dd></div><div class=\"concept-card\"><dt>Component scanning</dt><dd>Busca classes marcadas como componentes nos pacotes configurados para registrá-las como beans.</dd></div><div class=\"concept-card\"><dt>Starter</dt><dd>Dependência agregadora que traz um conjunto coerente de bibliotecas para uma capacidade, como web ou validação.</dd></div><div class=\"concept-card\"><dt>Auto-configuração</dt><dd>Configuração padrão ativada somente quando condições de classes, propriedades e beans são satisfeitas.</dd></div><div class=\"concept-card\"><dt>Servidor embutido</dt><dd>Servidor HTTP empacotado com a aplicação, iniciado pelo próprio processo em vez de receber um WAR externo.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoO que acontece quando a aplicação inicia Antes das anotações, conecte cada termo a uma etapa simples do início da aplicação. BootstrapSequência que cria e prepara a aplicação: lê configuração, monta o contexto e inicia o servidor.BeanObjeto criado e administrado pelo contêiner Spring.Component scanningBusca classes marcadas como componentes nos pacotes configurados para registrá-las como beans.StarterDependência agregadora que traz um conjunto coerente de bibliotecas para uma capacidade, como web ou validação.Auto-configuraçãoConfiguração padrão ativada somente quando condições de classes, propriedades e beans são satisfeitas.Servidor embutidoServidor HTTP empacotado com a aplicação, iniciado pelo próprio processo em vez de receber um WAR externo."},{"id":"spring-boot-fundamentos-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"@SpringBootApplication\n@ConfigurationPropertiesScan\npublic class Application {\n    public static void main(String[] args) {\n        SpringApplication.run(Application.class, args);\n    }\n}","fidelityText":"@SpringBootApplication @ConfigurationPropertiesScan public class Aplicacao { public static void main(String[] args) { SpringApplication.run(Aplicacao.class, args); } }","highlightedHtml":"<span class=\"annotation\">@SpringBootApplication</span>\n<span class=\"annotation\">@ConfigurationPropertiesScan</span>\n<span class=\"kw\">public class</span> Application {\n    <span class=\"kw\">public static void</span> main(String[] args) {\n        SpringApplication.run(Application.class, args);\n    }\n}","caption":"Exemplo executável de spring-boot-fundamentos.","explanation":["@SpringBootApplication combina @Configuration, @ComponentScan e @EnableAutoConfiguration em uma única anotação.","O main delega o bootstrap inteiro para SpringApplication.run(...)."],"commonMistakes":["Mover classe principal para pacote que não escaneia componentes","Achar que main contém regra de negócio"]},{"id":"spring-boot-fundamentos-content-5","type":"html","authorship":"legacy-preserved","html":"<p><code>@SpringBootApplication</code> combina três anotações em uma: <code>@Configuration</code> (a classe pode declarar <code>@Bean</code>), <code>@ComponentScan</code> (escaneia o pacote atual e subpacotes por <code>@Component</code>/<code>@Service</code>/<code>@Repository</code>/<code>@Controller</code>) e <code>@EnableAutoConfiguration</code> (ativa o mecanismo de auto-configuração). É esse terceiro pedaço que faz a \"mágica\" acontecer — e ela é mais mecânica do que parece.</p>","fidelityText":"@SpringBootApplication combina três anotações em uma: @Configuration (a classe pode declarar @Bean), @ComponentScan (escaneia o pacote atual e subpacotes por @Component/@Service/@Repository/@Controller) e @EnableAutoConfiguration (ativa o mecanismo de auto-configuração). É esse terceiro pedaço que faz a \"mágica\" acontecer — e ela é mais mecânica do que parece."},{"id":"spring-boot-fundamentos-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Auto-configuração não é mágica: é uma classe @Configuration com condições</h2>","fidelityText":"Auto-configuração não é mágica: é uma classe @Configuration com condições"},{"id":"spring-boot-fundamentos-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Por trás de cada \"recurso automático\" do Spring Boot existe uma classe <code>@Configuration</code> comum, anotada com condições que decidem se ela deve ou não se aplicar. Uma versão simplificada de como o Boot decide auto-configurar um <code>DataSource</code> H2 embutido se parece com isto:</p>","fidelityText":"Por trás de cada \"recurso automático\" do Spring Boot existe uma classe @Configuration comum, anotada com condições que decidem se ela deve ou não se aplicar. Uma versão simplificada de como o Boot decide auto-configurar um DataSource H2 embutido se parece com isto:"},{"id":"spring-boot-fundamentos-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"@Configuration\n@ConditionalOnClass(DataSource.class) // só se ativa se a classe DataSource estiver no classpath (driver JDBC presente)\n@ConditionalOnMissingBean(DataSource.class) // só se ativa se VOCÊ ainda não declarou seu próprio DataSource\n@ConditionalOnProperty(prefix = \"spring.datasource\", name = \"url\") // só se ativa se essa propriedade existir\npublic class DataSourceAutoConfiguration {\n    @Bean\n    public DataSource dataSource(DataSourceProperties props) { return props.initializeDataSourceBuilder().build(); }\n}","fidelityText":"@Configuration @ConditionalOnClass(DataSource.class) // só se ativa se a classe DataSource estiver no classpath (driver JDBC presente) @ConditionalOnMissingBean(DataSource.class) // só se ativa se VOCÊ ainda não declarou seu próprio DataSource @ConditionalOnProperty(prefix = \"spring.datasource\", name = \"url\") // só se ativa se essa propriedade existir public class DataSourceAutoConfiguration { @Bean public DataSource dataSource(DataSourceProperties props) { return props.initializeDataSourceBuilder().build(); } }","highlightedHtml":"<span class=\"annotation\">@Configuration</span>\n<span class=\"annotation\">@ConditionalOnClass</span>(DataSource.<span class=\"kw\">class</span>) <span class=\"com\">// só se ativa se a classe DataSource estiver no classpath (driver JDBC presente)</span>\n<span class=\"annotation\">@ConditionalOnMissingBean</span>(DataSource.<span class=\"kw\">class</span>) <span class=\"com\">// só se ativa se VOCÊ ainda não declarou seu próprio DataSource</span>\n<span class=\"annotation\">@ConditionalOnProperty</span>(prefix = <span class=\"str\">\"spring.datasource\"</span>, name = <span class=\"str\">\"url\"</span>) <span class=\"com\">// só se ativa se essa propriedade existir</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">DataSourceAutoConfiguration</span> {\n    <span class=\"annotation\">@Bean</span>\n    <span class=\"kw\">public</span> DataSource <span class=\"fn\">dataSource</span>(DataSourceProperties props) { <span class=\"kw\">return</span> props.initializeDataSourceBuilder().build(); }\n}","caption":"Exemplo executável de spring-boot-fundamentos.","explanation":["@ConditionalOnClass/@ConditionalOnMissingBean/@ConditionalOnProperty são checagens comuns avaliadas automaticamente -- não existe prioridade mágica, só condições que passam ou falham.","Declarar seu próprio @Bean DataSource faz @ConditionalOnMissingBean falhar, e essa auto-configuração específica simplesmente não roda."],"commonMistakes":["Achar que auto-configuração e bean próprio coexistem -- na prática o bean próprio desativa a condição"]},{"id":"spring-boot-fundamentos-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Isso explica exatamente por que \"declarar seu próprio bean faz a configuração padrão recuar\": <code>@ConditionalOnMissingBean</code> literalmente verifica se já existe um bean daquele tipo no contexto antes de criar o seu — se você declarar seu próprio <code>@Bean DataSource</code>, a condição falha, e a auto-configuração do Boot simplesmente não roda. Não há prioridade nem sobrescrita mágica: é uma checagem condicional comum, só que avaliada automaticamente pelo framework antes de cada <code>@Configuration</code> candidata.</p>","fidelityText":"Isso explica exatamente por que \"declarar seu próprio bean faz a configuração padrão recuar\": @ConditionalOnMissingBean literalmente verifica se já existe um bean daquele tipo no contexto antes de criar o seu — se você declarar seu próprio @Bean DataSource, a condição falha, e a auto-configuração do Boot simplesmente não roda. Não há prioridade nem sobrescrita mágica: é uma checagem condicional comum, só que avaliada automaticamente pelo framework antes de cada @Configuration candidata."},{"id":"spring-boot-fundamentos-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Laboratório: descobrindo auto-configuração de verdade, não só ouvindo falar dela</h2>","fidelityText":"Laboratório: descobrindo auto-configuração de verdade, não só ouvindo falar dela"},{"id":"spring-boot-fundamentos-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Em vez de aceitar que \"o Boot configura sozinho\", prove isso você mesmo:</p>","fidelityText":"Em vez de aceitar que \"o Boot configura sozinho\", prove isso você mesmo:"},{"id":"spring-boot-fundamentos-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"./mvnw spring-boot:run -Dspring-boot.run.arguments=--debug","fidelityText":"./mvnw spring-boot:run -Dspring-boot.run.arguments=--debug","highlightedHtml":"./mvnw spring-boot:run -Dspring-boot.run.arguments=--debug","caption":"Exemplo executável de spring-boot-fundamentos.","explanation":["--debug ativa o Condition Evaluation Report, mostrando exatamente quais auto-configurações ativaram (e por quê) e quais não ativaram (e por quê não)."],"commonMistakes":["Tentar adivinhar por que uma auto-configuração não ativou em vez de ler o relatório de condições"]},{"id":"spring-boot-fundamentos-content-13","type":"html","authorship":"legacy-preserved","html":"<p>A flag <code>--debug</code> imprime o <strong>Condition Evaluation Report</strong> no início do log — uma lista de TODAS as auto-configurações candidatas, divididas em \"Positive matches\" (as que ativaram, com a razão exata) e \"Negative matches\" (as que não ativaram, e por quê). Procure por <code>DataSourceAutoConfiguration</code> no relatório: se seu projeto tem um driver JDBC no classpath e uma propriedade <code>spring.datasource.url</code>, ela aparece em \"Positive matches\" citando exatamente as duas condições que discutimos acima. Remova a dependência do driver do <code>pom.xml</code> e rode de novo — ela migra para \"Negative matches\", com a razão \"required class DataSource not found\". Isso transforma \"auto-configuração\" de um conceito abstrato em algo que você literalmente observou ligar e desligar.</p>","fidelityText":"A flag --debug imprime o Condition Evaluation Report no início do log — uma lista de TODAS as auto-configurações candidatas, divididas em \"Positive matches\" (as que ativaram, com a razão exata) e \"Negative matches\" (as que não ativaram, e por quê). Procure por DataSourceAutoConfiguration no relatório: se seu projeto tem um driver JDBC no classpath e uma propriedade spring.datasource.url, ela aparece em \"Positive matches\" citando exatamente as duas condições que discutimos acima. Remova a dependência do driver do pom.xml e rode de novo — ela migra para \"Negative matches\", com a razão \"required class DataSource not found\". Isso transforma \"auto-configuração\" de um conceito abstrato em algo que você literalmente observou ligar e desligar."},{"id":"spring-boot-fundamentos-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Configuração tipada</h2>","fidelityText":"Configuração tipada"},{"id":"spring-boot-fundamentos-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"@ConfigurationProperties(\"payments\")\npublic record PaymentProperties(\n    @NotBlank String baseUrl,\n    @DurationMin(millis = 100) Duration timeout,\n    @Min(1) int attempts) {}","fidelityText":"@ConfigurationProperties(\"pagamentos\") public record PagamentoProperties( @NotBlank String baseUrl, @DurationMin(millis = 100) Duration timeout, @Min(1) int tentativas) {}","highlightedHtml":"<span class=\"annotation\">@ConfigurationProperties</span>(<span class=\"str\">\"payments\"</span>)\n<span class=\"kw\">public record</span> PaymentProperties(\n    <span class=\"annotation\">@NotBlank</span> String baseUrl,\n    <span class=\"annotation\">@DurationMin</span>(millis = 100) Duration timeout,\n    <span class=\"annotation\">@Min</span>(1) <span class=\"kw\">int</span> attempts) {}","caption":"Exemplo executável de spring-boot-fundamentos.","explanation":["@ConfigurationProperties agrupa configuração tipada em vez de @Value espalhado.","Validação (@NotBlank, @Min) impede a aplicação de subir com configuração inválida."],"commonMistakes":["Colocar senha real no arquivo versionado","Usar @Value para dezenas de propriedades relacionadas"]},{"id":"spring-boot-fundamentos-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"payments:\n  base-url: ${PAYMENTS_BASE_URL}\n  timeout: 2s\n  attempts: 3","fidelityText":"pagamentos: base-url: ${PAGAMENTOS_BASE_URL} timeout: 2s tentativas: 3","highlightedHtml":"payments:\n  base-url: ${PAYMENTS_BASE_URL}\n  timeout: 2s\n  attempts: 3","caption":"Exemplo executável de spring-boot-fundamentos.","explanation":["O YAML mapeia diretamente para os componentes do record de @ConfigurationProperties -- base-url vira baseUrl por convenção kebab-case/camelCase."]},{"id":"spring-boot-fundamentos-content-17","type":"html","authorship":"legacy-preserved","html":"<p><strong>Configuração externa</strong> mantém valores que variam por ambiente fora do código. O <code>@ConfigurationProperties</code> agrupa esses valores em um tipo Java; as anotações de validação impedem a aplicação de iniciar com configuração inválida. Prefira isso a espalhar <code>@Value</code>. Nunca forneça segredo real como padrão. <strong>Profiles</strong> ativam conjuntos nomeados de configuração e devem representar diferenças legítimas de ambiente, não combinações incontáveis de comportamento.</p>","fidelityText":"Configuração externa mantém valores que variam por ambiente fora do código. O @ConfigurationProperties agrupa esses valores em um tipo Java; as anotações de validação impedem a aplicação de iniciar com configuração inválida. Prefira isso a espalhar @Value. Nunca forneça segredo real como padrão. Profiles ativam conjuntos nomeados de configuração e devem representar diferenças legítimas de ambiente, não combinações incontáveis de comportamento."},{"id":"spring-boot-fundamentos-content-18","type":"html","authorship":"legacy-preserved","html":"<h2>Property sources e a ordem de precedência</h2>","fidelityText":"Property sources e a ordem de precedência"},{"id":"spring-boot-fundamentos-content-19","type":"html","authorship":"legacy-preserved","html":"<p>O mesmo nome de propriedade (<code>pagamentos.timeout</code>, por exemplo) pode vir de várias origens ao mesmo tempo — o Spring Boot resolve isso com uma ordem de precedência fixa. Do maior para o menor peso (as primeiras vencem as últimas em caso de conflito), as fontes mais usadas na prática:</p>","fidelityText":"O mesmo nome de propriedade (pagamentos.timeout, por exemplo) pode vir de várias origens ao mesmo tempo — o Spring Boot resolve isso com uma ordem de precedência fixa. Do maior para o menor peso (as primeiras vencem as últimas em caso de conflito), as fontes mais usadas na prática:"},{"id":"spring-boot-fundamentos-content-20","type":"html","authorship":"legacy-preserved","html":"<ol>\n        <li><strong>Argumentos de linha de comando</strong> — <code>java -jar app.jar --pagamentos.timeout=5s</code></li>\n        <li><strong>Variáveis de ambiente do sistema operacional</strong> — <code>PAGAMENTOS_TIMEOUT=5s</code> (o Boot converte automaticamente <code>PAGAMENTOS_TIMEOUT</code> para <code>pagamentos.timeout</code>)</li>\n        <li><strong><code>application-{profile}.properties/yml</code></strong> específico do profile ativo (ex.: <code>application-prod.yml</code>)</li>\n        <li><strong><code>application.properties/yml</code></strong> — a configuração base do projeto</li>\n        <li><strong>Valores default</strong> declarados no próprio código (ex.: um campo com valor inicial no record de <code>@ConfigurationProperties</code>)</li>\n      </ol>","fidelityText":"Argumentos de linha de comando — java -jar app.jar --pagamentos.timeout=5s Variáveis de ambiente do sistema operacional — PAGAMENTOS_TIMEOUT=5s (o Boot converte automaticamente PAGAMENTOS_TIMEOUT para pagamentos.timeout) application-{profile}.properties/yml específico do profile ativo (ex.: application-prod.yml) application.properties/yml — a configuração base do projeto Valores default declarados no próprio código (ex.: um campo com valor inicial no record de @ConfigurationProperties)"},{"id":"spring-boot-fundamentos-content-21","type":"html","authorship":"legacy-preserved","html":"<p>Essa ordem é o motivo pelo qual definir uma variável de ambiente em produção sobrescreve o que está no <code>application.properties</code> sem precisar tocar em código ou reconstruir o artefato — e por que um argumento de linha de comando passado manualmente (útil para depurar um valor específico) sempre vence qualquer arquivo de configuração.</p>","fidelityText":"Essa ordem é o motivo pelo qual definir uma variável de ambiente em produção sobrescreve o que está no application.properties sem precisar tocar em código ou reconstruir o artefato — e por que um argumento de linha de comando passado manualmente (útil para depurar um valor específico) sempre vence qualquer arquivo de configuração."},{"id":"spring-boot-fundamentos-content-22","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não confunda argumentos de aplicação com argumentos da JVM.</b> <code>java -jar app.jar --server.port=8081</code> (dois traços, depois do jar) é um argumento de <em>programa</em>, lido pelo Spring; <code>java -Dserver.port=8081 -jar app.jar</code> (um traço, antes do jar, com <code>-D</code>) define uma <em>system property</em> da JVM — o Spring também lê essa, mas em posição de precedência diferente. Misturar os dois formatos por engano é uma causa comum de \"a propriedade não está pegando\".</div>","fidelityText":"Não confunda argumentos de aplicação com argumentos da JVM. java -jar app.jar --server.port=8081 (dois traços, depois do jar) é um argumento de programa, lido pelo Spring; java -Dserver.port=8081 -jar app.jar (um traço, antes do jar, com -D) define uma system property da JVM — o Spring também lê essa, mas em posição de precedência diferente. Misturar os dois formatos por engano é uma causa comum de \"a propriedade não está pegando\"."},{"id":"spring-boot-fundamentos-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>Logging inicial e encerramento gracioso</h2>","fidelityText":"Logging inicial e encerramento gracioso"},{"id":"spring-boot-fundamentos-content-24","type":"html","authorship":"legacy-preserved","html":"<p>Por padrão, o Spring Boot usa <strong>Logback</strong> (via SLF4J, capítulo 27) e imprime no console durante o startup: a versão do Spring Boot, o profile ativo, o tempo até ficar pronto para aceitar requisições, e a porta do servidor embutido. Esses logs iniciais já são uma primeira fonte de diagnóstico — \"aplicação não sobe\" quase sempre tem a causa raiz visível ali, antes de qualquer stack trace.</p>","fidelityText":"Por padrão, o Spring Boot usa Logback (via SLF4J, capítulo 27) e imprime no console durante o startup: a versão do Spring Boot, o profile ativo, o tempo até ficar pronto para aceitar requisições, e a porta do servidor embutido. Esses logs iniciais já são uma primeira fonte de diagnóstico — \"aplicação não sobe\" quase sempre tem a causa raiz visível ali, antes de qualquer stack trace."},{"id":"spring-boot-fundamentos-code-25","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties\nserver.shutdown=graceful\nspring.lifecycle.timeout-per-shutdown-phase=20s","fidelityText":"# application.properties server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=20s","highlightedHtml":"<span class=\"com\"># application.properties</span>\nserver.shutdown=graceful\nspring.lifecycle.timeout-per-shutdown-phase=20s","caption":"Exemplo executável de spring-boot-fundamentos.","explanation":["server.shutdown=graceful faz o servidor parar de aceitar requisições novas mas esperar as em andamento terminarem, dentro do timeout configurado, antes de desligar de fato."],"commonMistakes":["Fazer deploy sem shutdown gracioso e cortar requisições em andamento pela metade"]},{"id":"spring-boot-fundamentos-content-26","type":"html","authorship":"legacy-preserved","html":"<p><strong>Shutdown gracioso</strong> (<code>server.shutdown=graceful</code>) faz o servidor embutido parar de aceitar <strong>novas</strong> requisições ao receber o sinal de encerramento, mas espera até um limite de tempo para as requisições <strong>em andamento</strong> terminarem antes de desligar de fato — evitando cortar pela metade uma requisição que já estava sendo processada quando o processo recebeu SIGTERM (por exemplo, durante um deploy). Sem isso, um redeploy pode derrubar requisições em andamento sem aviso.</p>","fidelityText":"Shutdown gracioso (server.shutdown=graceful) faz o servidor embutido parar de aceitar novas requisições ao receber o sinal de encerramento, mas espera até um limite de tempo para as requisições em andamento terminarem antes de desligar de fato — evitando cortar pela metade uma requisição que já estava sendo processada quando o processo recebeu SIGTERM (por exemplo, durante um deploy). Sem isso, um redeploy pode derrubar requisições em andamento sem aviso."},{"id":"spring-boot-fundamentos-content-27","type":"html","authorship":"legacy-preserved","html":"<h2>Starters e versões</h2>","fidelityText":"Starters e versões"},{"id":"spring-boot-fundamentos-content-28","type":"html","authorship":"legacy-preserved","html":"<p>Use o BOM/dependency management da versão de Spring Boot adotada e evite fixar versões individuais de bibliotecas gerenciadas sem motivo. O curso assume <strong>Java 21</strong>; a versão exata do Spring Boot deve estar registrada no projeto e no README, porque compatibilidade de springdoc, Testcontainers e plugins depende dela.</p>","fidelityText":"Use o BOM/dependency management da versão de Spring Boot adotada e evite fixar versões individuais de bibliotecas gerenciadas sem motivo. O curso assume Java 21; a versão exata do Spring Boot deve estar registrada no projeto e no README, porque compatibilidade de springdoc, Testcontainers e plugins depende dela."},{"id":"spring-boot-fundamentos-content-29","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>JSON e ObjectMapper:</b> Jackson é a biblioteca que o Boot normalmente usa para converter objetos Java e JSON; <code>ObjectMapper</code> é seu objeto central de configuração e conversão. Declarar um <code>new ObjectMapper()</code> isolado pode perder módulos e customizações do Boot. Prefira customizadores, módulos Jackson como beans ou injete o mapper auto-configurado.</div>","fidelityText":"JSON e ObjectMapper: Jackson é a biblioteca que o Boot normalmente usa para converter objetos Java e JSON; ObjectMapper é seu objeto central de configuração e conversão. Declarar um new ObjectMapper() isolado pode perder módulos e customizações do Boot. Prefira customizadores, módulos Jackson como beans ou injete o mapper auto-configurado."},{"id":"spring-boot-fundamentos-exercise-30","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — explique o bootstrap","prompt":"Crie uma API pelo Initializr com Web, Validation e Actuator. Rode com --debug, encontre a auto-configuração do servidor, sobrescreva uma propriedade tipada e escreva um teste que falha quando a configuração é inválida.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — explique o bootstrapmédioCrie uma API pelo Initializr com Web, Validation e Actuator. Rode com --debug, encontre a auto-configuração do servidor, sobrescreva uma propriedade tipada e escreva um teste que falha quando a configuração é inválida.Ver critériosO relatório deve distinguir condições positivas e negativas; a aplicação não deve acessar variável de ambiente diretamente dentro da regra de negócio.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — explique o bootstrap</h2><span class=\"exercise-tag m\">médio</span></div><p>Crie uma API pelo Initializr com Web, Validation e Actuator. Rode com <code>--debug</code>, encontre a auto-configuração do servidor, sobrescreva uma propriedade tipada e escreva um teste que falha quando a configuração é inválida.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>O relatório deve distinguir condições positivas e negativas; a aplicação não deve acessar variável de ambiente diretamente dentro da regra de negócio.</p></div></div>"},{"id":"spring-boot-fundamentos-exercise-31","type":"exercise","authorship":"legacy-preserved","title":"Exercício — precedência de configuração na prática","prompt":"Defina server.port=8080 em application.properties. Rode a aplicação três vezes: (1) sem nenhum override; (2) com a variável de ambiente SERVER_PORT=8090; (3) com SERVER_PORT=8090 definida E o argumento --server.port=8095 passado na linha de comando. Anote a porta real usada em cada execução e explique qual fonte venceu em cada caso.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício — precedência de configuração na práticamédioDefina server.port=8080 em application.properties. Rode a aplicação três vezes: (1) sem nenhum override; (2) com a variável de ambiente SERVER_PORT=8090; (3) com SERVER_PORT=8090 definida E o argumento --server.port=8095 passado na linha de comando. Anote a porta real usada em cada execução e explique qual fonte venceu em cada caso.Ver solução(1) 8080, do application.properties. (2) 8090, a variável de ambiente sobrescreve o arquivo. (3) 8095, o argumento de linha de comando vence tanto a variável de ambiente quanto o arquivo — é a fonte de maior precedência entre as três.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Exercício — precedência de configuração na prática</h2><span class=\"exercise-tag m\">médio</span></div><p>Defina <code>server.port=8080</code> em <code>application.properties</code>. Rode a aplicação três vezes: (1) sem nenhum override; (2) com a variável de ambiente <code>SERVER_PORT=8090</code>; (3) com <code>SERVER_PORT=8090</code> definida E o argumento <code>--server.port=8095</code> passado na linha de comando. Anote a porta real usada em cada execução e explique qual fonte venceu em cada caso.</p><button class=\"reveal-btn\">Ver solução</button><div class=\"solution\"><p>(1) 8080, do <code>application.properties</code>. (2) 8090, a variável de ambiente sobrescreve o arquivo. (3) 8095, o argumento de linha de comando vence tanto a variável de ambiente quanto o arquivo — é a fonte de maior precedência entre as três.</p></div></div>"},{"id":"boot-quiz","type":"quiz","authorship":"authored","conceptId":"boot-bootstrap-autoconfig","prompt":"Por que adicionar um starter pode mudar os beans disponíveis?","options":[{"id":"boot-a","label":"Porque o classpath passa a satisfazer condições de auto-configuração.","correct":true,"explanation":"Boot observa bibliotecas, propriedades e beans para ativar configurações."},{"id":"boot-b","label":"Porque qualquer dependência executa código antes do main.","correct":false,"explanation":"Dependência no classpath não executa tudo automaticamente; Boot avalia condições."},{"id":"boot-c","label":"Porque o Spring ignora @Configuration quando há starter.","correct":false,"explanation":"Configuração explícita continua participando."}]}],"resources":[{"id":"boot-autoconfiguration","type":"reference","title":"Spring Boot: Auto-configuration","url":"https://docs.spring.io/spring-boot/reference/using/auto-configuration.html","reinforces":"Condições, auto-configuração e substituição por configuração explícita.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"boot-external-config","type":"reference","title":"Spring Boot: Externalized Configuration","url":"https://docs.spring.io/spring-boot/reference/features/external-config.html","reinforces":"Ordem de propriedades, configuração externa e binding tipado.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The spring boot fundamentals component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to spring boot fundamentals. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible spring boot fundamentals failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"@SpringBootApplication","instruction":"The spring boot fundamentals component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible spring boot fundamentals failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"devtools","moduleId":"spring-api","order":2,"title":"Spring Boot DevTools","summary":"Sem DevTools, toda mudança em um controller ou service exige parar a aplicação, recompilar e subir de novo manualmente — um ciclo de segundos a minutos, repetido centenas de vezes por dia de trabalho.","objectives":["Usar DevTools para feedback local rápido","Distinguir restart de build/teste reproduzível","Manter DevTools fora de expectativa de produção","Entender limites de classloader/restart"],"whyItExists":"Depois do bootstrap, o aluno pode ganhar feedback local sem confundir ferramenta de desenvolvimento com garantia de entrega. DevTools acelera estudo, mas não substitui teste, build e reload controlado.","prerequisiteChapterIds":["spring-boot-fundamentos"],"conceptIds":["por-que-o-restart-do-devtools-e-rapido-dois-classloaders-nao-um"],"introducedConceptIds":["devtools-restart-boundary"],"usedConceptIds":["boot-bootstrap-autoconfig","build-lifecycle"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"devtools-intuition","type":"intuition","authorship":"authored","title":"Feedback rápido não é validação final","body":"DevTools reinicia partes da aplicação quando arquivos mudam para acelerar o ciclo local. Isso ajuda a estudar, mas o comportamento final continua precisando de testes e build limpo.","analogyLimit":"Rascunho rápido ajuda, mas não é prova de publicação."},{"id":"devtools-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Ferramenta</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i><i></i><i></i></span> <b>Iniciante</b></div>\n        <div class=\"time-est\">⏱ <b>~30min</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-core\">43 · Spring Core</a></div>\n      </div>","fidelityText":"Ferramenta Dificuldade: Iniciante ⏱ ~30min de estudo Pré-requisitos: 43 · Spring Core"},{"id":"devtools-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Sem DevTools, toda mudança em um controller ou service exige parar a aplicação, recompilar e subir de novo manualmente — um ciclo de segundos a minutos, repetido centenas de vezes por dia de trabalho.</p>","fidelityText":"Sem DevTools, toda mudança em um controller ou service exige parar a aplicação, recompilar e subir de novo manualmente — um ciclo de segundos a minutos, repetido centenas de vezes por dia de trabalho."},{"id":"devtools-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"<!-- pom.xml -->\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-devtools</artifactId>\n    <scope>runtime</scope>\n    <optional>true</optional> <!-- evita propagação transitiva para quem depender deste projeto -->\n</dependency>","fidelityText":"<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> <!-- evita propagação transitiva para quem depender deste projeto --> </dependency>","highlightedHtml":"<span class=\"com\">&lt;!-- pom.xml --&gt;</span>\n&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;\n    &lt;artifactId&gt;spring-boot-devtools&lt;/artifactId&gt;\n    &lt;scope&gt;runtime&lt;/scope&gt;\n    &lt;optional&gt;true&lt;/optional&gt; <span class=\"com\">&lt;!-- evita propagação transitiva para quem depender deste projeto --&gt;</span>\n&lt;/dependency&gt;","caption":"Exemplo executável de devtools.","explanation":["A dependência habilita comportamento de desenvolvimento como restart automático.","Ela deve ficar no escopo adequado para não virar contrato de produção."],"commonMistakes":["Depender do DevTools no servidor","Confundir restart parcial com build limpo"]},{"id":"devtools-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">DevTools é como escrever com um editor que salva e formata automaticamente a cada pausa, em vez de precisar clicar \"salvar\" manualmente toda vez — o <strong>restart automático</strong> detecta mudanças no classpath e reinicia a aplicação sozinho, muito mais rápido que um restart manual completo, porque reaproveita partes já carregadas da JVM.</div>","fidelityText":"DevTools é como escrever com um editor que salva e formata automaticamente a cada pausa, em vez de precisar clicar \"salvar\" manualmente toda vez — o restart automático detecta mudanças no classpath e reinicia a aplicação sozinho, muito mais rápido que um restart manual completo, porque reaproveita partes já carregadas da JVM."},{"id":"devtools-content-5","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Recurso</th><th>O que faz</th></tr>\n        <tr><td>Restart automático</td><td>Detecta mudança de código compilado e reinicia a aplicação sozinho</td></tr>\n        <tr><td>LiveReload</td><td>Recarrega o navegador automaticamente após o restart (com extensão do navegador)</td></tr>\n        <tr><td>Propriedades de desenvolvimento</td><td>Desabilita cache de templates e outras otimizações que atrapalham durante o desenvolvimento</td></tr>\n      </tbody></table>","fidelityText":"RecursoO que faz Restart automáticoDetecta mudança de código compilado e reinicia a aplicação sozinho LiveReloadRecarrega o navegador automaticamente após o restart (com extensão do navegador) Propriedades de desenvolvimentoDesabilita cache de templates e outras otimizações que atrapalham durante o desenvolvimento"},{"id":"devtools-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>O <code>&lt;optional&gt;true&lt;/optional&gt;</code> e <code>scope=runtime</code> não são detalhes decorativos, mas não são o que exclui o DevTools do jar final:</b> por si só, <code>scope=runtime</code> só controla se a dependência entra no <em>classpath de compilação</em> (não entra) — ele não impede que o artefato final a contenha. Quem de fato remove o DevTools do jar empacotado é o <strong>plugin de empacotamento do Spring Boot</strong> (<code>spring-boot-maven-plugin</code>/<code>org.springframework.boot</code> no Gradle): ao montar o jar/war executável, esse plugin exclui explicitamente a dependência <code>spring-boot-devtools</code> do artefato repackaged, independentemente do scope declarado. <code>optional=true</code> resolve um problema diferente: evita que <strong>outros projetos que dependem do seu como biblioteca</strong> herdem o DevTools transitivamente. Então: <code>scope=runtime</code> mantém o DevTools fora do classpath de compilação do seu próprio código; <code>optional=true</code> evita propagação transitiva para quem depende de você; e é o plugin de build do Spring Boot — não o scope — quem garante que ele não vá para o jar final. Além disso, mesmo que o DevTools acabasse presente, o próprio Spring Boot detecta em runtime se está rodando de um jar empacotado e se desativa sozinho, como camada extra de segurança.</div>","fidelityText":"O <optional>true</optional> e scope=runtime não são detalhes decorativos, mas não são o que exclui o DevTools do jar final: por si só, scope=runtime só controla se a dependência entra no classpath de compilação (não entra) — ele não impede que o artefato final a contenha. Quem de fato remove o DevTools do jar empacotado é o plugin de empacotamento do Spring Boot (spring-boot-maven-plugin/org.springframework.boot no Gradle): ao montar o jar/war executável, esse plugin exclui explicitamente a dependência spring-boot-devtools do artefato repackaged, independentemente do scope declarado. optional=true resolve um problema diferente: evita que outros projetos que dependem do seu como biblioteca herdem o DevTools transitivamente. Então: scope=runtime mantém o DevTools fora do classpath de compilação do seu próprio código; optional=true evita propagação transitiva para quem depende de você; e é o plugin de build do Spring Boot — não o scope — quem garante que ele não vá para o jar final. Além disso, mesmo que o DevTools acabasse presente, o próprio Spring Boot detecta em runtime se está rodando de um jar empacotado e se desativa sozinho, como camada extra de segurança."},{"id":"devtools-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Por que o restart do DevTools é rápido: dois classloaders, não um</h2>","fidelityText":"Por que o restart do DevTools é rápido: dois classloaders, não um"},{"id":"devtools-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Um restart manual completo reinicia a JVM inteira — recarrega até as bibliotecas de terceiros (Spring, Jackson, o driver JDBC), que quase nunca mudam entre uma edição e outra do seu código. O DevTools acelera isso dividindo as classes da aplicação em <strong>dois class loaders</strong>:</p>","fidelityText":"Um restart manual completo reinicia a JVM inteira — recarrega até as bibliotecas de terceiros (Spring, Jackson, o driver JDBC), que quase nunca mudam entre uma edição e outra do seu código. O DevTools acelera isso dividindo as classes da aplicação em dois class loaders:"},{"id":"devtools-content-9","type":"html","authorship":"legacy-preserved","html":"<ul>\n        <li><strong>Base classloader</strong>: carrega classes que não mudam durante o desenvolvimento — bibliotecas de terceiros, seus JARs de dependência. Carregado uma única vez.</li>\n        <li><strong>Restart classloader</strong>: carrega apenas as suas classes de projeto (o que está em <code>src/main/java</code>/<code>target/classes</code>). É <strong>esse</strong> classloader que o DevTools descarta e recria a cada mudança detectada.</li>\n      </ul>","fidelityText":"Base classloader: carrega classes que não mudam durante o desenvolvimento — bibliotecas de terceiros, seus JARs de dependência. Carregado uma única vez. Restart classloader: carrega apenas as suas classes de projeto (o que está em src/main/java/target/classes). É esse classloader que o DevTools descarta e recria a cada mudança detectada."},{"id":"devtools-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Como o base classloader nunca precisa recarregar, o restart do DevTools é muito mais rápido que reiniciar a JVM do zero — só as suas classes são recompiladas e religadas, não o ecossistema de bibliotecas inteiro por trás delas. É por isso que adicionar uma <strong>nova dependência</strong> ao <code>pom.xml</code> ainda exige um restart manual completo: uma dependência nova pertence ao base classloader, que o DevTools não recarrega sozinho.</p>","fidelityText":"Como o base classloader nunca precisa recarregar, o restart do DevTools é muito mais rápido que reiniciar a JVM do zero — só as suas classes são recompiladas e religadas, não o ecossistema de bibliotecas inteiro por trás delas. É por isso que adicionar uma nova dependência ao pom.xml ainda exige um restart manual completo: uma dependência nova pertence ao base classloader, que o DevTools não recarrega sozinho."},{"id":"devtools-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Remote DevTools é um risco de segurança real, não só uma curiosidade.</b> O DevTools pode ser configurado para funcionar remotamente (<code>spring.devtools.remote.secret</code>), sincronizando mudanças de classe com uma aplicação rodando em outro servidor. Isso nunca deveria estar habilitado em produção: além do custo de performance, é uma porta de entrada para executar código arbitrário remotamente se o segredo vazar. Confirme sempre que o DevTools está de fato excluído do artefato de produção (a auto-detecção de jar empacotado descrita acima) antes de considerar o risco eliminado.</div>","fidelityText":"Remote DevTools é um risco de segurança real, não só uma curiosidade. O DevTools pode ser configurado para funcionar remotamente (spring.devtools.remote.secret), sincronizando mudanças de classe com uma aplicação rodando em outro servidor. Isso nunca deveria estar habilitado em produção: além do custo de performance, é uma porta de entrada para executar código arbitrário remotamente se o segredo vazar. Confirme sempre que o DevTools está de fato excluído do artefato de produção (a auto-detecção de jar empacotado descrita acima) antes de considerar o risco eliminado."},{"id":"devtools-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">DevTools economiza tempo real, mas não é \"mágico\" — se o restart automático não disparar, geralmente é porque a IDE não está configurada para compilar automaticamente ao salvar (no IntelliJ: <code>Build → Compile automatically</code>). Vale configurar isso uma vez e esquecer, em vez de reiniciar manualmente achando que é bug do DevTools.</div>","fidelityText":"DevTools economiza tempo real, mas não é \"mágico\" — se o restart automático não disparar, geralmente é porque a IDE não está configurada para compilar automaticamente ao salvar (no IntelliJ: Build → Compile automatically). Vale configurar isso uma vez e esquecer, em vez de reiniciar manualmente achando que é bug do DevTools."},{"id":"devtools-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Exercício 69.1 — Configurando o ciclo rápido de desenvolvimento","prompt":"Adicione a dependência do DevTools ao pom.xml do projeto da biblioteca, com o escopo correto. Explique em uma frase por que optional=true é importante mesmo com scope=runtime já configurado.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 69.1 — Configurando o ciclo rápido de desenvolvimentofácil Adicione a dependência do DevTools ao pom.xml do projeto da biblioteca, com o escopo correto. Explique em uma frase por que optional=true é importante mesmo com scope=runtime já configurado. Ver solução optional=true evita que projetos que dependem do seu projeto (se ele for usado como biblioteca por outro módulo) herdem a dependência do DevTools transitivamente — scope=runtime sozinho não impede essa propagação transitiva em todos os cenários de build.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 69.1 — Configurando o ciclo rápido de desenvolvimento</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Adicione a dependência do DevTools ao <code>pom.xml</code> do projeto da biblioteca, com o escopo correto. Explique em uma frase por que <code>optional=true</code> é importante mesmo com <code>scope=runtime</code> já configurado.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><code>optional=true</code> evita que projetos que dependem <em>do seu</em> projeto (se ele for usado como biblioteca por outro módulo) herdem a dependência do DevTools transitivamente — <code>scope=runtime</code> sozinho não impede essa propagação transitiva em todos os cenários de build.</p>\n        </div>\n      </div>"},{"id":"devtools-quiz","type":"quiz","authorship":"authored","conceptId":"devtools-restart-boundary","prompt":"Qual uso de DevTools é saudável?","options":[{"id":"dev-a","label":"Acelerar o ciclo local mantendo build e testes como validação real.","correct":true,"explanation":"DevTools é ferramenta de desenvolvimento, não gate de qualidade."},{"id":"dev-b","label":"Depender dele em produção para atualizar código sem deploy.","correct":false,"explanation":"DevTools não é mecanismo de deploy."},{"id":"dev-c","label":"Ignorar falhas que só aparecem após reiniciar do zero.","correct":false,"explanation":"Restart limpo ainda precisa ser testado."}]}],"resources":[{"id":"boot-devtools","type":"reference","title":"Spring Boot: Developer Tools","url":"https://docs.spring.io/spring-boot/reference/using/devtools.html","reinforces":"Recursos, restart, LiveReload e limites do DevTools.","language":"en","publisher":"Spring","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"boot-devtools-restart","type":"reference","title":"Spring Boot DevTools: Automatic Restart","url":"https://docs.spring.io/spring-boot/reference/using/devtools.html#using.devtools.restart","reinforces":"Como o restart funciona e quando pode surpreender.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The devtools component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to devtools. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible devtools failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"<!-- pom.xml -->","instruction":"The devtools component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible devtools failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"lombok","moduleId":"spring-api","order":3,"title":"Lombok — eliminando boilerplate","summary":"Lembra de escrever getters, setters, construtores e equals/hashCode/toString na mão nos capítulos 04 e 05? Isso é exatamente o que Lombok gera automaticamente, sem reflection em runtime — a geração acontece durante a compilação, antes de existir qualquer .class.","objectives":["Entender Lombok como geração em compilação","Usar constructor injection sem boilerplate excessivo","Proteger invariantes ao evitar setters indiscriminados","Avaliar trade-off de legibilidade/tooling"],"whyItExists":"Lombok aparece cedo em projetos Spring, mas o aluno já sabe annotations, encapsulamento e records. Agora pode decidir quando gerar código repetitivo ajuda e quando esconde contrato importante.","prerequisiteChapterIds":["encapsulamento","anotacoes","spring-core"],"conceptIds":["instalando-maven-gradle-e-o-annotation-processor","anotacoes-mais-usadas","delombok-vendo-o-codigo-real-que-o-lombok-gera","lombok-config-regras-de-projeto-para-o-lombok"],"introducedConceptIds":["lombok-generated-code-contract"],"usedConceptIds":["annotation-metadata-contract","encapsulamento-invariante","spring-component-scan-bean"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"lombok-intuition","type":"intuition","authorship":"authored","title":"Lombok escreve código por você; ele não decide o design","body":"@Getter ou @RequiredArgsConstructor reduzem repetição, mas os métodos gerados continuam existindo no contrato da classe. Se você gera setter para tudo, também gera caminhos para quebrar invariantes.","analogyLimit":"Atalho de teclado ajuda, mas ainda pode inserir texto ruim."},{"id":"lombok-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Ferramenta</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i><i></i><i></i></span> <b>Iniciante</b></div>\n        <div class=\"time-est\">⏱ <b>~1h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#encapsulamento\">04 · Encapsulamento</a>, <a class=\"prereq-tag\" href=\"#anotacoes\">20 · Anotações &amp; Reflection</a></div>\n      </div>","fidelityText":"Ferramenta Dificuldade: Iniciante ⏱ ~1h de estudo Pré-requisitos: 04 · Encapsulamento, 20 · Anotações & Reflection"},{"id":"lombok-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Lembra de escrever getters, setters, construtores e <code>equals</code>/<code>hashCode</code>/<code>toString</code> na mão nos capítulos 04 e 05? Isso é exatamente o que <strong>Lombok</strong> gera automaticamente, sem reflection em runtime — a geração acontece durante a compilação, antes de existir qualquer <code>.class</code>.</p>","fidelityText":"Lembra de escrever getters, setters, construtores e equals/hashCode/toString na mão nos capítulos 04 e 05? Isso é exatamente o que Lombok gera automaticamente, sem reflection em runtime — a geração acontece durante a compilação, antes de existir qualquer .class."},{"id":"lombok-comparison-3","type":"html","html":"<div class=\"code-comparison\"><div class=\"code-toggle-bar\" role=\"tablist\">\n        <button class=\"ct-btn bad active\" role=\"tab\" aria-selected=\"true\" type=\"button\">❌ Sem Lombok</button>\n        <button class=\"ct-btn good\" role=\"tab\" aria-selected=\"false\" type=\"button\" tabindex=\"-1\">✅ Com Lombok</button>\n      </div><div class=\"ct-panel active\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"kw\">private</span> <span class=\"kw\">Long</span> id;\n    <span class=\"kw\">private String</span> title;\n    <span class=\"kw\">private int</span> pages;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Book</span>(<span class=\"kw\">Long</span> id, <span class=\"kw\">String</span> title, <span class=\"kw\">int</span> pages) {\n        <span class=\"kw\">this</span>.id = id; <span class=\"kw\">this</span>.title = title; <span class=\"kw\">this</span>.pages = pages;\n    }\n    <span class=\"kw\">public Long</span> <span class=\"fn\">getId</span>() { <span class=\"kw\">return</span> id; }\n    <span class=\"kw\">public String</span> <span class=\"fn\">getTitle</span>() { <span class=\"kw\">return</span> title; }\n    <span class=\"kw\">public void</span> <span class=\"fn\">setTitle</span>(<span class=\"kw\">String</span> title) { <span class=\"kw\">this</span>.title = title; }\n    <span class=\"kw\">public int</span> <span class=\"fn\">getPages</span>() { <span class=\"kw\">return</span> pages; }\n    <span class=\"kw\">public void</span> <span class=\"fn\">setPages</span>(<span class=\"kw\">int</span> pages) { <span class=\"kw\">this</span>.pages = pages; }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public boolean</span> <span class=\"fn\">equals</span>(<span class=\"kw\">Object</span> o) { <span class=\"com\">/* ~10 linhas */</span> }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public int</span> <span class=\"fn\">hashCode</span>() { <span class=\"com\">/* ~3 linhas */</span> }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public String</span> <span class=\"fn\">toString</span>() { <span class=\"com\">/* ~3 linhas */</span> }\n}\n<span class=\"com\">// ~35 linhas para uma classe de 3 campos</span></pre>\n      </div><div class=\"ct-panel\">\n<pre class=\"code\"><span class=\"annotation\">@Getter</span> <span class=\"annotation\">@Setter</span>\n<span class=\"annotation\">@InArgsConstructor</span> <span class=\"annotation\">@AllArgsConstructor</span>\n<span class=\"annotation\">@EqualsAndHashCode</span> <span class=\"annotation\">@ToString</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"kw\">private</span> <span class=\"kw\">Long</span> id;\n    <span class=\"kw\">private String</span> title;\n    <span class=\"kw\">private int</span> pages;\n}\n<span class=\"com\">// 8 linhas, mesmo resultado -- o código \"chato\" nunca existiu no .java\n// que você escreveu, mas o compilador o insere na árvore da classe\n// antes de gerar o .class -- por isso ele aparece ao decompilar</span></pre>\n      </div></div>","sourceIndexes":[3,4,5],"fidelityText":"❌ Sem Lombok ✅ Com Lombok public class Livro { private Long id; private String titulo; private int paginas; public Livro(Long id, String titulo, int paginas) { this.id = id; this.titulo = titulo; this.paginas = paginas; } public Long getId() { return id; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public int getPaginas() { return paginas; } public void setPaginas(int paginas) { this.paginas = paginas; } @Override public boolean equals(Object o) { /* ~10 linhas */ } @Override public int hashCode() { /* ~3 linhas */ } @Override public String toString() { /* ~3 linhas */ } } // ~35 linhas para uma classe de 3 campos @Getter @Setter @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode @ToString public class Livro { private Long id; private String titulo; private int paginas; } // 8 linhas, mesmo resultado -- o código \"chato\" nunca existiu no .java // que você escreveu, mas o compilador o insere na árvore da classe // antes de gerar o .class -- por isso ele aparece ao decompilar"},{"id":"lombok-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Instalando: Maven, Gradle e o annotation processor</h2>","fidelityText":"Instalando: Maven, Gradle e o annotation processor"},{"id":"lombok-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"<!-- pom.xml -->\n<dependency>\n    <groupId>org.projectlombok</groupId>\n    <artifactId>lombok</artifactId>\n    <scope>provided</scope> <!-- só precisa em compilação -- não deveria estar no runtime/jar final -->\n</dependency>","fidelityText":"<!-- pom.xml --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> <!-- só precisa em compilação -- não deveria estar no runtime/jar final --> </dependency>","highlightedHtml":"<span class=\"com\">&lt;!-- pom.xml --&gt;</span>\n&lt;dependency&gt;\n    &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;\n    &lt;artifactId&gt;lombok&lt;/artifactId&gt;\n    &lt;scope&gt;provided&lt;/scope&gt; <span class=\"com\">&lt;!-- só precisa em compilação -- não deveria estar no runtime/jar final --&gt;</span>\n&lt;/dependency&gt;","caption":"Exemplo executável de lombok.","explanation":["scope=provided é suficiente no Maven: o plugin de compilação descobre annotation processors automaticamente no classpath de compilação."]},{"id":"lombok-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"// build.gradle.kts\ndependencies {\n    compileOnly(\"org.projectlombok:lombok:1.18.32\")\n    annotationProcessor(\"org.projectlombok:lombok:1.18.32\") // registra o Lombok como annotation processor no build do Gradle\n    testCompileOnly(\"org.projectlombok:lombok:1.18.32\")\n    testAnnotationProcessor(\"org.projectlombok:lombok:1.18.32\")\n}","fidelityText":"// build.gradle.kts dependencies { compileOnly(\"org.projectlombok:lombok:1.18.32\") annotationProcessor(\"org.projectlombok:lombok:1.18.32\") // registra o Lombok como annotation processor no build do Gradle testCompileOnly(\"org.projectlombok:lombok:1.18.32\") testAnnotationProcessor(\"org.projectlombok:lombok:1.18.32\") }","highlightedHtml":"<span class=\"com\">// build.gradle.kts</span>\ndependencies {\n    compileOnly(<span class=\"str\">\"org.projectlombok:lombok:1.18.32\"</span>)\n    annotationProcessor(<span class=\"str\">\"org.projectlombok:lombok:1.18.32\"</span>) <span class=\"com\">// registra o Lombok como annotation processor no build do Gradle</span>\n    testCompileOnly(<span class=\"str\">\"org.projectlombok:lombok:1.18.32\"</span>)\n    testAnnotationProcessor(<span class=\"str\">\"org.projectlombok:lombok:1.18.32\"</span>)\n}","caption":"Exemplo executável de lombok.","explanation":["No Gradle, compileOnly torna as anotações visíveis, mas annotationProcessor é quem de fato registra o Lombok para rodar durante a compilação -- esquecer essa segunda linha é a causa mais comum de \"Lombok não funciona\" no Gradle."],"commonMistakes":["Declarar só compileOnly e esquecer annotationProcessor no Gradle"]},{"id":"lombok-content-9","type":"html","authorship":"legacy-preserved","html":"<p>No Maven, <code>scope=provided</code> já é suficiente porque o plugin de compilação descobre annotation processors automaticamente no classpath de compilação. No Gradle, é preciso declarar explicitamente tanto <code>compileOnly</code> (para o código enxergar as anotações) quanto <code>annotationProcessor</code> (para o processador rodar de fato durante a compilação) — esquecer a segunda linha é a causa mais comum de \"Lombok não funciona\" em projetos Gradle. A IDE também precisa de um plugin/suporte a annotation processing habilitado para exibir os métodos gerados sem sublinhar como erro.</p>","fidelityText":"No Maven, scope=provided já é suficiente porque o plugin de compilação descobre annotation processors automaticamente no classpath de compilação. No Gradle, é preciso declarar explicitamente tanto compileOnly (para o código enxergar as anotações) quanto annotationProcessor (para o processador rodar de fato durante a compilação) — esquecer a segunda linha é a causa mais comum de \"Lombok não funciona\" em projetos Gradle. A IDE também precisa de um plugin/suporte a annotation processing habilitado para exibir os métodos gerados sem sublinhar como erro."},{"id":"lombok-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Anotações mais usadas</h2>","fidelityText":"Anotações mais usadas"},{"id":"lombok-content-11","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Anotação</th><th>Gera</th></tr>\n        <tr><td><code>@Getter</code>/<code>@Setter</code></td><td>Getters/setters de todos os campos (ou de um específico, aplicado no campo). <code>@Setter(AccessLevel.PROTECTED)</code> restringe a visibilidade do método gerado.</td></tr>\n        <tr><td><code>@NoArgsConstructor</code></td><td>Construtor vazio</td></tr>\n        <tr><td><code>@AllArgsConstructor</code></td><td>Construtor com todos os campos</td></tr>\n        <tr><td><code>@RequiredArgsConstructor</code></td><td>Construtor apenas com os campos <code>final</code> (ou <code>@NonNull</code>) — a forma mais usada em injeção de dependência via construtor (capítulo 22), pois não força passar campos com valor default</td></tr>\n        <tr><td><code>@Data</code></td><td>Combo: Getter + Setter + EqualsAndHashCode + ToString + <code>@RequiredArgsConstructor</code></td></tr>\n        <tr><td><code>@Value</code></td><td>A versão <strong>imutável</strong> de <code>@Data</code>: classe e campos <code>final</code> por padrão, sem setters, com <code>@AllArgsConstructor</code></td></tr>\n        <tr><td><code>@Builder</code></td><td>Gera o Builder Pattern inteiro (capítulo 09) automaticamente. <code>@Singular</code> em um campo de coleção gera métodos para adicionar itens um a um ao builder.</td></tr>\n        <tr><td><code>@With</code></td><td>Gera um método <code>withCampo(novoValor)</code> que devolve uma <strong>nova</strong> instância com aquele campo alterado — útil em classes imutáveis (<code>@Value</code>), no mesmo espírito do <code>transladar(...)</code> de <code>Ponto</code> (capítulo 04)</td></tr>\n        <tr><td><code>@Slf4j</code></td><td>Injeta um <code>Logger</code> pronto (capítulo 27), sem precisar declarar manualmente</td></tr>\n      </tbody></table>","fidelityText":"AnotaçãoGera @Getter/@SetterGetters/setters de todos os campos (ou de um específico, aplicado no campo). @Setter(AccessLevel.PROTECTED) restringe a visibilidade do método gerado. @NoArgsConstructorConstrutor vazio @AllArgsConstructorConstrutor com todos os campos @RequiredArgsConstructorConstrutor apenas com os campos final (ou @NonNull) — a forma mais usada em injeção de dependência via construtor (capítulo 22), pois não força passar campos com valor default @DataCombo: Getter + Setter + EqualsAndHashCode + ToString + @RequiredArgsConstructor @ValueA versão imutável de @Data: classe e campos final por padrão, sem setters, com @AllArgsConstructor @BuilderGera o Builder Pattern inteiro (capítulo 09) automaticamente. @Singular em um campo de coleção gera métodos para adicionar itens um a um ao builder. @WithGera um método withCampo(novoValor) que devolve uma nova instância com aquele campo alterado — útil em classes imutáveis (@Value), no mesmo espírito do transladar(...) de Ponto (capítulo 04) @Slf4jInjeta um Logger pronto (capítulo 27), sem precisar declarar manualmente"},{"id":"lombok-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"@Value // imutável: campos final, sem setters, equals/hashCode/toString incluídos\npublic class Cash {\n    BigDecimal value;\n    String currency;\n}\nCash d = new Cash(new BigDecimal(\"10.00\"), \"BRL\");\n\n@With\npublic class Order {\n    private final String status;\n    public Order(String status) { this.status = status; }\n}\nOrder confirmed = orderOriginal.withStatus(\"CONFIRMED\"); // nova instância, pedidoOriginal continua intacto","fidelityText":"@Value // imutável: campos final, sem setters, equals/hashCode/toString incluídos public class Dinheiro { BigDecimal valor; String moeda; } Dinheiro d = new Dinheiro(new BigDecimal(\"10.00\"), \"BRL\"); @With public class Pedido { private final String status; public Pedido(String status) { this.status = status; } } Pedido confirmado = pedidoOriginal.withStatus(\"CONFIRMADO\"); // nova instância, pedidoOriginal continua intacto","highlightedHtml":"<span class=\"annotation\">@Value</span> <span class=\"com\">// imutável: campos final, sem setters, equals/hashCode/toString incluídos</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Cash</span> {\n    BigDecimal value;\n    String currency;\n}\n<span class=\"cls\">Cash</span> d = <span class=\"kw\">new</span> <span class=\"cls\">Cash</span>(<span class=\"kw\">new</span> BigDecimal(<span class=\"str\">\"10.00\"</span>), <span class=\"str\">\"BRL\"</span>);\n\n<span class=\"annotation\">@With</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Order</span> {\n    <span class=\"kw\">private final String</span> status;\n    <span class=\"kw\">public</span> <span class=\"fn\">Order</span>(<span class=\"kw\">String</span> status) { <span class=\"kw\">this</span>.status = status; }\n}\n<span class=\"cls\">Order</span> confirmed = orderOriginal.withStatus(<span class=\"str\">\"CONFIRMED\"</span>); <span class=\"com\">// nova instância, pedidoOriginal continua intacto</span>","caption":"Exemplo executável de lombok.","explanation":["@Value gera uma classe imutável (campos final, sem setters); @With gera um método que devolve uma NOVA instância com um campo alterado, preservando a original."]},{"id":"lombok-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>delombok: vendo o código real que o Lombok gera</h2>","fidelityText":"delombok: vendo o código real que o Lombok gera"},{"id":"lombok-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"./mvnw lombok:delombok -Dlombok.copyJavadoc=false -DoutputDirectory=target/delombok","fidelityText":"./mvnw lombok:delombok -Dlombok.copyJavadoc=false -DoutputDirectory=target/delombok","highlightedHtml":"./mvnw lombok:delombok -Dlombok.copyJavadoc=<span class=\"kw\">false</span> -DoutputDirectory=target/delombok","caption":"Exemplo executável de lombok.","explanation":["delombok materializa em .java reais exatamente o código que o Lombok insere na AST -- útil para conferir o que uma anotação está gerando sem depender só do plugin da IDE."]},{"id":"lombok-content-15","type":"html","authorship":"legacy-preserved","html":"<p><strong>Delombok</strong> materializa, em arquivos <code>.java</code> reais dentro de <code>target/delombok</code>, exatamente o código que o Lombok insere na AST durante a compilação normal — getters, setters, construtores, tudo escrito por extenso. É a ferramenta certa para tirar a dúvida \"o que exatamente essa anotação está gerando aqui?\" sem depender só do plugin da IDE, e é útil para depurar um comportamento inesperado (por exemplo, conferir se <code>@EqualsAndHashCode</code> realmente incluiu um campo que você não esperava).</p>","fidelityText":"Delombok materializa, em arquivos .java reais dentro de target/delombok, exatamente o código que o Lombok insere na AST durante a compilação normal — getters, setters, construtores, tudo escrito por extenso. É a ferramenta certa para tirar a dúvida \"o que exatamente essa anotação está gerando aqui?\" sem depender só do plugin da IDE, e é útil para depurar um comportamento inesperado (por exemplo, conferir se @EqualsAndHashCode realmente incluiu um campo que você não esperava)."},{"id":"lombok-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>lombok.config: regras de projeto para o Lombok</h2>","fidelityText":"lombok.config: regras de projeto para o Lombok"},{"id":"lombok-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"# lombok.config, na raiz do módulo\nconfig.stopBubbling = true\nlombok.equalsAndHashCode.callSuper = warn # avisa se @EqualsAndHashCode não considerar explicitamente a superclasse\nlombok.anyConstructor.addConstructorProperties = true","fidelityText":"# lombok.config, na raiz do módulo config.stopBubbling = true lombok.equalsAndHashCode.callSuper = warn # avisa se @EqualsAndHashCode não considerar explicitamente a superclasse lombok.anyConstructor.addConstructorProperties = true","highlightedHtml":"<span class=\"com\"># lombok.config, na raiz do módulo</span>\nconfig.stopBubbling = true\nlombok.equalsAndHashCode.callSuper = warn <span class=\"com\"># avisa se @EqualsAndHashCode não considerar explicitamente a superclasse</span>\nlombok.anyConstructor.addConstructorProperties = true","caption":"Exemplo executável de lombok.","explanation":["lombok.config fixa convenções de projeto (nível de acesso, comportamento de callSuper) sem repetir configuração em cada anotação individualmente."]},{"id":"lombok-content-18","type":"html","authorship":"legacy-preserved","html":"<p>Um arquivo <code>lombok.config</code> permite fixar convenções de projeto (nível de acesso padrão, comportamento de <code>callSuper</code>, avisos habilitados) sem repetir configuração em cada anotação — útil em times grandes para manter consistência sobre como equals/hashCode/toString se comportam em hierarquias com herança.</p>","fidelityText":"Um arquivo lombok.config permite fixar convenções de projeto (nível de acesso padrão, comportamento de callSuper, avisos habilitados) sem repetir configuração em cada anotação — útil em times grandes para manter consistência sobre como equals/hashCode/toString se comportam em hierarquias com herança."},{"id":"lombok-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Diferente do Jackson (reflection em runtime) e do Spring (reflection + proxies em runtime), o Lombok atua em <strong>tempo de compilação</strong> — mas de um jeito mais invasivo do que o mecanismo público de Annotation Processing (<code>javax.annotation.processing</code>) foi projetado para permitir. A API padrão de annotation processors só pode <em>gerar novos arquivos-fonte</em> a partir de anotações; ela não tem permissão para alterar a árvore sintática (AST) de uma classe já existente. O Lombok contorna isso: ele se registra como um annotation processor (é assim que <code>javac</code> o carrega e o executa durante a compilação), mas, uma vez dentro do processo, usa APIs <strong>internas e não documentadas</strong> do compilador (o pacote <code>com.sun.source</code>/a árvore interna do <code>javac</code>) para modificar diretamente a AST da sua classe <em>antes</em> dela ser convertida em bytecode — inserindo os métodos <code>getX()</code>, construtores etc. como se você os tivesse digitado no <code>.java</code>. O <code>.class</code> final reflete essa AST já modificada; o Lombok não \"reescreve o `.class`\" depois de pronto, ele muda o que o compilador vai transformar em `.class`. Essa dependência de internals do compilador é também por que, historicamente, cada nova versão do JDK exige uma atualização do Lombok e por que algumas IDEs precisam de um plugin específico para exibir os métodos gerados no editor.</div>","fidelityText":"Diferente do Jackson (reflection em runtime) e do Spring (reflection + proxies em runtime), o Lombok atua em tempo de compilação — mas de um jeito mais invasivo do que o mecanismo público de Annotation Processing (javax.annotation.processing) foi projetado para permitir. A API padrão de annotation processors só pode gerar novos arquivos-fonte a partir de anotações; ela não tem permissão para alterar a árvore sintática (AST) de uma classe já existente. O Lombok contorna isso: ele se registra como um annotation processor (é assim que javac o carrega e o executa durante a compilação), mas, uma vez dentro do processo, usa APIs internas e não documentadas do compilador (o pacote com.sun.source/a árvore interna do javac) para modificar diretamente a AST da sua classe antes dela ser convertida em bytecode — inserindo os métodos getX(), construtores etc. como se você os tivesse digitado no .java. O .class final reflete essa AST já modificada; o Lombok não \"reescreve o `.class`\" depois de pronto, ele muda o que o compilador vai transformar em `.class`. Essa dependência de internals do compilador é também por que, historicamente, cada nova versão do JDK exige uma atualização do Lombok e por que algumas IDEs precisam de um plugin específico para exibir os métodos gerados no editor."},{"id":"lombok-content-20","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>@Data em entidades JPA é uma armadilha comum:</b> o <code>equals</code>/<code>hashCode</code> gerado usa <strong>todos</strong> os campos por padrão, incluindo relacionamentos — o que pode disparar carregamento LAZY inesperado (capítulo 46) ou comparações incorretas antes do <code>id</code> ser gerado pelo banco. Para entidades JPA, prefira anotações específicas (<code>@Getter</code>, <code>@Setter</code>) e escreva <code>equals</code>/<code>hashCode</code> manualmente baseado só no <code>id</code>, como você já fez no exercício 5.2.</div>","fidelityText":"@Data em entidades JPA é uma armadilha comum: o equals/hashCode gerado usa todos os campos por padrão, incluindo relacionamentos — o que pode disparar carregamento LAZY inesperado (capítulo 46) ou comparações incorretas antes do id ser gerado pelo banco. Para entidades JPA, prefira anotações específicas (@Getter, @Setter) e escreva equals/hashCode manualmente baseado só no id, como você já fez no exercício 5.2."},{"id":"lombok-content-21","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Você já sabe escrever tudo isso na mão (capítulos 04, 05, 09) — não é acaso. Ferramentas de geração de código só deveriam ser adotadas depois que você entende exatamente o que está sendo automatizado, senão vira \"magia\" que você não sabe depurar quando algo foge do padrão comum.</div>","fidelityText":"Você já sabe escrever tudo isso na mão (capítulos 04, 05, 09) — não é acaso. Ferramentas de geração de código só deveriam ser adotadas depois que você entende exatamente o que está sendo automatizado, senão vira \"magia\" que você não sabe depurar quando algo foge do padrão comum."},{"id":"lombok-exercise-22","type":"exercise","authorship":"legacy-preserved","title":"Exercício 68.1 — Refatorando com Lombok","prompt":"Pegue a classe Livro encapsulada do exercício 4.1 (getters, setters, construtor) e reescreva usando as anotações Lombok adequadas, mantendo o mesmo comportamento público.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 68.1 — Refatorando com Lombokfácil Pegue a classe Livro encapsulada do exercício 4.1 (getters, setters, construtor) e reescreva usando as anotações Lombok adequadas, mantendo o mesmo comportamento público. Ver solução @Getter @AllArgsConstructor public class Livro { private String titulo; private String autor; private int paginas; public void setPaginas(int paginas) { // setter customizado -- Lombok não gera validação, então mantém manual if (paginas > 0) this.paginas = paginas; } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 68.1 — Refatorando com Lombok</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Pegue a classe <code>Livro</code> encapsulada do exercício 4.1 (getters, setters, construtor) e reescreva usando as anotações Lombok adequadas, mantendo o mesmo comportamento público.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Getter</span>\n<span class=\"annotation\">@AllArgsConstructor</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"kw\">private String</span> title;\n    <span class=\"kw\">private String</span> author;\n    <span class=\"kw\">private int</span> pages;\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">setPages</span>(<span class=\"kw\">int</span> pages) { <span class=\"com\">// setter customizado -- Lombok não gera validação, então mantém manual</span>\n        <span class=\"kw\">if</span> (pages &gt; 0) <span class=\"kw\">this</span>.pages = pages;\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"lombok-exercise-23","type":"exercise","authorship":"legacy-preserved","title":"Exercício 68.2 — @RequiredArgsConstructor em um @Service","prompt":"Reescreva uma classe PedidoServico com dois campos final (NotificadorEmprestimo notificador e Logger log, este último gerado por @Slf4j) usando @RequiredArgsConstructor em vez de escrever o construtor manualmente. Explique por que essa combinação é o padrão mais comum em classes @Service do Spring — e por que @Data seria uma escolha ruim aqui.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 68.2 — @RequiredArgsConstructor em um @Servicemédio Reescreva uma classe PedidoServico com dois campos final (NotificadorEmprestimo notificador e Logger log, este último gerado por @Slf4j) usando @RequiredArgsConstructor em vez de escrever o construtor manualmente. Explique por que essa combinação é o padrão mais comum em classes @Service do Spring — e por que @Data seria uma escolha ruim aqui. Ver solução @Service @RequiredArgsConstructor @Slf4j public class PedidoServico { private final NotificadorEmprestimo notificador; // campo final -- @RequiredArgsConstructor gera o construtor com ele public void finalizar(Pedido pedido) { log.info(\"Finalizando pedido {}\", pedido.getId()); notificador.notificar(\"Pedido confirmado\"); } } @RequiredArgsConstructor gera exatamente o construtor que a injeção via construtor (capítulo 22) precisa, sem boilerplate — e nada mais: sem getters/setters desnecessários para um serviço sem estado exposto. @Data seria ruim aqui porque gera setters (um @Service não deveria expor mutação de suas próprias dependências) e um equals/hashCode que não fazem sentido para uma classe de comportamento sem identidade de valor.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 68.2 — @RequiredArgsConstructor em um @Service</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Reescreva uma classe <code>PedidoServico</code> com dois campos <code>final</code> (<code>NotificadorEmprestimo notificador</code> e <code>Logger log</code>, este último gerado por <code>@Slf4j</code>) usando <code>@RequiredArgsConstructor</code> em vez de escrever o construtor manualmente. Explique por que essa combinação é o padrão mais comum em classes <code>@Service</code> do Spring — e por que <code>@Data</code> seria uma escolha ruim aqui.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Service</span>\n<span class=\"annotation\">@RequiredArgsConstructor</span>\n<span class=\"annotation\">@Slf4j</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">OrderService</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">NotifierLoan</span> notifier; <span class=\"com\">// campo final -- @RequiredArgsConstructor gera o construtor com ele</span>\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">complete</span>(<span class=\"cls\">Order</span> order) {\n        log.info(<span class=\"str\">\"Completing order {}\"</span>, order.getId());\n        notifier.notify(<span class=\"str\">\"Order confirmed\"</span>);\n    }\n}</pre>\n          <p style=\"margin-top:12px\"><code>@RequiredArgsConstructor</code> gera exatamente o construtor que a injeção via construtor (capítulo 22) precisa, sem boilerplate — e nada mais: sem getters/setters desnecessários para um serviço sem estado exposto. <code>@Data</code> seria ruim aqui porque gera setters (um <code>@Service</code> não deveria expor mutação de suas próprias dependências) e um <code>equals</code>/<code>hashCode</code> que não fazem sentido para uma classe de comportamento sem identidade de valor.</p>\n        </div>\n      </div>"},{"id":"lombok-quiz","type":"quiz","authorship":"authored","conceptId":"lombok-generated-code-contract","prompt":"Qual é o risco de usar @Data em uma entidade rica de domínio?","options":[{"id":"lom-a","label":"Gerar setters/equals/toString que podem expor ou quebrar invariantes sem intenção.","correct":true,"explanation":"Boilerplate gerado ainda faz parte do comportamento público."},{"id":"lom-b","label":"Impedir totalmente a compilação de qualquer projeto Spring.","correct":false,"explanation":"Lombok compila com plugin/processador configurado."},{"id":"lom-c","label":"Transformar Java em linguagem dinâmica em runtime.","correct":false,"explanation":"A geração ocorre em compilação."}]}],"resources":[{"id":"lombok-features","type":"reference","title":"Project Lombok Features","url":"https://projectlombok.org/features/","reinforces":"Lista oficial de anotações, geração e trade-offs.","language":"en","publisher":"Project Lombok","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"lombok-constructor","type":"reference","title":"Project Lombok: Constructor annotations","url":"https://projectlombok.org/features/constructor","reinforces":"@RequiredArgsConstructor, @AllArgsConstructor e constructor injection.","language":"en","publisher":"Project Lombok","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The lombok component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to lombok. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible lombok failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"public class Book {","instruction":"The lombok component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible lombok failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"arquitetura-api-rest","moduleId":"spring-api","order":4,"title":"Arquitetura de uma API — do requisito ao código","summary":"Os próximos capítulos vão te dar cada peça técnica de uma API: como o Spring mapeia um método pra uma URL, como separar DTO de entidade, como validar entrada, como paginar. Nenhum deles, porém, responde a uma pergunta anterior a todas as outras: dado um requisito de negócio em português, como você chega no design da API antes de escrever a primeira linha de código? Este capítulo existe pra fechar exatamente essa lacuna — o processo de decisão, não mais uma técnica isolada.","objectives":["Decidir recurso, URL e verbo HTTP a partir de um requisito em português","Aplicar um algoritmo explícito para decidir em qual camada cada lógica mora","Nomear métodos de forma consistente com a responsabilidade de cada camada","Estruturar pacotes de uma API nova (controller/service/repository/dto/mapper/exception)"],"whyItExists":"Os próximos capítulos ensinam cada peça técnica de uma API (MVC, DTO, validação, paginação), mas nenhum ensina o processo de decisão que vem antes: como sair de um requisito em português para URL/verbo/camadas/nomes de método. Sem esse capítulo, o aluno vê os exemplos prontos e nunca pratica chegar neles sozinho.","prerequisiteChapterIds":["spring-boot-fundamentos","http"],"conceptIds":["1-do-requisito-ao-recurso-rest","2-onde-cada-logica-mora-um-algoritmo-nao-um-palpite","3-convencao-de-nome-de-metodo-por-camada","4-estrutura-de-pacotes"],"introducedConceptIds":["requisito-para-recurso-rest","algoritmo-de-decisao-de-camada","convencao-de-nome-por-camada","estrutura-de-pacotes"],"usedConceptIds":["http-mensagem-recurso","http-metodo-semantica","http-status-classe"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"arquitetura-api-rest-intuition","type":"intuition","authorship":"authored","title":"O design vem antes do código","body":"Toda técnica de API que você vai aprender daqui pra frente (DTO, validação, paginação) resolve um problema específico dentro de um desenho que já foi decidido. Este capítulo é sobre como tomar essa decisão — recurso, verbo, camada, nome — antes de qualquer anotação do Spring entrar em cena.","analogyLimit":"A analogia recepcionista/cozinheiro (retomada aqui e formalizada no capítulo seguinte) ajuda a lembrar a fronteira de responsabilidade, mas não substitui o algoritmo de decisão das quatro perguntas."},{"id":"arquitetura-api-rest-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-boot-fundamentos\">42 · Spring Boot Fundamentos</a>, <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; APIs REST</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~1h30 de estudo + prática Pré-requisitos: 42 · Spring Boot Fundamentos, 26 · HTTP & APIs REST"},{"id":"arquitetura-api-rest-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Os próximos capítulos vão te dar cada peça técnica de uma API: como o Spring mapeia um método pra uma URL, como separar DTO de entidade, como validar entrada, como paginar. Nenhum deles, porém, responde a uma pergunta anterior a todas as outras: <strong>dado um requisito de negócio em português, como você chega no design da API antes de escrever a primeira linha de código?</strong> Este capítulo existe pra fechar exatamente essa lacuna — o processo de decisão, não mais uma técnica isolada.</p>","fidelityText":"Os próximos capítulos vão te dar cada peça técnica de uma API: como o Spring mapeia um método pra uma URL, como separar DTO de entidade, como validar entrada, como paginar. Nenhum deles, porém, responde a uma pergunta anterior a todas as outras: dado um requisito de negócio em português, como você chega no design da API antes de escrever a primeira linha de código? Este capítulo existe pra fechar exatamente essa lacuna — o processo de decisão, não mais uma técnica isolada."},{"id":"arquitetura-api-rest-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\">Isso não é um capítulo sobre sintaxe do Spring — é sobre as decisões que você toma <em>antes</em> de abrir o editor: qual URL, qual verbo, qual camada recebe qual responsabilidade, e como nomear cada método pra que o próximo desenvolvedor (ou você mesmo, seis meses depois) entenda o papel de cada peça só pelo nome.</div>","fidelityText":"Isso não é um capítulo sobre sintaxe do Spring — é sobre as decisões que você toma antes de abrir o editor: qual URL, qual verbo, qual camada recebe qual responsabilidade, e como nomear cada método pra que o próximo desenvolvedor (ou você mesmo, seis meses depois) entenda o papel de cada peça só pelo nome."},{"id":"arquitetura-api-rest-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>1. Do requisito ao recurso REST</h2>","fidelityText":"1. Do requisito ao recurso REST"},{"id":"arquitetura-api-rest-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Requisito: <em>\"o sistema da biblioteca precisa permitir que um usuário pegue um livro emprestado.\"</em> Nenhuma URL, verbo ou status code está escrito aí — é você quem decide, e a decisão segue uma ordem:</p>","fidelityText":"Requisito: \"o sistema da biblioteca precisa permitir que um usuário pegue um livro emprestado.\" Nenhuma URL, verbo ou status code está escrito aí — é você quem decide, e a decisão segue uma ordem:"},{"id":"arquitetura-api-rest-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"1. Identifique o noun (o resource): \"loan\" -- nao \"emprestar\"\n2. Decida a hierarchy: loan belongs a um book especifico?\n   -> POST /books/{id}/loans  (loan as sub-resource do book)\n3. Ou o loan e um resource own que referencia book e user?\n   -> POST /loans  (com bookId e userId no body)\n4. Verb: create um loan e sempre POST (nao exists loan before\n   de a action happen -- nao ha \"recurso\" pra fazer PUT/PATCH nele)\n5. Status de success: 201 Created, com Location pointing pro new\n   loan (chapter 26)","fidelityText":"1. Identifique o substantivo (o recurso): \"emprestimo\" -- nao \"emprestar\" 2. Decida a hierarquia: emprestimo pertence a um livro especifico? -> POST /livros/{id}/emprestimos (emprestimo como sub-recurso do livro) 3. Ou o emprestimo e um recurso proprio que referencia livro e usuario? -> POST /emprestimos (com livroId e usuarioId no corpo) 4. Verbo: criar um emprestimo e sempre POST (nao existe emprestimo antes de a acao acontecer -- nao ha \"recurso\" pra fazer PUT/PATCH nele) 5. Status de sucesso: 201 Created, com Location apontando pro novo emprestimo (capitulo 26)","highlightedHtml":"<span class=\"com\">1. Identifique o substantivo (o recurso): \"emprestimo\" -- nao \"emprestar\"\n2. Decida a hierarquia: emprestimo pertence a um livro especifico?\n   -&gt; POST /livros/{id}/emprestimos  (emprestimo como sub-recurso do livro)\n3. Ou o emprestimo e um recurso proprio que referencia livro e usuario?\n   -&gt; POST /emprestimos  (com livroId e usuarioId no corpo)\n4. Verbo: criar um emprestimo e sempre POST (nao existe emprestimo antes\n   de a acao acontecer -- nao ha \"recurso\" pra fazer PUT/PATCH nele)\n5. Status de sucesso: 201 Created, com Location apontando pro novo\n   emprestimo (capitulo 26)</span>","caption":"Exemplo executável de arquitetura-api-rest.","explanation":["A ordem importa: primeiro decide o substantivo (o recurso), só depois o verbo.","O mesmo requisito admite mais de um desenho válido — a escolha é um trade-off, não uma regra fixa (ver tabela logo abaixo)."],"commonMistakes":["Modelar a ação como verbo na URL (ex.: /emprestarLivro) em vez de um recurso","Usar PATCH para esconder uma ação de negócio com efeitos colaterais atrás de uma mudança de campo"]},{"id":"arquitetura-api-rest-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Repare que a etapa 2 e a etapa 3 chegam em desenhos diferentes pro <strong>mesmo</strong> requisito — e ambos são REST válidos. A diferença é um trade-off real, não uma regra fixa:</p>","fidelityText":"Repare que a etapa 2 e a etapa 3 chegam em desenhos diferentes pro mesmo requisito — e ambos são REST válidos. A diferença é um trade-off real, não uma regra fixa:"},{"id":"arquitetura-api-rest-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Desenho</th><th>Quando faz sentido</th><th>Custo</th></tr>\n        <tr><td><code>POST /livros/{id}/emprestimos</code></td><td>Empréstimo só existe no contexto de um livro; a API nunca precisa listar \"todos os empréstimos\" sem filtrar por livro primeiro.</td><td>Se depois você precisar de <code>GET /emprestimos?usuario=42</code> (todos os empréstimos de um usuário, cruzando livros), o recurso já nasceu aninhado demais.</td></tr>\n        <tr><td><code>POST /emprestimos</code> (corpo com <code>livroId</code>)</td><td>Empréstimo é uma entidade de primeira classe, consultável por si só (histórico do usuário, relatórios).</td><td>A URL sozinha não mostra a relação com o livro — quem lê <code>POST /emprestimos</code> não sabe, sem olhar o corpo, que existe um livro envolvido.</td></tr>\n      </tbody></table>","fidelityText":"DesenhoQuando faz sentidoCusto POST /livros/{id}/emprestimosEmpréstimo só existe no contexto de um livro; a API nunca precisa listar \"todos os empréstimos\" sem filtrar por livro primeiro.Se depois você precisar de GET /emprestimos?usuario=42 (todos os empréstimos de um usuário, cruzando livros), o recurso já nasceu aninhado demais. POST /emprestimos (corpo com livroId)Empréstimo é uma entidade de primeira classe, consultável por si só (histórico do usuário, relatórios).A URL sozinha não mostra a relação com o livro — quem lê POST /emprestimos não sabe, sem olhar o corpo, que existe um livro envolvido."},{"id":"arquitetura-api-rest-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Uma alternativa tentadora e <strong>errada</strong> seria modelar como <code>PATCH /livros/{id}</code> alterando um campo <code>status: \"emprestado\"</code>. Isso quebra a semântica de PATCH (que descreve uma alteração parcial de estado do recurso <code>livro</code>, não uma ação de negócio com efeitos colaterais — decrementar estoque, criar um registro de auditoria, notificar o usuário) e esconde, atrás de uma atualização de campo, uma ação que merece ser um recurso próprio (o empréstimo). A regra geral: <strong>se a ação cria um registro novo com vida própria (data de início, prazo, devolução), ela é um recurso — não um campo alterado por PATCH.</strong></p>","fidelityText":"Uma alternativa tentadora e errada seria modelar como PATCH /livros/{id} alterando um campo status: \"emprestado\". Isso quebra a semântica de PATCH (que descreve uma alteração parcial de estado do recurso livro, não uma ação de negócio com efeitos colaterais — decrementar estoque, criar um registro de auditoria, notificar o usuário) e esconde, atrás de uma atualização de campo, uma ação que merece ser um recurso próprio (o empréstimo). A regra geral: se a ação cria um registro novo com vida própria (data de início, prazo, devolução), ela é um recurso — não um campo alterado por PATCH."},{"id":"arquitetura-api-rest-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>2. Onde cada lógica mora — um algoritmo, não um palpite</h2>","fidelityText":"2. Onde cada lógica mora — um algoritmo, não um palpite"},{"id":"arquitetura-api-rest-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Toda vez que você escrever um método novo, faça estas quatro perguntas nesta ordem. A primeira pergunta que responder \"sim\" decide a camada:</p>","fidelityText":"Toda vez que você escrever um método novo, faça estas quatro perguntas nesta ordem. A primeira pergunta que responder \"sim\" decide a camada:"},{"id":"arquitetura-api-rest-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"1. \"This and about the Format of the data recebido (nulo, size, type)?\"\n   -> DTO / Bean Validation (@NotBlank, @Positive -- capitulo 45)\n\n2. \"Isso e uma RULE DE BUSINESS (o book pode be borrowed agora?\n   o user already tem loans demais em open?)\"\n   -> Service\n\n3. \"This and about As The Data Are Stored Or BUSCADOS?\"\n   -> Repository\n\n4. \"Isso e sobre TRADUZIR a decision anterior to HTTP (qual status,\n   qual body, qual header)?\"\n   -> Controller","fidelityText":"1. \"Isso e sobre o FORMATO do dado recebido (nulo, tamanho, tipo)?\" -> DTO / Bean Validation (@NotBlank, @Positive -- capitulo 45) 2. \"Isso e uma REGRA DE NEGOCIO (o livro pode ser emprestado agora? o usuario ja tem emprestimos demais em aberto?)\" -> Service 3. \"Isso e sobre COMO OS DADOS SAO GUARDADOS OU BUSCADOS?\" -> Repository 4. \"Isso e sobre TRADUZIR a decisao anterior pra HTTP (qual status, qual corpo, qual header)?\" -> Controller","highlightedHtml":"<span class=\"com\">1. \"Isso e sobre o FORMATO do dado recebido (nulo, tamanho, tipo)?\"\n   -&gt; DTO / Bean Validation (@NotBlank, @Positive -- capitulo 45)\n\n2. \"Isso e uma REGRA DE NEGOCIO (o livro pode ser emprestado agora?\n   o usuario ja tem emprestimos demais em aberto?)\"\n   -&gt; Service\n\n3. \"Isso e sobre COMO OS DADOS SAO GUARDADOS OU BUSCADOS?\"\n   -&gt; Repository\n\n4. \"Isso e sobre TRADUZIR a decisao anterior pra HTTP (qual status,\n   qual corpo, qual header)?\"\n   -&gt; Controller</span>","caption":"Exemplo executável de arquitetura-api-rest.","explanation":["As quatro perguntas têm ordem: a primeira que responder \"sim\" decide a camada.","Regra de negócio nunca é a mesma coisa que formato de entrada, mesmo quando os dois parecem uma validação."],"commonMistakes":["Colocar regra de negócio dentro do Controller porque \"é só um if\"","Colocar regra de negócio dentro do Repository porque \"a query já está ali\""]},{"id":"arquitetura-api-rest-content-13","type":"html","authorship":"legacy-preserved","html":"<p>O erro mais comum de quem começa é colocar a pergunta 2 dentro do Controller (porque \"já tá ali mesmo, é só um <code>if</code>\") ou dentro do Repository (porque \"a query já está ali\"). As duas fogem do algoritmo: regra de negócio não é sobre formato de entrada nem sobre acesso a dado — é sobre <em>decisão</em>, e decisão de negócio é trabalho do Service, ponto.</p>","fidelityText":"O erro mais comum de quem começa é colocar a pergunta 2 dentro do Controller (porque \"já tá ali mesmo, é só um if\") ou dentro do Repository (porque \"a query já está ali\"). As duas fogem do algoritmo: regra de negócio não é sobre formato de entrada nem sobre acesso a dado — é sobre decisão, e decisão de negócio é trabalho do Service, ponto."},{"id":"arquitetura-api-rest-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>3. Convenção de nome de método por camada</h2>","fidelityText":"3. Convenção de nome de método por camada"},{"id":"arquitetura-api-rest-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Repare que o mesmo empréstimo passa por três nomes de método diferentes ao atravessar as camadas — isso não é inconsistência, é o nome mudando pra refletir a <em>responsabilidade</em> de cada camada:</p>","fidelityText":"Repare que o mesmo empréstimo passa por três nomes de método diferentes ao atravessar as camadas — isso não é inconsistência, é o nome mudando pra refletir a responsabilidade de cada camada:"},{"id":"arquitetura-api-rest-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"@RestController\npublic class LoanController {\n    @PostMapping(\"/books/{id}/loans\")\n    public ResponseEntity<LoanDTO> create(@PathVariable Long id, @RequestBody NewLoanDTO dto) {\n        LoanDTO created = service.borrow(id, dto.userId()); // traduz HTTP -> chamada de negocio\n        return ResponseEntity.status(HttpStatus.CREATED).body(created);\n    }\n}\n\n@Service\npublic class LoanService {\n    public LoanDTO borrow(Long bookId, Long userId) {\n        if (!repository.bookAvailable(bookId)) { // regra de negocio: decisao, nao formato\n            throw new BookUnavailableException(bookId);\n        }\n        return mapper.toDTO(repository.registerLoan(bookId, userId));\n    }\n}\n\npublic interface LoanRepository extends JpaRepository<Loan, Long> {\n    boolean existsByBookIdAndDevolvidoFalse(Long bookId); // convencao Spring Data, capitulo 45\n}","fidelityText":"@RestController public class EmprestimoController { @PostMapping(\"/livros/{id}/emprestimos\") public ResponseEntity<EmprestimoDTO> criar(@PathVariable Long id, @RequestBody NovoEmprestimoDTO dto) { EmprestimoDTO criado = servico.emprestar(id, dto.usuarioId()); // traduz HTTP -> chamada de negocio return ResponseEntity.status(HttpStatus.CREATED).body(criado); } } @Service public class EmprestimoServico { public EmprestimoDTO emprestar(Long livroId, Long usuarioId) { if (!repositorio.livroDisponivel(livroId)) { // regra de negocio: decisao, nao formato throw new LivroIndisponivelException(livroId); } return mapper.toDTO(repositorio.registrarEmprestimo(livroId, usuarioId)); } } public interface EmprestimoRepository extends JpaRepository<Emprestimo, Long> { boolean existsByLivroIdAndDevolvidoFalse(Long livroId); // convencao Spring Data, capitulo 45 }","highlightedHtml":"<span class=\"annotation\">@RestController</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">LoanController</span> {\n    <span class=\"annotation\">@PostMapping</span>(<span class=\"str\">\"/books/{id}/loans\"</span>)\n    <span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"cls\">LoanDTO</span>&gt; <span class=\"fn\">create</span>(<span class=\"annotation\">@PathVariable</span> <span class=\"kw\">Long</span> id, <span class=\"annotation\">@RequestBody</span> <span class=\"cls\">NewLoanDTO</span> dto) {\n        <span class=\"cls\">LoanDTO</span> created = service.borrow(id, dto.userId()); <span class=\"com\">// traduz HTTP -&gt; chamada de negocio</span>\n        <span class=\"kw\">return</span> ResponseEntity.status(HttpStatus.CREATED).body(created);\n    }\n}\n\n<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">LoanService</span> {\n    <span class=\"kw\">public</span> <span class=\"cls\">LoanDTO</span> <span class=\"fn\">borrow</span>(<span class=\"kw\">Long</span> bookId, <span class=\"kw\">Long</span> userId) {\n        <span class=\"kw\">if</span> (!repository.bookAvailable(bookId)) { <span class=\"com\">// regra de negocio: decisao, nao formato</span>\n            <span class=\"kw\">throw new</span> <span class=\"cls\">BookUnavailableException</span>(bookId);\n        }\n        <span class=\"kw\">return</span> mapper.toDTO(repository.registerLoan(bookId, userId));\n    }\n}\n\n<span class=\"annotation\">public interface</span> <span class=\"cls\">LoanRepository</span> <span class=\"kw\">extends</span> JpaRepository&lt;<span class=\"cls\">Loan</span>, <span class=\"kw\">Long</span>&gt; {\n    <span class=\"kw\">boolean</span> <span class=\"fn\">existsByBookIdAndDevolvidoFalse</span>(<span class=\"kw\">Long</span> bookId); <span class=\"com\">// convencao Spring Data, capitulo 45</span>\n}","caption":"Exemplo executável de arquitetura-api-rest.","explanation":["O mesmo empréstimo muda de nome em cada camada: criar (Controller) → emprestar (Service) → existsByLivroIdAndDevolvidoFalse (Repository).","O nome no Service descreve o domínio; o nome no Repository segue a convenção do Spring Data (capítulo 45)."],"commonMistakes":["Nomear o método do Service igual ao do Controller, perdendo a informação de que é uma ação de domínio","Nomear o método do Repository como se fosse uma intenção de negócio em vez de uma consulta"]},{"id":"arquitetura-api-rest-content-17","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Camada</th><th>Convenção de nome</th><th>Por quê</th></tr>\n        <tr><td>Controller</td><td>Verbo de ação HTTP-mapeado: <code>criar</code>/<code>buscarPorId</code>/<code>atualizar</code>/<code>remover</code></td><td>O nome espelha o verbo HTTP que o método atende — quem lê a classe já sabe qual rota cada método serve, sem abrir a anotação.</td></tr>\n        <tr><td>Service</td><td>Verbo de processo de negócio: <code>emprestar</code>/<code>processar</code>/<code>validar</code>/<code>aplicar</code></td><td>O nome descreve <em>o que acontece no domínio</em>, não a operação HTTP que o disparou — o mesmo <code>emprestar</code> poderia ser chamado por um job agendado, não só por um controller.</td></tr>\n        <tr><td>Repository</td><td>Convenção Spring Data: <code>findBy...</code>/<code>existsBy...</code>/<code>countBy...</code></td><td>O nome descreve a <em>consulta</em>, não a intenção de negócio — é assim que o Spring Data gera a implementação sozinho a partir do nome do método (capítulo 45).</td></tr>\n      </tbody></table>","fidelityText":"CamadaConvenção de nomePor quê ControllerVerbo de ação HTTP-mapeado: criar/buscarPorId/atualizar/removerO nome espelha o verbo HTTP que o método atende — quem lê a classe já sabe qual rota cada método serve, sem abrir a anotação. ServiceVerbo de processo de negócio: emprestar/processar/validar/aplicarO nome descreve o que acontece no domínio, não a operação HTTP que o disparou — o mesmo emprestar poderia ser chamado por um job agendado, não só por um controller. RepositoryConvenção Spring Data: findBy.../existsBy.../countBy...O nome descreve a consulta, não a intenção de negócio — é assim que o Spring Data gera a implementação sozinho a partir do nome do método (capítulo 45)."},{"id":"arquitetura-api-rest-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Se você nomear o método do Service igual ao do Controller (os dois se chamando <code>criar</code>, por exemplo), a primeira vítima é a legibilidade: ao ler só <code>servico.criar(...)</code> em outro lugar do código, você perde a informação de que aquilo é uma ação de negócio (emprestar) e não uma operação genérica de persistência. Nomear por camada é documentação que não fica desatualizada, porque está no próprio código.</div>","fidelityText":"Se você nomear o método do Service igual ao do Controller (os dois se chamando criar, por exemplo), a primeira vítima é a legibilidade: ao ler só servico.criar(...) em outro lugar do código, você perde a informação de que aquilo é uma ação de negócio (emprestar) e não uma operação genérica de persistência. Nomear por camada é documentação que não fica desatualizada, porque está no próprio código."},{"id":"arquitetura-api-rest-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>4. Estrutura de pacotes</h2>","fidelityText":"4. Estrutura de pacotes"},{"id":"arquitetura-api-rest-content-20","type":"html","authorship":"legacy-preserved","html":"<p>A convenção de nome de método anda junto com a convenção de onde o arquivo mora. Para o recurso de empréstimo acima, a árvore de pacotes fica:</p>","fidelityText":"A convenção de nome de método anda junto com a convenção de onde o arquivo mora. Para o recurso de empréstimo acima, a árvore de pacotes fica:"},{"id":"arquitetura-api-rest-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"com.library.loan\n├── LoanController.java     -> translates HTTP, nao conhece rule de business\n├── LoanService.java        -> rule de business, nao conhece HTTP nem SQL\n├── LoanRepository.java     -> access a data, so interface (Spring Data gera o resto)\n├── LoanMapper.java         -> entity <-> DTO (chapter 47)\n├── dto\n│   ├── NewLoanDTO.java    -> o que o customer pode send (request)\n│   └── LoanDTO.java        -> o que a API promete return (response)\n└── exception\n    └── BookUnavailableException.java","fidelityText":"com.biblioteca.emprestimo ├── EmprestimoController.java -> traduz HTTP, nao conhece regra de negocio ├── EmprestimoServico.java -> regra de negocio, nao conhece HTTP nem SQL ├── EmprestimoRepository.java -> acesso a dado, so interface (Spring Data gera o resto) ├── EmprestimoMapper.java -> entity <-> DTO (capitulo 47) ├── dto │ ├── NovoEmprestimoDTO.java -> o que o cliente pode enviar (request) │ └── EmprestimoDTO.java -> o que a API promete devolver (response) └── exception └── LivroIndisponivelException.java","highlightedHtml":"<span class=\"com\">com.biblioteca.emprestimo\n├── EmprestimoController.java     -&gt; traduz HTTP, nao conhece regra de negocio\n├── EmprestimoServico.java        -&gt; regra de negocio, nao conhece HTTP nem SQL\n├── EmprestimoRepository.java     -&gt; acesso a dado, so interface (Spring Data gera o resto)\n├── EmprestimoMapper.java         -&gt; entity &lt;-&gt; DTO (capitulo 47)\n├── dto\n│   ├── NovoEmprestimoDTO.java    -&gt; o que o cliente pode enviar (request)\n│   └── EmprestimoDTO.java        -&gt; o que a API promete devolver (response)\n└── exception\n    └── LivroIndisponivelException.java</span>","caption":"Exemplo executável de arquitetura-api-rest.","explanation":["Cada arquivo mora no pacote que reflete sua responsabilidade, não em uma pasta única \"models\" ou \"utils\".","DTOs de request e response ficam no mesmo pacote dto/, mas são classes diferentes — nunca a mesma classe reaproveitada dos dois lados."],"commonMistakes":["Colocar Controller, Service e Repository todos soltos no pacote raiz","Reaproveitar a mesma classe DTO para request e response"]},{"id":"arquitetura-api-rest-content-22","type":"html","authorship":"legacy-preserved","html":"<p>Separar <code>dto</code> em pacote próprio (em vez de misturar com as classes de domínio) deixa explícito, só pela estrutura de pastas, onde fica o contrato público da API — a mesma fronteira que o capítulo 47 (DTO Mapping) vai aprofundar tecnicamente.</p>","fidelityText":"Separar dto em pacote próprio (em vez de misturar com as classes de domínio) deixa explícito, só pela estrutura de pastas, onde fica o contrato público da API — a mesma fronteira que o capítulo 47 (DTO Mapping) vai aprofundar tecnicamente."},{"id":"arquitetura-api-rest-content-23","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense nas quatro camadas como uma recepção de hotel (capítulo seguinte formaliza essa analogia): o <strong>Controller</strong> é o recepcionista — atende o hóspede, entende o pedido, nunca decide sozinho se o quarto pode ser trocado. O <strong>Service</strong> é o gerente — decide se a troca de quarto é possível, seguindo as regras do hotel. O <strong>Repository</strong> é o sistema de reservas — só sabe consultar e gravar, não decide nada. Errar a camada é como pedir pro recepcionista decidir política de overbooking sozinho: funciona às vezes, mas quando dá errado, ninguém mais sabe onde essa decisão foi tomada.</div>","fidelityText":"Pense nas quatro camadas como uma recepção de hotel (capítulo seguinte formaliza essa analogia): o Controller é o recepcionista — atende o hóspede, entende o pedido, nunca decide sozinho se o quarto pode ser trocado. O Service é o gerente — decide se a troca de quarto é possível, seguindo as regras do hotel. O Repository é o sistema de reservas — só sabe consultar e gravar, não decide nada. Errar a camada é como pedir pro recepcionista decidir política de overbooking sozinho: funciona às vezes, mas quando dá errado, ninguém mais sabe onde essa decisão foi tomada."},{"id":"arquitetura-api-rest-content-24","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Da próxima vez que for escrever um método novo, faça o caminho inverso deste capítulo: primeiro escreva a frase do requisito em português, depois aplique as quatro perguntas da seção 2 uma por uma, só então escreva código. Nos primeiros capítulos seguintes (Spring MVC, DTO Mapping, Validação), você vai reconhecer esse mesmo processo de decisão sendo aplicado — a diferença é que lá a resposta já vem pronta; aqui você pratica chegar nela sozinho.</div>","fidelityText":"Da próxima vez que for escrever um método novo, faça o caminho inverso deste capítulo: primeiro escreva a frase do requisito em português, depois aplique as quatro perguntas da seção 2 uma por uma, só então escreva código. Nos primeiros capítulos seguintes (Spring MVC, DTO Mapping, Validação), você vai reconhecer esse mesmo processo de decisão sendo aplicado — a diferença é que lá a resposta já vem pronta; aqui você pratica chegar nela sozinho."},{"id":"arquitetura-api-rest-exercise-25","type":"exercise","authorship":"legacy-preserved","title":"Exercício 44A.1 — Do requisito ao desenho","prompt":"Requisito: \"o sistema precisa permitir que um usuário avalie um livro que já devolveu, com nota de 1 a 5 e um comentário opcional.\" Sem escrever nenhum código Java, decida: (a) o recurso e sua URL; (b) o verbo HTTP e o status de sucesso; (c) para cada uma das quatro perguntas da seção 2, qual camada resolve o quê nesse caso específico; (d) o nome do método em cada camada (Controller/Service/Repository); (e) a árvore de pacotes resultante.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 44A.1 — Do requisito ao desenhomédio Requisito: \"o sistema precisa permitir que um usuário avalie um livro que já devolveu, com nota de 1 a 5 e um comentário opcional.\" Sem escrever nenhum código Java, decida: (a) o recurso e sua URL; (b) o verbo HTTP e o status de sucesso; (c) para cada uma das quatro perguntas da seção 2, qual camada resolve o quê nesse caso específico; (d) o nome do método em cada camada (Controller/Service/Repository); (e) a árvore de pacotes resultante. Ver solução (a) POST /livros/{id}/avaliacoes — avaliação é um sub-recurso do livro, com vida própria (nota, comentário, data). (b) POST, 201 Created com Location. (c) Formato (nota entre 1-5, comentário opcional) → DTO/Bean Validation; regra de negócio (\"usuário só pode avaliar livro que já devolveu\") → Service, consultando o histórico de empréstimos antes de aceitar; acesso a dado (gravar a avaliação) → Repository; tradução pra 201/Location → Controller. (d) AvaliacaoController.criar → AvaliacaoServico.avaliar → AvaliacaoRepository.existsByUsuarioIdAndLivroIdAndDevolvidoTrue (ou equivalente). (e) Mesmo padrão do exemplo desta seção: com.biblioteca.avaliacao com Controller/Servico/Repository/Mapper/dto/exception.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 44A.1 — Do requisito ao desenho</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Requisito: \"o sistema precisa permitir que um usuário avalie um livro que já devolveu, com nota de 1 a 5 e um comentário opcional.\" Sem escrever nenhum código Java, decida: (a) o recurso e sua URL; (b) o verbo HTTP e o status de sucesso; (c) para cada uma das quatro perguntas da seção 2, qual camada resolve o quê nesse caso específico; (d) o nome do método em cada camada (Controller/Service/Repository); (e) a árvore de pacotes resultante.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>(a) <code>POST /livros/{id}/avaliacoes</code> — avaliação é um sub-recurso do livro, com vida própria (nota, comentário, data). (b) <code>POST</code>, <code>201 Created</code> com <code>Location</code>. (c) Formato (nota entre 1-5, comentário opcional) → DTO/Bean Validation; regra de negócio (\"usuário só pode avaliar livro que já devolveu\") → Service, consultando o histórico de empréstimos antes de aceitar; acesso a dado (gravar a avaliação) → Repository; tradução pra 201/Location → Controller. (d) <code>AvaliacaoController.criar</code> → <code>AvaliacaoServico.avaliar</code> → <code>AvaliacaoRepository.existsByUsuarioIdAndLivroIdAndDevolvidoTrue</code> (ou equivalente). (e) Mesmo padrão do exemplo desta seção: <code>com.biblioteca.avaliacao</code> com <code>Controller</code>/<code>Servico</code>/<code>Repository</code>/<code>Mapper</code>/<code>dto</code>/<code>exception</code>.</p>\n        </div>\n      </div>"},{"id":"arquitetura-api-rest-quiz-1","type":"quiz","authorship":"authored","conceptId":"requisito-para-recurso-rest","prompt":"Requisito: \"permitir que um cliente cancele um pedido já feito.\" Qual desenho de API melhor evita esconder uma ação de negócio atrás de uma atualização de campo genérica?","options":[{"id":"arq-1-a","label":"POST /pedidos/{id}/cancelamentos — cancelamento como recurso próprio, com data e motivo.","correct":true,"explanation":"Cancelar um pedido tem efeitos colaterais (estorno, notificação, auditoria) — modelar como recurso deixa isso explícito, em vez de escondido atrás de um PATCH de status."},{"id":"arq-1-b","label":"PATCH /pedidos/{id} com {\"status\": \"cancelado\"} — reaproveita o endpoint de atualização que já existe.","correct":false,"explanation":"Esconde uma ação de negócio com efeitos colaterais atrás de uma atualização de campo genérica — o mesmo erro do exemplo de empréstimo do capítulo."},{"id":"arq-1-c","label":"GET /pedidos/{id}/cancelar — usa GET porque é mais simples de testar no navegador.","correct":false,"explanation":"GET nunca deve ter efeito colateral (idempotência/segurança do método, capítulo 26) — cancelar um pedido muda estado, então nunca pode ser GET."}]},{"id":"arquitetura-api-rest-quiz-2","type":"quiz","authorship":"authored","conceptId":"algoritmo-de-decisao-de-camada","prompt":"Uma regra diz \"um usuário não pode ter mais de 3 empréstimos em aberto ao mesmo tempo\". Em qual camada essa checagem deve morar?","options":[{"id":"arq-2-a","label":"Service — é uma decisão de negócio, não formato de entrada nem acesso a dado.","correct":true,"explanation":"A pergunta 2 do algoritmo (\"é regra de negócio?\") responde sim primeiro — a checagem envolve consultar estado e decidir, não só validar formato."},{"id":"arq-2-b","label":"DTO, com uma anotação de Bean Validation customizada.","correct":false,"explanation":"Bean Validation valida o FORMATO do dado recebido isoladamente (ex.: um número positivo) — não consegue expressar uma regra que depende de consultar o estado atual de outros empréstimos no banco."},{"id":"arq-2-c","label":"Controller, verificando antes de chamar o Service.","correct":false,"explanation":"Duplicaria a regra em todo controller que puder disparar essa ação, e o Controller não deveria conhecer regra de negócio — exatamente o erro que a seção 2 nomeia."}]},{"id":"arquitetura-api-rest-quiz-3","type":"quiz","authorship":"authored","conceptId":"convencao-de-nome-por-camada","prompt":"Por que o método do Service se chama emprestar(...) em vez de criar(...), já que o Controller chama esse método a partir de um criar(...) que atende POST?","options":[{"id":"arq-3-a","label":"Porque o nome do Service descreve o que acontece no domínio, não a operação HTTP que disparou a chamada.","correct":true,"explanation":"emprestar() poderia ser chamado por um job agendado ou outro fluxo — o nome não deveria depender de ter vindo de um POST."},{"id":"arq-3-b","label":"Porque nomes de método no Service não podem repetir nomes usados no Controller.","correct":false,"explanation":"Não há essa restrição técnica — a razão é de clareza semântica, não de uma regra de nomes proibidos."},{"id":"arq-3-c","label":"Porque o Spring exige nomes diferentes entre camadas para o DI funcionar.","correct":false,"explanation":"Injeção de dependência (capítulo 22) funciona por tipo/qualifier, nunca por nome de método coincidir ou não entre classes."}]},{"id":"arquitetura-api-rest-quiz-4","type":"quiz","authorship":"authored","conceptId":"estrutura-de-pacotes","prompt":"Por que separar as classes de DTO (request/response) em um pacote próprio (dto/), em vez de deixá-las junto das classes de domínio?","options":[{"id":"arq-4-a","label":"Porque a separação de pastas torna visível, sem ler código, onde está o contrato público da API — a mesma fronteira que o DTO Mapping formaliza.","correct":true,"explanation":"Estrutura de pacotes é documentação que não fica desatualizada, porque está no próprio código — quem abre o projeto já vê a fronteira."},{"id":"arq-4-b","label":"Porque o Spring só encontra DTOs automaticamente se estiverem em um pacote chamado dto.","correct":false,"explanation":"DTOs não são componentes gerenciados pelo Spring (não têm @Component/@Service) — não existe scan automático que dependa do nome do pacote."},{"id":"arq-4-c","label":"Porque classes de domínio e DTOs não podem coexistir no mesmo arquivo .java.","correct":false,"explanation":"Poderiam tecnicamente — a separação é uma convenção de clareza de fronteira, não uma restrição da linguagem."}]}],"resources":[{"id":"rfc9110-http-semantics-arq-api","type":"reference","title":"RFC 9110: HTTP Semantics","url":"https://www.rfc-editor.org/rfc/rfc9110.html","reinforces":"Define semântica de métodos HTTP e status codes usada para decidir verbo/status ao desenhar um recurso novo.","language":"en","publisher":"IETF","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-23","auditStatus":"approved"},{"id":"spring-mvc-ann-methods-arq-api","type":"official-docs","title":"Spring MVC — Handler Methods","url":"https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods.html","reinforces":"Referência oficial de como o Spring mapeia métodos de controller — usada no capítulo seguinte, mas relevante para decidir a forma do endpoint aqui.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-23","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The architecture api rest component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to architecture api rest. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible architecture api rest failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"1. Identifique o noun (o resource): \"loan\" -- nao \"emprestar\"","instruction":"The architecture api rest component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible architecture api rest failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-23"},"editorialReview":{"requiredTopics":["requisito-para-recurso-rest","algoritmo-de-decisao-de-camada","convencao-de-nome-por-camada","estrutura-de-pacotes"],"evidenceBlocks":{"requisito-para-recurso-rest":["arquitetura-api-rest-code-6","arquitetura-api-rest-quiz-1"],"algoritmo-de-decisao-de-camada":["arquitetura-api-rest-code-12","arquitetura-api-rest-quiz-2"],"convencao-de-nome-por-camada":["arquitetura-api-rest-code-16","arquitetura-api-rest-quiz-3"],"estrutura-de-pacotes":["arquitetura-api-rest-code-21","arquitetura-api-rest-quiz-4"]},"primarySources":["RFC 9110: HTTP Semantics -- https://www.rfc-editor.org/rfc/rfc9110.html","Spring Framework Reference -- Web MVC -- https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods.html"],"factualReviewedAt":"2026-08-23","pedagogicalReviewedAt":"2026-08-23","openIssues":[]}},{"id":"spring-mvc","moduleId":"spring-api","order":5,"title":"Spring Web / MVC — controllers REST","summary":"Aqui o pseudo-REST desenhado no capítulo 26 vira código de verdade. Um controller Spring é uma classe cujos métodos são mapeados para combinações de verbo HTTP + caminho de URL — exatamente como você previu naquele capítulo.","objectives":["Mapear requisições HTTP para métodos de controller","Separar binding, validação, regra e resposta","Escolher status e headers de forma explícita","Evitar controller como camada de domínio"],"whyItExists":"O aluno já conhece HTTP, JSON e DI. Spring MVC entra para adaptar mensagens HTTP a casos de uso, mostrando o que o framework automatiza e o que continua sendo decisão de contrato.","prerequisiteChapterIds":["arquitetura-api-rest","spring-boot-fundamentos","http-wire-contract","java-httpclient-json"],"conceptIds":["o-que-acontece-antes-do-seu-controller-dispatcherservlet","tratamento-de-excecao-quem-decide-a-resposta-de-erro","multipart-recebendo-arquivos","testando-de-verdade-webmvctest-e-mockmvc"],"introducedConceptIds":["mvc-controller-binding","mvc-response-status-contract"],"usedConceptIds":["http-mensagem-recurso","json-formato-contrato","spring-component-scan-bean"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"spring-mvc-intuition","type":"intuition","authorship":"authored","title":"Controller é adaptador HTTP, não dono da regra","body":"O controller recebe método, URI, headers e corpo; transforma isso em comando/consulta para o caso de uso; depois traduz o resultado para status, headers e corpo. Ele deve ser fino o bastante para trocar transporte sem reescrever domínio.","analogyLimit":"Adaptador não significa pass-through burro: ele ainda decide contrato HTTP, mas não regra central de negócio."},{"id":"spring-mvc-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~3h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-core\">43 · Spring Core</a>, <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; APIs REST</a>, <a class=\"prereq-tag\" href=\"#json\">25 · JSON &amp; serialização</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~3h30 de estudo + prática Pré-requisitos: 43 · Spring Core, 26 · HTTP & APIs REST, 25 · JSON & serialização"},{"id":"spring-mvc-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Aqui o pseudo-REST desenhado no capítulo 26 vira código de verdade. Um <strong>controller</strong> Spring é uma classe cujos métodos são mapeados para combinações de verbo HTTP + caminho de URL — exatamente como você previu naquele capítulo.</p>","fidelityText":"Aqui o pseudo-REST desenhado no capítulo 26 vira código de verdade. Um controller Spring é uma classe cujos métodos são mapeados para combinações de verbo HTTP + caminho de URL — exatamente como você previu naquele capítulo."},{"id":"spring-mvc-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"@RestController // @Controller + @ResponseBody: retorno vira JSON automaticamente (via Jackson, capítulo 25)\n@RequestMapping(\"/books\")\npublic class BookController {\n\n    private final BookService service;\n\n    public BookController(BookService service) { this.service = service; } // injeção, capítulo 22\n\n    @GetMapping\n    public List<BookDTO> listarAll() {\n        return service.listarAll();\n    }\n\n    @GetMapping(\"/{id}\")\n    public ResponseEntity<BookDTO> findById(@PathVariable Long id) {\n        return service.findById(id)\n            .map(ResponseEntity::ok)                    // 200 OK com o corpo\n            .orElseGet(() -> ResponseEntity.notFound().build()); // 404, capítulo 26\n    }\n\n    @PostMapping\n    public ResponseEntity<BookDTO> create(@RequestBody BookDTO dto) {\n        BookDTO created = service.save(dto);\n        return ResponseEntity.status(HttpStatus.CREATED).body(created); // 201\n    }\n\n    @DeleteMapping(\"/{id}\")\n    public ResponseEntity<Void> delete(@PathVariable Long id) {\n        service.delete(id);\n        return ResponseEntity.noContent().build(); // 204\n    }\n}","fidelityText":"@RestController // @Controller + @ResponseBody: retorno vira JSON automaticamente (via Jackson, capítulo 25) @RequestMapping(\"/livros\") public class LivroController { private final LivroServico servico; public LivroController(LivroServico servico) { this.servico = servico; } // injeção, capítulo 22 @GetMapping public List<LivroDTO> listarTodos() { return servico.listarTodos(); } @GetMapping(\"/{id}\") public ResponseEntity<LivroDTO> buscarPorId(@PathVariable Long id) { return servico.buscarPorId(id) .map(ResponseEntity::ok) // 200 OK com o corpo .orElseGet(() -> ResponseEntity.notFound().build()); // 404, capítulo 26 } @PostMapping public ResponseEntity<LivroDTO> criar(@RequestBody LivroDTO dto) { LivroDTO criado = servico.salvar(dto); return ResponseEntity.status(HttpStatus.CREATED).body(criado); // 201 } @DeleteMapping(\"/{id}\") public ResponseEntity<Void> deletar(@PathVariable Long id) { servico.deletar(id); return ResponseEntity.noContent().build(); // 204 } }","highlightedHtml":"<span class=\"annotation\">@RestController</span> <span class=\"com\">// @Controller + @ResponseBody: retorno vira JSON automaticamente (via Jackson, capítulo 25)</span>\n<span class=\"annotation\">@RequestMapping</span>(<span class=\"str\">\"/books\"</span>)\n<span class=\"kw\">public class</span> <span class=\"cls\">BookController</span> {\n\n    <span class=\"kw\">private final</span> <span class=\"cls\">BookService</span> service;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">BookController</span>(<span class=\"cls\">BookService</span> service) { <span class=\"kw\">this</span>.service = service; } <span class=\"com\">// injeção, capítulo 22</span>\n\n    <span class=\"annotation\">@GetMapping</span>\n    <span class=\"kw\">public</span> List&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">listarAll</span>() {\n        <span class=\"kw\">return</span> service.listarAll();\n    }\n\n    <span class=\"annotation\">@GetMapping</span>(<span class=\"str\">\"/{id}\"</span>)\n    <span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">findById</span>(<span class=\"annotation\">@PathVariable</span> <span class=\"kw\">Long</span> id) {\n        <span class=\"kw\">return</span> service.findById(id)\n            .map(ResponseEntity::ok)                    <span class=\"com\">// 200 OK com o corpo</span>\n            .orElseGet(() -&gt; ResponseEntity.notFound().build()); <span class=\"com\">// 404, capítulo 26</span>\n    }\n\n    <span class=\"annotation\">@PostMapping</span>\n    <span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">create</span>(<span class=\"annotation\">@RequestBody</span> <span class=\"cls\">BookDTO</span> dto) {\n        <span class=\"cls\">BookDTO</span> created = service.save(dto);\n        <span class=\"kw\">return</span> ResponseEntity.status(HttpStatus.CREATED).body(created); <span class=\"com\">// 201</span>\n    }\n\n    <span class=\"annotation\">@DeleteMapping</span>(<span class=\"str\">\"/{id}\"</span>)\n    <span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"kw\">Void</span>&gt; <span class=\"fn\">delete</span>(<span class=\"annotation\">@PathVariable</span> <span class=\"kw\">Long</span> id) {\n        service.delete(id);\n        <span class=\"kw\">return</span> ResponseEntity.noContent().build(); <span class=\"com\">// 204</span>\n    }\n}","caption":"Exemplo executável de spring-mvc.","explanation":["@RestController combina controller com serialização de resposta.","Anotações de mapping conectam método/URI a um método Java, mas não validam regra de negócio sozinhas."],"commonMistakes":["Colocar regra transacional no controller","Retornar entidade JPA diretamente como contrato público"]},{"id":"spring-mvc-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Anotação</th><th>Equivale a</th></tr>\n        <tr><td><code>@GetMapping</code></td><td><code>GET</code> (capítulo 26)</td></tr>\n        <tr><td><code>@PostMapping</code></td><td><code>POST</code></td></tr>\n        <tr><td><code>@PutMapping</code></td><td><code>PUT</code></td></tr>\n        <tr><td><code>@DeleteMapping</code></td><td><code>DELETE</code></td></tr>\n        <tr><td><code>@PathVariable</code></td><td>Captura um segmento da URL (<code>/livros/{id}</code>)</td></tr>\n        <tr><td><code>@RequestParam</code></td><td>Captura query string (<code>?titulo=Duna</code>)</td></tr>\n        <tr><td><code>@RequestBody</code></td><td>Desserializa o corpo JSON da requisição em objeto Java</td></tr>\n      </tbody></table>","fidelityText":"AnotaçãoEquivale a @GetMappingGET (capítulo 26) @PostMappingPOST @PutMappingPUT @DeleteMappingDELETE @PathVariableCaptura um segmento da URL (/livros/{id}) @RequestParamCaptura query string (?titulo=Duna) @RequestBodyDesserializa o corpo JSON da requisição em objeto Java"},{"id":"spring-mvc-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um controller é o \"recepcionista\" da sua aplicação — ele não sabe cozinhar (regra de negócio) nem sabe onde os ingredientes estão guardados (banco de dados). Ele só recebe o pedido do cliente HTTP, entende o que foi pedido (via <code>@PathVariable</code>/<code>@RequestBody</code>), repassa para quem sabe fazer (o <code>@Service</code>) e devolve a resposta formatada certinha. Se seu controller tem lógica de negócio complexa dentro, ele deixou de ser recepcionista e virou o cozinheiro — sinal de que essa lógica deveria estar no Service (mesma lição de SRP do capítulo 16).</div>","fidelityText":"Um controller é o \"recepcionista\" da sua aplicação — ele não sabe cozinhar (regra de negócio) nem sabe onde os ingredientes estão guardados (banco de dados). Ele só recebe o pedido do cliente HTTP, entende o que foi pedido (via @PathVariable/@RequestBody), repassa para quem sabe fazer (o @Service) e devolve a resposta formatada certinha. Se seu controller tem lógica de negócio complexa dentro, ele deixou de ser recepcionista e virou o cozinheiro — sinal de que essa lógica deveria estar no Service (mesma lição de SRP do capítulo 16)."},{"id":"spring-mvc-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>O que acontece antes do seu controller: DispatcherServlet</h2>","fidelityText":"O que acontece antes do seu controller: DispatcherServlet"},{"id":"spring-mvc-content-7","type":"html","authorship":"legacy-preserved","html":"<p>O método do controller acima não é chamado diretamente pelo servidor HTTP — várias peças nomeadas decidem, em sequência, que aquele método específico deve rodar e como transformar o retorno dele em resposta HTTP:</p>","fidelityText":"O método do controller acima não é chamado diretamente pelo servidor HTTP — várias peças nomeadas decidem, em sequência, que aquele método específico deve rodar e como transformar o retorno dele em resposta HTTP:"},{"id":"spring-mvc-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"1. Request HTTP arrives no servlet container embedded (Tomcat, por padrao)\n2. Passes pela chain de Filter do servlet (ex.: filters do Spring Security, se present)\n3. Arrives ao DispatcherServlet -- o \"front controller\" unico que o Spring Boot\n   registers para handle all as requests da application\n4. DispatcherServlet asks a um HandlerMapping qual method de controller\n   combina com o verb HTTP + path + headers from this request\n5. DispatcherServlet delivers essa decision a um HandlerAdapter, que knows\n   invoke that method especifico: resolve @PathVariable, @RequestParam,\n   @RequestBody (via HttpMessageConverter) e assembles os argumentos do method\n6. O method do controller roda e devolve um object Java (ou ResponseEntity)\n7. HandlerAdapter chooses, por content negotiation (header Accept), um\n   HttpMessageConverter para serialize o return -- o do Jackson e o\n   padrao para JSON, mas e so um entre several converters possible\n8. Se uma exception for thrown em qualquer point, ela goes para um\n   HandlerExceptionResolver -- e aqui que @ControllerAdvice/@ExceptionHandler\n   enter (chapter 45, Validation e handling de errors)\n9. A response volta pela chain de Filter ate o customer","fidelityText":"1. Requisição HTTP chega no servlet container embarcado (Tomcat, por padrao) 2. Passa pela cadeia de Filter do servlet (ex.: filtros do Spring Security, se presentes) 3. Chega ao DispatcherServlet -- o \"front controller\" unico que o Spring Boot registra para tratar todas as requisicoes da aplicacao 4. DispatcherServlet pergunta a um HandlerMapping qual metodo de controller combina com o verbo HTTP + caminho + cabecalhos desta requisicao 5. DispatcherServlet entrega essa decisao a um HandlerAdapter, que sabe invocar aquele metodo especifico: resolve @PathVariable, @RequestParam, @RequestBody (via HttpMessageConverter) e monta os argumentos do metodo 6. O metodo do controller roda e devolve um objeto Java (ou ResponseEntity) 7. HandlerAdapter escolhe, por content negotiation (cabecalho Accept), um HttpMessageConverter para serializar o retorno -- o do Jackson e o padrao para JSON, mas e so um entre varios conversores possiveis 8. Se uma excecao for lancada em qualquer ponto, ela vai para um HandlerExceptionResolver -- e aqui que @ControllerAdvice/@ExceptionHandler entram (capitulo 45, Validacao e tratamento de erros) 9. A resposta volta pela cadeia de Filter ate o cliente","highlightedHtml":"<span class=\"com\">1. Requisição HTTP chega no servlet container embarcado (Tomcat, por padrao)\n2. Passa pela cadeia de Filter do servlet (ex.: filtros do Spring Security, se presentes)\n3. Chega ao DispatcherServlet -- o \"front controller\" unico que o Spring Boot\n   registra para tratar todas as requisicoes da aplicacao\n4. DispatcherServlet pergunta a um HandlerMapping qual metodo de controller\n   combina com o verbo HTTP + caminho + cabecalhos desta requisicao\n5. DispatcherServlet entrega essa decisao a um HandlerAdapter, que sabe\n   invocar aquele metodo especifico: resolve @PathVariable, @RequestParam,\n   @RequestBody (via HttpMessageConverter) e monta os argumentos do metodo\n6. O metodo do controller roda e devolve um objeto Java (ou ResponseEntity)\n7. HandlerAdapter escolhe, por content negotiation (cabecalho Accept), um\n   HttpMessageConverter para serializar o retorno -- o do Jackson e o\n   padrao para JSON, mas e so um entre varios conversores possiveis\n8. Se uma excecao for lancada em qualquer ponto, ela vai para um\n   HandlerExceptionResolver -- e aqui que @ControllerAdvice/@ExceptionHandler\n   entram (capitulo 45, Validacao e tratamento de erros)\n9. A resposta volta pela cadeia de Filter ate o cliente</span>","caption":"Exemplo executável de spring-mvc.","explanation":["O método do controller nunca é chamado diretamente: DispatcherServlet delega a decisão de qual método (HandlerMapping) e a invocação em si (HandlerAdapter) para peças substituíveis.","A serialização do retorno (etapa 7) e o tratamento de exceção (etapa 8) também são decisões de peças nomeadas -- HttpMessageConverter e HandlerExceptionResolver -- não mágica do @RestController.","Uma dependência nova no classpath (ex.: adicionar suporte a XML) se encaixa nesse fluxo plugando outro HttpMessageConverter, sem o controller mudar."],"commonMistakes":["Achar que o DispatcherServlet chama o controller diretamente, sem nomear HandlerMapping/HandlerAdapter","Tratar a serialização da resposta como algo que o Jackson faz sozinho, sem passar por HttpMessageConverter/content negotiation"]},{"id":"spring-mvc-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Duas peças merecem nome porque costumam aparecer como \"mágica\" em tutoriais copiados: <strong><code>HandlerMapping</code></strong> é quem decide <em>qual</em> método atende a requisição (o \"roteador\"); <strong><code>HandlerAdapter</code></strong> é quem sabe <em>como</em> invocar esse método específico, incluindo resolver seus parâmetros. Sem esses nomes, é fácil achar que o <code>DispatcherServlet</code> \"sabe\" chamar qualquer controller sozinho — na verdade ele delega as duas decisões (qual método, como invocá-lo) para peças substituíveis, o que é o que permite o Spring suportar controllers anotados, funcionais (<code>RouterFunction</code>) e outros estilos ao mesmo tempo.</p>","fidelityText":"Duas peças merecem nome porque costumam aparecer como \"mágica\" em tutoriais copiados: HandlerMapping é quem decide qual método atende a requisição (o \"roteador\"); HandlerAdapter é quem sabe como invocar esse método específico, incluindo resolver seus parâmetros. Sem esses nomes, é fácil achar que o DispatcherServlet \"sabe\" chamar qualquer controller sozinho — na verdade ele delega as duas decisões (qual método, como invocá-lo) para peças substituíveis, o que é o que permite o Spring suportar controllers anotados, funcionais (RouterFunction) e outros estilos ao mesmo tempo."},{"id":"spring-mvc-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Repare que <code>@RestController</code> nunca \"sabe\" que existe JSON explicitamente no código — você retorna objetos Java normais (<code>LivroDTO</code>, <code>List&lt;LivroDTO&gt;</code>) e quem decide a serialização é a etapa 7 do fluxo acima: <strong>content negotiation</strong> baseada no cabeçalho <code>Accept</code> da requisição, seguida da escolha de um <code>HttpMessageConverter</code> compatível. O <code>MappingJackson2HttpMessageConverter</code> (Jackson, capítulo 25) é o conversor padrão auto-configurado para JSON — mas é plugável: uma API que também precisasse devolver XML registraria outro <code>HttpMessageConverter</code> para isso, sem o controller saber ou se importar. É por isso que entender getters/records + Jackson <em>antes</em> deste capítulo evita achar essa conversão mágica.</div>","fidelityText":"Repare que @RestController nunca \"sabe\" que existe JSON explicitamente no código — você retorna objetos Java normais (LivroDTO, List<LivroDTO>) e quem decide a serialização é a etapa 7 do fluxo acima: content negotiation baseada no cabeçalho Accept da requisição, seguida da escolha de um HttpMessageConverter compatível. O MappingJackson2HttpMessageConverter (Jackson, capítulo 25) é o conversor padrão auto-configurado para JSON — mas é plugável: uma API que também precisasse devolver XML registraria outro HttpMessageConverter para isso, sem o controller saber ou se importar. É por isso que entender getters/records + Jackson antes deste capítulo evita achar essa conversão mágica."},{"id":"spring-mvc-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Tratamento de exceção: quem decide a resposta de erro</h2>","fidelityText":"Tratamento de exceção: quem decide a resposta de erro"},{"id":"spring-mvc-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Quando um método de controller lança uma exceção (de validação, de domínio, ou inesperada), ela não vira uma resposta HTTP sozinha — ela é entregue a um <code>HandlerExceptionResolver</code>, e <code>@ControllerAdvice</code>/<code>@ExceptionHandler</code> são a forma declarativa de configurar esse resolver para decidir status, corpo e cabeçalhos por tipo de exceção, num único lugar central em vez de <code>try/catch</code> repetido em cada controller. O capítulo <a href=\"#validacao-erros\">45 · Validação e tratamento de erros</a> aprofunda esse mecanismo com um <code>GlobalExceptionHandler</code> completo — aqui basta saber que ele se encaixa exatamente na etapa 8 do fluxo do <code>DispatcherServlet</code> acima, não em algum lugar separado e desconectado.</p>","fidelityText":"Quando um método de controller lança uma exceção (de validação, de domínio, ou inesperada), ela não vira uma resposta HTTP sozinha — ela é entregue a um HandlerExceptionResolver, e @ControllerAdvice/@ExceptionHandler são a forma declarativa de configurar esse resolver para decidir status, corpo e cabeçalhos por tipo de exceção, num único lugar central em vez de try/catch repetido em cada controller. O capítulo 45 · Validação e tratamento de erros aprofunda esse mecanismo com um GlobalExceptionHandler completo — aqui basta saber que ele se encaixa exatamente na etapa 8 do fluxo do DispatcherServlet acima, não em algum lugar separado e desconectado."},{"id":"spring-mvc-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Multipart: recebendo arquivos</h2>","fidelityText":"Multipart: recebendo arquivos"},{"id":"spring-mvc-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Nem todo corpo de requisição é JSON. Upload de arquivo usa <code>multipart/form-data</code>, e o Spring resolve isso com <code>MultipartFile</code>:</p>","fidelityText":"Nem todo corpo de requisição é JSON. Upload de arquivo usa multipart/form-data, e o Spring resolve isso com MultipartFile:"},{"id":"spring-mvc-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"@PostMapping(value = \"/{id}/cover\", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)\npublic ResponseEntity<Void> sendCover(@PathVariable Long id,\n        @RequestPart(\"file\") MultipartFile file) throws IOException {\n    if (!\"image/png\".equals(file.getContentType())) { // nunca confie cegamente no Content-Type declarado pelo cliente\n        throw new FileInvalidException(file.getContentType());\n    }\n    service.saveCover(id, file.getBytes(), file.getOriginalFilename());\n    return ResponseEntity.noContent().build();\n}","fidelityText":"@PostMapping(value = \"/{id}/capa\", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<Void> enviarCapa(@PathVariable Long id, @RequestPart(\"arquivo\") MultipartFile arquivo) throws IOException { if (!\"image/png\".equals(arquivo.getContentType())) { // nunca confie cegamente no Content-Type declarado pelo cliente throw new ArquivoInvalidoException(arquivo.getContentType()); } servico.salvarCapa(id, arquivo.getBytes(), arquivo.getOriginalFilename()); return ResponseEntity.noContent().build(); }","highlightedHtml":"<span class=\"annotation\">@PostMapping</span>(value = <span class=\"str\">\"/{id}/cover\"</span>, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)\n<span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"kw\">Void</span>&gt; <span class=\"fn\">sendCover</span>(<span class=\"annotation\">@PathVariable</span> <span class=\"kw\">Long</span> id,\n        <span class=\"annotation\">@RequestPart</span>(<span class=\"str\">\"file\"</span>) MultipartFile file) <span class=\"kw\">throws</span> IOException {\n    <span class=\"kw\">if</span> (!<span class=\"str\">\"image/png\"</span>.equals(file.getContentType())) { <span class=\"com\">// nunca confie cegamente no Content-Type declarado pelo cliente</span>\n        <span class=\"kw\">throw new</span> <span class=\"cls\">FileInvalidException</span>(file.getContentType());\n    }\n    service.saveCover(id, file.getBytes(), file.getOriginalFilename());\n    <span class=\"kw\">return</span> ResponseEntity.noContent().build();\n}","caption":"Exemplo executável de spring-mvc.","explanation":["consumes = MediaType.MULTIPART_FORM_DATA_VALUE declara o Content-Type esperado; @RequestPart extrai a parte nomeada do multipart como MultipartFile.","O Content-Type de um MultipartFile é declarado pelo cliente -- validá-lo é conveniência, não prova de segurança; conteúdo que importa precisa de validação mais forte (assinatura de bytes, tamanho real, limites do servidor)."],"commonMistakes":["Confiar no Content-Type do MultipartFile como se fosse garantia de segurança","Não configurar spring.servlet.multipart.max-file-size/max-request-size e permitir upload sem limite"]},{"id":"spring-mvc-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\">O <code>Content-Type</code> de um <code>MultipartFile</code> é declarado pelo <strong>cliente</strong> — validá-lo é uma checagem de conveniência, não uma prova de segurança. Para conteúdo que importa de verdade, valide também o próprio conteúdo do arquivo (assinatura de bytes, tamanho real) e configure limites no servidor (<code>spring.servlet.multipart.max-file-size</code>, <code>max-request-size</code>) — sem isso, um upload gigante ou malformado vira um vetor de negação de serviço ou de arquivo malicioso disfarçado.</div>","fidelityText":"O Content-Type de um MultipartFile é declarado pelo cliente — validá-lo é uma checagem de conveniência, não uma prova de segurança. Para conteúdo que importa de verdade, valide também o próprio conteúdo do arquivo (assinatura de bytes, tamanho real) e configure limites no servidor (spring.servlet.multipart.max-file-size, max-request-size) — sem isso, um upload gigante ou malformado vira um vetor de negação de serviço ou de arquivo malicioso disfarçado."},{"id":"spring-mvc-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Testando de verdade: @WebMvcTest e MockMvc</h2>","fidelityText":"Testando de verdade: @WebMvcTest e MockMvc"},{"id":"spring-mvc-content-18","type":"html","authorship":"legacy-preserved","html":"<p>O exercício deste capítulo sempre pediu testes MockMvc — aqui está o exemplo real, não apenas a menção:</p>","fidelityText":"O exercício deste capítulo sempre pediu testes MockMvc — aqui está o exemplo real, não apenas a menção:"},{"id":"spring-mvc-code-19","type":"code","authorship":"legacy-preserved","language":"java","source":"@WebMvcTest(BookController.class)\nclass BookControllerTest {\n\n    @Autowired MockMvc mockMvc;\n    @MockBean BookService service; // troca o Service real -- este teste não usa banco nem regra de negócio real\n\n    @Test\n    void shouldReturnBooksAsJson() throws Exception {\n        when(service.listarAll()).thenReturn(List.of(new BookDTO(1L, \"Duna\", 412)));\n\n        mockMvc.perform(get(\"/books\"))\n            .andExpect(status().isOk())\n            .andExpect(jsonPath(\"$[0].title\").value(\"Duna\"));\n    }\n\n    @Test\n    void shouldReturn404WhenBookNotExists() throws Exception {\n        when(service.findById(99L)).thenReturn(Optional.empty());\n\n        mockMvc.perform(get(\"/books/99\"))\n            .andExpect(status().isNotFound());\n    }\n}","fidelityText":"@WebMvcTest(LivroController.class) class LivroControllerTest { @Autowired MockMvc mockMvc; @MockBean LivroServico servico; // troca o Service real -- este teste não usa banco nem regra de negócio real @Test void deveRetornarLivrosComoJson() throws Exception { when(servico.listarTodos()).thenReturn(List.of(new LivroDTO(1L, \"Duna\", 412))); mockMvc.perform(get(\"/livros\")) .andExpect(status().isOk()) .andExpect(jsonPath(\"$[0].titulo\").value(\"Duna\")); } @Test void deveRetornar404QuandoLivroNaoExiste() throws Exception { when(servico.buscarPorId(99L)).thenReturn(Optional.empty()); mockMvc.perform(get(\"/livros/99\")) .andExpect(status().isNotFound()); } }","highlightedHtml":"<span class=\"annotation\">@WebMvcTest</span>(BookController.<span class=\"kw\">class</span>)\n<span class=\"kw\">class</span> <span class=\"cls\">BookControllerTest</span> {\n\n    <span class=\"annotation\">@Autowired</span> MockMvc mockMvc;\n    <span class=\"annotation\">@MockBean</span> BookService service; <span class=\"com\">// troca o Service real -- este teste não usa banco nem regra de negócio real</span>\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldReturnBooksAsJson</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n        when(service.listarAll()).thenReturn(List.of(<span class=\"kw\">new</span> <span class=\"cls\">BookDTO</span>(<span class=\"str\">1L</span>, <span class=\"str\">\"Duna\"</span>, <span class=\"str\">412</span>)));\n\n        mockMvc.perform(get(<span class=\"str\">\"/books\"</span>))\n            .andExpect(status().isOk())\n            .andExpect(jsonPath(<span class=\"str\">\"$[0].title\"</span>).value(<span class=\"str\">\"Duna\"</span>));\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldReturn404WhenBookNotExists</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n        when(service.findById(<span class=\"str\">99L</span>)).thenReturn(Optional.empty());\n\n        mockMvc.perform(get(<span class=\"str\">\"/books/99\"</span>))\n            .andExpect(status().isNotFound());\n    }\n}","caption":"Exemplo executável de spring-mvc.","explanation":["@WebMvcTest(BookController.class) sobe só a camada web (DispatcherServlet, HandlerMapping/HandlerAdapter, HttpMessageConverter) em vez do ApplicationContext inteiro.","@MockBean substitui o Service real por um dublê configurável -- o teste não depende de banco nem de regra de negócio real.","MockMvc simula a requisição HTTP completa (roteamento, binding, serialização) sem abrir uma porta de rede real, por isso roda em milissegundos."],"commonMistakes":["Usar @SpringBootTest (sobe tudo) quando @WebMvcTest já é suficiente e mais rápido para testar só o controller","Esquecer @MockBean e deixar o teste depender do Service/repositório reais"]},{"id":"spring-mvc-content-20","type":"html","authorship":"legacy-preserved","html":"<p><code>@WebMvcTest</code> sobe só a camada web (o <code>DispatcherServlet</code>, <code>HandlerMapping</code>/<code>HandlerAdapter</code>, os <code>HttpMessageConverter</code>) em vez do <code>ApplicationContext</code> inteiro — <code>@MockBean</code> substitui o <code>LivroServico</code> real por um dublê configurável, então o teste não depende de banco de dados nem de regra de negócio de verdade. <code>MockMvc</code> simula a requisição HTTP inteira (roteamento, binding de parâmetros, serialização da resposta) sem abrir uma porta de rede real — é por isso que esses testes rodam em milissegundos, mesmo exercitando o mesmo caminho que o <code>DispatcherServlet</code> percorre em produção.</p>","fidelityText":"@WebMvcTest sobe só a camada web (o DispatcherServlet, HandlerMapping/HandlerAdapter, os HttpMessageConverter) em vez do ApplicationContext inteiro — @MockBean substitui o LivroServico real por um dublê configurável, então o teste não depende de banco de dados nem de regra de negócio de verdade. MockMvc simula a requisição HTTP inteira (roteamento, binding de parâmetros, serialização da resposta) sem abrir uma porta de rede real — é por isso que esses testes rodam em milissegundos, mesmo exercitando o mesmo caminho que o DispatcherServlet percorre em produção."},{"id":"spring-mvc-exercise-21","type":"exercise","authorship":"legacy-preserved","title":"Exercício 44.1 — Controller completo de Livro","prompt":"Sem copiar o exemplo, implemente um AutorController com listar, buscar, criar e deletar. A criação deve retornar 201 com Location; busca ausente retorna 404; deleção inexistente também possui contrato explícito. Escreva testes @WebMvcTest/MockMvc para cada status, seguindo o exemplo desta seção.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 44.1 — Controller completo de Livromédio Sem copiar o exemplo, implemente um AutorController com listar, buscar, criar e deletar. A criação deve retornar 201 com Location; busca ausente retorna 404; deleção inexistente também possui contrato explícito. Escreva testes @WebMvcTest/MockMvc para cada status, seguindo o exemplo desta seção. Ver solução Critérios: controller não contém regra de negócio; usa DTO; constrói Location a partir da requisição; converte ausência em 404; valida o corpo; e os testes verificam status, header e JSON. Compare uma solução com ResponseEntity.of(optional) e outra com exceção de domínio traduzida globalmente.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 44.1 — Controller completo de Livro</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Sem copiar o exemplo, implemente um <code>AutorController</code> com listar, buscar, criar e deletar. A criação deve retornar 201 com <code>Location</code>; busca ausente retorna 404; deleção inexistente também possui contrato explícito. Escreva testes <code>@WebMvcTest</code>/<code>MockMvc</code> para cada status, seguindo o exemplo desta seção.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Critérios: controller não contém regra de negócio; usa DTO; constrói <code>Location</code> a partir da requisição; converte ausência em 404; valida o corpo; e os testes verificam status, header e JSON. Compare uma solução com <code>ResponseEntity.of(optional)</code> e outra com exceção de domínio traduzida globalmente.</p>\n        </div>\n      </div>"},{"id":"spring-mvc-exercise-22","type":"exercise","authorship":"legacy-preserved","title":"Exercício 44.2 — Onde cada peça decide o quê","prompt":"Para a requisição GET /livros/7 com cabeçalho Accept: application/json, liste em ordem: qual peça decide que buscarPorId é o método certo, qual peça invoca esse método e resolve o @PathVariable, e qual peça decide como serializar o LivroDTO de volta. Explique por que trocar essas três respostas por \"o Spring faz tudo isso\" perde exatamente a informação que este capítulo ensina.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 44.2 — Onde cada peça decide o quêfácil Para a requisição GET /livros/7 com cabeçalho Accept: application/json, liste em ordem: qual peça decide que buscarPorId é o método certo, qual peça invoca esse método e resolve o @PathVariable, e qual peça decide como serializar o LivroDTO de volta. Explique por que trocar essas três respostas por \"o Spring faz tudo isso\" perde exatamente a informação que este capítulo ensina. Ver solução HandlerMapping decide que buscarPorId combina com GET /livros/7. HandlerAdapter invoca buscarPorId, resolvendo id=7 a partir do @PathVariable. A escolha do HttpMessageConverter (Jackson, por content negotiation com o Accept: application/json) decide como o LivroDTO devolvido vira o corpo JSON da resposta. \"O Spring faz tudo isso\" esconde que são três decisões distintas, cada uma numa peça substituível — é essa substituibilidade que permite, por exemplo, adicionar suporte a XML sem tocar no controller.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 44.2 — Onde cada peça decide o quê</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Para a requisição <code>GET /livros/7</code> com cabeçalho <code>Accept: application/json</code>, liste em ordem: qual peça decide que <code>buscarPorId</code> é o método certo, qual peça invoca esse método e resolve o <code>@PathVariable</code>, e qual peça decide como serializar o <code>LivroDTO</code> de volta. Explique por que trocar essas três respostas por \"o Spring faz tudo isso\" perde exatamente a informação que este capítulo ensina.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><code>HandlerMapping</code> decide que <code>buscarPorId</code> combina com <code>GET /livros/7</code>. <code>HandlerAdapter</code> invoca <code>buscarPorId</code>, resolvendo <code>id=7</code> a partir do <code>@PathVariable</code>. A escolha do <code>HttpMessageConverter</code> (Jackson, por content negotiation com o <code>Accept: application/json</code>) decide como o <code>LivroDTO</code> devolvido vira o corpo JSON da resposta. \"O Spring faz tudo isso\" esconde que são três decisões distintas, cada uma numa peça substituível — é essa substituibilidade que permite, por exemplo, adicionar suporte a XML sem tocar no controller.</p>\n        </div>\n      </div>"},{"id":"spring-mvc-status-quiz","type":"quiz","authorship":"authored","conceptId":"mvc-response-status-contract","prompt":"Quando ResponseEntity é preferível a retornar apenas um objeto?","options":[{"id":"mvc-a","label":"Quando status, headers ou ausência de body fazem parte do contrato HTTP.","correct":true,"explanation":"A resposta publicada não é só JSON; status e headers também comunicam semântica."},{"id":"mvc-b","label":"Quando o service precisa acessar diretamente o servlet request.","correct":false,"explanation":"Isso acopla regra ao transporte e costuma piorar testabilidade."},{"id":"mvc-c","label":"Quando todo erro deve virar 200 com uma mensagem no body.","correct":false,"explanation":"Status HTTP precisa preservar significado para cliente, cache e observabilidade."}]}],"resources":[{"id":"spring-mvc-controller","type":"reference","title":"Spring MVC: Annotated Controllers","url":"https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller.html","reinforces":"Controllers anotados, binding e métodos de handler.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-mvc-request-mapping","type":"reference","title":"Spring MVC: Mapping Requests","url":"https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-requestmapping.html","reinforces":"Mapeamento de método, path, parâmetros, headers e consumes/produces.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The spring mvc component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to spring mvc. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible spring mvc failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"@RestController // @Controller + @ResponseBody: retorno vira JSON automaticamente (via Jackson, capítulo 25)","instruction":"The spring mvc component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible spring mvc failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"spring-jpa","moduleId":"spring-api","order":6,"title":"Spring Data JPA — repositories & relacionamentos","summary":"Este é o capítulo em que todo o JDBC manual do capítulo 24 vira uma interface de poucas linhas. Se você entendeu PreparedStatement, ResultSet e o padrão Repository (capítulo 23), o que o Spring Data JPA automatiza vai parecer natural, não mágico.","objectives":["Mapear entity, id e repository sem perder custo SQL","Entender repository como adapter de persistência","Modelar relacionamentos considerando consulta e aggregate","Evitar expor entity JPA como DTO"],"whyItExists":"Depois de SQL, JDBC e Spring MVC, JPA aparece como abstração de persistência com ganhos reais e custos reais. O aluno precisa enxergar a tabela e a consulta por trás do repository.","prerequisiteChapterIds":["jdbc","spring-mvc","lombok"],"conceptIds":["entity-mapeando-classe-java-para-tabela","jparepository-implementacao-gerada-por-proxy-nao-magica","query-quando-o-nome-do-metodo-nao-basta","relacionamentos-onetomany-manytoone-manytomany","igualdade-de-entidade-por-que-equals-hashcode-aqui-e-traicoeiro"],"introducedConceptIds":["jpa-entity-identity","springdata-repository-contract","jpa-relationship-loading"],"usedConceptIds":["modelo-relacional-tabela-chave","jdbc-driver-connection","adapter-fronteira-externa"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"spring-jpa-intuition","type":"intuition","authorship":"authored","title":"Repository não apaga o banco; ele encapsula acesso","body":"Spring Data reduz código repetitivo de persistência, mas continua emitindo SQL, abrindo conexões e carregando dados com custos. A abstração boa deixa o caso de uso limpo sem fingir que tabela, índice e transação sumiram.","analogyLimit":"Um câmbio automático ainda depende do motor; repository ainda depende do banco."},{"id":"spring-jpa-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~4h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#jdbc\">24 · JDBC</a>, <a class=\"prereq-tag\" href=\"#padroes\">23 · Padrões de projeto</a>, <a class=\"prereq-tag\" href=\"#sql\">33 · SQL fundamentos</a>, <a class=\"prereq-tag\" href=\"#migrations\">35 · Migrations</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Avançado ⏱ ~4h de estudo + prática Pré-requisitos: 24 · JDBC, 23 · Padrões de projeto, 33 · SQL fundamentos, 35 · Migrations"},{"id":"spring-jpa-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Este é o capítulo em que todo o JDBC manual do capítulo 24 vira uma <strong>interface de poucas linhas</strong>. Se você entendeu <code>PreparedStatement</code>, <code>ResultSet</code> e o padrão Repository (capítulo 23), o que o Spring Data JPA automatiza vai parecer natural, não mágico.</p>","fidelityText":"Este é o capítulo em que todo o JDBC manual do capítulo 24 vira uma interface de poucas linhas. Se você entendeu PreparedStatement, ResultSet e o padrão Repository (capítulo 23), o que o Spring Data JPA automatiza vai parecer natural, não mágico."},{"id":"spring-jpa-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>@Entity — mapeando classe Java para tabela</h2>","fidelityText":"@Entity — mapeando classe Java para tabela"},{"id":"spring-jpa-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"@Entity\n@Table(name = \"books\")\npublic class Book {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY) // equivale a SERIAL do capítulo 34\n    private Long id;\n\n    @Column(nullable = false, length = 200)\n    private String title;\n\n    private int pages;\n\n    @ManyToOne\n    @JoinColumn(name = \"author_id\") // a chave estrangeira do capítulo 33\n    private Author author;\n\n    // construtor sem args OBRIGATÓRIO (Hibernate cria via reflection, capítulo 20)\n    // getters e setters...\n}","fidelityText":"@Entity @Table(name = \"livros\") public class Livro { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // equivale a SERIAL do capítulo 34 private Long id; @Column(nullable = false, length = 200) private String titulo; private int paginas; @ManyToOne @JoinColumn(name = \"autor_id\") // a chave estrangeira do capítulo 33 private Autor autor; // construtor sem args OBRIGATÓRIO (Hibernate cria via reflection, capítulo 20) // getters e setters... }","highlightedHtml":"<span class=\"annotation\">@Entity</span>\n<span class=\"annotation\">@Table</span>(name = <span class=\"str\">\"books\"</span>)\n<span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"annotation\">@Id</span>\n    <span class=\"annotation\">@GeneratedValue</span>(strategy = GenerationType.IDENTITY) <span class=\"com\">// equivale a SERIAL do capítulo 34</span>\n    <span class=\"kw\">private</span> <span class=\"kw\">Long</span> id;\n\n    <span class=\"annotation\">@Column</span>(nullable = <span class=\"kw\">false</span>, length = 200)\n    <span class=\"kw\">private String</span> title;\n\n    <span class=\"kw\">private int</span> pages;\n\n    <span class=\"annotation\">@ManyToOne</span>\n    <span class=\"annotation\">@JoinColumn</span>(name = <span class=\"str\">\"author_id\"</span>) <span class=\"com\">// a chave estrangeira do capítulo 33</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">Author</span> author;\n\n    <span class=\"com\">// construtor sem args OBRIGATÓRIO (Hibernate cria via reflection, capítulo 20)\n    // getters e setters...</span>\n}","caption":"Exemplo executável de spring-jpa.","explanation":["@Entity marca uma classe como persistível e @Id define identidade no banco.","Identidade persistente não é o mesmo que igualdade de valor em todo domínio."],"commonMistakes":["Gerar setter público para todo campo","Misturar entidade JPA com response DTO"]},{"id":"spring-jpa-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>JpaRepository — implementação gerada por proxy, não mágica</h2>","fidelityText":"JpaRepository — implementação gerada por proxy, não mágica"},{"id":"spring-jpa-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Você declara só a assinatura da interface abaixo. É o Spring Data JPA quem gera a implementação inteira em runtime:</p>","fidelityText":"Você declara só a assinatura da interface abaixo. É o Spring Data JPA quem gera a implementação inteira em runtime:"},{"id":"spring-jpa-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface BookRepository extends JpaRepository<Book, Long> {\n    // Dynamic Proxy (capítulo 20!) gera a implementação em runtime. Cada\n    // chamada vira uma query que o EntityManager -- a API real do Hibernate --\n    // executa de verdade.\n\n    List<Book> findByAuthorName(String name);        // vira: WHERE autor.nome = ?\n    List<Book> findByPagesGreaterThan(int minimum); // vira: WHERE paginas > ?\n    Optional<Book> findByTitle(String title);       // vira: WHERE titulo = ? (retorna Optional!)\n}\n\n// já vem de graça, sem você escrever nada:\nbookRepository.save(book);       // INSERT ou UPDATE\nbookRepository.findById(5L);      // SELECT ... WHERE id = ?  -- Optional<Livro>, capítulo 21\nbookRepository.findAll();         // SELECT * FROM livros\nbookRepository.deleteById(5L);    // DELETE ... WHERE id = ?","fidelityText":"public interface LivroRepository extends JpaRepository<Livro, Long> { // Dynamic Proxy (capítulo 20!) gera a implementação em runtime. Cada // chamada vira uma query que o EntityManager -- a API real do Hibernate -- // executa de verdade. List<Livro> findByAutorNome(String nome); // vira: WHERE autor.nome = ? List<Livro> findByPaginasGreaterThan(int minimo); // vira: WHERE paginas > ? Optional<Livro> findByTitulo(String titulo); // vira: WHERE titulo = ? (retorna Optional!) } // já vem de graça, sem você escrever nada: livroRepository.save(livro); // INSERT ou UPDATE livroRepository.findById(5L); // SELECT ... WHERE id = ? -- Optional<Livro>, capítulo 21 livroRepository.findAll(); // SELECT * FROM livros livroRepository.deleteById(5L); // DELETE ... WHERE id = ?","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">BookRepository</span> <span class=\"kw\">extends</span> JpaRepository&lt;<span class=\"cls\">Book</span>, <span class=\"kw\">Long</span>&gt; {\n    <span class=\"com\">// Dynamic Proxy (capítulo 20!) gera a implementação em runtime. Cada\n    // chamada vira uma query que o EntityManager -- a API real do Hibernate --\n    // executa de verdade.</span>\n\n    List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findByAuthorName</span>(<span class=\"kw\">String</span> name);        <span class=\"com\">// vira: WHERE autor.nome = ?</span>\n    List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findByPagesGreaterThan</span>(<span class=\"kw\">int</span> minimum); <span class=\"com\">// vira: WHERE paginas &gt; ?</span>\n    Optional&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findByTitle</span>(<span class=\"kw\">String</span> title);       <span class=\"com\">// vira: WHERE titulo = ? (retorna Optional!)</span>\n}\n\n<span class=\"com\">// já vem de graça, sem você escrever nada:</span>\nbookRepository.save(book);       <span class=\"com\">// INSERT ou UPDATE</span>\nbookRepository.findById(5L);      <span class=\"com\">// SELECT ... WHERE id = ?  -- Optional&lt;Livro&gt;, capítulo 21</span>\nbookRepository.findAll();         <span class=\"com\">// SELECT * FROM livros</span>\nbookRepository.deleteById(5L);    <span class=\"com\">// DELETE ... WHERE id = ?</span>","caption":"Exemplo executável de spring-jpa.","explanation":["A implementação da interface é gerada em runtime via Dynamic Proxy (capítulo 20); o handler traduz cada chamada numa query que o EntityManager -- a API real do Hibernate por trás do repository -- de fato executa.","O nome do método vira contrato de consulta (Query Method); índices e cardinalidade continuam importando mesmo sem escrever SQL."],"commonMistakes":["Criar query derivada longa e ambígua em vez de usar @Query","Ignorar o SQL gerado e nunca conferir se o índice certo está sendo usado"]},{"id":"spring-jpa-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">O <strong>Query Method</strong> (<code>findByAutorNome</code>) funciona porque o Spring lê o <em>nome do método</em> via reflection e monta a query correspondente automaticamente — é como se o método fosse escrito em uma \"mini-linguagem\" que descreve a consulta, e o Spring traduzisse isso para SQL na hora de rodar. É exatamente o mesmo espírito do seu <code>@NaoNulo</code> do capítulo 20: uma convenção lida via reflection, virando comportamento automático.</div>","fidelityText":"O Query Method (findByAutorNome) funciona porque o Spring lê o nome do método via reflection e monta a query correspondente automaticamente — é como se o método fosse escrito em uma \"mini-linguagem\" que descreve a consulta, e o Spring traduzisse isso para SQL na hora de rodar. É exatamente o mesmo espírito do seu @NaoNulo do capítulo 20: uma convenção lida via reflection, virando comportamento automático."},{"id":"spring-jpa-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>@Query: quando o nome do método não basta</h2>","fidelityText":"@Query: quando o nome do método não basta"},{"id":"spring-jpa-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Query Methods derivados do nome ficam ilegíveis rápido para joins complexos, agregações ou queries com múltiplas condições opcionais — <code>@Query</code> é a válvula de escape, sem abandonar o Repository:</p>","fidelityText":"Query Methods derivados do nome ficam ilegíveis rápido para joins complexos, agregações ou queries com múltiplas condições opcionais — @Query é a válvula de escape, sem abandonar o Repository:"},{"id":"spring-jpa-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"@Query(\"SELECT l FROM Book l WHERE l.pages > :minimum ORDER BY l.title\") // JPQL usa entidades e campos, não tabelas e colunas\nList<Book> findBooksLong(@Param(\"minimum\") int minimum);\n\n@Query(value = \"SELECT * FROM books WHERE title ILIKE %:term%\", nativeQuery = true) // SQL de verdade, específico do Postgres\nList<Book> findByTitlePartial(@Param(\"term\") String term);\n\n// projeção: traz só os campos escolhidos, sem carregar a entidade inteira\n@Query(\"SELECT new library.dto.BookSummaryDTO(l.id, l.title) FROM Book l\")\nList<BookSummaryDTO> findSummaries();","fidelityText":"@Query(\"SELECT l FROM Livro l WHERE l.paginas > :minimo ORDER BY l.titulo\") // JPQL usa entidades e campos, não tabelas e colunas List<Livro> buscarLivrosLongos(@Param(\"minimo\") int minimo); @Query(value = \"SELECT * FROM livros WHERE titulo ILIKE %:termo%\", nativeQuery = true) // SQL de verdade, específico do Postgres List<Livro> buscarPorTituloParcial(@Param(\"termo\") String termo); // projeção: traz só os campos escolhidos, sem carregar a entidade inteira @Query(\"SELECT new biblioteca.dto.LivroResumoDTO(l.id, l.titulo) FROM Livro l\") List<LivroResumoDTO> buscarResumos();","highlightedHtml":"<span class=\"annotation\">@Query</span>(<span class=\"str\">\"SELECT l FROM Book l WHERE l.pages &gt; :minimum ORDER BY l.title\"</span>) <span class=\"com\">// JPQL usa entidades e campos, não tabelas e colunas</span>\nList&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findBooksLong</span>(<span class=\"annotation\">@Param</span>(<span class=\"str\">\"minimum\"</span>) <span class=\"kw\">int</span> minimum);\n\n<span class=\"annotation\">@Query</span>(value = <span class=\"str\">\"SELECT * FROM books WHERE title ILIKE %:term%\"</span>, nativeQuery = <span class=\"kw\">true</span>) <span class=\"com\">// SQL de verdade, específico do Postgres</span>\nList&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findByTitlePartial</span>(<span class=\"annotation\">@Param</span>(<span class=\"str\">\"term\"</span>) <span class=\"kw\">String</span> term);\n\n<span class=\"com\">// projeção: traz só os campos escolhidos, sem carregar a entidade inteira</span>\n<span class=\"annotation\">@Query</span>(<span class=\"str\">\"SELECT new library.dto.BookSummaryDTO(l.id, l.title) FROM Book l\"</span>)\nList&lt;<span class=\"cls\">BookSummaryDTO</span>&gt; <span class=\"fn\">findSummaries</span>();","caption":"Exemplo executável de spring-jpa.","explanation":["JPQL opera sobre entidades e seus campos, não sobre nomes de tabela/coluna -- por isso funciona igual em qualquer banco suportado.","nativeQuery=true abre mão dessa portabilidade em troca de recursos específicos do banco (como ILIKE do Postgres).","A projeção via constructor expression (SELECT new pacote.DTO(...)) evita carregar a entidade inteira quando só alguns campos importam."],"commonMistakes":["Usar nativeQuery sempre por hábito, perdendo portabilidade sem necessidade real","Carregar a entidade inteira quando uma projeção resolveria com menos dados trafegados"]},{"id":"spring-jpa-content-12","type":"html","authorship":"legacy-preserved","html":"<p>JPQL opera sobre <strong>entidades e seus campos</strong> (<code>Livro l WHERE l.paginas</code>), não sobre nomes de tabela/coluna do banco — é por isso que funciona igual em qualquer banco suportado. <code>nativeQuery = true</code> abre mão dessa portabilidade em troca de recursos específicos do banco (como <code>ILIKE</code> do Postgres). A projeção via <em>constructor expression</em> (<code>new pacote.DTO(...)</code>) evita carregar a entidade completa quando só alguns campos importam — útil para listagens grandes.</p>","fidelityText":"JPQL opera sobre entidades e seus campos (Livro l WHERE l.paginas), não sobre nomes de tabela/coluna do banco — é por isso que funciona igual em qualquer banco suportado. nativeQuery = true abre mão dessa portabilidade em troca de recursos específicos do banco (como ILIKE do Postgres). A projeção via constructor expression (new pacote.DTO(...)) evita carregar a entidade completa quando só alguns campos importam — útil para listagens grandes."},{"id":"spring-jpa-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Relacionamentos: @OneToMany, @ManyToOne, @ManyToMany</h2>","fidelityText":"Relacionamentos: @OneToMany, @ManyToOne, @ManyToMany"},{"id":"spring-jpa-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"@Entity\npublic class Author {\n    @Id @GeneratedValue\n    private Long id;\n    private String name;\n\n    @OneToMany(mappedBy = \"author\", cascade = CascadeType.ALL)\n    private List<Book> books = new ArrayList<>();\n\n    // método auxiliar -- mantém os dois lados do relacionamento consistentes juntos\n    public void addBook(Book book) {\n        books.add(book);\n        book.setAuthor(this); // sem isso, o lado \"dono\" (Livro.autor) fica desatualizado em memória\n    }\n\n    public void removeBook(Book book) {\n        books.remove(book);\n        book.setAuthor(null);\n    }\n}","fidelityText":"@Entity public class Autor { @Id @GeneratedValue private Long id; private String nome; @OneToMany(mappedBy = \"autor\", cascade = CascadeType.ALL) private List<Livro> livros = new ArrayList<>(); // método auxiliar -- mantém os dois lados do relacionamento consistentes juntos public void adicionarLivro(Livro livro) { livros.add(livro); livro.setAutor(this); // sem isso, o lado \"dono\" (Livro.autor) fica desatualizado em memória } public void removerLivro(Livro livro) { livros.remove(livro); livro.setAutor(null); } }","highlightedHtml":"<span class=\"annotation\">@Entity</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Author</span> {\n    <span class=\"annotation\">@Id</span> <span class=\"annotation\">@GeneratedValue</span>\n    <span class=\"kw\">private</span> <span class=\"kw\">Long</span> id;\n    <span class=\"kw\">private String</span> name;\n\n    <span class=\"annotation\">@OneToMany</span>(mappedBy = <span class=\"str\">\"author\"</span>, cascade = CascadeType.ALL)\n    <span class=\"kw\">private</span> List&lt;<span class=\"cls\">Book</span>&gt; books = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n\n    <span class=\"com\">// método auxiliar -- mantém os dois lados do relacionamento consistentes juntos</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">addBook</span>(<span class=\"cls\">Book</span> book) {\n        books.add(book);\n        book.setAuthor(<span class=\"kw\">this</span>); <span class=\"com\">// sem isso, o lado \"dono\" (Livro.autor) fica desatualizado em memória</span>\n    }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">removeBook</span>(<span class=\"cls\">Book</span> book) {\n        books.remove(book);\n        book.setAuthor(<span class=\"kw\">null</span>);\n    }\n}","caption":"Exemplo executável de spring-jpa.","explanation":["O lado @OneToMany (mappedBy) é só leitura em termos de persistência -- quem o Hibernate realmente persiste é o lado dono (@ManyToOne, que tem a chave estrangeira).","Métodos auxiliares como addBook/removeBook existem para nunca esquecer de atualizar os dois lados da relação ao mesmo tempo."],"commonMistakes":["Manipular a coleção mappedBy diretamente de fora da entidade, deixando o lado dono desatualizado em memória","Usar EAGER para resolver um erro de sessão em vez de entender o ciclo de vida do relacionamento"]},{"id":"spring-jpa-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\">Chamar só <code>autor.getLivros().add(livro)</code> sem também setar <code>livro.setAutor(autor)</code> deixa a relação <strong>inconsistente em memória</strong> — o lado \"dono\" da relação (<code>@ManyToOne</code>, quem tem a chave estrangeira) é quem o Hibernate realmente persiste; o lado <code>mappedBy</code> (<code>@OneToMany</code>) é só leitura em termos de persistência. Métodos auxiliares como <code>adicionarLivro</code>/<code>removerLivro</code> existem justamente para nunca esquecer de atualizar os dois lados — chame sempre eles, nunca manipule a coleção diretamente de fora da entidade.</div>","fidelityText":"Chamar só autor.getLivros().add(livro) sem também setar livro.setAutor(autor) deixa a relação inconsistente em memória — o lado \"dono\" da relação (@ManyToOne, quem tem a chave estrangeira) é quem o Hibernate realmente persiste; o lado mappedBy (@OneToMany) é só leitura em termos de persistência. Métodos auxiliares como adicionarLivro/removerLivro existem justamente para nunca esquecer de atualizar os dois lados — chame sempre eles, nunca manipule a coleção diretamente de fora da entidade."},{"id":"spring-jpa-content-16","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Anotação</th><th>Relação</th><th>Exemplo</th></tr>\n        <tr><td><code>@ManyToOne</code></td><td>Vários para um</td><td>Vários livros → um autor</td></tr>\n        <tr><td><code>@OneToMany</code></td><td>Um para vários</td><td>Um autor → vários livros (o \"lado inverso\" do de cima)</td></tr>\n        <tr><td><code>@OneToOne</code></td><td>Um para um</td><td>Um usuário → um perfil</td></tr>\n        <tr><td><code>@ManyToMany</code></td><td>Vários para vários</td><td>Livros ↔ categorias, via tabela de junção</td></tr>\n      </tbody></table>","fidelityText":"AnotaçãoRelaçãoExemplo @ManyToOneVários para umVários livros → um autor @OneToManyUm para váriosUm autor → vários livros (o \"lado inverso\" do de cima) @OneToOneUm para umUm usuário → um perfil @ManyToManyVários para váriosLivros ↔ categorias, via tabela de junção"},{"id":"spring-jpa-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Igualdade de entidade: por que equals/hashCode aqui é traiçoeiro</h2>","fidelityText":"Igualdade de entidade: por que equals/hashCode aqui é traiçoeiro"},{"id":"spring-jpa-content-18","type":"html","authorship":"legacy-preserved","html":"<p>Três armadilhas específicas de <code>@Entity</code> que não existem em objetos comuns:</p>","fidelityText":"Três armadilhas específicas de @Entity que não existem em objetos comuns:"},{"id":"spring-jpa-content-19","type":"html","authorship":"legacy-preserved","html":"<ul style=\"color:var(--ink-dim)\">\n        <li><strong>Nunca use <code>@Data</code> do Lombok numa entidade</strong> (já visto no capítulo de Lombok) — ele gera <code>equals</code>/<code>hashCode</code> sobre <em>todos</em> os campos, inclusive coleções <code>@OneToMany</code>; numa relação bidirecional isso causa recursão infinita (<code>Autor.equals</code> chama <code>Livro.equals</code> que chama <code>Autor.equals</code>...) e força carregar coleções lazy só para comparar objetos.</li>\n        <li><strong>Igualdade baseada só no <code>id</code> gerado quebra antes de persistir</strong>: enquanto o <code>id</code> ainda é <code>null</code> (objeto recém-criado, antes do <code>save</code>), um <code>equals</code> ingênuo baseado em <code>id</code> torna <em>todo</em> objeto transiente \"igual\" a qualquer outro — colocar dois livros novos, ainda não salvos, no mesmo <code>Set</code> pode descartar um deles silenciosamente.</li>\n        <li><strong>Proxies do Hibernate quebram <code>getClass() != o.getClass()</code></strong>: uma associação carregada lazy vem como um <em>proxy</em> gerado pelo Hibernate (subclasse dinâmica, mesmo mecanismo do CGLIB visto no capítulo de Spring AOP), não a classe exata da entidade — comparar com <code>getClass()</code> falha entre a entidade real e seu proxy. Prefira <code>instanceof</code>.</li>\n      </ul>","fidelityText":"Nunca use @Data do Lombok numa entidade (já visto no capítulo de Lombok) — ele gera equals/hashCode sobre todos os campos, inclusive coleções @OneToMany; numa relação bidirecional isso causa recursão infinita (Autor.equals chama Livro.equals que chama Autor.equals...) e força carregar coleções lazy só para comparar objetos. Igualdade baseada só no id gerado quebra antes de persistir: enquanto o id ainda é null (objeto recém-criado, antes do save), um equals ingênuo baseado em id torna todo objeto transiente \"igual\" a qualquer outro — colocar dois livros novos, ainda não salvos, no mesmo Set pode descartar um deles silenciosamente. Proxies do Hibernate quebram getClass() != o.getClass(): uma associação carregada lazy vem como um proxy gerado pelo Hibernate (subclasse dinâmica, mesmo mecanismo do CGLIB visto no capítulo de Spring AOP), não a classe exata da entidade — comparar com getClass() falha entre a entidade real e seu proxy. Prefira instanceof."},{"id":"spring-jpa-content-20","type":"html","authorship":"legacy-preserved","html":"<p>Uma alternativa comum: implementar <code>equals</code>/<code>hashCode</code> baseados numa <em>business key</em> estável (campo que nunca muda e identifica a entidade no domínio, como um ISBN), quando ela existir — evita depender do momento exato em que o <code>id</code> gerado pelo banco passa a existir.</p>","fidelityText":"Uma alternativa comum: implementar equals/hashCode baseados numa business key estável (campo que nunca muda e identifica a entidade no domínio, como um ISBN), quando ela existir — evita depender do momento exato em que o id gerado pelo banco passa a existir."},{"id":"spring-jpa-content-21","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li><code>ddl-auto=validate</code> em produção, <strong>nunca</strong> <code>update</code> — o schema é gerenciado pelo Flyway (capítulo 35), não pelo Hibernate.</li>\n          <li>Prefira <code>FetchType.LAZY</code> em relacionamentos <code>@ManyToOne</code>/<code>@OneToMany</code> — carregar tudo eager por padrão é a raiz do problema do próximo capítulo (N+1).</li>\n          <li>Nunca exponha entidades <code>@Entity</code> diretamente em um controller — sempre converta para DTO (capítulo 47) antes de retornar via API.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro ddl-auto=validate em produção, nunca update — o schema é gerenciado pelo Flyway (capítulo 35), não pelo Hibernate. Prefira FetchType.LAZY em relacionamentos @ManyToOne/@OneToMany — carregar tudo eager por padrão é a raiz do problema do próximo capítulo (N+1). Nunca exponha entidades @Entity diretamente em um controller — sempre converta para DTO (capítulo 47) antes de retornar via API."},{"id":"spring-jpa-content-22","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Toda vez que um Query Method (<code>findByX</code>) parecer \"mágico demais\", escreva a query SQL equivalente à mão primeiro (usando o que você aprendeu no capítulo 33), depois compare com o nome do método. Isso constrói a intuição de tradução método↔SQL rapidamente, em vez de depender de decorar padrões de nomenclatura.</div>","fidelityText":"Toda vez que um Query Method (findByX) parecer \"mágico demais\", escreva a query SQL equivalente à mão primeiro (usando o que você aprendeu no capítulo 33), depois compare com o nome do método. Isso constrói a intuição de tradução método↔SQL rapidamente, em vez de depender de decorar padrões de nomenclatura."},{"id":"spring-jpa-exercise-23","type":"exercise","authorship":"legacy-preserved","title":"Exercício 45.1 — Modelando com relacionamento","prompt":"Modele Pedido e ItemPedido com relacionamento um-para-muitos, ownership explícito e remoção coerente com o agregado. Crie queries por status e por intervalo de criação. Teste persistência, orphan removal, lazy loading e a constraint que impede quantidade não positiva.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 45.1 — Modelando com relacionamentodifícil Modele Pedido e ItemPedido com relacionamento um-para-muitos, ownership explícito e remoção coerente com o agregado. Crie queries por status e por intervalo de criação. Teste persistência, orphan removal, lazy loading e a constraint que impede quantidade não positiva. Ver solução Critérios: ItemPedido referencia o dono da relação; métodos auxiliares mantêm os dois lados consistentes; cascade/orphan removal têm justificativa de ciclo de vida; queries retornam tipos adequados; e o teste inspeciona o comportamento após flush e clear, não apenas a coleção em memória.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 45.1 — Modelando com relacionamento</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Modele <code>Pedido</code> e <code>ItemPedido</code> com relacionamento um-para-muitos, ownership explícito e remoção coerente com o agregado. Crie queries por status e por intervalo de criação. Teste persistência, orphan removal, lazy loading e a constraint que impede quantidade não positiva.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Critérios: <code>ItemPedido</code> referencia o dono da relação; métodos auxiliares mantêm os dois lados consistentes; cascade/orphan removal têm justificativa de ciclo de vida; queries retornam tipos adequados; e o teste inspeciona o comportamento após <code>flush</code> e <code>clear</code>, não apenas a coleção em memória.</p>\n        </div>\n      </div>"},{"id":"spring-jpa-quiz","type":"quiz","authorship":"authored","conceptId":"springdata-repository-contract","prompt":"Qual afirmação descreve melhor JpaRepository?","options":[{"id":"jpa-a","label":"Uma porta/adaptador que oferece operações de persistência e gera/execura consultas conforme contrato.","correct":true,"explanation":"Ele reduz boilerplate, mas não elimina consulta, transação nem custo."},{"id":"jpa-b","label":"Um substituto para modelagem relacional, índices e transações.","correct":false,"explanation":"Essas decisões continuam determinando correção e performance."},{"id":"jpa-c","label":"Um DTO público seguro para retornar diretamente ao frontend.","correct":false,"explanation":"Entity persistente e contrato HTTP evoluem por motivos diferentes."}]}],"resources":[{"id":"spring-data-jpa-start","type":"reference","title":"Spring Data JPA: Getting Started","url":"https://docs.spring.io/spring-data/jpa/reference/jpa/getting-started.html","reinforces":"Configuração, repository e primeira persistência com Spring Data JPA.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-data-repositories","type":"reference","title":"Spring Data: Repository Core Concepts","url":"https://docs.spring.io/spring-data/jpa/reference/repositories/core-concepts.html","reinforces":"Repository, interfaces, métodos e contrato de abstração.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The spring jpa component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to spring jpa. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible spring jpa failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"@Table(name = \"books\")","instruction":"The spring jpa component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible spring jpa failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"jpa-transacoes","moduleId":"spring-api","order":7,"title":"JPA profundo e transações Spring","summary":"JPA mantém uma unidade de trabalho chamada persistence context. Dentro dela, cada identidade de banco corresponde a uma instância gerenciada; alterações nessa instância podem ser detectadas e sincronizadas no flush. Entender esse ciclo evita SQL inesperado, entidades desatualizadas e transações gigantes.","objectives":["Separar persistence context, flush e commit","Posicionar @Transactional no caso de uso","Entender dirty checking e estado detached","Evitar self-invocation e rollback implícito mal entendido"],"whyItExists":"JPA sem transação vira superstição. Depois de repository e JDBC transaction, este capítulo aprofunda o ciclo em memória/SQL/commit para o aluno prever comportamento antes de depurar no escuro.","prerequisiteChapterIds":["spring-jpa","migrations"],"conceptIds":["o-mapa-minimo-entre-objetos-e-banco","flush-nao-e-commit","propagacao-isolamento-e-rollback","requires-new-e-nested-com-exemplo-e-limite-real","deadlock-e-lost-update-as-duas-falhas-de-concorrencia-que-todo-sistema-c","por-que-uma-chamada-http-externa-dentro-de-uma-transacao-e-um-problema-r","testando-concorrencia-de-verdade","o-limite-do-proxy","relacionamentos-e-igualdade"],"introducedConceptIds":["jpa-flush-dirty-checking","transaction-proxy-boundary"],"usedConceptIds":["jpa-entity-identity","sql-transacao-atomicidade","jdbc-transacao-rollback"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"jpa-transacoes-intuition","type":"intuition","authorship":"authored","title":"Flush não é commit","body":"JPA mantém entidades gerenciadas em um contexto. Dirty checking detecta mudanças, flush envia SQL, commit confirma a transação. Confundir essas etapas faz bug parecer magia.","analogyLimit":"Fila de comandos ajuda a imaginar flush, mas isolamento, locks e rollback são contratos do banco/transação."},{"id":"jpa-transacoes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-spring\">Spring</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~7h30</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-jpa\">Spring Data JPA</a>, <a class=\"prereq-tag\" href=\"#postgres-concorrencia\">Concorrência no PostgreSQL</a></div></div>","fidelityText":"SpringDificuldade: Avançado⏱ ~7h30 de estudo e práticaPré-requisitos: Spring Data JPA, Concorrência no PostgreSQL"},{"id":"jpa-transacoes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>JPA mantém uma unidade de trabalho chamada persistence context. Dentro dela, cada identidade de banco corresponde a uma instância gerenciada; alterações nessa instância podem ser detectadas e sincronizadas no <code>flush</code>. Entender esse ciclo evita SQL inesperado, entidades desatualizadas e transações gigantes.</p>","fidelityText":"JPA mantém uma unidade de trabalho chamada persistence context. Dentro dela, cada identidade de banco corresponde a uma instância gerenciada; alterações nessa instância podem ser detectadas e sincronizadas no flush. Entender esse ciclo evita SQL inesperado, entidades desatualizadas e transações gigantes."},{"id":"jpa-transacoes-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>O mapa mínimo entre objetos e banco</h2></div>\n    <p>JPA não é o banco e uma entidade não é apenas uma linha. Estes termos explicam quem acompanha as mudanças e quando elas viram SQL.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>JPA</dt><dd>Especificação Java de persistência objeto-relacional. Hibernate é uma implementação comum dessa especificação.</dd></div><div class=\"concept-card\"><dt>Entidade</dt><dd>Objeto persistente com identidade própria, normalmente mapeado a uma tabela.</dd></div><div class=\"concept-card\"><dt>Persistence context</dt><dd>Conjunto de entidades que o EntityManager acompanha durante uma unidade de trabalho.</dd></div><div class=\"concept-card\"><dt>Dirty checking</dt><dd>Detecção automática de alterações em entidades gerenciadas.</dd></div><div class=\"concept-card\"><dt>Flush</dt><dd>Sincronização das mudanças pendentes com SQL; não confirma a transação por si só.</dd></div><div class=\"concept-card\"><dt>Commit</dt><dd>Confirma a transação como unidade; rollback descarta seus efeitos ainda não confirmados.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoO mapa mínimo entre objetos e banco JPA não é o banco e uma entidade não é apenas uma linha. Estes termos explicam quem acompanha as mudanças e quando elas viram SQL. JPAEspecificação Java de persistência objeto-relacional. Hibernate é uma implementação comum dessa especificação.EntidadeObjeto persistente com identidade própria, normalmente mapeado a uma tabela.Persistence contextConjunto de entidades que o EntityManager acompanha durante uma unidade de trabalho.Dirty checkingDetecção automática de alterações em entidades gerenciadas.FlushSincronização das mudanças pendentes com SQL; não confirma a transação por si só.CommitConfirma a transação como unidade; rollback descarta seus efeitos ainda não confirmados."},{"id":"jpa-transacoes-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Estado</th><th>Significado</th></tr><tr><td>transient</td><td>objeto novo, ainda não associado ao contexto</td></tr><tr><td>managed</td><td>rastreado; mudanças participam de dirty checking</td></tr><tr><td>detached</td><td>já teve identidade persistente, mas não é mais rastreado</td></tr><tr><td>removed</td><td>marcado para remoção no flush</td></tr></tbody></table>","fidelityText":"EstadoSignificadotransientobjeto novo, ainda não associado ao contextomanagedrastreado; mudanças participam de dirty checkingdetachedjá teve identidade persistente, mas não é mais rastreadoremovedmarcado para remoção no flush"},{"id":"jpa-transacoes-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"@Transactional\npublic Order confirm(Long id, Long versionEsperada) {\n    Order order = repository.findById(id).orElseThrow(OrderNotFound::new);\n    order.confirm(); // dirty checking: não precisa chamar save em entidade já gerenciada\n    return order;\n}","fidelityText":"@Transactional public Pedido confirmar(Long id, Long versaoEsperada) { Pedido pedido = repositorio.findById(id).orElseThrow(PedidoNaoEncontrado::new); pedido.confirmar(); // dirty checking: não precisa chamar save em entidade já gerenciada return pedido; }","highlightedHtml":"<span class=\"annotation\">@Transactional</span>\n<span class=\"kw\">public</span> Order confirm(Long id, Long versionEsperada) {\n    Order order = repository.findById(id).orElseThrow(OrderNotFound::new);\n    order.confirm(); <span class=\"com\">// dirty checking: não precisa chamar save em entidade já gerenciada</span>\n    <span class=\"kw\">return</span> order;\n}","caption":"Exemplo executável de jpa-transacoes.","explanation":["@Transactional delimita uma unidade de trabalho no proxy Spring.","A fronteira saudável costuma ser o caso de uso, não cada linha de repository."],"commonMistakes":["Anotar método privado esperando proxy","Abrir transação no controller por conveniência"]},{"id":"jpa-transacoes-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Flush não é commit</h2>","fidelityText":"Flush não é commit"},{"id":"jpa-transacoes-content-7","type":"html","authorship":"legacy-preserved","html":"<p><code>flush</code> sincroniza alterações pendentes com o banco para executar SQL e verificar parte das constraints; a transação ainda pode sofrer rollback. O commit encerra a unidade atômica. Uma consulta pode provocar flush automático antes de ser executada.</p>","fidelityText":"flush sincroniza alterações pendentes com o banco para executar SQL e verificar parte das constraints; a transação ainda pode sofrer rollback. O commit encerra a unidade atômica. Uma consulta pode provocar flush automático antes de ser executada."},{"id":"jpa-transacoes-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Propagação, isolamento e rollback</h2>","fidelityText":"Propagação, isolamento e rollback"},{"id":"jpa-transacoes-content-9","type":"html","authorship":"legacy-preserved","html":"<p><code>REQUIRED</code> participa da transação existente ou cria uma. <code>REQUIRES_NEW</code> suspende a atual e cria outra; use raramente, pois o efeito interno pode ser confirmado mesmo quando a operação externa falha. <code>NESTED</code> depende de savepoints e do gerenciador. Isolamento é uma propriedade do banco, não um substituto para invariantes.</p>","fidelityText":"REQUIRED participa da transação existente ou cria uma. REQUIRES_NEW suspende a atual e cria outra; use raramente, pois o efeito interno pode ser confirmado mesmo quando a operação externa falha. NESTED depende de savepoints e do gerenciador. Isolamento é uma propriedade do banco, não um substituto para invariantes."},{"id":"jpa-transacoes-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"@Transactional(\n    isolation = Isolation.READ_COMMITTED,\n    timeout = 5,\n    rollbackFor = IntegrationFailure.class)\npublic void transfer(Long source, Long recipient, BigDecimal value) { ... }","fidelityText":"@Transactional( isolation = Isolation.READ_COMMITTED, timeout = 5, rollbackFor = FalhaIntegracao.class) public void transferir(Long origem, Long destino, BigDecimal valor) { ... }","highlightedHtml":"<span class=\"annotation\">@Transactional</span>(\n    isolation = Isolation.READ_COMMITTED,\n    timeout = 5,\n    rollbackFor = IntegrationFailure.class)\n<span class=\"kw\">public void</span> transfer(Long source, Long recipient, BigDecimal value) { ... }","caption":"Exemplo executável de jpa-transacoes.","explanation":["Entidade gerenciada pode ser sincronizada por dirty checking sem chamar save a cada alteração.","O commit depende da transação e pode falhar por constraint ou concorrência."],"commonMistakes":["Achar que flush confirmou durable commit","Ignorar exceções no commit"]},{"id":"jpa-transacoes-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Por padrão, o modelo declarativo tradicional faz rollback automático para exceções não verificadas; checked exceptions precisam de regra explícita quando representam falha atômica. <code>readOnly=true</code> é uma dica de otimização, não uma barreira universal contra escrita.</p>","fidelityText":"Por padrão, o modelo declarativo tradicional faz rollback automático para exceções não verificadas; checked exceptions precisam de regra explícita quando representam falha atômica. readOnly=true é uma dica de otimização, não uma barreira universal contra escrita."},{"id":"jpa-transacoes-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>REQUIRES_NEW e NESTED, com exemplo e limite real</h2>","fidelityText":"REQUIRES_NEW e NESTED, com exemplo e limite real"},{"id":"jpa-transacoes-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"@Transactional\npublic void processOrder(Order order) {\n    order.confirm();\n    audit.register(\"order confirmed: \" + order.getId()); // método com REQUIRES_NEW\n    if (order.valueSuspicious()) {\n        throw new IntegrationFailure(\"value outside of the default\"); // reverte processarPedido inteiro...\n    }\n}\n\n@Service\npublic class AuditService {\n    @Transactional(propagation = Propagation.REQUIRES_NEW)\n    public void register(String event) {\n        repositoryAudit.save(new RecordAudit(event));\n        // ...mas este registro de auditoria já foi commitado em SUA PRÓPRIA transação\n        // suspensa/independente -- sobrevive ao rollback de processarPedido acima.\n    }\n}","fidelityText":"@Transactional public void processarPedido(Pedido pedido) { pedido.confirmar(); auditoria.registrar(\"pedido confirmado: \" + pedido.getId()); // método com REQUIRES_NEW if (pedido.valorSuspeito()) { throw new FalhaIntegracao(\"valor fora do padrão\"); // reverte processarPedido inteiro... } } @Service public class AuditoriaServico { @Transactional(propagation = Propagation.REQUIRES_NEW) public void registrar(String evento) { repositorioAuditoria.save(new RegistroAuditoria(evento)); // ...mas este registro de auditoria já foi commitado em SUA PRÓPRIA transação // suspensa/independente -- sobrevive ao rollback de processarPedido acima. } }","highlightedHtml":"<span class=\"annotation\">@Transactional</span>\n<span class=\"kw\">public void</span> processOrder(Order order) {\n    order.confirm();\n    audit.register(<span class=\"str\">\"order confirmed: \"</span> + order.getId()); <span class=\"com\">// método com REQUIRES_NEW</span>\n    <span class=\"kw\">if</span> (order.valueSuspicious()) {\n        <span class=\"kw\">throw new</span> <span class=\"cls\">IntegrationFailure</span>(<span class=\"str\">\"value outside of the default\"</span>); <span class=\"com\">// reverte processarPedido inteiro...</span>\n    }\n}\n\n<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">AuditService</span> {\n    <span class=\"annotation\">@Transactional</span>(propagation = Propagation.REQUIRES_NEW)\n    <span class=\"kw\">public void</span> <span class=\"fn\">register</span>(<span class=\"kw\">String</span> event) {\n        repositoryAudit.save(<span class=\"kw\">new</span> <span class=\"cls\">RecordAudit</span>(event));\n        <span class=\"com\">// ...mas este registro de auditoria já foi commitado em SUA PRÓPRIA transação\n        // suspensa/independente -- sobrevive ao rollback de processarPedido acima.</span>\n    }\n}","caption":"Exemplo executável de jpa-transacoes.","explanation":["REQUIRES_NEW suspende a transação atual e abre outra totalmente independente -- o registro de auditoria sobrevive mesmo se processarPedido reverter depois.","O preço real: duas conexões/transações simultâneas; se a externa falhar após a interna já ter commitado, não há como desfazer esse commit."],"commonMistakes":["Usar REQUIRES_NEW sem entender que cria uma segunda transação/conexão de verdade","Esperar que o rollback da transação externa desfaça o que já foi commitado na REQUIRES_NEW"]},{"id":"jpa-transacoes-content-14","type":"html","authorship":"legacy-preserved","html":"<p><code>REQUIRES_NEW</code> suspende a transação atual e abre outra, totalmente independente — é exatamente o que um log de auditoria precisa: registrar que a tentativa aconteceu, mesmo que o resto falhe e reverta. O preço é real: agora existem duas conexões/transações simultâneas, e se a externa falhar depois que a interna já commitou, não há como desfazer o commit da interna.</p>","fidelityText":"REQUIRES_NEW suspende a transação atual e abre outra, totalmente independente — é exatamente o que um log de auditoria precisa: registrar que a tentativa aconteceu, mesmo que o resto falhe e reverta. O preço é real: agora existem duas conexões/transações simultâneas, e se a externa falhar depois que a interna já commitou, não há como desfazer o commit da interna."},{"id":"jpa-transacoes-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><code>NESTED</code> depende de <strong>savepoints</strong> do banco/driver JDBC — nem todo gerenciador de transação suporta (não funciona, por exemplo, sobre transações JTA distribuídas). Diferente de <code>REQUIRES_NEW</code>, <code>NESTED</code> continua dentro da <strong>mesma</strong> transação física: um rollback do bloco aninhado volta só até o savepoint, sem derrubar a transação externa inteira — mas se a transação externa fizer rollback, o savepoint some junto. É uma ferramenta para \"tentar uma parte, sem comprometer o resto\", não para independência real como <code>REQUIRES_NEW</code>.</div>","fidelityText":"NESTED depende de savepoints do banco/driver JDBC — nem todo gerenciador de transação suporta (não funciona, por exemplo, sobre transações JTA distribuídas). Diferente de REQUIRES_NEW, NESTED continua dentro da mesma transação física: um rollback do bloco aninhado volta só até o savepoint, sem derrubar a transação externa inteira — mas se a transação externa fizer rollback, o savepoint some junto. É uma ferramenta para \"tentar uma parte, sem comprometer o resto\", não para independência real como REQUIRES_NEW."},{"id":"jpa-transacoes-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>Deadlock e lost update: as duas falhas de concorrência que todo sistema com escrita concorrente encontra</h2>","fidelityText":"Deadlock e lost update: as duas falhas de concorrência que todo sistema com escrita concorrente encontra"},{"id":"jpa-transacoes-content-17","type":"html","authorship":"legacy-preserved","html":"<ul>\n        <li><strong>Lost update</strong>: duas transações leem a mesma linha, cada uma decide uma mudança com base no valor lido, e a segunda escrita sobrescreve a primeira silenciosamente — sem <code>@Version</code> (optimistic locking) ou lock explícito, nada avisa que isso aconteceu. É o cenário exato que <code>@Version</code>, já citado acima, existe para detectar: a segunda transação recebe um erro de versão em vez de sobrescrever silenciosamente.</li>\n        <li><strong>Deadlock</strong>: a transação A tem lock na linha 1 e espera a linha 2; a transação B tem lock na linha 2 e espera a linha 1 — nenhuma consegue prosseguir. O banco detecta esse ciclo e força o rollback de uma das duas (no Postgres, com <code>SQLState 40P01</code>) para liberar a outra. A resposta correta não é evitar deadlock a qualquer custo (às vezes é inevitável com locks concorrentes), mas capturar a exceção de deadlock especificamente e <strong>reexecutar a transação vencida</strong> com um pequeno backoff — nunca propagar isso como erro genérico 500 para o usuário sem tentar de novo.</li>\n      </ul>","fidelityText":"Lost update: duas transações leem a mesma linha, cada uma decide uma mudança com base no valor lido, e a segunda escrita sobrescreve a primeira silenciosamente — sem @Version (optimistic locking) ou lock explícito, nada avisa que isso aconteceu. É o cenário exato que @Version, já citado acima, existe para detectar: a segunda transação recebe um erro de versão em vez de sobrescrever silenciosamente. Deadlock: a transação A tem lock na linha 1 e espera a linha 2; a transação B tem lock na linha 2 e espera a linha 1 — nenhuma consegue prosseguir. O banco detecta esse ciclo e força o rollback de uma das duas (no Postgres, com SQLState 40P01) para liberar a outra. A resposta correta não é evitar deadlock a qualquer custo (às vezes é inevitável com locks concorrentes), mas capturar a exceção de deadlock especificamente e reexecutar a transação vencida com um pequeno backoff — nunca propagar isso como erro genérico 500 para o usuário sem tentar de novo."},{"id":"jpa-transacoes-content-18","type":"html","authorship":"legacy-preserved","html":"<h2>Por que uma chamada HTTP externa dentro de uma transação é um problema real</h2>","fidelityText":"Por que uma chamada HTTP externa dentro de uma transação é um problema real"},{"id":"jpa-transacoes-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Uma transação aberta mantém uma <strong>conexão do pool</strong> presa até o commit/rollback (o mesmo pool visto em Postgres/JDBC). Se o método transacional faz uma chamada HTTP para outro serviço no meio do caminho, essa conexão fica presa pela duração inteira da chamada de rede — que pode ser 10x, 100x mais lenta que qualquer operação de banco. Com volume concorrente, isso esgota o pool de conexões rapidamente: outras requisições, que nem tocam o serviço externo, começam a falhar por timeout esperando uma conexão livre — um problema de banco de dados causado por uma chamada de rede que nunca devia estar ali dentro. Mova a chamada HTTP para fora da transação (antes de abri-la, ou depois de fechá-la).</p>","fidelityText":"Uma transação aberta mantém uma conexão do pool presa até o commit/rollback (o mesmo pool visto em Postgres/JDBC). Se o método transacional faz uma chamada HTTP para outro serviço no meio do caminho, essa conexão fica presa pela duração inteira da chamada de rede — que pode ser 10x, 100x mais lenta que qualquer operação de banco. Com volume concorrente, isso esgota o pool de conexões rapidamente: outras requisições, que nem tocam o serviço externo, começam a falhar por timeout esperando uma conexão livre — um problema de banco de dados causado por uma chamada de rede que nunca devia estar ali dentro. Mova a chamada HTTP para fora da transação (antes de abri-la, ou depois de fechá-la)."},{"id":"jpa-transacoes-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Testando concorrência de verdade</h2>","fidelityText":"Testando concorrência de verdade"},{"id":"jpa-transacoes-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"@Test\nvoid shouldDetectConflictOfVersionInLoanConcurrent() throws Exception {\n    Exemplar exemplar = repository.save(new Exemplar(\"book-1\"));\n    ExecutorService pool = Executors.newFixedThreadPool(2);\n    CountDownLatch largada = new CountDownLatch(1);\n\n    Callable<Boolean> tentarBorrow = () -> {\n        largada.await(); // força as duas threads a iniciar juntas, no mesmo instante\n        try {\n            serviceLoan.borrow(exemplar.getId());\n            return true;\n        } catch (ObjectOptimisticLockingFailureException e) {\n            return false; // a tentativa que falha termina exatamente aqui\n        }\n    };\n\n    List<Future<Boolean>> results = pool.invokeAll(List.of(tentarBorrow, tentarBorrow));\n    largada.countDown();\n\n    long sucessos = results.stream().filter(f -> {\n        try { return f.get(); } catch (Exception e) { return false; }\n    }).count();\n\n    assertThat(sucessos).isEqualTo(1L); // vence exatamente um resultado, nunca os dois nem nenhum\n}","fidelityText":"@Test void deveDetectarConflitoDeVersaoEmEmprestimoConcorrente() throws Exception { Exemplar exemplar = repositorio.save(new Exemplar(\"livro-1\")); ExecutorService pool = Executors.newFixedThreadPool(2); CountDownLatch largada = new CountDownLatch(1); Callable<Boolean> tentarEmprestar = () -> { largada.await(); // força as duas threads a iniciar juntas, no mesmo instante try { servicoEmprestimo.emprestar(exemplar.getId()); return true; } catch (ObjectOptimisticLockingFailureException e) { return false; // a tentativa que falha termina exatamente aqui } }; List<Future<Boolean>> resultados = pool.invokeAll(List.of(tentarEmprestar, tentarEmprestar)); largada.countDown(); long sucessos = resultados.stream().filter(f -> { try { return f.get(); } catch (Exception e) { return false; } }).count(); assertThat(sucessos).isEqualTo(1L); // vence exatamente um resultado, nunca os dois nem nenhum }","highlightedHtml":"<span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldDetectConflictOfVersionInLoanConcurrent</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n    <span class=\"cls\">Exemplar</span> exemplar = repository.save(<span class=\"kw\">new</span> <span class=\"cls\">Exemplar</span>(<span class=\"str\">\"book-1\"</span>));\n    ExecutorService pool = Executors.newFixedThreadPool(<span class=\"num\">2</span>);\n    CountDownLatch largada = <span class=\"kw\">new</span> CountDownLatch(<span class=\"num\">1</span>);\n\n    Callable&lt;<span class=\"kw\">Boolean</span>&gt; tentarBorrow = () -&gt; {\n        largada.await(); <span class=\"com\">// força as duas threads a iniciar juntas, no mesmo instante</span>\n        <span class=\"kw\">try</span> {\n            serviceLoan.borrow(exemplar.getId());\n            <span class=\"kw\">return true</span>;\n        } <span class=\"kw\">catch</span> (ObjectOptimisticLockingFailureException e) {\n            <span class=\"kw\">return false</span>; <span class=\"com\">// a tentativa que falha termina exatamente aqui</span>\n        }\n    };\n\n    List&lt;Future&lt;<span class=\"kw\">Boolean</span>&gt;&gt; results = pool.invokeAll(List.of(tentarBorrow, tentarBorrow));\n    largada.countDown();\n\n    <span class=\"kw\">long</span> sucessos = results.stream().filter(f -&gt; {\n        <span class=\"kw\">try</span> { <span class=\"kw\">return</span> f.get(); } <span class=\"kw\">catch</span> (Exception e) { <span class=\"kw\">return false</span>; }\n    }).count();\n\n    assertThat(sucessos).isEqualTo(<span class=\"num\">1</span>L); <span class=\"com\">// vence exatamente um resultado, nunca os dois nem nenhum</span>\n}","caption":"Exemplo executável de jpa-transacoes.","explanation":["CountDownLatch força as duas threads a competirem pelo mesmo instante -- um teste sequencial não prova nada sobre concorrência real.","ObjectOptimisticLockingFailureException é a exceção esperada na transação perdedora quando @Version detecta o conflito."],"commonMistakes":["Testar concorrência com chamadas sequenciais, sem sobreposição real de threads","Não capturar especificamente ObjectOptimisticLockingFailureException, tratando-a como erro genérico"]},{"id":"jpa-transacoes-content-22","type":"html","authorship":"legacy-preserved","html":"<p>Um teste sequencial (chamar <code>emprestar</code> duas vezes seguidas, sem sobreposição real) não prova nada sobre concorrência — as duas chamadas nunca disputam a mesma linha ao mesmo tempo. <code>CountDownLatch</code> força as duas threads a competirem de verdade pelo mesmo instante, o que é o único jeito de provar que o optimistic locking funciona sob disputa real, não apenas na teoria.</p>","fidelityText":"Um teste sequencial (chamar emprestar duas vezes seguidas, sem sobreposição real) não prova nada sobre concorrência — as duas chamadas nunca disputam a mesma linha ao mesmo tempo. CountDownLatch força as duas threads a competirem de verdade pelo mesmo instante, o que é o único jeito de provar que o optimistic locking funciona sob disputa real, não apenas na teoria."},{"id":"jpa-transacoes-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>O limite do proxy</h2>","fidelityText":"O limite do proxy"},{"id":"jpa-transacoes-content-24","type":"html","authorship":"legacy-preserved","html":"<p><code>@Transactional</code> normalmente é aplicado por <strong>proxy</strong>: um objeto intermediário criado pelo Spring intercepta a chamada, abre a transação, chama o serviço e confirma ou reverte. Uma chamada de um método para outro no mesmo objeto não atravessa esse intermediário; métodos privados também não formam bons limites declarativos. Coloque a transação em um serviço público orientado ao caso de uso e mantenha chamadas lentas de rede fora dela.</p>","fidelityText":"@Transactional normalmente é aplicado por proxy: um objeto intermediário criado pelo Spring intercepta a chamada, abre a transação, chama o serviço e confirma ou reverte. Uma chamada de um método para outro no mesmo objeto não atravessa esse intermediário; métodos privados também não formam bons limites declarativos. Coloque a transação em um serviço público orientado ao caso de uso e mantenha chamadas lentas de rede fora dela."},{"id":"jpa-transacoes-content-25","type":"html","authorship":"legacy-preserved","html":"<h2>Relacionamentos e igualdade</h2>","fidelityText":"Relacionamentos e igualdade"},{"id":"jpa-transacoes-content-26","type":"html","authorship":"legacy-preserved","html":"<ul><li>Use <code>cascade</code> somente quando o ciclo de vida realmente pertence ao agregado.</li><li><code>orphanRemoval</code> expressa propriedade forte e remove filhos retirados da coleção.</li><li>Evite incluir associações lazy em <code>toString</code>, <code>equals</code> e serialização.</li><li>Uma implementação ingênua baseada apenas em ID gerado falha enquanto o ID é nulo. Defina igualdade conforme a identidade do domínio e teste entidades antes e depois da persistência.</li><li>Use <code>@Version</code> para optimistic locking e trate conflito como resultado esperado.</li></ul>","fidelityText":"Use cascade somente quando o ciclo de vida realmente pertence ao agregado.orphanRemoval expressa propriedade forte e remove filhos retirados da coleção.Evite incluir associações lazy em toString, equals e serialização.Uma implementação ingênua baseada apenas em ID gerado falha enquanto o ID é nulo. Defina igualdade conforme a identidade do domínio e teste entidades antes e depois da persistência.Use @Version para optimistic locking e trate conflito como resultado esperado."},{"id":"jpa-transacoes-code-27","type":"code","authorship":"legacy-preserved","language":"java","source":"@Version\nprivate long version;","fidelityText":"@Version private long versao;","highlightedHtml":"<span class=\"annotation\">@Version</span>\n<span class=\"kw\">private long</span> version;","caption":"Exemplo executável de jpa-transacoes.","explanation":["@Version habilita optimistic locking: cada update verifica se a versão lida ainda é a versão atual no banco antes de escrever.","Se outra transação já alterou a linha (versão diferente), o update afeta zero linhas e o Hibernate lança ObjectOptimisticLockingFailureException -- a detecção de lost update citada acima."],"commonMistakes":["Achar que @Version sozinho impede deadlock -- ele resolve lost update, não deadlock","Ignorar a exceção de conflito de versão em vez de tratá-la como resultado esperado sob concorrência"]},{"id":"jpa-transacoes-exercise-28","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — empréstimo concorrente","prompt":"Escreva um teste com duas transações tentando emprestar o mesmo exemplar. Implemente uma versão com optimistic locking e outra com lock pessimista. Verifique que exatamente uma vence e que a outra recebe conflito traduzido para HTTP 409.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — empréstimo concorrentedifícilEscreva um teste com duas transações tentando emprestar o mesmo exemplar. Implemente uma versão com optimistic locking e outra com lock pessimista. Verifique que exatamente uma vence e que a outra recebe conflito traduzido para HTTP 409.Ver critériosO teste deve usar duas transações reais e sincronizar o ponto de disputa; um teste sequencial não comprova concorrência. Registre o trade-off entre contenção pessimista e repetição otimista.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — empréstimo concorrente</h2><span class=\"exercise-tag d\">difícil</span></div><p>Escreva um teste com duas transações tentando emprestar o mesmo exemplar. Implemente uma versão com optimistic locking e outra com lock pessimista. Verifique que exatamente uma vence e que a outra recebe conflito traduzido para HTTP 409.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>O teste deve usar duas transações reais e sincronizar o ponto de disputa; um teste sequencial não comprova concorrência. Registre o trade-off entre contenção pessimista e repetição otimista.</p></div></div>"},{"id":"jpa-transacoes-quiz","type":"quiz","authorship":"authored","conceptId":"jpa-flush-dirty-checking","prompt":"O que dirty checking faz dentro de uma transação?","options":[{"id":"tx-a","label":"Compara entidades gerenciadas e gera alterações SQL no flush quando necessário.","correct":true,"explanation":"A entidade precisa estar gerenciada; flush envia SQL, commit confirma."},{"id":"tx-b","label":"Comita imediatamente qualquer setter chamado em qualquer objeto.","correct":false,"explanation":"Setter em objeto detached não sincroniza automaticamente."},{"id":"tx-c","label":"Substitui constraints e isolamento do banco.","correct":false,"explanation":"Banco ainda valida constraints e aplica isolamento/locks."}]}],"resources":[{"id":"spring-tx-declarative","type":"reference","title":"Spring Framework: Declarative Transaction Management","url":"https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative.html","reinforces":"Modelo declarativo de transações, proxies e fronteiras.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-tx-annotations","type":"reference","title":"Spring Framework: @Transactional Settings","url":"https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html","reinforces":"Semântica de @Transactional, propagation, rollback e limites.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The jpa transactions component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to jpa transactions. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible jpa transactions failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"@Transactional","instruction":"The jpa transactions component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible jpa transactions failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"dto-mapping","moduleId":"spring-api","order":8,"title":"DTO Mapping — Entity ≠ DTO","summary":"O capítulo 25 já mencionou DTOs de leve. Agora que você tem entidades JPA reais (capítulo 45), o problema fica concreto: por que não simplesmente retornar a @Entity direto no controller?","objectives":["Separar request DTO, response DTO e entity","Mapear sem esconder regra de negócio","Evitar mass assignment e vazamento de campos internos","Decidir quando mapper manual ou MapStruct compensa"],"whyItExists":"Com MVC e JPA ensinados, o risco natural é devolver entity como JSON e chamar isso de API. DTO mapping cria fronteira explícita entre contrato público, domínio e persistência.","prerequisiteChapterIds":["spring-mvc","spring-jpa"],"conceptIds":["mapeamento-manual-entenda-antes-de-automatizar","mapstruct-automatizando-o-boilerplate-obvio"],"introducedConceptIds":["dto-entity-boundary-spring"],"usedConceptIds":["dto-mapping-fronteira","jpa-entity-identity","mvc-controller-binding"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"dto-mapping-intuition","type":"intuition","authorship":"authored","title":"DTO é contrato público; entity é estado persistente","body":"Um request DTO descreve o que o cliente pode enviar. Um response DTO descreve o que você promete devolver. A entity descreve persistência e invariantes internas. Misturar tudo prende evolução da API ao banco.","analogyLimit":"Formulário e ficha interna ajudam a imaginar, mas DTO também carrega versionamento e segurança de contrato."},{"id":"dto-mapping-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-jpa\">45 · Spring Data JPA</a>, <a class=\"prereq-tag\" href=\"#json\">25 · JSON &amp; serialização</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~1h30 de estudo + prática Pré-requisitos: 45 · Spring Data JPA, 25 · JSON & serialização"},{"id":"dto-mapping-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 25 já mencionou DTOs de leve. Agora que você tem entidades JPA reais (capítulo 45), o problema fica concreto: por que não simplesmente retornar a <code>@Entity</code> direto no controller?</p>","fidelityText":"O capítulo 25 já mencionou DTOs de leve. Agora que você tem entidades JPA reais (capítulo 45), o problema fica concreto: por que não simplesmente retornar a @Entity direto no controller?"},{"id":"dto-mapping-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Três problemas de expor Entity diretamente:</b> (1) você vaza detalhes internos do banco (nomes de coluna, relacionamentos inteiros) para quem consome a API; (2) mudar a entidade para uma necessidade interna do banco quebra silenciosamente o contrato público da API; (3) tentar serializar uma entidade com relacionamento <code>LAZY</code> não carregado fora de uma transação ativa lança <code>LazyInitializationException</code> — um erro clássico e confuso de quem pula direto para produção sem DTO.</div>","fidelityText":"Três problemas de expor Entity diretamente: (1) você vaza detalhes internos do banco (nomes de coluna, relacionamentos inteiros) para quem consome a API; (2) mudar a entidade para uma necessidade interna do banco quebra silenciosamente o contrato público da API; (3) tentar serializar uma entidade com relacionamento LAZY não carregado fora de uma transação ativa lança LazyInitializationException — um erro clássico e confuso de quem pula direto para produção sem DTO."},{"id":"dto-mapping-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense na <code>@Entity</code> como o estoque interno de uma loja — cheio de código de fornecedor, custo de aquisição, localização exata na prateleira. O DTO é a vitrine — mostra só nome, foto e preço, o que o cliente realmente precisa ver. Ninguém deixa o cliente vasculhar o estoque diretamente; sempre existe uma \"vitrine\" controlada entre o mundo externo e os dados internos.</div>","fidelityText":"Pense na @Entity como o estoque interno de uma loja — cheio de código de fornecedor, custo de aquisição, localização exata na prateleira. O DTO é a vitrine — mostra só nome, foto e preço, o que o cliente realmente precisa ver. Ninguém deixa o cliente vasculhar o estoque diretamente; sempre existe uma \"vitrine\" controlada entre o mundo externo e os dados internos."},{"id":"dto-mapping-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Mapeamento manual — entenda antes de automatizar</h2>","fidelityText":"Mapeamento manual — entenda antes de automatizar"},{"id":"dto-mapping-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"public record BookDTO(Long id, String title, int pages, String nameAuthor) {\n    public static BookDTO from(Book book) {\n        return new BookDTO(\n            book.getId(),\n            book.getTitle(),\n            book.getPages(),\n            book.getAuthor().getName() // achata o relacionamento -- o cliente da API nem precisa saber que existe uma tabela \"autores\"\n        );\n    }\n}\n\n// no service:\npublic List<BookDTO> listarAll() {\n    return bookRepository.findAllWithAuthor() // JOIN FETCH, capítulo 46 -- evita N+1 aqui!\n        .stream()\n        .map(BookDTO::from) // method reference, capítulo 13\n        .toList();\n}","fidelityText":"public record LivroDTO(Long id, String titulo, int paginas, String nomeAutor) { public static LivroDTO from(Livro livro) { return new LivroDTO( livro.getId(), livro.getTitulo(), livro.getPaginas(), livro.getAutor().getNome() // achata o relacionamento -- o cliente da API nem precisa saber que existe uma tabela \"autores\" ); } } // no service: public List<LivroDTO> listarTodos() { return livroRepository.buscarTodosComAutor() // JOIN FETCH, capítulo 46 -- evita N+1 aqui! .stream() .map(LivroDTO::from) // method reference, capítulo 13 .toList(); }","highlightedHtml":"<span class=\"kw\">public record</span> <span class=\"cls\">BookDTO</span>(<span class=\"kw\">Long</span> id, <span class=\"kw\">String</span> title, <span class=\"kw\">int</span> pages, <span class=\"kw\">String</span> nameAuthor) {\n    <span class=\"kw\">public static</span> <span class=\"cls\">BookDTO</span> <span class=\"fn\">from</span>(<span class=\"cls\">Book</span> book) {\n        <span class=\"kw\">return new</span> <span class=\"cls\">BookDTO</span>(\n            book.getId(),\n            book.getTitle(),\n            book.getPages(),\n            book.getAuthor().getName() <span class=\"com\">// achata o relacionamento -- o cliente da API nem precisa saber que existe uma tabela \"autores\"</span>\n        );\n    }\n}\n\n<span class=\"com\">// no service:</span>\n<span class=\"kw\">public</span> List&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">listarAll</span>() {\n    <span class=\"kw\">return</span> bookRepository.findAllWithAuthor() <span class=\"com\">// JOIN FETCH, capítulo 46 -- evita N+1 aqui!</span>\n        .stream()\n        .map(BookDTO::from) <span class=\"com\">// method reference, capítulo 13</span>\n        .toList();\n}","caption":"Exemplo executável de dto-mapping.","explanation":["Mapper manual deixa cada campo visível e revisável.","Campos ausentes no DTO não podem ser alterados pelo cliente por acidente."],"commonMistakes":["Copiar todos os campos sem decidir contrato","Colocar validação de domínio dentro do mapper"]},{"id":"dto-mapping-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>MapStruct — automatizando o boilerplate óbvio</h2>","fidelityText":"MapStruct — automatizando o boilerplate óbvio"},{"id":"dto-mapping-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Mapeamento manual é ótimo para entender o problema, mas em uma entidade com 15 campos vira repetição cansativa. <strong>MapStruct</strong> gera o código de mapeamento em tempo de <em>compilação</em> (não reflection em runtime, ao contrário do Jackson) — rápido e com erros pegos ainda em build.</p>","fidelityText":"Mapeamento manual é ótimo para entender o problema, mas em uma entidade com 15 campos vira repetição cansativa. MapStruct gera o código de mapeamento em tempo de compilação (não reflection em runtime, ao contrário do Jackson) — rápido e com erros pegos ainda em build."},{"id":"dto-mapping-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"@Mapper(componentModel = \"spring\") // gera um @Component pronto para injeção!\npublic interface BookMapper {\n    @Mapping(target = \"nameAuthor\", source = \"author.name\") // campo com nome diferente\n    BookDTO toDTO(Book book);\n\n    List<BookDTO> toDTOList(List<Book> books);\n}\n// MapStruct gera, em tempo de compilação, uma classe LivroMapperImpl\n// com exatamente o código que você escreveria manualmente -- sem custo\n// de reflection em runtime, ao contrário do Jackson (capítulo 25)","fidelityText":"@Mapper(componentModel = \"spring\") // gera um @Component pronto para injeção! public interface LivroMapper { @Mapping(target = \"nomeAutor\", source = \"autor.nome\") // campo com nome diferente LivroDTO toDTO(Livro livro); List<LivroDTO> toDTOList(List<Livro> livros); } // MapStruct gera, em tempo de compilação, uma classe LivroMapperImpl // com exatamente o código que você escreveria manualmente -- sem custo // de reflection em runtime, ao contrário do Jackson (capítulo 25)","highlightedHtml":"<span class=\"annotation\">@Mapper</span>(componentModel = <span class=\"str\">\"spring\"</span>) <span class=\"com\">// gera um @Component pronto para injeção!</span>\n<span class=\"kw\">public interface</span> <span class=\"cls\">BookMapper</span> {\n    <span class=\"annotation\">@Mapping</span>(target = <span class=\"str\">\"nameAuthor\"</span>, source = <span class=\"str\">\"author.name\"</span>) <span class=\"com\">// campo com nome diferente</span>\n    <span class=\"cls\">BookDTO</span> <span class=\"fn\">toDTO</span>(<span class=\"cls\">Book</span> book);\n\n    List&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">toDTOList</span>(List&lt;<span class=\"cls\">Book</span>&gt; books);\n}\n<span class=\"com\">// MapStruct gera, em tempo de compilação, uma classe LivroMapperImpl\n// com exatamente o código que você escreveria manualmente -- sem custo\n// de reflection em runtime, ao contrário do Jackson (capítulo 25)</span>","caption":"Exemplo executável de dto-mapping.","explanation":["MapStruct gera implementação em compilação para mappings repetitivos.","Mesmo com ferramenta, o contrato de origem/destino precisa ser intencional."],"commonMistakes":["Usar mapper para esconder regra complexa","Não testar campos sensíveis omitidos"]},{"id":"dto-mapping-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A diferença entre MapStruct e Jackson não é só de propósito (mapear Java↔Java vs Java↔JSON) — é de <strong>quando</strong> o trabalho acontece. Jackson usa reflection em <em>runtime</em>, a cada chamada. MapStruct gera código Java <em>de verdade</em> durante a compilação (você pode literalmente abrir o <code>.class</code> gerado e ver um método comum, sem reflection nenhuma) — por isso é consideravelmente mais rápido em cenários de alto volume.</div>","fidelityText":"A diferença entre MapStruct e Jackson não é só de propósito (mapear Java↔Java vs Java↔JSON) — é de quando o trabalho acontece. Jackson usa reflection em runtime, a cada chamada. MapStruct gera código Java de verdade durante a compilação (você pode literalmente abrir o .class gerado e ver um método comum, sem reflection nenhuma) — por isso é consideravelmente mais rápido em cenários de alto volume."},{"id":"dto-mapping-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Comece sempre mapeando manualmente (como no primeiro exemplo) até o padrão da sua entidade estabilizar. Só introduza MapStruct quando o mapeamento manual virar repetição óbvia entre várias entidades parecidas — ferramentas de geração de código economizam tempo, mas só valem a pena depois que você já entende exatamente o que estão automatizando.</div>","fidelityText":"Comece sempre mapeando manualmente (como no primeiro exemplo) até o padrão da sua entidade estabilizar. Só introduza MapStruct quando o mapeamento manual virar repetição óbvia entre várias entidades parecidas — ferramentas de geração de código economizam tempo, mas só valem a pena depois que você já entende exatamente o que estão automatizando."},{"id":"dto-mapping-exercise-12","type":"exercise","authorship":"legacy-preserved","title":"Exercício 47.1 — DTO manual vs MapStruct","prompt":"Escreva o LivroDTO (record) e o mapeamento manual from(Livro) como no exemplo. Depois, reescreva o mesmo mapeamento usando uma interface @Mapper do MapStruct. Compare as duas abordagens e escreva, em uma frase, quando cada uma faz mais sentido.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 47.1 — DTO manual vs MapStructmédio Escreva o LivroDTO (record) e o mapeamento manual from(Livro) como no exemplo. Depois, reescreva o mesmo mapeamento usando uma interface @Mapper do MapStruct. Compare as duas abordagens e escreva, em uma frase, quando cada uma faz mais sentido. Ver solução Ver os dois exemplos completos acima. Manual faz mais sentido em projetos pequenos ou quando o mapeamento tem lógica especial não-trivial (cálculos, formatação condicional). MapStruct compensa quando existem muitas entidades com muitos campos e o mapeamento é majoritariamente \"campo bate com campo\", já que elimina a repetição sem custo de performance em runtime.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 47.1 — DTO manual vs MapStruct</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva o <code>LivroDTO</code> (record) e o mapeamento manual <code>from(Livro)</code> como no exemplo. Depois, reescreva o mesmo mapeamento usando uma interface <code>@Mapper</code> do MapStruct. Compare as duas abordagens e escreva, em uma frase, quando cada uma faz mais sentido.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Ver os dois exemplos completos acima. <strong>Manual</strong> faz mais sentido em projetos pequenos ou quando o mapeamento tem lógica especial não-trivial (cálculos, formatação condicional). <strong>MapStruct</strong> compensa quando existem muitas entidades com muitos campos e o mapeamento é majoritariamente \"campo bate com campo\", já que elimina a repetição sem custo de performance em runtime.</p>\n        </div>\n      </div>"},{"id":"dto-mapping-quiz","type":"quiz","authorship":"authored","conceptId":"dto-entity-boundary-spring","prompt":"Por que não devolver Entity JPA diretamente como response?","options":[{"id":"dto-a","label":"Porque contrato HTTP e persistência evoluem por motivos diferentes e expor entity vaza detalhes internos.","correct":true,"explanation":"DTO preserva boundary e reduz acoplamento."},{"id":"dto-b","label":"Porque JSON não consegue representar objetos Java simples.","correct":false,"explanation":"Serialização representa objetos; o problema é design e segurança."},{"id":"dto-c","label":"Porque repository só aceita records como retorno.","correct":false,"explanation":"Repository pode retornar entities/projeções; a escolha do contrato é sua."}]}],"resources":[{"id":"mapstruct-reference","type":"reference","title":"MapStruct Reference Guide","url":"https://mapstruct.org/documentation/stable/reference/html/","reinforces":"Geração de mappers, convenções e limites do mapeamento em compilação.","language":"en","publisher":"MapStruct","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-request-body","type":"reference","title":"Spring MVC: @RequestBody","url":"https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/requestbody.html","reinforces":"Binding de corpo HTTP para objetos de entrada.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The dto mapping component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to dto mapping. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible dto mapping failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"public record BookDTO(Long id, String title, int pages, String nameAuthor) {","instruction":"The dto mapping component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible dto mapping failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"validacao-erros","moduleId":"spring-api","order":9,"title":"Bean Validation & tratamento global de erros","summary":"Duas peças que sempre andam juntas: validar o que entra na API, e tratar de forma consistente o que dá errado — sem nenhuma delas, uma API real vira um campo minado de 500 Internal Server Error genéricos e stack traces vazando para o cliente.","objectives":["Aplicar Bean Validation na fronteira de entrada","Diferenciar erro de formato, regra e estado","Centralizar tradução com ControllerAdvice","Publicar erros estáveis sem stack trace"],"whyItExists":"Depois de DTO, a API precisa rejeitar entrada inválida e comunicar falhas sem vazar implementação. Validação e erro são parte do contrato, não detalhes decorativos.","prerequisiteChapterIds":["dto-mapping","excecoes"],"conceptIds":["bean-validation-seu-naonulo-oficial","controlleradvice-centralizando-o-tratamento-de-erro"],"introducedConceptIds":["bean-validation-boundary","controller-advice-problem-details"],"usedConceptIds":["mvc-response-status-contract","causa-excecao","dto-entity-boundary-spring"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"validacao-erros-intuition","type":"intuition","authorship":"authored","title":"Erro bom orienta o próximo passo do cliente","body":"Bean Validation verifica formato e restrições de entrada. Regras de domínio verificam estado e intenção. ControllerAdvice traduz exceções para uma resposta HTTP previsível, sem expor stack trace.","analogyLimit":"Mensagem amigável ajuda, mas o contrato também precisa de status, tipo, correlação e estabilidade."},{"id":"validacao-erros-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-mvc\">44 · Spring Web/MVC</a>, <a class=\"prereq-tag\" href=\"#anotacoes\">20 · Anotações &amp; Reflection</a>, <a class=\"prereq-tag\" href=\"#excecoes\">10 · Exceções</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~2h de estudo + prática Pré-requisitos: 44 · Spring Web/MVC, 20 · Anotações & Reflection, 10 · Exceções"},{"id":"validacao-erros-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Duas peças que sempre andam juntas: validar o que entra na API, e tratar de forma consistente o que dá errado — sem nenhuma delas, uma API real vira um campo minado de <code>500 Internal Server Error</code> genéricos e stack traces vazando para o cliente.</p>","fidelityText":"Duas peças que sempre andam juntas: validar o que entra na API, e tratar de forma consistente o que dá errado — sem nenhuma delas, uma API real vira um campo minado de 500 Internal Server Error genéricos e stack traces vazando para o cliente."},{"id":"validacao-erros-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Bean Validation — seu @NaoNulo, oficial</h2>","fidelityText":"Bean Validation — seu @NaoNulo, oficial"},{"id":"validacao-erros-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Lembra do <code>@NaoNulo</code> que você construiu no exercício 20.1? Isso é exatamente o Bean Validation, versão oficial e madura.</p>","fidelityText":"Lembra do @NaoNulo que você construiu no exercício 20.1? Isso é exatamente o Bean Validation, versão oficial e madura."},{"id":"validacao-erros-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"public record BookDTO(\n    @NotBlank(message = \"Title required\")\n    String title,\n\n    @Positive(message = \"Pages must be a value positive\")\n    int pages,\n\n    @Email\n    String emailContactPublisher\n) {}\n\n@PostMapping\npublic ResponseEntity<BookDTO> create(@Valid @RequestBody BookDTO dto) {\n    // se a validação falhar, o método NUNCA é executado --\n    // o Spring já responde 400 Bad Request antes de chegar aqui\n    return ResponseEntity.ok(service.save(dto));\n}","fidelityText":"public record LivroDTO( @NotBlank(message = \"Título obrigatório\") String titulo, @Positive(message = \"Páginas deve ser um valor positivo\") int paginas, @Email String emailContatoEditora ) {} @PostMapping public ResponseEntity<LivroDTO> criar(@Valid @RequestBody LivroDTO dto) { // se a validação falhar, o método NUNCA é executado -- // o Spring já responde 400 Bad Request antes de chegar aqui return ResponseEntity.ok(servico.salvar(dto)); }","highlightedHtml":"<span class=\"kw\">public record</span> <span class=\"cls\">BookDTO</span>(\n    <span class=\"annotation\">@NotBlank</span>(message = <span class=\"str\">\"Title required\"</span>)\n    <span class=\"kw\">String</span> title,\n\n    <span class=\"annotation\">@Positive</span>(message = <span class=\"str\">\"Pages must be a value positive\"</span>)\n    <span class=\"kw\">int</span> pages,\n\n    <span class=\"annotation\">@Email</span>\n    <span class=\"kw\">String</span> emailContactPublisher\n) {}\n\n<span class=\"annotation\">@PostMapping</span>\n<span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">create</span>(<span class=\"annotation\">@Valid</span> <span class=\"annotation\">@RequestBody</span> <span class=\"cls\">BookDTO</span> dto) {\n    <span class=\"com\">// se a validação falhar, o método NUNCA é executado --\n    // o Spring já responde 400 Bad Request antes de chegar aqui</span>\n    <span class=\"kw\">return</span> ResponseEntity.ok(service.save(dto));\n}","caption":"Exemplo executável de validacao-erros.","explanation":["@Valid aciona validação do DTO na fronteira do controller.","Anotações como @NotBlank descrevem restrições de entrada, não todo o domínio."],"commonMistakes":["Validar entity JPA como request público","Achar que @Valid substitui regra de negócio"]},{"id":"validacao-erros-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Anotação</th><th>Valida</th></tr>\n        <tr><td><code>@NotNull</code></td><td>Não pode ser <code>null</code></td></tr>\n        <tr><td><code>@NotBlank</code></td><td>String não nula e não vazia (ignorando espaços)</td></tr>\n        <tr><td><code>@Positive</code> / <code>@Min</code> / <code>@Max</code></td><td>Restrições numéricas</td></tr>\n        <tr><td><code>@Email</code></td><td>Formato de e-mail válido</td></tr>\n        <tr><td><code>@Size(min=, max=)</code></td><td>Tamanho de String/Coleção</td></tr>\n      </tbody></table>","fidelityText":"AnotaçãoValida @NotNullNão pode ser null @NotBlankString não nula e não vazia (ignorando espaços) @Positive / @Min / @MaxRestrições numéricas @EmailFormato de e-mail válido @Size(min=, max=)Tamanho de String/Coleção"},{"id":"validacao-erros-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>@ControllerAdvice — centralizando o tratamento de erro</h2>","fidelityText":"@ControllerAdvice — centralizando o tratamento de erro"},{"id":"validacao-erros-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Sem isso, cada controller precisaria de seu próprio <code>try/catch</code> repetido. Um <code>@ControllerAdvice</code> intercepta exceções lançadas em <strong>qualquer</strong> controller da aplicação, um único lugar para decidir como cada tipo de erro vira resposta HTTP — usando <code>ProblemDetail</code>, o formato padronizado da <a href=\"https://www.rfc-editor.org/rfc/rfc9457\" target=\"_blank\" rel=\"noopener\">RFC 9457</a> para erros HTTP, em vez de inventar um DTO de erro próprio que cada cliente da API precisaria aprender do zero.</p>","fidelityText":"Sem isso, cada controller precisaria de seu próprio try/catch repetido. Um @ControllerAdvice intercepta exceções lançadas em qualquer controller da aplicação, um único lugar para decidir como cada tipo de erro vira resposta HTTP — usando ProblemDetail, o formato padronizado da RFC 9457 para erros HTTP, em vez de inventar um DTO de erro próprio que cada cliente da API precisaria aprender do zero."},{"id":"validacao-erros-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"@ControllerAdvice\npublic class GlobalExceptionHandler {\n\n    @ExceptionHandler(ItemUnavailableException.class) // sua exceção do capítulo 17!\n    public ProblemDetail handleItemUnavailable(ItemUnavailableException e) {\n        ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.CONFLICT); // 409\n        problema.setTitle(\"item unavailable\");\n        problema.setDetail(e.getMessage());\n        problema.setProperty(\"code\", \"item.unavailable\");\n        return problema;\n    }\n\n    @ExceptionHandler(MethodArgumentNotValidException.class) // disparada por @Valid quando falha\n    public ProblemDetail handleValidation(MethodArgumentNotValidException e) {\n        ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); // 400\n        problema.setTitle(\"Data invalid\");\n        problema.setProperty(\"code\", \"validation.field-invalid\");\n        problema.setProperty(\"errors\", e.getBindingResult().getFieldErrors().stream()\n            .map(error -> error.getField() + \": \" + error.getDefaultMessage())\n            .toList()); // streams, capítulo 13\n        return problema;\n    }\n\n    @ExceptionHandler(Exception.class) // rede de segurança -- NUNCA deixe stack trace vazar\n    public ProblemDetail handleErrorGeneric(Exception e) {\n        ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR); // 500\n        problema.setTitle(\"internal error\");\n        problema.setDetail(\"Something gave wrong. Tente again.\");\n        problema.setProperty(\"code\", \"error.internal\");\n        return problema;\n    }\n}","fidelityText":"@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ItemIndisponivelException.class) // sua exceção do capítulo 17! public ProblemDetail tratarItemIndisponivel(ItemIndisponivelException e) { ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.CONFLICT); // 409 problema.setTitle(\"Item indisponível\"); problema.setDetail(e.getMessage()); problema.setProperty(\"codigo\", \"item.indisponivel\"); return problema; } @ExceptionHandler(MethodArgumentNotValidException.class) // disparada por @Valid quando falha public ProblemDetail tratarValidacao(MethodArgumentNotValidException e) { ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); // 400 problema.setTitle(\"Dados inválidos\"); problema.setProperty(\"codigo\", \"validacao.campo-invalido\"); problema.setProperty(\"erros\", e.getBindingResult().getFieldErrors().stream() .map(erro -> erro.getField() + \": \" + erro.getDefaultMessage()) .toList()); // streams, capítulo 13 return problema; } @ExceptionHandler(Exception.class) // rede de segurança -- NUNCA deixe stack trace vazar public ProblemDetail tratarErroGenerico(Exception e) { ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR); // 500 problema.setTitle(\"Erro interno\"); problema.setDetail(\"Algo deu errado. Tente novamente.\"); problema.setProperty(\"codigo\", \"erro.interno\"); return problema; } }","highlightedHtml":"<span class=\"annotation\">@ControllerAdvice</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">GlobalExceptionHandler</span> {\n\n    <span class=\"annotation\">@ExceptionHandler</span>(<span class=\"cls\">ItemUnavailableException</span>.<span class=\"kw\">class</span>) <span class=\"com\">// sua exceção do capítulo 17!</span>\n    <span class=\"kw\">public</span> ProblemDetail <span class=\"fn\">handleItemUnavailable</span>(<span class=\"cls\">ItemUnavailableException</span> e) {\n        ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.CONFLICT); <span class=\"com\">// 409</span>\n        problema.setTitle(<span class=\"str\">\"item unavailable\"</span>);\n        problema.setDetail(e.getMessage());\n        problema.setProperty(<span class=\"str\">\"code\"</span>, <span class=\"str\">\"item.unavailable\"</span>);\n        <span class=\"kw\">return</span> problema;\n    }\n\n    <span class=\"annotation\">@ExceptionHandler</span>(MethodArgumentNotValidException.<span class=\"kw\">class</span>) <span class=\"com\">// disparada por @Valid quando falha</span>\n    <span class=\"kw\">public</span> ProblemDetail <span class=\"fn\">handleValidation</span>(MethodArgumentNotValidException e) {\n        ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); <span class=\"com\">// 400</span>\n        problema.setTitle(<span class=\"str\">\"Data invalid\"</span>);\n        problema.setProperty(<span class=\"str\">\"code\"</span>, <span class=\"str\">\"validation.field-invalid\"</span>);\n        problema.setProperty(<span class=\"str\">\"errors\"</span>, e.getBindingResult().getFieldErrors().stream()\n            .map(error -&gt; error.getField() + <span class=\"str\">\": \"</span> + error.getDefaultMessage())\n            .toList()); <span class=\"com\">// streams, capítulo 13</span>\n        <span class=\"kw\">return</span> problema;\n    }\n\n    <span class=\"annotation\">@ExceptionHandler</span>(<span class=\"cls\">Exception</span>.<span class=\"kw\">class</span>) <span class=\"com\">// rede de segurança -- NUNCA deixe stack trace vazar</span>\n    <span class=\"kw\">public</span> ProblemDetail <span class=\"fn\">handleErrorGeneric</span>(<span class=\"cls\">Exception</span> e) {\n        ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR); <span class=\"com\">// 500</span>\n        problema.setTitle(<span class=\"str\">\"internal error\"</span>);\n        problema.setDetail(<span class=\"str\">\"Something gave wrong. Tente again.\"</span>);\n        problema.setProperty(<span class=\"str\">\"code\"</span>, <span class=\"str\">\"error.internal\"</span>);\n        <span class=\"kw\">return</span> problema;\n    }\n}","caption":"Exemplo executável de validacao-erros.","explanation":["@ControllerAdvice centraliza tradução de exceções para respostas HTTP.","ProblemDetail padroniza type, title, status e detalhes úteis."],"commonMistakes":["Capturar Exception genérica e esconder causa","Expor stack trace no body"]},{"id":"validacao-erros-content-10","type":"html","authorship":"legacy-preserved","html":"<p><code>ProblemDetail</code> traz de fábrica os campos padronizados pela RFC (<code>type</code>, <code>title</code>, <code>status</code>, <code>detail</code>, <code>instance</code>) e ainda permite <code>setProperty(...)</code> para estender com campos próprios do seu domínio (<code>codigo</code>, <code>erros</code>) — um cliente HTTP genérico já sabe interpretar os campos padrão sem nem conhecer sua API específica, exatamente o ganho de usar um formato interoperável em vez de um <code>record</code> de erro inventado.</p>","fidelityText":"ProblemDetail traz de fábrica os campos padronizados pela RFC (type, title, status, detail, instance) e ainda permite setProperty(...) para estender com campos próprios do seu domínio (codigo, erros) — um cliente HTTP genérico já sabe interpretar os campos padrão sem nem conhecer sua API específica, exatamente o ganho de usar um formato interoperável em vez de um record de erro inventado."},{"id":"validacao-erros-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Repare que os <code>@ExceptionHandler</code> mais específicos deveriam vir antes do genérico — exatamente a mesma regra dos blocos <code>catch</code> em cascata do capítulo 10 (exercício 10.2), só que agora aplicada a nível de aplicação inteira em vez de um único método. O <code>@ControllerAdvice</code> é, no fundo, um dynamic proxy interceptando exceções não capturadas — o mesmo mecanismo do capítulo 20.</div>","fidelityText":"Repare que os @ExceptionHandler mais específicos deveriam vir antes do genérico — exatamente a mesma regra dos blocos catch em cascata do capítulo 10 (exercício 10.2), só que agora aplicada a nível de aplicação inteira em vez de um único método. O @ControllerAdvice é, no fundo, um dynamic proxy interceptando exceções não capturadas — o mesmo mecanismo do capítulo 20."},{"id":"validacao-erros-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Nunca inclua a mensagem original da exceção genérica (<code>e.getMessage()</code>) na resposta do handler de <code>Exception.class</code></b> — ela pode conter detalhes internos (nome de tabela, query SQL, caminho de arquivo) que ajudam um atacante a entender sua infraestrutura. Log o detalhe internamente, devolva uma mensagem genérica para o cliente.</div>","fidelityText":"Nunca inclua a mensagem original da exceção genérica (e.getMessage()) na resposta do handler de Exception.class — ela pode conter detalhes internos (nome de tabela, query SQL, caminho de arquivo) que ajudam um atacante a entender sua infraestrutura. Log o detalhe internamente, devolva uma mensagem genérica para o cliente."},{"id":"validacao-erros-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Trate seu <code>@ControllerAdvice</code> como o \"cardápio de erros\" da sua API — cada tipo de falha do domínio (item indisponível, saldo insuficiente, validação) deveria ter uma entrada clara, assim como você documentaria diferentes tipos de exceção customizada no capítulo 10. Um bom teste: se alguém que nunca viu seu código conseguir entender o que deu errado só pelo <code>codigo</code> retornado, o design está bom.</div>","fidelityText":"Trate seu @ControllerAdvice como o \"cardápio de erros\" da sua API — cada tipo de falha do domínio (item indisponível, saldo insuficiente, validação) deveria ter uma entrada clara, assim como você documentaria diferentes tipos de exceção customizada no capítulo 10. Um bom teste: se alguém que nunca viu seu código conseguir entender o que deu errado só pelo codigo retornado, o design está bom."},{"id":"validacao-erros-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 48.1 — Validação + erro end-to-end","prompt":"Adicione validação Bean Validation ao LivroDTO (título obrigatório, páginas positivas). Escreva um @ControllerAdvice tratando MethodArgumentNotValidException (400) e ItemIndisponivelException (409), mais um handler genérico para Exception (500, sem vazar detalhes internos) — todos devolvendo ProblemDetail.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 48.1 — Validação + erro end-to-enddifícil Adicione validação Bean Validation ao LivroDTO (título obrigatório, páginas positivas). Escreva um @ControllerAdvice tratando MethodArgumentNotValidException (400) e ItemIndisponivelException (409), mais um handler genérico para Exception (500, sem vazar detalhes internos) — todos devolvendo ProblemDetail. Ver solução Ver o GlobalExceptionHandler completo acima — cobre exatamente os três casos pedidos.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 48.1 — Validação + erro end-to-end</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Adicione validação Bean Validation ao <code>LivroDTO</code> (título obrigatório, páginas positivas). Escreva um <code>@ControllerAdvice</code> tratando <code>MethodArgumentNotValidException</code> (400) e <code>ItemIndisponivelException</code> (409), mais um handler genérico para <code>Exception</code> (500, sem vazar detalhes internos) — todos devolvendo <code>ProblemDetail</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Ver o <code>GlobalExceptionHandler</code> completo acima — cobre exatamente os três casos pedidos.</p>\n        </div>\n      </div>"},{"id":"validacao-erros-quiz","type":"quiz","authorship":"authored","conceptId":"controller-advice-problem-details","prompt":"Qual resposta é melhor para erro de validação em API pública?","options":[{"id":"val-a","label":"Um formato estável com status, campos inválidos e mensagem acionável, sem stack trace.","correct":true,"explanation":"Cliente consegue tratar programaticamente e usuário entende correção."},{"id":"val-b","label":"Sempre 500 com e.getMessage() bruto.","correct":false,"explanation":"Isso mistura erro de cliente com falha interna e vaza detalhe."},{"id":"val-c","label":"Sempre 200 com success=false.","correct":false,"explanation":"Status HTTP deixa de comunicar semântica para clientes e ferramentas."}]}],"resources":[{"id":"spring-mvc-validation","type":"reference","title":"Spring MVC: Validation","url":"https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-validation.html","reinforces":"Validação de controller, Bean Validation e tratamento de erros de entrada.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-controller-advice","type":"reference","title":"Spring MVC: Controller Advice","url":"https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-advice.html","reinforces":"Tratamento global de exceções e respostas consistentes.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The validation errors component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to validation errors. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible validation errors failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"public record BookDTO(","instruction":"The validation errors component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible validation errors failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"paginacao-swagger","moduleId":"spring-api","order":10,"title":"Paginação, filtros & documentação com Swagger","summary":"O List<Livro> findAll() do capítulo 45 funciona bem com 20 livros de exemplo. Com 50 mil, devolver tudo de uma vez derruba a API e o front-end. Paginação é obrigatória em qualquer listagem real.","objectives":["Publicar paginação com limite e ordenação estável","Entender Pageable sem prometer custo invisível","Documentar schemas, exemplos e erros com OpenAPI","Evitar documentação divergente do comportamento real"],"whyItExists":"Depois que a API devolve dados e erros, ela precisa escalar contrato de listagem e documentação. Paginação e OpenAPI reduzem surpresa para clientes e protegem o servidor.","prerequisiteChapterIds":["validacao-erros","spring-jpa"],"conceptIds":["swagger-openapi-documentacao-que-se-atualiza-sozinha"],"introducedConceptIds":["pagination-sorting-contract","openapi-documentation-contract"],"usedConceptIds":["rate-limit-paginacao","springdata-repository-contract","http-header-body-negociacao"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"paginacao-swagger-intuition","type":"intuition","authorship":"authored","title":"Listagem sem limite vira contrato perigoso","body":"Paginação define quanto o cliente pode pedir, em que ordem e como avançar. OpenAPI registra esse contrato para humanos e ferramentas, mas só vale se exemplos e testes refletirem a API real.","analogyLimit":"Índice de livro ajuda a imaginar páginas, mas bancos exigem ordenação estável, índices e limites."},{"id":"paginacao-swagger-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-jpa\">45 · Spring Data JPA</a>, <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; REST</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~1h30 de estudo + prática Pré-requisitos: 45 · Spring Data JPA, 26 · HTTP & REST"},{"id":"paginacao-swagger-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O <code>List&lt;Livro&gt; findAll()</code> do capítulo 45 funciona bem com 20 livros de exemplo. Com 50 mil, devolver tudo de uma vez derruba a API e o front-end. <strong>Paginação</strong> é obrigatória em qualquer listagem real.</p>","fidelityText":"O List<Livro> findAll() do capítulo 45 funciona bem com 20 livros de exemplo. Com 50 mil, devolver tudo de uma vez derruba a API e o front-end. Paginação é obrigatória em qualquer listagem real."},{"id":"paginacao-swagger-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em paginação como folhear um livro físico grosso em vez de tentar ler todas as 900 páginas simultaneamente coladas na parede. Você pede \"me dá a página 3, com 20 linhas por página\" em vez de \"me dá o livro inteiro de uma vez\" — o Spring Data já entende esse pedido nativamente.</div>","fidelityText":"Pense em paginação como folhear um livro físico grosso em vez de tentar ler todas as 900 páginas simultaneamente coladas na parede. Você pede \"me dá a página 3, com 20 linhas por página\" em vez de \"me dá o livro inteiro de uma vez\" — o Spring Data já entende esse pedido nativamente."},{"id":"paginacao-swagger-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"public interface BookRepository extends JpaRepository<Book, Long> {\n    Page<Book> findByTitleContainingIgnoreCase(String title, Pageable pageable);\n}\n\n@GetMapping\npublic Page<BookDTO> listar(\n        @RequestParam(required = false) String title,\n        @PageableDefault(size = 20, sort = \"title\") Pageable pageable) {\n    return bookRepository\n        .findByTitleContainingIgnoreCase(title == null ? \"\" : title, pageable)\n        .map(BookDTO::from);\n}\n// GET /livros?page=0&size=20&sort=titulo,desc&titulo=duna","fidelityText":"public interface LivroRepository extends JpaRepository<Livro, Long> { Page<Livro> findByTituloContainingIgnoreCase(String titulo, Pageable pageable); } @GetMapping public Page<LivroDTO> listar( @RequestParam(required = false) String titulo, @PageableDefault(size = 20, sort = \"titulo\") Pageable pageable) { return livroRepository .findByTituloContainingIgnoreCase(titulo == null ? \"\" : titulo, pageable) .map(LivroDTO::from); } // GET /livros?page=0&size=20&sort=titulo,desc&titulo=duna","highlightedHtml":"<span class=\"kw\">public interface</span> <span class=\"cls\">BookRepository</span> <span class=\"kw\">extends</span> JpaRepository&lt;<span class=\"cls\">Book</span>, <span class=\"kw\">Long</span>&gt; {\n    Page&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findByTitleContainingIgnoreCase</span>(<span class=\"kw\">String</span> title, Pageable pageable);\n}\n\n<span class=\"annotation\">@GetMapping</span>\n<span class=\"kw\">public</span> Page&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">listar</span>(\n        <span class=\"annotation\">@RequestParam</span>(required = <span class=\"kw\">false</span>) <span class=\"kw\">String</span> title,\n        <span class=\"annotation\">@PageableDefault</span>(size = 20, sort = <span class=\"str\">\"title\"</span>) Pageable pageable) {\n    <span class=\"kw\">return</span> bookRepository\n        .findByTitleContainingIgnoreCase(title == <span class=\"kw\">null</span> ? <span class=\"str\">\"\"</span> : title, pageable)\n        .map(BookDTO::from);\n}\n<span class=\"com\">// GET /livros?page=0&amp;size=20&amp;sort=titulo,desc&amp;titulo=duna</span>","caption":"Exemplo executável de paginacao-swagger.","explanation":["Pageable recebe page, size e sort e integra com repositories Spring Data.","A API deve impor limites e ordenar de forma previsível."],"commonMistakes":["Aceitar size enorme","Ordenar por campo não indexado ou sensível"]},{"id":"paginacao-swagger-content-5","type":"html","authorship":"legacy-preserved","html":"<p>O <code>Page&lt;T&gt;</code> retornado já vem com metadados prontos: total de elementos, total de páginas, se é a primeira/última — tudo que o front-end precisa para montar os controles de \"próxima página\".</p>","fidelityText":"O Page<T> retornado já vem com metadados prontos: total de elementos, total de páginas, se é a primeira/última — tudo que o front-end precisa para montar os controles de \"próxima página\"."},{"id":"paginacao-swagger-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Swagger/OpenAPI — documentação que se atualiza sozinha</h2>","fidelityText":"Swagger/OpenAPI — documentação que se atualiza sozinha"},{"id":"paginacao-swagger-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"<!-- pom.xml -->\n<dependency>\n    <groupId>org.springdoc</groupId>\n    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>\n    <version>${springdoc.version}</version>\n</dependency>\n<!-- defina springdoc.version no pom conforme a matriz de compatibilidade\n     da sua version do Spring Boot; acesse /swagger-ui.html -->","fidelityText":"<!-- pom.xml --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${springdoc.version}</version> </dependency> <!-- defina springdoc.version no pom conforme a matriz de compatibilidade da sua versão do Spring Boot; acesse /swagger-ui.html -->","highlightedHtml":"<span class=\"com\">&lt;!-- pom.xml --&gt;</span>\n&lt;dependency&gt;\n    &lt;groupId&gt;org.springdoc&lt;/groupId&gt;\n    &lt;artifactId&gt;springdoc-openapi-starter-webmvc-ui&lt;/artifactId&gt;\n    &lt;version&gt;${springdoc.version}&lt;/version&gt;\n&lt;/dependency&gt;\n<span class=\"com\">&lt;!-- defina springdoc.version no pom conforme a matriz de compatibilidade\n     da sua versão do Spring Boot; acesse /swagger-ui.html --&gt;</span>","caption":"Exemplo executável de paginacao-swagger.","explanation":["OpenAPI descreve endpoints, schemas, parâmetros e respostas.","O contrato documentado deve incluir exemplos de sucesso e erro."],"commonMistakes":["Documentar só status 200","Deixar schema diferente do DTO real"]},{"id":"paginacao-swagger-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"@Operation(summary = \"List books with pagination and filter optional by title\")\n@GetMapping\npublic Page<BookDTO> listar(...) { ... }","fidelityText":"@Operation(summary = \"Lista livros com paginação e filtro opcional por título\") @GetMapping public Page<LivroDTO> listar(...) { ... }","highlightedHtml":"<span class=\"annotation\">@Operation</span>(summary = <span class=\"str\">\"List books with pagination and filter optional by title\"</span>)\n<span class=\"annotation\">@GetMapping</span>\n<span class=\"kw\">public</span> Page&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">listar</span>(...) { ... }","caption":"Exemplo executável de paginacao-swagger.","explanation":["Anotações enriquecem documentação, mas não substituem teste de comportamento.","Use descrições para explicar semântica, não para repetir nomes de campos."],"commonMistakes":["Usar Swagger como validação única","Não revisar documentação após mudar DTO"]},{"id":"paginacao-swagger-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O Swagger não lê \"documentação\" separada em lugar nenhum — ele usa reflection (capítulo 20, de novo) para inspecionar seus controllers, DTOs e anotações de Bean Validation em tempo de inicialização, e gera a interface interativa a partir disso. É por isso que a documentação nunca fica desatualizada em relação ao código: ela <em>é</em> o código, lido e apresentado de outra forma.</div>","fidelityText":"O Swagger não lê \"documentação\" separada em lugar nenhum — ele usa reflection (capítulo 20, de novo) para inspecionar seus controllers, DTOs e anotações de Bean Validation em tempo de inicialização, e gera a interface interativa a partir disso. É por isso que a documentação nunca fica desatualizada em relação ao código: ela é o código, lido e apresentado de outra forma."},{"id":"paginacao-swagger-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Nunca deixe <code>size</code> sem limite máximo configurável pelo cliente — sem isso, alguém pode pedir <code>?size=999999999</code> e derrubar o banco.</li>\n          <li>Sempre ordene resultados paginados de forma determinística (<code>sort=id</code> como fallback) — sem ordem fixa, a mesma página pode retornar itens diferentes entre requisições.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Nunca deixe size sem limite máximo configurável pelo cliente — sem isso, alguém pode pedir ?size=999999999 e derrubar o banco. Sempre ordene resultados paginados de forma determinística (sort=id como fallback) — sem ordem fixa, a mesma página pode retornar itens diferentes entre requisições."},{"id":"paginacao-swagger-exercise-11","type":"exercise","authorship":"legacy-preserved","title":"Exercício 49.1 — Endpoint paginado com filtro","prompt":"Adicione ao LivroRepository um método Page<Livro> findByTituloContainingIgnoreCase(String titulo, Pageable pageable). Escreva o endpoint GET /livros aceitando filtro opcional por título e paginação com tamanho padrão de 20, ordenado por título.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 49.1 — Endpoint paginado com filtromédio Adicione ao LivroRepository um método Page<Livro> findByTituloContainingIgnoreCase(String titulo, Pageable pageable). Escreva o endpoint GET /livros aceitando filtro opcional por título e paginação com tamanho padrão de 20, ordenado por título. Ver solução Além do query method, limite size no controller, imponha desempate por ID e teste filtro vazio, página além do fim e tentativa de tamanho excessivo. Como extensão, implemente cursor por (titulo,id) e explique por que é mais estável sob inserções concorrentes.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 49.1 — Endpoint paginado com filtro</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Adicione ao <code>LivroRepository</code> um método <code>Page&lt;Livro&gt; findByTituloContainingIgnoreCase(String titulo, Pageable pageable)</code>. Escreva o endpoint <code>GET /livros</code> aceitando filtro opcional por título e paginação com tamanho padrão de 20, ordenado por título.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Além do query method, limite <code>size</code> no controller, imponha desempate por ID e teste filtro vazio, página além do fim e tentativa de tamanho excessivo. Como extensão, implemente cursor por <code>(titulo,id)</code> e explique por que é mais estável sob inserções concorrentes.</p>\n        </div>\n      </div>"},{"id":"paginacao-swagger-quiz","type":"quiz","authorship":"authored","conceptId":"pagination-sorting-contract","prompt":"Qual cuidado torna paginação mais previsível?","options":[{"id":"pag-a","label":"Definir limite máximo e ordenação estável documentada.","correct":true,"explanation":"Sem ordem estável, itens podem repetir ou sumir entre páginas."},{"id":"pag-b","label":"Permitir size ilimitado porque Pageable já protege o banco.","correct":false,"explanation":"Pageable representa pedido; você ainda deve limitar custo."},{"id":"pag-c","label":"Omitir erros da documentação para deixar o Swagger limpo.","correct":false,"explanation":"Erros fazem parte do contrato que clientes precisam tratar."}]}],"resources":[{"id":"spring-data-web-pagination","type":"reference","title":"Spring Data Web Support","url":"https://docs.spring.io/spring-data/commons/reference/repositories/core-extensions.html","reinforces":"Integração de Pageable, Sort e suporte web do Spring Data.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"openapi-311","type":"reference","title":"OpenAPI Specification 3.1.1","url":"https://spec.openapis.org/oas/v3.1.1.html","reinforces":"Contrato formal de endpoints, schemas, parâmetros e respostas.","language":"en","publisher":"OpenAPI Initiative","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The pagination swagger component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to pagination swagger. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible pagination swagger failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"public interface BookRepository extends JpaRepository<Book, Long> {","instruction":"The pagination swagger component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible pagination swagger failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"api-design-avancado","moduleId":"spring-api","order":11,"title":"Contratos HTTP: idempotência, concorrência e evolução de APIs","summary":"Uma API é um contrato público: método, URI, headers, status, corpo e semântica de repetição formam uma unidade. Uma resposta válida não é apenas JSON que o cliente conseguiu desserializar.","objectives":["Projetar erros, compatibilidade e concorrência de contrato","Identificar breaking changes antes de publicar","Usar Problem Details, ETag e status adequados","Separar evolução interna de versão pública"],"whyItExists":"Com endpoints, DTOs e documentação no lugar, o próximo salto é desenhar API para clientes reais. O capítulo aprofunda compatibilidade, concorrência e semântica HTTP além do CRUD feliz.","prerequisiteChapterIds":["paginacao-swagger","jpa-transacoes"],"conceptIds":["a-requisicao-como-contrato-completo","problem-details","idempotencia-e-concorrencia-otimista-http","paginacao-e-evolucao"],"introducedConceptIds":["http-optimistic-concurrency","api-compatible-evolution"],"usedConceptIds":["controller-advice-problem-details","openapi-documentation-contract","mvcc-isolamento-lock"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"api-design-avancado-intuition","type":"intuition","authorship":"authored","title":"API publicada vira promessa para desconhecidos","body":"Depois que clientes dependem da API, mudar campo, status, enum ou erro pode quebrar automações. Design avançado é prever conflito, repetição, cache, versão e depreciação antes de virar incidente.","analogyLimit":"Contrato assinado ajuda a imaginar estabilidade, mas APIs também têm tolerância a campos extras e evolução incremental."},{"id":"api-design-avancado-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-spring\">Spring</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~4h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-mvc\">Spring MVC</a>, <a class=\"prereq-tag\" href=\"#validacao-erros\">Validação e erros</a></div></div>","fidelityText":"SpringDificuldade: Avançado⏱ ~4h de estudo e práticaPré-requisitos: Spring MVC, Validação e erros"},{"id":"api-design-avancado-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Uma API é um contrato público: método, URI, headers, status, corpo e semântica de repetição formam uma unidade. Uma resposta válida não é apenas JSON que o cliente conseguiu desserializar.</p>","fidelityText":"Uma API é um contrato público: método, URI, headers, status, corpo e semântica de repetição formam uma unidade. Uma resposta válida não é apenas JSON que o cliente conseguiu desserializar."},{"id":"api-design-avancado-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>A requisição como contrato completo</h2></div>\n    <p>Comece identificando as peças visíveis pelo cliente; depois acrescente garantias de repetição e concorrência.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Método e URI</dt><dd>O método expressa a intenção HTTP; a URI identifica o recurso sobre o qual ela atua.</dd></div><div class=\"concept-card\"><dt>Header</dt><dd>Metadado da mensagem, como tipo do conteúdo, autenticação ou condição de versão.</dd></div><div class=\"concept-card\"><dt>Status</dt><dd>Código que resume o resultado HTTP; clientes não deveriam deduzi-lo lendo uma frase.</dd></div><div class=\"concept-card\"><dt>Idempotência</dt><dd>Propriedade em que repetir a mesma intenção não produz efeitos adicionais indevidos.</dd></div><div class=\"concept-card\"><dt>ETag</dt><dd>Identificador da versão de uma representação; com If-Match permite rejeitar atualização baseada em versão antiga.</dd></div><div class=\"concept-card\"><dt>Correlation ID</dt><dd>Identificador seguro para localizar a mesma operação em logs e serviços, sem expor detalhes internos.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoA requisição como contrato completo Comece identificando as peças visíveis pelo cliente; depois acrescente garantias de repetição e concorrência. Método e URIO método expressa a intenção HTTP; a URI identifica o recurso sobre o qual ela atua.HeaderMetadado da mensagem, como tipo do conteúdo, autenticação ou condição de versão.StatusCódigo que resume o resultado HTTP; clientes não deveriam deduzi-lo lendo uma frase.IdempotênciaPropriedade em que repetir a mesma intenção não produz efeitos adicionais indevidos.ETagIdentificador da versão de uma representação; com If-Match permite rejeitar atualização baseada em versão antiga.Correlation IDIdentificador seguro para localizar a mesma operação em logs e serviços, sem expor detalhes internos."},{"id":"api-design-avancado-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Operação</th><th>Resposta típica</th><th>Detalhe</th></tr><tr><td>criar</td><td>201</td><td>inclua <code>Location</code> do novo recurso</td></tr><tr><td>substituir</td><td>200/204</td><td><code>PUT</code> deve ser idempotente</td></tr><tr><td>alterar parcialmente</td><td>200/204</td><td>defina semântica de PATCH e campos ausentes</td></tr><tr><td>conflito</td><td>409</td><td>invariante, versão ou idempotency key conflitante</td></tr><tr><td>pré-condição</td><td>412</td><td><code>If-Match</code>/<code>ETag</code> não corresponde</td></tr><tr><td>assíncrono</td><td>202</td><td>ofereça recurso para consultar estado</td></tr></tbody></table>","fidelityText":"OperaçãoResposta típicaDetalhecriar201inclua Location do novo recursosubstituir200/204PUT deve ser idempotentealterar parcialmente200/204defina semântica de PATCH e campos ausentesconflito409invariante, versão ou idempotency key conflitantepré-condição412If-Match/ETag não correspondeassíncrono202ofereça recurso para consultar estado"},{"id":"api-design-avancado-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Problem Details</h2>","fidelityText":"Problem Details"},{"id":"api-design-avancado-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"@ExceptionHandler(BalanceInsufficientException.class)\nProblemDetail balanceInsufficient(BalanceInsufficientException e) {\n    ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.CONFLICT);\n    problema.setTitle(\"insufficient balance\");\n    problema.setDetail(e.getMessage());\n    problema.setProperty(\"code\", \"account.balance-insufficient\");\n    return problema;\n}","fidelityText":"@ExceptionHandler(SaldoInsuficienteException.class) ProblemDetail saldoInsuficiente(SaldoInsuficienteException e) { ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.CONFLICT); problema.setTitle(\"Saldo insuficiente\"); problema.setDetail(e.getMessage()); problema.setProperty(\"codigo\", \"conta.saldo-insuficiente\"); return problema; }","highlightedHtml":"<span class=\"annotation\">@ExceptionHandler</span>(BalanceInsufficientException.class)\nProblemDetail balanceInsufficient(BalanceInsufficientException e) {\n    ProblemDetail problema = ProblemDetail.forStatus(HttpStatus.CONFLICT);\n    problema.setTitle(<span class=\"str\">\"insufficient balance\"</span>);\n    problema.setDetail(e.getMessage());\n    problema.setProperty(<span class=\"str\">\"code\"</span>, <span class=\"str\">\"account.balance-insufficient\"</span>);\n    <span class=\"kw\">return</span> problema;\n}","caption":"Exemplo executável de api-design-avancado.","explanation":["ETag/If-Match permite rejeitar atualização baseada em versão antiga.","Isso evita lost update quando dois clientes alteram o mesmo recurso."],"commonMistakes":["Usar ETag só como cache e ignorar precondition","Aceitar última escrita vence sem regra clara"]},{"id":"api-design-avancado-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Nunca devolva stack trace, nome de tabela ou segredo. Forneça um código estável legível por máquina, detalhes seguros e um correlation ID. Erros de validação precisam indicar campo e regra sem ecoar dados sensíveis.</p>","fidelityText":"Nunca devolva stack trace, nome de tabela ou segredo. Forneça um código estável legível por máquina, detalhes seguros e um correlation ID. Erros de validação precisam indicar campo e regra sem ecoar dados sensíveis."},{"id":"api-design-avancado-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Idempotência e concorrência otimista HTTP</h2>","fidelityText":"Idempotência e concorrência otimista HTTP"},{"id":"api-design-avancado-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Para criação ou pagamento sujeito a <strong>retry</strong> — nova tentativa após falha ou resposta perdida — aceite <code>Idempotency-Key</code>. Persista chave, escopo, <strong>hash</strong> da requisição (resumo determinístico usado para comparar o conteúdo) e resposta na mesma transação da operação. Repetir a mesma intenção devolve o resultado original; reutilizar a chave com outro corpo é conflito.</p>","fidelityText":"Para criação ou pagamento sujeito a retry — nova tentativa após falha ou resposta perdida — aceite Idempotency-Key. Persista chave, escopo, hash da requisição (resumo determinístico usado para comparar o conteúdo) e resposta na mesma transação da operação. Repetir a mesma intenção devolve o resultado original; reutilizar a chave com outro corpo é conflito."},{"id":"api-design-avancado-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"AndTag: \"order-42-v7\"\nIf-Match: \"order-42-v7\"\n\n// se a versão atual for 8: 412 Precondition Failed","fidelityText":"ETag: \"pedido-42-v7\" If-Match: \"pedido-42-v7\" // se a versão atual for 8: 412 Precondition Failed","highlightedHtml":"AndTag: \"order-42-v7\"\nIf-Match: \"order-42-v7\"\n\n<span class=\"com\">// se a versão atual for 8: 412 Precondition Failed</span>","caption":"Exemplo executável de api-design-avancado.","explanation":["Problem Details cria estrutura interoperável para erro HTTP.","type, title, status e detalhe devem ser estáveis e úteis para suporte."],"commonMistakes":["Criar erro diferente em cada endpoint","Expor exceção interna como contrato"]},{"id":"api-design-avancado-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Paginação e evolução</h2>","fidelityText":"Paginação e evolução"},{"id":"api-design-avancado-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Na paginação por <strong>offset</strong>, o cliente pede para pular N linhas; é simples, mas fica caro e pode duplicar ou omitir itens se houver alterações. Na paginação por <strong>keyset/cursor</strong>, o cliente envia uma posição opaca derivada da última chave ordenada, como data e ID; a próxima consulta continua dali. Limite o tamanho no servidor e imponha ordem estável com desempate por ID. Mudanças aditivas costumam ser compatíveis, desde que clientes ignorem campos desconhecidos. Mudanças de significado exigem versão, transição e política de depreciação.</p>","fidelityText":"Na paginação por offset, o cliente pede para pular N linhas; é simples, mas fica caro e pode duplicar ou omitir itens se houver alterações. Na paginação por keyset/cursor, o cliente envia uma posição opaca derivada da última chave ordenada, como data e ID; a próxima consulta continua dali. Limite o tamanho no servidor e imponha ordem estável com desempate por ID. Mudanças aditivas costumam ser compatíveis, desde que clientes ignorem campos desconhecidos. Mudanças de significado exigem versão, transição e política de depreciação."},{"id":"api-design-avancado-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — endpoint de pagamento repetível","prompt":"Desenhe e implemente POST /pagamentos com idempotency key, Problem Details, 201 + Location e teste para duas requisições concorrentes com a mesma chave.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — endpoint de pagamento repetíveldifícilDesenhe e implemente POST /pagamentos com idempotency key, Problem Details, 201 + Location e teste para duas requisições concorrentes com a mesma chave.Ver critériosUma constraint única deve decidir a disputa. Não confie em um if em memória; ele falha com múltiplas threads ou instâncias.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — endpoint de pagamento repetível</h2><span class=\"exercise-tag d\">difícil</span></div><p>Desenhe e implemente <code>POST /pagamentos</code> com idempotency key, Problem Details, 201 + Location e teste para duas requisições concorrentes com a mesma chave.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>Uma constraint única deve decidir a disputa. Não confie em um <code>if</code> em memória; ele falha com múltiplas threads ou instâncias.</p></div></div>"},{"id":"api-design-avancado-quiz","type":"quiz","authorship":"authored","conceptId":"api-compatible-evolution","prompt":"Qual mudança tem maior chance de quebrar clientes?","options":[{"id":"api-a","label":"Renomear um campo obrigatório já publicado sem período de compatibilidade.","correct":true,"explanation":"Clientes que desserializam ou validam o campo antigo falham."},{"id":"api-b","label":"Adicionar campo opcional quando clientes toleram campos desconhecidos.","correct":false,"explanation":"Pode ser compatível se a política dos clientes permitir."},{"id":"api-c","label":"Melhorar log interno sem alterar contrato HTTP.","correct":false,"explanation":"Mudança interna não deveria afetar clientes."}]}],"resources":[{"id":"problem-details-rfc9457-api","type":"reference","title":"RFC 9457: Problem Details for HTTP APIs","url":"https://www.rfc-editor.org/rfc/rfc9457","reinforces":"Formato padronizado para erros HTTP interoperáveis.","language":"en","publisher":"IETF","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"http-semantics-rfc9110","type":"reference","title":"RFC 9110: HTTP Semantics","url":"https://www.rfc-editor.org/rfc/rfc9110","reinforces":"Semântica de métodos, status, representação e preconditions.","language":"en","publisher":"IETF","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The api design advanced component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to api design advanced. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible api design advanced failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"@ExceptionHandler(BalanceInsufficientException.class)","instruction":"The api design advanced component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible api design advanced failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"n-mais-1","moduleId":"spring-api","order":12,"title":"O problema N+1","summary":"Este é o bug de performance mais comum em aplicações Spring Data JPA — silencioso, fácil de introduzir, e devastador em produção com volume real de dados.","objectives":["Reconhecer N+1 pelo padrão de SQL gerado","Escolher fetch join, entity graph ou projeção conforme caso","Medir antes/depois com logs e plano de execução","Evitar EAGER global como remendo"],"whyItExists":"Depois de JPA e design de API, o aluno precisa ver como uma representação aparentemente simples pode disparar consultas repetidas. Performance aqui é consequência de modelagem, carregamento e contrato de resposta.","prerequisiteChapterIds":["spring-jpa","paginacao-swagger"],"conceptIds":["enxergando-o-problema-com-explain-e-logs","batch-fetching-quando-lazy-sozinho-ainda-e-lento-demais","n-1-paginacao-por-que-join-fetch-de-uma-colecao-quebra-o-pageable"],"introducedConceptIds":["nplusone-query-shape"],"usedConceptIds":["jpa-relationship-loading","postgres-explain-analyze","pagination-sorting-contract"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"n-mais-1-intuition","type":"intuition","authorship":"authored","title":"N+1 é formato de acesso, não azar do banco","body":"Uma consulta busca N registros e, para cada um, outra consulta busca dependências. O problema nasce da forma como o código navega relações e da representação que a API pediu.","analogyLimit":"Buscar itens um por um ajuda a imaginar, mas a correção depende de cardinalidade, filtro, paginação e memória."},{"id":"n-mais-1-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-jpa\">45 · Spring Data JPA</a>, <a class=\"prereq-tag\" href=\"#postgres\">34 · PostgreSQL</a> (EXPLAIN)</div>\n      </div>","fidelityText":"Spring Dificuldade: Avançado ⏱ ~2h30 de estudo + prática Pré-requisitos: 45 · Spring Data JPA, 34 · PostgreSQL (EXPLAIN)"},{"id":"n-mais-1-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Este é <strong>o</strong> bug de performance mais comum em aplicações Spring Data JPA — silencioso, fácil de introduzir, e devastador em produção com volume real de dados.</p>","fidelityText":"Este é o bug de performance mais comum em aplicações Spring Data JPA — silencioso, fácil de introduzir, e devastador em produção com volume real de dados."},{"id":"n-mais-1-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Imagine pedir a lista de 20 pedidos de um restaurante, e para descobrir o nome do cliente de <strong>cada</strong> pedido, o garçom volta à cozinha <strong>uma vez para cada pedido</strong>, em vez de perguntar tudo de uma vez. Uma consulta vira 21: uma para pegar os pedidos, mais uma <em>por pedido</em> para buscar o cliente. É exatamente isso que \"N+1\" significa: 1 consulta inicial + N consultas extras, uma para cada linha do resultado.</div>","fidelityText":"Imagine pedir a lista de 20 pedidos de um restaurante, e para descobrir o nome do cliente de cada pedido, o garçom volta à cozinha uma vez para cada pedido, em vez de perguntar tudo de uma vez. Uma consulta vira 21: uma para pegar os pedidos, mais uma por pedido para buscar o cliente. É exatamente isso que \"N+1\" significa: 1 consulta inicial + N consultas extras, uma para cada linha do resultado."},{"id":"n-mais-1-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"// o código parece completamente inocente:\nList<Book> books = bookRepository.findAll(); // 1 query: SELECT * FROM livros\n\nfor (Book book : books) {\n    System.out.println(book.getAuthor().getName()); // se @ManyToOne for EAGER (padrão!), dispara\n                                                        // 1 query NOVA por livro para buscar o autor\n}\n// resultado: 1 (livros) + N (um SELECT de autor por livro) = N+1 queries!","fidelityText":"// o código parece completamente inocente: List<Livro> livros = livroRepository.findAll(); // 1 query: SELECT * FROM livros for (Livro livro : livros) { System.out.println(livro.getAutor().getNome()); // se @ManyToOne for EAGER (padrão!), dispara // 1 query NOVA por livro para buscar o autor } // resultado: 1 (livros) + N (um SELECT de autor por livro) = N+1 queries!","highlightedHtml":"<span class=\"com\">// o código parece completamente inocente:</span>\nList&lt;<span class=\"cls\">Book</span>&gt; books = bookRepository.findAll(); <span class=\"com\">// 1 query: SELECT * FROM livros</span>\n\n<span class=\"kw\">for</span> (<span class=\"cls\">Book</span> book : books) {\n    System.out.println(book.getAuthor().getName()); <span class=\"com\">// se @ManyToOne for EAGER (padrão!), dispara\n                                                        // 1 query NOVA por livro para buscar o autor</span>\n}\n<span class=\"com\">// resultado: 1 (livros) + N (um SELECT de autor por livro) = N+1 queries!</span>","caption":"Exemplo executável de n-mais-1.","explanation":["A navegação de coleção LAZY dentro de loop pode disparar consulta por item.","Logs SQL mostram o padrão repetitivo com clareza."],"commonMistakes":["Não ligar logs SQL em diagnóstico","Culpar apenas o banco sem olhar acesso"]},{"id":"n-mais-1-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Com 20 livros, isso são <strong>21 idas ao banco</strong> em vez de uma ou duas. Com 10.000 livros, o sistema trava.</p>","fidelityText":"Com 20 livros, isso são 21 idas ao banco em vez de uma ou duas. Com 10.000 livros, o sistema trava."},{"id":"n-mais-1-comparison-6","type":"html","html":"<div class=\"code-comparison\"><div class=\"code-toggle-bar\" role=\"tablist\">\n        <button class=\"ct-btn bad active\" role=\"tab\" aria-selected=\"true\" type=\"button\">❌ Causa o N+1</button>\n        <button class=\"ct-btn good\" role=\"tab\" aria-selected=\"false\" type=\"button\" tabindex=\"-1\">✅ Resolve o N+1</button>\n      </div><div class=\"ct-panel active\">\n<pre class=\"code\"><span class=\"annotation\">@Entity</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"annotation\">@ManyToOne</span>(fetch = FetchType.EAGER) <span class=\"com\">// PADRÃO do @ManyToOne se você não especificar!</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">Author</span> author;\n}\n<span class=\"com\">// findAll() + acessar .getAutor() em cada um = N+1</span></pre>\n      </div><div class=\"ct-panel\">\n<pre class=\"code\"><span class=\"com\">// opção 1: LAZY por padrão (não resolve sozinho, só adia o problema)</span>\n<span class=\"annotation\">@ManyToOne</span>(fetch = FetchType.LAZY)\n<span class=\"kw\">private</span> <span class=\"cls\">Author</span> author;\n\n<span class=\"com\">// opção 2: JOIN FETCH explícito -- resolve de verdade, tudo em UMA query</span>\n<span class=\"annotation\">@Query</span>(<span class=\"str\">\"SELECT l FROM Book l JOIN FETCH l.author\"</span>)\nList&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findAllWithAuthor</span>();\n\n<span class=\"com\">// opção 3: EntityGraph -- declarativo, mesmo efeito do JOIN FETCH</span>\n<span class=\"annotation\">@EntityGraph</span>(attributePaths = <span class=\"str\">\"author\"</span>)\nList&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findAll</span>();</pre>\n      </div></div>","sourceIndexes":[6,7,8],"fidelityText":"❌ Causa o N+1 ✅ Resolve o N+1 @Entity public class Livro { @ManyToOne(fetch = FetchType.EAGER) // PADRÃO do @ManyToOne se você não especificar! private Autor autor; } // findAll() + acessar .getAutor() em cada um = N+1 // opção 1: LAZY por padrão (não resolve sozinho, só adia o problema) @ManyToOne(fetch = FetchType.LAZY) private Autor autor; // opção 2: JOIN FETCH explícito -- resolve de verdade, tudo em UMA query @Query(\"SELECT l FROM Livro l JOIN FETCH l.autor\") List<Livro> buscarTodosComAutor(); // opção 3: EntityGraph -- declarativo, mesmo efeito do JOIN FETCH @EntityGraph(attributePaths = \"autor\") List<Livro> findAll();"},{"id":"n-mais-1-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Enxergando o problema com EXPLAIN e logs</h2>","fidelityText":"Enxergando o problema com EXPLAIN e logs"},{"id":"n-mais-1-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties -- mostra CADA query SQL executada:\nspring.jpa.show-sql=true\nspring.jpa.properties.hibernate.format_sql=true","fidelityText":"# application.properties -- mostra CADA query SQL executada: spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true","highlightedHtml":"<span class=\"com\"># application.properties -- mostra CADA query SQL executada:</span>\nspring.jpa.show-sql=true\nspring.jpa.properties.hibernate.format_sql=true","caption":"Exemplo executável de n-mais-1.","explanation":["Fetch join/projeção carrega exatamente o que o caso precisa em menos round-trips.","A paginação precisa ser revisada quando há joins com coleção."],"commonMistakes":["Aplicar fetch join em toda consulta","Ignorar duplicação e memória"]},{"id":"n-mais-1-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Com isso ligado, o N+1 fica visível imediatamente no console: você vê uma query <code>SELECT * FROM livros</code>, seguida de <strong>vinte</strong> queries repetidas <code>SELECT * FROM autores WHERE id = ?</code> — o padrão salta aos olhos assim que você sabe o que procurar.</p>","fidelityText":"Com isso ligado, o N+1 fica visível imediatamente no console: você vê uma query SELECT * FROM livros, seguida de vinte queries repetidas SELECT * FROM autores WHERE id = ? — o padrão salta aos olhos assim que você sabe o que procurar."},{"id":"n-mais-1-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A configuração <code>FetchType.LAZY</code> sozinha não <em>resolve</em> N+1 — ela só adia: em vez de buscar o autor no momento do <code>findAll()</code>, o Hibernate busca no momento exato em que você chama <code>.getAutor()</code>, ainda uma query por vez. A solução de verdade é sempre buscar <strong>o que você sabe que vai precisar</strong> em uma única consulta com <code>JOIN FETCH</code> ou <code>@EntityGraph</code> — antecipando a necessidade, em vez de deixar o ORM decidir sozinho, linha por linha.</div>","fidelityText":"A configuração FetchType.LAZY sozinha não resolve N+1 — ela só adia: em vez de buscar o autor no momento do findAll(), o Hibernate busca no momento exato em que você chama .getAutor(), ainda uma query por vez. A solução de verdade é sempre buscar o que você sabe que vai precisar em uma única consulta com JOIN FETCH ou @EntityGraph — antecipando a necessidade, em vez de deixar o ORM decidir sozinho, linha por linha."},{"id":"n-mais-1-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Batch fetching: quando LAZY sozinho ainda é lento demais</h2>","fidelityText":"Batch fetching: quando LAZY sozinho ainda é lento demais"},{"id":"n-mais-1-content-14","type":"html","authorship":"legacy-preserved","html":"<p><code>JOIN FETCH</code> resolve bem quando você <strong>sempre</strong> precisa do relacionamento. Mas às vezes você só precisa dele <em>às vezes</em>, e trazer tudo eager pesaria demais na maioria dos casos. <strong>Batch fetching</strong> é o meio-termo: continua LAZY, mas em vez de uma query por linha, o Hibernate agrupa várias buscas pendentes em uma única query com <code>IN</code>:</p>","fidelityText":"JOIN FETCH resolve bem quando você sempre precisa do relacionamento. Mas às vezes você só precisa dele às vezes, e trazer tudo eager pesaria demais na maioria dos casos. Batch fetching é o meio-termo: continua LAZY, mas em vez de uma query por linha, o Hibernate agrupa várias buscas pendentes em uma única query com IN:"},{"id":"n-mais-1-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"@ManyToOne(fetch = FetchType.LAZY)\n@BatchSize(size = 20) // agrupa até 20 IDs pendentes em uma única query\nprivate Author author;","fidelityText":"@ManyToOne(fetch = FetchType.LAZY) @BatchSize(size = 20) // agrupa até 20 IDs pendentes em uma única query private Autor autor;","highlightedHtml":"<span class=\"annotation\">@ManyToOne</span>(fetch = FetchType.LAZY)\n<span class=\"annotation\">@BatchSize</span>(size = <span class=\"num\">20</span>) <span class=\"com\">// agrupa até 20 IDs pendentes em uma única query</span>\n<span class=\"kw\">private</span> <span class=\"cls\">Author</span> author;","caption":"Exemplo executável de n-mais-1.","explanation":["@BatchSize mantém LAZY mas agrupa buscas pendentes numa query com IN, em vez de uma query por linha -- N vira aproximadamente N/tamanho-do-lote.","É o meio-termo certo quando o relacionamento só é acessado às vezes, não sempre (caso em que JOIN FETCH resolveria melhor)."],"commonMistakes":["Esperar que @BatchSize chegue a uma única query como JOIN FETCH -- ele reduz N, não elimina","Usar JOIN FETCH em vez de batch fetching quando o relacionamento é acessado só condicionalmente"]},{"id":"n-mais-1-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Com isso, acessar <code>.getAutor()</code> de 20 livros vira <code>SELECT * FROM autores WHERE id IN (?, ?, ..., ?)</code> — uma query para até 20 autores, não 20 queries separadas. Para não precisar anotar campo por campo, <code>spring.jpa.properties.hibernate.default_batch_fetch_size=20</code> aplica o mesmo comportamento globalmente. Batch fetching não chega a UMA query como <code>JOIN FETCH</code>, mas transforma N queries em N/tamanho-do-lote — uma correção real quando o relacionamento é acessado condicionalmente.</p>","fidelityText":"Com isso, acessar .getAutor() de 20 livros vira SELECT * FROM autores WHERE id IN (?, ?, ..., ?) — uma query para até 20 autores, não 20 queries separadas. Para não precisar anotar campo por campo, spring.jpa.properties.hibernate.default_batch_fetch_size=20 aplica o mesmo comportamento globalmente. Batch fetching não chega a UMA query como JOIN FETCH, mas transforma N queries em N/tamanho-do-lote — uma correção real quando o relacionamento é acessado condicionalmente."},{"id":"n-mais-1-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>N+1 × paginação: por que JOIN FETCH de uma coleção quebra o Pageable</h2>","fidelityText":"N+1 × paginação: por que JOIN FETCH de uma coleção quebra o Pageable"},{"id":"n-mais-1-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\">Combinar <code>JOIN FETCH</code> numa coleção (<code>@OneToMany</code>) com <code>Pageable</code>/<code>LIMIT</code> é uma armadilha conhecida: o <code>JOIN</code> multiplica as linhas do resultado (um livro com 3 autores vira 3 linhas), e o banco não consegue aplicar <code>LIMIT</code>/<code>OFFSET</code> corretamente sobre isso no nível SQL. O Hibernate então faz algo perigoso silenciosamente: <strong>traz o resultado inteiro para a memória e pagina lá</strong> — exatamente o cenário de \"sistema trava com 10.000 registros\" que este capítulo descreve, só que disfarçado atrás de uma paginação que parecia ter resolvido o problema. O próprio Hibernate loga um aviso (<code>firstResult/maxResults specified with collection fetch; applying in memory</code>) quando isso acontece — nunca ignore esse log.</div>","fidelityText":"Combinar JOIN FETCH numa coleção (@OneToMany) com Pageable/LIMIT é uma armadilha conhecida: o JOIN multiplica as linhas do resultado (um livro com 3 autores vira 3 linhas), e o banco não consegue aplicar LIMIT/OFFSET corretamente sobre isso no nível SQL. O Hibernate então faz algo perigoso silenciosamente: traz o resultado inteiro para a memória e pagina lá — exatamente o cenário de \"sistema trava com 10.000 registros\" que este capítulo descreve, só que disfarçado atrás de uma paginação que parecia ter resolvido o problema. O próprio Hibernate loga um aviso (firstResult/maxResults specified with collection fetch; applying in memory) quando isso acontece — nunca ignore esse log."},{"id":"n-mais-1-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Correção comum: separe em duas queries. Primeiro pagina só os <strong>IDs</strong> (sem <code>JOIN FETCH</code>, paginação real no banco); depois busca esses IDs específicos com <code>JOIN FETCH</code> (sem <code>Pageable</code>, mas já filtrado a um conjunto pequeno). Quando o relacionamento é <code>@ManyToOne</code> (não uma coleção), esse problema não existe — o <code>JOIN</code> não multiplica linhas, então <code>JOIN FETCH</code> + <code>Pageable</code> continuam compatíveis normalmente.</p>","fidelityText":"Correção comum: separe em duas queries. Primeiro pagina só os IDs (sem JOIN FETCH, paginação real no banco); depois busca esses IDs específicos com JOIN FETCH (sem Pageable, mas já filtrado a um conjunto pequeno). Quando o relacionamento é @ManyToOne (não uma coleção), esse problema não existe — o JOIN não multiplica linhas, então JOIN FETCH + Pageable continuam compatíveis normalmente."},{"id":"n-mais-1-content-20","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Sempre que escrever um endpoint que lista objetos com relacionamento, pergunte antes de rodar: \"esse endpoint vai acessar o objeto relacionado de cada item da lista?\" Se sim, já escreva com <code>JOIN FETCH</code> desde o início — é muito mais fácil prevenir N+1 na hora de escrever do que caçá-lo depois com <code>show-sql</code> em um sistema que já está lento em produção. E se a lista for paginada e o relacionamento for uma coleção, lembre da armadilha acima antes de simplesmente adicionar <code>JOIN FETCH</code>.</div>","fidelityText":"Sempre que escrever um endpoint que lista objetos com relacionamento, pergunte antes de rodar: \"esse endpoint vai acessar o objeto relacionado de cada item da lista?\" Se sim, já escreva com JOIN FETCH desde o início — é muito mais fácil prevenir N+1 na hora de escrever do que caçá-lo depois com show-sql em um sistema que já está lento em produção. E se a lista for paginada e o relacionamento for uma coleção, lembre da armadilha acima antes de simplesmente adicionar JOIN FETCH."},{"id":"n-mais-1-exercise-21","type":"exercise","authorship":"legacy-preserved","title":"Exercício 46.1 — Caçando e corrigindo um N+1","prompt":"Usando as entidades Autor/Livro do capítulo anterior com spring.jpa.show-sql=true, escreva um endpoint que lista todos os livros e imprime o nome do autor de cada um. Confirme no log a quantidade de queries geradas. Depois, reescreva o repository com um método usando JOIN FETCH e confirme que a quantidade de queries caiu para uma só.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 46.1 — Caçando e corrigindo um N+1difícil Usando as entidades Autor/Livro do capítulo anterior com spring.jpa.show-sql=true, escreva um endpoint que lista todos os livros e imprime o nome do autor de cada um. Confirme no log a quantidade de queries geradas. Depois, reescreva o repository com um método usando JOIN FETCH e confirme que a quantidade de queries caiu para uma só. Ver solução // versão com N+1 (usando findAll() padrão + acesso ao relacionamento): List<Livro> livros = livroRepository.findAll(); livros.forEach(l -> System.out.println(l.getAutor().getNome())); // log mostra: 1 SELECT de livros + N SELECTs de autor (um por livro) // correção -- método dedicado no repository: public interface LivroRepository extends JpaRepository<Livro, Long> { @Query(\"SELECT l FROM Livro l JOIN FETCH l.autor\") List<Livro> buscarTodosComAutor(); } // agora: List<Livro> livros = livroRepository.buscarTodosComAutor(); // log mostra UMA ÚNICA query, com JOIN, trazendo livro + autor juntos","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 46.1 — Caçando e corrigindo um N+1</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Usando as entidades <code>Autor</code>/<code>Livro</code> do capítulo anterior com <code>spring.jpa.show-sql=true</code>, escreva um endpoint que lista todos os livros e imprime o nome do autor de cada um. Confirme no log a quantidade de queries geradas. Depois, reescreva o repository com um método usando <code>JOIN FETCH</code> e confirme que a quantidade de queries caiu para uma só.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"com\">// versão com N+1 (usando findAll() padrão + acesso ao relacionamento):</span>\nList&lt;<span class=\"cls\">Book</span>&gt; books = bookRepository.findAll();\nbooks.forEach(l -&gt; System.out.println(l.getAuthor().getName()));\n<span class=\"com\">// log mostra: 1 SELECT de livros + N SELECTs de autor (um por livro)</span>\n\n<span class=\"com\">// correção -- método dedicado no repository:</span>\n<span class=\"kw\">public interface</span> <span class=\"cls\">BookRepository</span> <span class=\"kw\">extends</span> JpaRepository&lt;<span class=\"cls\">Book</span>, <span class=\"kw\">Long</span>&gt; {\n    <span class=\"annotation\">@Query</span>(<span class=\"str\">\"SELECT l FROM Book l JOIN FETCH l.author\"</span>)\n    List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findAllWithAuthor</span>();\n}\n<span class=\"com\">// agora: List&lt;Livro&gt; livros = livroRepository.buscarTodosComAutor();\n// log mostra UMA ÚNICA query, com JOIN, trazendo livro + autor juntos</span></pre>\n        </div>\n      </div>"},{"id":"n-mais-1-quiz","type":"quiz","authorship":"authored","conceptId":"nplusone-query-shape","prompt":"Qual correção é mais responsável para N+1?","options":[{"id":"n1-a","label":"Medir o SQL gerado e escolher fetch/projeção adequada ao caso de uso.","correct":true,"explanation":"A solução depende da consulta e do contrato de resposta."},{"id":"n1-b","label":"Trocar todos os relacionamentos para EAGER.","correct":false,"explanation":"EAGER global pode criar consultas maiores, ciclos e payload desnecessário."},{"id":"n1-c","label":"Aumentar o pool de conexões sem mudar consulta.","correct":false,"explanation":"Isso pode mascarar sintoma e piorar pressão no banco."}]}],"resources":[{"id":"spring-data-query-methods","type":"reference","title":"Spring Data JPA: Query Methods","url":"https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html","reinforces":"Consultas derivadas, @Query e formas de consulta em repositories.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"postgres-explain-doc","type":"reference","title":"PostgreSQL: Using EXPLAIN","url":"https://www.postgresql.org/docs/current/using-explain.html","reinforces":"Leitura de plano de execução para confirmar custo e forma da consulta.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The N+1 query problem component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to N+1 query problem. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible N+1 query problem failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// o código parece completamente inocente:","instruction":"The N+1 query problem component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible N+1 query problem failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"cors-rate-limit","moduleId":"spring-api","order":13,"title":"CORS & rate limiting","summary":"Essa é a primeira dor de cabeça que praticamente todo mundo enfrenta ao conectar um front-end separado a uma API Spring pela primeira vez — geralmente descoberta via um erro confuso no console do navegador, não do back-end.","objectives":["Entender CORS como política aplicada pelo navegador","Configurar origens, métodos, headers e credenciais com critério","Distinguir CORS de autenticação/autorização","Aplicar rate limit e comunicar 429/Retry-After"],"whyItExists":"Quando a API começa a ser consumida por frontend e clientes externos, surgem fronteiras reais de browser e capacidade. CORS e rate limit protegem integração sem virar falsa segurança.","prerequisiteChapterIds":["spring-mvc","api-design-avancado"],"conceptIds":["cors-por-que-o-navegador-bloqueia-sua-propria-api","configurando-cors-no-spring","o-preflight-a-requisicao-options-que-o-navegador-manda-sozinho","rate-limiting-primeira-linha-de-defesa-contra-abuso"],"introducedConceptIds":["cors-browser-policy","api-rate-limit-boundary"],"usedConceptIds":["http-header-body-negociacao","mvc-controller-binding","http-status-classe"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"cors-rate-limit-intuition","type":"intuition","authorship":"authored","title":"CORS deixa o navegador chamar; não prova quem está chamando","body":"CORS é uma política de segurança do navegador para scripts cross-origin. Rate limit controla abuso e capacidade. Nenhum dos dois substitui autenticação, autorização e validação no servidor.","analogyLimit":"Portaria ajuda a imaginar origem permitida, mas CORS não bloqueia cURL, backend ou atacante fora do navegador."},{"id":"cors-rate-limit-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-security\">Segurança</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-mvc\">44 · Spring Web/MVC</a>, <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; REST</a></div>\n      </div>","fidelityText":"Segurança Dificuldade: Intermediário ⏱ ~2h de estudo + prática Pré-requisitos: 44 · Spring Web/MVC, 26 · HTTP & REST"},{"id":"cors-rate-limit-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Essa é a primeira dor de cabeça que praticamente todo mundo enfrenta ao conectar um front-end separado a uma API Spring pela primeira vez — geralmente descoberta via um erro confuso no console do navegador, não do back-end.</p>","fidelityText":"Essa é a primeira dor de cabeça que praticamente todo mundo enfrenta ao conectar um front-end separado a uma API Spring pela primeira vez — geralmente descoberta via um erro confuso no console do navegador, não do back-end."},{"id":"cors-rate-limit-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>CORS — por que o navegador bloqueia sua própria API</h2>","fidelityText":"CORS — por que o navegador bloqueia sua própria API"},{"id":"cors-rate-limit-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">CORS existe por causa de uma regra de segurança do <strong>navegador</strong> chamada \"same-origin policy\": por padrão, JavaScript rodando em <code>meusite.com</code> não pode fazer requisições para <code>api.outrosite.com</code> sem permissão explícita. É como um segurança de prédio que só deixa passar visitantes cujo nome está numa lista pré-aprovada pela recepção — mesmo que o visitante seja legítimo, sem estar na lista ele é barrado na porta.</div>","fidelityText":"CORS existe por causa de uma regra de segurança do navegador chamada \"same-origin policy\": por padrão, JavaScript rodando em meusite.com não pode fazer requisições para api.outrosite.com sem permissão explícita. É como um segurança de prédio que só deixa passar visitantes cujo nome está numa lista pré-aprovada pela recepção — mesmo que o visitante seja legítimo, sem estar na lista ele é barrado na porta."},{"id":"cors-rate-limit-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"// erro típico no console do navegador ao chamar a API sem CORS configurado:\n// \"Access to fetch at 'http://localhost:8080/livros' from origin\n//  'http://localhost:3000' has been blocked by CORS policy\"","fidelityText":"// erro típico no console do navegador ao chamar a API sem CORS configurado: // \"Access to fetch at 'http://localhost:8080/livros' from origin // 'http://localhost:3000' has been blocked by CORS policy\"","highlightedHtml":"<span class=\"com\">// erro típico no console do navegador ao chamar a API sem CORS configurado:\n// \"Access to fetch at 'http://localhost:8080/livros' from origin\n//  'http://localhost:3000' has been blocked by CORS policy\"</span>","caption":"Exemplo executável de cors-rate-limit.","explanation":["O erro de CORS aparece no console do navegador, não como resposta HTTP visível -- por isso curl/Postman funcionam normalmente enquanto o front-end falha.","Same-origin policy é uma regra do navegador, não da sua API; testar fora do navegador não prova que CORS está configurado corretamente."],"commonMistakes":["Testar só com curl/Postman e concluir que CORS não é problema"]},{"id":"cors-rate-limit-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Note que isso é um bloqueio do <strong>navegador</strong>, não da sua API — testar o mesmo endpoint via Postman (capítulo futuro) ou <code>curl</code> funciona normalmente, porque essas ferramentas não aplicam same-origin policy. Esse é o motivo de tanta gente ficar confusa: \"funciona no Postman mas não no meu front\" é quase sempre CORS.</p>","fidelityText":"Note que isso é um bloqueio do navegador, não da sua API — testar o mesmo endpoint via Postman (capítulo futuro) ou curl funciona normalmente, porque essas ferramentas não aplicam same-origin policy. Esse é o motivo de tanta gente ficar confusa: \"funciona no Postman mas não no meu front\" é quase sempre CORS."},{"id":"cors-rate-limit-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Configurando CORS no Spring</h2>","fidelityText":"Configurando CORS no Spring"},{"id":"cors-rate-limit-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"@Configuration\npublic class CorsConfig {\n    @Bean\n    public WebMvcConfigurer corsConfigurer() {\n        return new WebMvcConfigurer() {\n            @Override\n            public void addCorsMappings(CorsRegistry registry) {\n                registry.addMapping(\"/**\")\n                    .allowedOrigins(\"http://localhost:3000\", \"https://meufrontend.com\") // lista explícita!\n                    .allowedMethods(\"GET\", \"POST\", \"PUT\", \"DELETE\")\n                    .allowedHeaders(\"*\")\n                    .allowCredentials(true); // necessário se usar cookies de autenticação\n            }\n        };\n    }\n}","fidelityText":"@Configuration public class CorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(\"/**\") .allowedOrigins(\"http://localhost:3000\", \"https://meufrontend.com\") // lista explícita! .allowedMethods(\"GET\", \"POST\", \"PUT\", \"DELETE\") .allowedHeaders(\"*\") .allowCredentials(true); // necessário se usar cookies de autenticação } }; } }","highlightedHtml":"<span class=\"annotation\">@Configuration</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">CorsConfig</span> {\n    <span class=\"annotation\">@Bean</span>\n    <span class=\"kw\">public</span> WebMvcConfigurer <span class=\"fn\">corsConfigurer</span>() {\n        <span class=\"kw\">return new</span> WebMvcConfigurer() {\n            <span class=\"annotation\">@Override</span>\n            <span class=\"kw\">public void</span> <span class=\"fn\">addCorsMappings</span>(CorsRegistry registry) {\n                registry.addMapping(<span class=\"str\">\"/**\"</span>)\n                    .allowedOrigins(<span class=\"str\">\"http://localhost:3000\"</span>, <span class=\"str\">\"https://meufrontend.com\"</span>) <span class=\"com\">// lista explícita!</span>\n                    .allowedMethods(<span class=\"str\">\"GET\"</span>, <span class=\"str\">\"POST\"</span>, <span class=\"str\">\"PUT\"</span>, <span class=\"str\">\"DELETE\"</span>)\n                    .allowedHeaders(<span class=\"str\">\"*\"</span>)\n                    .allowCredentials(<span class=\"kw\">true</span>); <span class=\"com\">// necessário se usar cookies de autenticação</span>\n            }\n        };\n    }\n}","caption":"Exemplo executável de cors-rate-limit.","explanation":["Configuração CORS define origens, métodos, headers e credenciais aceitos explicitamente.","allowCredentials(true) exige lista explícita de origens -- nunca funciona combinado com \"*\"."],"commonMistakes":["Usar * com credenciais","Liberar tudo em produção por erro local"]},{"id":"cors-rate-limit-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não trate <code>allowedOrigins(\"*\")</code> como padrão de produção.</b> O wildcard permite leitura por qualquer origem em requisições sem credenciais; navegadores não permitem combiná-lo com credenciais. Para cookies ou autorização entre origens, liste origens confiáveis explicitamente, processe CORS antes do Spring Security e mantenha CSRF coerente com o modelo de autenticação.</div>","fidelityText":"Não trate allowedOrigins(\"*\") como padrão de produção. O wildcard permite leitura por qualquer origem em requisições sem credenciais; navegadores não permitem combiná-lo com credenciais. Para cookies ou autorização entre origens, liste origens confiáveis explicitamente, processe CORS antes do Spring Security e mantenha CSRF coerente com o modelo de autenticação."},{"id":"cors-rate-limit-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>O preflight: a requisição OPTIONS que o navegador manda sozinho</h2>","fidelityText":"O preflight: a requisição OPTIONS que o navegador manda sozinho"},{"id":"cors-rate-limit-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Para requisições \"não simples\" (método diferente de GET/POST/HEAD básicos, <code>Content-Type</code> diferente de formulário comum, ou qualquer cabeçalho customizado como <code>Authorization</code>), o navegador <strong>nunca</strong> envia a requisição real primeiro — ele manda uma requisição <code>OPTIONS</code> de sondagem, chamada <em>preflight</em>, perguntando se a requisição real seria permitida:</p>","fidelityText":"Para requisições \"não simples\" (método diferente de GET/POST/HEAD básicos, Content-Type diferente de formulário comum, ou qualquer cabeçalho customizado como Authorization), o navegador nunca envia a requisição real primeiro — ele manda uma requisição OPTIONS de sondagem, chamada preflight, perguntando se a requisição real seria permitida:"},{"id":"cors-rate-limit-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"// preflight -- o navegador gera isso sozinho, antes do seu código rodar:\nOPTIONS /books HTTP/1.1\nOrigin: http://localhost:3000\nAccess-Control-Request-Method: POST\nAccess-Control-Request-Headers: content-type, authorization\n\n// resposta do Spring (CorsConfig acima já gera isso):\nHTTP/1.1 200 OK\nAccess-Control-Allow-Origin: http://localhost:3000\nAccess-Control-Allow-Methods: GET, POST, PUT, DELETE\nAccess-Control-Allow-Headers: content-type, authorization\nAccess-Control-Allow-Credentials: true","fidelityText":"// preflight -- o navegador gera isso sozinho, antes do seu código rodar: OPTIONS /livros HTTP/1.1 Origin: http://localhost:3000 Access-Control-Request-Method: POST Access-Control-Request-Headers: content-type, authorization // resposta do Spring (CorsConfig acima já gera isso): HTTP/1.1 200 OK Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: content-type, authorization Access-Control-Allow-Credentials: true","highlightedHtml":"<span class=\"com\">// preflight -- o navegador gera isso sozinho, antes do seu código rodar:</span>\nOPTIONS /books HTTP/1.1\nOrigin: http://localhost:3000\nAccess-Control-Request-Method: POST\nAccess-Control-Request-Headers: content-type, authorization\n\n<span class=\"com\">// resposta do Spring (CorsConfig acima já gera isso):</span>\nHTTP/1.1 200 OK\nAccess-Control-Allow-Origin: http://localhost:3000\nAccess-Control-Allow-Methods: GET, POST, PUT, DELETE\nAccess-Control-Allow-Headers: content-type, authorization\nAccess-Control-Allow-Credentials: true","caption":"Exemplo executável de cors-rate-limit.","explanation":["Filtros/interceptors aplicam política antes do controller quando apropriado.","A política deve ser observável em logs e métricas."],"commonMistakes":["Implementar contador em memória sem entender múltiplas instâncias","Punir todos os usuários por uma chave mal escolhida"]},{"id":"cors-rate-limit-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Só depois que o preflight responde com os cabeçalhos <code>Access-Control-Allow-*</code> compatíveis, o navegador dispara a requisição <code>POST</code> real — e mesmo essa resposta real precisa trazer <code>Access-Control-Allow-Origin</code> de volta, ou o navegador bloqueia o JavaScript de ler o corpo da resposta (mesmo que a requisição tenha chegado e sido processada pelo servidor). É por isso que abrir as ferramentas de desenvolvedor do navegador durante um erro de CORS geralmente mostra <strong>duas</strong> requisições na aba de rede — o preflight <code>OPTIONS</code> e a requisição real — e o problema pode estar em qualquer uma das duas.</p>","fidelityText":"Só depois que o preflight responde com os cabeçalhos Access-Control-Allow-* compatíveis, o navegador dispara a requisição POST real — e mesmo essa resposta real precisa trazer Access-Control-Allow-Origin de volta, ou o navegador bloqueia o JavaScript de ler o corpo da resposta (mesmo que a requisição tenha chegado e sido processada pelo servidor). É por isso que abrir as ferramentas de desenvolvedor do navegador durante um erro de CORS geralmente mostra duas requisições na aba de rede — o preflight OPTIONS e a requisição real — e o problema pode estar em qualquer uma das duas."},{"id":"cors-rate-limit-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Rate Limiting — primeira linha de defesa contra abuso</h2>","fidelityText":"Rate Limiting — primeira linha de defesa contra abuso"},{"id":"cors-rate-limit-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Antes mesmo de autenticação (próximo capítulo), uma API pública precisa se proteger contra excesso de requisições — seja abuso deliberado ou um bug em algum cliente fazendo requisição em loop infinito.</p>","fidelityText":"Antes mesmo de autenticação (próximo capítulo), uma API pública precisa se proteger contra excesso de requisições — seja abuso deliberado ou um bug em algum cliente fazendo requisição em loop infinito."},{"id":"cors-rate-limit-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"// exemplo conceitual com Bucket4j (biblioteca de rate limiting):\nBucket bucket = Bucket.builder()\n    .addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1)))) // 100 requisições/minuto\n    .build();\n\nif (!bucket.tryConsume(1)) {\n    return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); // 429\n}","fidelityText":"// exemplo conceitual com Bucket4j (biblioteca de rate limiting): Bucket bucket = Bucket.builder() .addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1)))) // 100 requisições/minuto .build(); if (!bucket.tryConsume(1)) { return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); // 429 }","highlightedHtml":"<span class=\"com\">// exemplo conceitual com Bucket4j (biblioteca de rate limiting):</span>\nBucket bucket = Bucket.builder()\n    .addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1)))) <span class=\"com\">// 100 requisições/minuto</span>\n    .build();\n\n<span class=\"kw\">if</span> (!bucket.tryConsume(1)) {\n    <span class=\"kw\">return</span> ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); <span class=\"com\">// 429</span>\n}","caption":"Exemplo executável de cors-rate-limit.","explanation":["Rate limit deve retornar 429 quando a política é excedida.","Bucket4j é uma biblioteca, não um recurso nativo do Spring -- rate limiting não vem de fábrica."],"commonMistakes":["Retornar 500 para excesso de uso","Não separar limites por identidade/chave/origem"]},{"id":"cors-rate-limit-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Rate limiting é como uma catraca de estádio: mesmo que 10 mil pessoas tenham ingresso válido, a catraca só deixa passar uma de cada vez, num ritmo controlado, para nunca deixar todo mundo entrar simultaneamente e derrubar a estrutura.</div>","fidelityText":"Rate limiting é como uma catraca de estádio: mesmo que 10 mil pessoas tenham ingresso válido, a catraca só deixa passar uma de cada vez, num ritmo controlado, para nunca deixar todo mundo entrar simultaneamente e derrubar a estrutura."},{"id":"cors-rate-limit-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não precisa implementar rate limiting sofisticado (com Redis distribuído, algoritmos de sliding window) no primeiro projeto — o importante agora é entender <em>por que</em> ele existe e reconhecer o código <code>429 Too Many Requests</code> quando aparecer. A implementação robusta em escala geralmente vive numa camada de infraestrutura (API Gateway, CDN) antes mesmo de chegar no seu Spring Boot.</div>","fidelityText":"Não precisa implementar rate limiting sofisticado (com Redis distribuído, algoritmos de sliding window) no primeiro projeto — o importante agora é entender por que ele existe e reconhecer o código 429 Too Many Requests quando aparecer. A implementação robusta em escala geralmente vive numa camada de infraestrutura (API Gateway, CDN) antes mesmo de chegar no seu Spring Boot."},{"id":"cors-rate-limit-exercise-19","type":"exercise","authorship":"legacy-preserved","title":"Exercício 50.1 — Configurando CORS corretamente","prompt":"Escreva a configuração de CORS do Spring permitindo requisições apenas de http://localhost:5173 (porta padrão do Vite, ferramenta de front-end comum), métodos GET/POST/PUT/DELETE, com suporte a credenciais. Explique em uma frase por que allowedOrigins(\"*\") não funcionaria junto com allowCredentials(true) (dica: é uma restrição de segurança do próprio navegador).","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 50.1 — Configurando CORS corretamentefácil Escreva a configuração de CORS do Spring permitindo requisições apenas de http://localhost:5173 (porta padrão do Vite, ferramenta de front-end comum), métodos GET/POST/PUT/DELETE, com suporte a credenciais. Explique em uma frase por que allowedOrigins(\"*\") não funcionaria junto com allowCredentials(true) (dica: é uma restrição de segurança do próprio navegador). Ver solução registry.addMapping(\"/**\") .allowedOrigins(\"http://localhost:5173\") .allowedMethods(\"GET\", \"POST\", \"PUT\", \"DELETE\") .allowCredentials(true); Navegadores modernos recusam a combinação origin: * + credentials: true por design — permitir cookies/credenciais de qualquer origem simultaneamente anularia a proteção que CORS existe para dar. Por isso, sempre que precisar de allowCredentials(true), é obrigatório listar origens específicas.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 50.1 — Configurando CORS corretamente</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Escreva a configuração de CORS do Spring permitindo requisições apenas de <code>http://localhost:5173</code> (porta padrão do Vite, ferramenta de front-end comum), métodos GET/POST/PUT/DELETE, com suporte a credenciais. Explique em uma frase por que <code>allowedOrigins(\"*\")</code> não funcionaria junto com <code>allowCredentials(true)</code> (dica: é uma restrição de segurança do próprio navegador).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">registry.addMapping(<span class=\"str\">\"/**\"</span>)\n    .allowedOrigins(<span class=\"str\">\"http://localhost:5173\"</span>)\n    .allowedMethods(<span class=\"str\">\"GET\"</span>, <span class=\"str\">\"POST\"</span>, <span class=\"str\">\"PUT\"</span>, <span class=\"str\">\"DELETE\"</span>)\n    .allowCredentials(<span class=\"kw\">true</span>);</pre>\n          <p style=\"margin-top:12px\">Navegadores modernos recusam a combinação <code>origin: *</code> + <code>credentials: true</code> por design — permitir cookies/credenciais de <strong>qualquer</strong> origem simultaneamente anularia a proteção que CORS existe para dar. Por isso, sempre que precisar de <code>allowCredentials(true)</code>, é obrigatório listar origens específicas.</p>\n        </div>\n      </div>"},{"id":"cors-rate-limit-quiz","type":"quiz","authorship":"authored","conceptId":"cors-browser-policy","prompt":"Qual afirmação sobre CORS é correta?","options":[{"id":"cors-a","label":"Ele controla se o navegador permite uma página chamar uma origem diferente com certos métodos/headers.","correct":true,"explanation":"CORS é enforcement do browser, negociado por headers e preflight."},{"id":"cors-b","label":"Ele autentica usuários e substitui JWT/sessão.","correct":false,"explanation":"Origem permitida não identifica usuário nem autorização."},{"id":"cors-c","label":"Ele impede qualquer cliente HTTP de chamar a API.","correct":false,"explanation":"Clientes fora do navegador não obedecem CORS da mesma forma."}]}],"resources":[{"id":"spring-cors-webmvc","type":"reference","title":"Spring MVC: CORS","url":"https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html","reinforces":"Configuração CORS global e por controller no Spring MVC.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"rfc6585-429","type":"reference","title":"RFC 6585: Additional HTTP Status Codes","url":"https://www.rfc-editor.org/rfc/rfc6585","reinforces":"Status 429 Too Many Requests e semântica de rate limiting.","language":"en","publisher":"IETF","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The cors rate limit component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to cors rate limit. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible cors rate limit failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// erro típico no console do navegador ao chamar a API sem CORS configurado:","instruction":"The cors rate limit component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible cors rate limit failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mini-helpdesk-api","moduleId":"spring-api","order":14,"title":"Mini-projeto: API de help desk com Spring","summary":"Crie uma API de chamados com clientes, técnicos, comentários, prioridade e transições de status. A API precisa expressar regras do domínio, não apenas CRUD de tabelas.","objectives":["Construir uma fatia vertical de API Spring","Unir controller, DTO, validação, service, repository e erros","Testar contrato feliz e falhas principais","Demonstrar paginação, transação, CORS/rate limit e N+1 sob controle"],"whyItExists":"O módulo de Spring precisa terminar em uma API pequena, verificável e sem teatro. O projeto junta os conceitos ensinados em uma fatia de helpdesk que funciona de ponta a ponta antes de crescer.","prerequisiteChapterIds":["cors-rate-limit","n-mais-1","validacao-erros"],"conceptIds":["contrato","regras-minimas","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["helpdesk-api-vertical-slice"],"usedConceptIds":["mvc-controller-binding","springdata-repository-contract","controller-advice-problem-details","bean-validation-boundary","nplusone-query-shape"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"mini-helpdesk-intuition","type":"intuition","authorship":"authored","title":"Uma fatia funcionando ensina mais que dez camadas vazias","body":"Comece por um caso real: abrir chamado, listar chamados, alterar status e tratar erro. Cada camada entra porque resolve uma necessidade observável da fatia, não porque um desenho genérico mandou.","analogyLimit":"Fatia vertical não elimina arquitetura; ela força arquitetura a provar valor cedo."},{"id":"mini-helpdesk-api-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>API REST completa</b></div><div class=\"time-est\">Tempo: <b>18–30 horas</b></div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-jpa\">Spring Data JPA</a>, <a class=\"prereq-tag\" href=\"#validacao-erros\">Validação e erros</a></div></div>","fidelityText":"Objetivo: API REST completaTempo: 18–30 horasPré-requisitos: Spring Data JPA, Validação e erros"},{"id":"mini-helpdesk-api-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Crie uma API de chamados com clientes, técnicos, comentários, prioridade e transições de status. A API precisa expressar regras do domínio, não apenas CRUD de tabelas.</p>","fidelityText":"Crie uma API de chamados com clientes, técnicos, comentários, prioridade e transições de status. A API precisa expressar regras do domínio, não apenas CRUD de tabelas."},{"id":"mini-helpdesk-api-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Contrato</h2>","fidelityText":"Contrato"},{"id":"mini-helpdesk-api-checklist-4","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-helpdesk-api-checklist-0","label":"DTOs distintos de entidades e validação de entrada."},{"id":"mini-helpdesk-api-checklist-1","label":"Respostas de erro padronizadas com código, mensagem, campo e timestamp."},{"id":"mini-helpdesk-api-checklist-2","label":"Paginação, filtros por status/técnico e ordenação permitida por lista segura."},{"id":"mini-helpdesk-api-checklist-3","label":"Prevenção de N+1 demonstrada por logs ou teste."},{"id":"mini-helpdesk-api-checklist-4","label":"Documentação OpenAPI e coleção de requisições reproduzível."}]},{"id":"mini-helpdesk-api-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Regras mínimas</h2>","fidelityText":"Regras mínimas"},{"id":"mini-helpdesk-api-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Chamado fechado não recebe comentário; apenas chamado aberto pode ser atribuído; toda transição registra autor e instante; prioridade crítica exige justificativa.</p>","fidelityText":"Chamado fechado não recebe comentário; apenas chamado aberto pode ser atribuído; toda transição registra autor e instante; prioridade crítica exige justificativa."},{"id":"mini-helpdesk-api-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Aceitação</h2><ul><li>Testes unitários cobrem regras e testes de integração cobrem persistência/API.</li><li>Controller não contém regra de negócio.</li><li>O projeto sobe do zero por instruções do README.</li></ul></div>","fidelityText":"AceitaçãoTestes unitários cobrem regras e testes de integração cobrem persistência/API.Controller não contém regra de negócio.O projeto sobe do zero por instruções do README."},{"id":"mini-helpdesk-api-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-helpdesk-api:0","type":"quiz","authorship":"legacy-preserved","conceptId":"qual-resposta-representa-criacao-bem-sucedida","prompt":"Qual resposta representa criação bem-sucedida?","options":[{"id":"mini-helpdesk-api:0:option:0","label":"201 Created com representação ou Location do recurso.","correct":true,"explanation":"Uma API didática precisa ser pequena, observável e testável de ponta a ponta."},{"id":"mini-helpdesk-api:0:option:1","label":"200 para qualquer situação.","correct":false,"explanation":"Camadas entram para sustentar contrato e regra, não para cumprir ritual."},{"id":"mini-helpdesk-api:0:option:2","label":"500 quando o título for inválido.","correct":false,"explanation":"Sem testes de erro e contrato, o projeto vira demonstração frágil."}],"sourceIndex":9},{"id":"mini-helpdesk-api:1","type":"quiz","authorship":"legacy-preserved","conceptId":"um-chamado-esta-com-status-fechado-uma-requisicao-post-chamados-id-comen","prompt":"Um chamado está com status FECHADO. Uma requisição POST /chamados/{id}/comentarios chega mesmo assim. Onde a regra que impede esse comentário deve estar?","options":[{"id":"mini-helpdesk-api:1:option:0","label":"No service/domínio, que recusa a operação e o controller traduz isso em uma resposta HTTP apropriada (ex.: 409 Conflict) -- não apenas uma validação de formato no DTO.","correct":true,"explanation":"A regra de negócio (chamado fechado não recebe comentário) pertence ao domínio/service; o controller só traduz a recusa em um status HTTP coerente."},{"id":"mini-helpdesk-api:1:option:1","label":"Só no frontend, escondendo o botão de comentário para chamados fechados.","correct":false,"explanation":"Esconder o botão no frontend não impede uma chamada direta à API -- a regra continua ausente do lado que realmente decide."},{"id":"mini-helpdesk-api:1:option:2","label":"No banco de dados, com uma constraint que impede fisicamente o insert.","correct":false,"explanation":"Uma constraint de banco é rígida demais e não produz uma resposta HTTP útil para quem chamou a API."}],"sourceIndex":10},{"id":"mini-helpdesk-api:2","type":"quiz","authorship":"legacy-preserved","conceptId":"a-listagem-paginada-de-chamados-executa-para-cada-chamado-uma-consulta-a","prompt":"A listagem paginada de chamados executa, para cada chamado, uma consulta adicional para buscar o técnico responsável. Com 20 chamados por página, isso vira 21 consultas. Qual é o nome desse problema e como evitá-lo?","options":[{"id":"mini-helpdesk-api:2:option:0","label":"Problema N+1; resolvido com fetch join / EntityGraph ou uma projeção que já traz o técnico na consulta principal.","correct":true,"explanation":"N+1 é exatamente esse padrão: uma consulta principal mais uma consulta extra por item; fetch join/EntityGraph ou projeção resolvem trazendo tudo em menos consultas."},{"id":"mini-helpdesk-api:2:option:1","label":"Não é um problema real, já que o banco cacheia automaticamente consultas repetidas.","correct":false,"explanation":"Bancos não cacheiam automaticamente consultas repetidas parametrizadas por ID diferente a cada chamado."},{"id":"mini-helpdesk-api:2:option:2","label":"É resolvido aumentando o tamanho da página para reduzir o número de páginas.","correct":false,"explanation":"Páginas maiores só adiam o problema -- o número de consultas extras cresce com o número de itens da página, não desaparece."}],"sourceIndex":11},{"id":"mini-helpdesk-api:3","type":"quiz","authorship":"legacy-preserved","conceptId":"um-endpoint-devolve-diretamente-a-entidade-jpa-chamado-como-resposta-htt","prompt":"Um endpoint devolve diretamente a entidade JPA Chamado como resposta HTTP, incluindo um campo interno de auditoria que nunca deveria ser público. Qual é o risco estrutural?","options":[{"id":"mini-helpdesk-api:3:option:0","label":"Acoplar o contrato público à estrutura de persistência: qualquer mudança na entidade (renomear coluna, adicionar campo sensível) vaza automaticamente para o contrato, sem controle explícito.","correct":true,"explanation":"Expor a entidade diretamente significa que qualquer mudança na persistência (renomear coluna, novo campo) se propaga automaticamente para o contrato público, sem controle explícito."},{"id":"mini-helpdesk-api:3:option:1","label":"Nenhum risco, desde que o campo de auditoria nunca seja alterado.","correct":false,"explanation":"O campo de auditoria já está exposto no momento em que a entidade vira resposta -- \"nunca mudar\" não desfaz o vazamento presente."},{"id":"mini-helpdesk-api:3:option:2","label":"O único risco é o nome do campo ficar pouco elegante na resposta JSON.","correct":false,"explanation":"O problema é de acoplamento e exposição de dado interno, não de estética do nome do campo."}],"sourceIndex":12},{"id":"mini-helpdesk-api-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"mini-helpdesk-exercise-estados","type":"exercise","authorship":"authored","title":"Antes de codificar: contrato de transições de status do chamado","prompt":"Antes de implementar os endpoints, desenhe por escrito a máquina de estados do chamado (ex.: ABERTO, EM_ANDAMENTO, FECHADO) respondendo: (1) quais transições são permitidas e quais são proibidas (ex.: comentar em chamado fechado); (2) o que a API responde (status HTTP e corpo) quando alguém tenta uma transição proibida; (3) que dado é registrado a cada transição válida (autor, instante) e onde ele fica persistido; (4) que consulta da listagem principal você vai testar para provar que não dispara N+1.","difficulty":"advanced","criteria":["A resposta 1 lista transições permitidas e proibidas explicitamente, incluindo o caso do chamado fechado.","A resposta 2 usa um status HTTP com semântica de conflito/estado inválido (não 200 nem 500) para a transição proibida.","A resposta 3 descreve autor e instante como parte do registro de domínio, não como log solto.","A resposta 4 descreve um teste ou verificação concreta (contagem de queries) para a listagem principal, não uma afirmação sem evidência."]},{"id":"mini-helpdesk-project","type":"project","authorship":"authored","title":"API de helpdesk verificável","brief":"Implemente uma API Spring Boot para chamados com criação, consulta paginada, mudança de status, validação, erro padronizado, persistência e testes de contrato.","requirements":["Endpoint para criar chamado com request DTO validado","Endpoint paginado para listar chamados com ordenação estável","Endpoint para mudar status respeitando transação e regra de domínio","Problem Details para validação, não encontrado e conflito","Repository JPA com consulta que não gera N+1 no caso listado","Configuração CORS restrita ao frontend de estudo e política simples de rate limit","Testes de controller/service/repository cobrindo sucesso e falhas"],"guidance":"bounded","acceptanceCriteria":["OpenAPI ou README documenta request, response, status e exemplos de erro.","Nenhuma Entity JPA é exposta como response público.","Teste demonstra erro de validação, 404, 409 e criação/listagem felizes.","Logs ou teste comprovam que listagem principal não dispara N+1.","Secrets e configurações variáveis ficam fora do código versionado."],"knowledgeMatrix":[{"requirement":"Contrato HTTP verificável","conceptIds":["mvc-controller-binding","mvc-response-status-contract","openapi-documentation-contract"],"chapterIds":["spring-mvc","paginacao-swagger"],"expectedEvidence":"Endpoints possuem status, DTOs, exemplos e testes que falham se o contrato mudar sem intenção."},{"requirement":"Boundary DTO/Entity","conceptIds":["dto-entity-boundary-spring","jpa-entity-identity"],"chapterIds":["dto-mapping","spring-jpa"],"expectedEvidence":"Request/response DTOs não expõem campos internos nem permitem mass assignment."},{"requirement":"Erro e validação","conceptIds":["bean-validation-boundary","controller-advice-problem-details"],"chapterIds":["validacao-erros"],"expectedEvidence":"Problem Details estável cobre validação, inexistência e conflito com correlação."},{"requirement":"Transação e persistência","conceptIds":["transaction-proxy-boundary","springdata-repository-contract"],"chapterIds":["jpa-transacoes","spring-jpa"],"expectedEvidence":"Caso de uso transacional altera status de forma atômica e testável."},{"requirement":"Performance e borda","conceptIds":["nplusone-query-shape","cors-browser-policy","api-rate-limit-boundary"],"chapterIds":["n-mais-1","cors-rate-limit"],"expectedEvidence":"Listagem não gera N+1 e políticas de CORS/rate limit são explícitas."}],"englishSpecification":{"title":"Verifiable help desk API","brief":"Build a small Spring Boot API whose contract, errors, persistence boundaries, and tests are observable.","requirements":["Validated request DTOs","Stable paginated list endpoint","Problem Details for errors","JPA repository without N+1 in the main list","Controller and integration tests"],"acceptanceCriteria":["No JPA entity is exposed as a public response.","Tests cover success, validation error, not found, and conflict.","The public contract is documented with examples."]}},{"id":"mini-helpdesk-quiz","type":"quiz","authorship":"authored","conceptId":"helpdesk-api-vertical-slice","prompt":"Qual decisão torna o projeto mais didático e confiável?","options":[{"id":"help-a","label":"Entregar uma fatia vertical pequena com contrato, erro, persistência e testes observáveis.","correct":true,"explanation":"A fatia prova integração real sem espalhar complexidade vazia."},{"id":"help-b","label":"Criar todas as classes de todas as camadas antes de escolher um caso de uso.","correct":false,"explanation":"Isso gera arquitetura teatral e pouco feedback."},{"id":"help-c","label":"Retornar entity diretamente para acelerar o frontend.","correct":false,"explanation":"Acelera agora e cobra acoplamento depois."}]}],"resources":[{"id":"boot-testing-applications","type":"reference","title":"Spring Boot: Testing Spring Boot Applications","url":"https://docs.spring.io/spring-boot/reference/testing/spring-boot-applications.html","reinforces":"Testes de aplicação Spring Boot, slices e integração verificável.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-mvc-test","type":"reference","title":"Spring Framework: MockMvc","url":"https://docs.spring.io/spring-framework/reference/testing/mockmvc.html","reinforces":"Teste de contrato MVC sem depender de servidor externo.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The Spring help desk API component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to Spring help desk API. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible Spring help desk API failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// Observe how names reveal the Spring help desk API contract.","instruction":"The Spring help desk API component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible Spring help desk API failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["regra-de-transicao-no-dominio-nao-no-controller","prevencao-de-n-mais-1","dto-nao-vaza-entidade","erro-padronizado-com-status-coerente","evidencia-de-transicao-auditavel"],"evidenceBlocks":{"regra-de-transicao-no-dominio-nao-no-controller":["mini-helpdesk-api-content-6","mini-helpdesk-api:1","mini-helpdesk-exercise-estados"],"prevencao-de-n-mais-1":["mini-helpdesk-api-checklist-4","mini-helpdesk-api:2","mini-helpdesk-project"],"dto-nao-vaza-entidade":["mini-helpdesk-api-checklist-4","mini-helpdesk-api:3","mini-helpdesk-project"],"erro-padronizado-com-status-coerente":["mini-helpdesk-api-checklist-4","mini-helpdesk-api:0","mini-helpdesk-project"],"evidencia-de-transicao-auditavel":["mini-helpdesk-api-content-6","mini-helpdesk-exercise-estados","mini-helpdesk-project"]},"primarySources":["Spring Boot: Testing Spring Boot Applications -- https://docs.spring.io/spring-boot/reference/testing/spring-boot-applications.html","Spring Framework: MockMvc -- https://docs.spring.io/spring-framework/reference/testing/mockmvc.html"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"testes","moduleId":"testing-engineering","order":4,"title":"Testes unitários","summary":"Um teste unitário verifica automaticamente que uma unidade de código (geralmente um método) se comporta como esperado, isolada do resto do sistema. Em Java, o padrão de mercado é o JUnit 5 (Jupiter).","objectives":["Escrever testes JUnit 5 com AAA e FIRST","Cobrir caminho feliz, limites e exceções","Usar parametrização e TDD em escopo pequeno"],"whyItExists":"Depois de build e debugging, testes tornam comportamento verificável sem depender de inspeção manual. Eles protegem algoritmos, regras e regressões antes de frameworks pesados.","prerequisiteChapterIds":["logging"],"conceptIds":["anatomia-de-um-teste-junit-5","principais-assertions","beforeeach-aftereach-e-ciclo-de-vida","testes-parametrizados-um-teste-varios-cenarios","mocks-isolando-dependencias","tdd-test-driven-development-em-3-passos"],"introducedConceptIds":["teste-aaa-first","assertions-excecoes-parametrizado","tdd-red-green-refactor"],"usedConceptIds":["build-lifecycle","checked-unchecked-contrato","estrutura-operacao-dominante"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"testes-intuition","type":"intuition","authorship":"authored","title":"Teste é contrato executável","body":"Um teste unitário bom transforma uma regra esperada em código que roda sempre, falha sozinho e aponta o comportamento que quebrou.","analogyLimit":"Checklist ajuda a imaginar cobertura, mas teste executável precisa de dados, ação, assertiva e isolamento."},{"id":"testes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#excecoes\">10 · Exceções</a></div>\n      </div>","fidelityText":"Dificuldade: Intermediário Pré-requisito: 10 · Exceções"},{"id":"testes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Um <strong>teste unitário</strong> verifica automaticamente que uma unidade de código (geralmente um método) se comporta como esperado, isolada do resto do sistema. Em Java, o padrão de mercado é o <strong>JUnit 5</strong> (Jupiter).</p>","fidelityText":"Um teste unitário verifica automaticamente que uma unidade de código (geralmente um método) se comporta como esperado, isolada do resto do sistema. Em Java, o padrão de mercado é o JUnit 5 (Jupiter)."},{"id":"testes-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Anatomia de um teste JUnit 5</h2>","fidelityText":"Anatomia de um teste JUnit 5"},{"id":"testes-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"import org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass BankAccountTest {\n\n    @Test\n    void shouldDepositCorrectly() {\n        // Arrange (organizar)\n        BankAccount account = new BankAccount(\"Ana\", 100);\n\n        // Act (agir)\n        account.deposit(50);\n\n        // Assert (verificar)\n        assertEquals(150, account.getBalance());\n    }\n\n    @Test\n    void shouldRejectWithdrawalGreaterQueBalance() {\n        BankAccount account = new BankAccount(\"Ana\", 100);\n        assertFalse(account.withdraw(200));\n        assertEquals(100, account.getBalance()); // saldo não deve ter mudado\n    }\n\n    @Test\n    void shouldThrowExceptionForCurrenciesDifferent() {\n        Cash real = new Cash(10, \"BRL\");\n        Cash dollar = new Cash(10, \"USD\");\n        assertThrows(IllegalArgumentException.class, () -> real.sum(dollar));\n    }\n}","fidelityText":"import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class ContaBancariaTest { @Test void deveDepositarCorretamente() { // Arrange (organizar) ContaBancaria conta = new ContaBancaria(\"Ana\", 100); // Act (agir) conta.depositar(50); // Assert (verificar) assertEquals(150, conta.getSaldo()); } @Test void deveRecusarSaqueMaiorQueSaldo() { ContaBancaria conta = new ContaBancaria(\"Ana\", 100); assertFalse(conta.sacar(200)); assertEquals(100, conta.getSaldo()); // saldo não deve ter mudado } @Test void deveLancarExcecaoParaMoedasDiferentes() { Dinheiro real = new Dinheiro(10, \"BRL\"); Dinheiro dolar = new Dinheiro(10, \"USD\"); assertThrows(IllegalArgumentException.class, () -> real.somar(dolar)); } }","highlightedHtml":"<span class=\"kw\">import</span> org.junit.jupiter.api.Test;\n<span class=\"kw\">import static</span> org.junit.jupiter.api.Assertions.*;\n\n<span class=\"kw\">class</span> <span class=\"cls\">BankAccountTest</span> {\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldDepositCorrectly</span>() {\n        <span class=\"com\">// Arrange (organizar)</span>\n        <span class=\"cls\">BankAccount</span> account = <span class=\"kw\">new</span> <span class=\"cls\">BankAccount</span>(<span class=\"str\">\"Ana\"</span>, 100);\n\n        <span class=\"com\">// Act (agir)</span>\n        account.deposit(50);\n\n        <span class=\"com\">// Assert (verificar)</span>\n        assertEquals(150, account.getBalance());\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldRejectWithdrawalGreaterQueBalance</span>() {\n        <span class=\"cls\">BankAccount</span> account = <span class=\"kw\">new</span> <span class=\"cls\">BankAccount</span>(<span class=\"str\">\"Ana\"</span>, 100);\n        assertFalse(account.withdraw(200));\n        assertEquals(100, account.getBalance()); <span class=\"com\">// saldo não deve ter mudado</span>\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldThrowExceptionForCurrenciesDifferent</span>() {\n        <span class=\"cls\">Cash</span> real = <span class=\"kw\">new</span> <span class=\"cls\">Cash</span>(10, <span class=\"str\">\"BRL\"</span>);\n        <span class=\"cls\">Cash</span> dollar = <span class=\"kw\">new</span> <span class=\"cls\">Cash</span>(10, <span class=\"str\">\"USD\"</span>);\n        assertThrows(<span class=\"cls\">IllegalArgumentException</span>.<span class=\"kw\">class</span>, () -&gt; real.sum(dollar));\n    }\n}","caption":"Exemplo executável de testes.","explanation":["@Test marca métodos executáveis pelo JUnit Jupiter.","AAA separa preparação, ação e verificação para deixar o comportamento legível.","assertThrows verifica falha esperada como parte do contrato."],"commonMistakes":["Testar vários comportamentos no mesmo método","Depender de ordem entre testes","Não verificar que estado permaneceu igual em falha"]},{"id":"testes-content-5","type":"html","authorship":"legacy-preserved","html":"<p>O padrão <strong>AAA — Arrange, Act, Assert</strong> (organizar, agir, verificar) é a estrutura universal de um teste bem escrito: prepare o cenário, execute a ação, confirme o resultado. Cada teste deve testar <strong>uma única coisa</strong>.</p>","fidelityText":"O padrão AAA — Arrange, Act, Assert (organizar, agir, verificar) é a estrutura universal de um teste bem escrito: prepare o cenário, execute a ação, confirme o resultado. Cada teste deve testar uma única coisa."},{"id":"testes-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Principais assertions</h2>","fidelityText":"Principais assertions"},{"id":"testes-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"assertEquals(expected, current);          // valores iguais\nassertTrue(condition);  assertFalse(condition);\nassertNull(object);    assertNotNull(object);\nassertThrows(MyException.class, () -> methodQueShouldFail());\nassertEquals(3.14159, result, 0.001); // double: sempre com margem de erro (delta)!","fidelityText":"assertEquals(esperado, atual); // valores iguais assertTrue(condicao); assertFalse(condicao); assertNull(objeto); assertNotNull(objeto); assertThrows(MinhaExcecao.class, () -> metodoQueDeveFalhar()); assertEquals(3.14159, resultado, 0.001); // double: sempre com margem de erro (delta)!","highlightedHtml":"assertEquals(expected, current);          <span class=\"com\">// valores iguais</span>\nassertTrue(condition);  assertFalse(condition);\nassertNull(object);    assertNotNull(object);\nassertThrows(<span class=\"cls\">MyException</span>.<span class=\"kw\">class</span>, () -&gt; methodQueShouldFail());\nassertEquals(3.14159, result, 0.001); <span class=\"com\">// double: sempre com margem de erro (delta)!</span>","caption":"Exemplo executável de testes.","explanation":["Assertions transformam expectativa em falha automática do teste.","Comparação de double usa delta por causa de representação de ponto flutuante."],"commonMistakes":["Usar println como verificação","Comparar double sem tolerância","Esquecer mensagem/contexto quando ajuda diagnóstico"]},{"id":"testes-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Nunca compare <code>double</code> com <code>assertEquals</code> sem um delta.</b> Ponto flutuante tem imprecisão inerente — <code>0.1 + 0.2 == 0.3</code> é <code>false</code> em Java (e em quase toda linguagem). Sempre use a versão com terceiro parâmetro de tolerância.</div>","fidelityText":"Nunca compare double com assertEquals sem um delta. Ponto flutuante tem imprecisão inerente — 0.1 + 0.2 == 0.3 é false em Java (e em quase toda linguagem). Sempre use a versão com terceiro parâmetro de tolerância."},{"id":"testes-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>@BeforeEach, @AfterEach e ciclo de vida</h2>","fidelityText":"@BeforeEach, @AfterEach e ciclo de vida"},{"id":"testes-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"class BankAccountTest {\n    private BankAccount account;\n\n    @BeforeEach // roda antes de CADA teste -- evita repetir setup\n    void setup() { account = new BankAccount(\"Ana\", 100); }\n\n    @AfterEach // roda depois de CADA teste -- útil para limpar recursos\n    void tearDown() { /* fechar conexão, apagar arquivo temporário... */ }\n\n    @Test\n    void testUmUsandoAccount() { assertEquals(100, account.getBalance()); }\n}","fidelityText":"class ContaBancariaTest { private ContaBancaria conta; @BeforeEach // roda antes de CADA teste -- evita repetir setup void setup() { conta = new ContaBancaria(\"Ana\", 100); } @AfterEach // roda depois de CADA teste -- útil para limpar recursos void tearDown() { /* fechar conexão, apagar arquivo temporário... */ } @Test void testeUmUsandoConta() { assertEquals(100, conta.getSaldo()); } }","highlightedHtml":"<span class=\"kw\">class</span> <span class=\"cls\">BankAccountTest</span> {\n    <span class=\"kw\">private</span> <span class=\"cls\">BankAccount</span> account;\n\n    <span class=\"annotation\">@BeforeEach</span> <span class=\"com\">// roda antes de CADA teste -- evita repetir setup</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">setup</span>() { account = <span class=\"kw\">new</span> <span class=\"cls\">BankAccount</span>(<span class=\"str\">\"Ana\"</span>, 100); }\n\n    <span class=\"annotation\">@AfterEach</span> <span class=\"com\">// roda depois de CADA teste -- útil para limpar recursos</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">tearDown</span>() { <span class=\"com\">/* fechar conexão, apagar arquivo temporário... */</span> }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">testUmUsandoAccount</span>() { assertEquals(100, account.getBalance()); }\n}","caption":"Exemplo executável de testes.","explanation":["@BeforeEach cria cenário novo para cada teste, preservando independência.","@AfterEach é reservado para cleanup de recurso aberto ou estado externo."],"commonMistakes":["Compartilhar objeto mutável entre testes","Fazer setup grande demais","Limpar recurso que o teste nem abriu"]},{"id":"testes-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Testes parametrizados — um teste, vários cenários</h2>","fidelityText":"Testes parametrizados — um teste, vários cenários"},{"id":"testes-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"@ParameterizedTest\n@ValueSource(ints = {-1, -10, -100})\nvoid shouldRejectBalanceInitialNegative(int valueInvalid) {\n    BankAccount account = new BankAccount(\"Ana\", valueInvalid);\n    assertEquals(0, account.getBalance()); // roda 3 vezes, uma para cada valor\n}","fidelityText":"@ParameterizedTest @ValueSource(ints = {-1, -10, -100}) void deveRecusarSaldoInicialNegativo(int valorInvalido) { ContaBancaria conta = new ContaBancaria(\"Ana\", valorInvalido); assertEquals(0, conta.getSaldo()); // roda 3 vezes, uma para cada valor }","highlightedHtml":"<span class=\"annotation\">@ParameterizedTest</span>\n<span class=\"annotation\">@ValueSource</span>(ints = {-1, -10, -100})\n<span class=\"kw\">void</span> <span class=\"fn\">shouldRejectBalanceInitialNegative</span>(<span class=\"kw\">int</span> valueInvalid) {\n    <span class=\"cls\">BankAccount</span> account = <span class=\"kw\">new</span> <span class=\"cls\">BankAccount</span>(<span class=\"str\">\"Ana\"</span>, valueInvalid);\n    assertEquals(0, account.getBalance()); <span class=\"com\">// roda 3 vezes, uma para cada valor</span>\n}","caption":"Exemplo executável de testes.","explanation":["ParameterizedTest executa a mesma regra para vários valores.","ValueSource reduz duplicação quando só o dado muda."],"commonMistakes":["Colocar casos com expectativas diferentes no mesmo teste sem clareza","Não nomear o cenário","Usar parametrização para esconder regra complexa"]},{"id":"testes-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Mocks: isolando dependências</h2>","fidelityText":"Mocks: isolando dependências"},{"id":"testes-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Quando uma classe depende de outra (ex: um serviço que depende de um banco de dados ou de uma API externa), testar a classe real acoplada a essa dependência deixa o teste lento e frágil. Um <strong>mock</strong> (com a biblioteca Mockito) simula essa dependência.</p>","fidelityText":"Quando uma classe depende de outra (ex: um serviço que depende de um banco de dados ou de uma API externa), testar a classe real acoplada a essa dependência deixa o teste lento e frágil. Um mock (com a biblioteca Mockito) simula essa dependência."},{"id":"testes-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"import static org.mockito.Mockito.*;\n\n@Test\nvoid shouldSendEmailOnCompleteOrder() {\n    ServiceEmail emailMock = mock(ServiceEmail.class); // dependência \"falsa\"\n    Order order = new Order(emailMock);\n\n    order.complete();\n\n    verify(emailMock).send(any()); // confirma que o método FOI chamado, sem mandar e-mail de verdade\n}","fidelityText":"import static org.mockito.Mockito.*; @Test void deveEnviarEmailAoFinalizarPedido() { ServicoEmail emailMock = mock(ServicoEmail.class); // dependência \"falsa\" Pedido pedido = new Pedido(emailMock); pedido.finalizar(); verify(emailMock).enviar(any()); // confirma que o método FOI chamado, sem mandar e-mail de verdade }","highlightedHtml":"<span class=\"kw\">import static</span> org.mockito.Mockito.*;\n\n<span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldSendEmailOnCompleteOrder</span>() {\n    <span class=\"cls\">ServiceEmail</span> emailMock = mock(<span class=\"cls\">ServiceEmail</span>.<span class=\"kw\">class</span>); <span class=\"com\">// dependência \"falsa\"</span>\n    <span class=\"cls\">Order</span> order = <span class=\"kw\">new</span> <span class=\"cls\">Order</span>(emailMock);\n\n    order.complete();\n\n    verify(emailMock).send(<span class=\"kw\">any</span>()); <span class=\"com\">// confirma que o método FOI chamado, sem mandar e-mail de verdade</span>\n}","caption":"Exemplo executável de testes.","explanation":["Mock substitui dependência externa para observar interação sem enviar e-mail real.","verify confirma colaboração esperada depois da ação."],"commonMistakes":["Mockar a própria classe sob teste","Verificar implementação interna irrelevante","Usar mock quando uma regra pura bastaria"]},{"id":"testes-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A regra de ouro dos testes unitários é <strong>F.I.R.S.T.</strong>: <b>F</b>ast (rápidos, rodam em milissegundos), <b>I</b>ndependent (não dependem de ordem nem de outros testes), <b>R</b>epeatable (mesmo resultado sempre, em qualquer máquina), <b>S</b>elf-validating (o teste diz sozinho se passou ou falhou, sem inspeção manual de log) e <b>T</b>imely (escrito perto de quando o código de produção foi escrito — idealmente antes, no estilo TDD).</div>","fidelityText":"A regra de ouro dos testes unitários é F.I.R.S.T.: Fast (rápidos, rodam em milissegundos), Independent (não dependem de ordem nem de outros testes), Repeatable (mesmo resultado sempre, em qualquer máquina), Self-validating (o teste diz sozinho se passou ou falhou, sem inspeção manual de log) e Timely (escrito perto de quando o código de produção foi escrito — idealmente antes, no estilo TDD)."},{"id":"testes-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>TDD — Test-Driven Development em 3 passos</h2>","fidelityText":"TDD — Test-Driven Development em 3 passos"},{"id":"testes-content-18","type":"html","authorship":"legacy-preserved","html":"<p>O ciclo <strong>Red → Green → Refactor</strong>: (1) escreva um teste que falha, porque o código ainda não existe; (2) escreva o código mínimo para o teste passar; (3) refatore o código com confiança, porque o teste garante que você não quebrou o comportamento.</p>","fidelityText":"O ciclo Red → Green → Refactor: (1) escreva um teste que falha, porque o código ainda não existe; (2) escreva o código mínimo para o teste passar; (3) refatore o código com confiança, porque o teste garante que você não quebrou o comportamento."},{"id":"testes-exercise-19","type":"exercise","authorship":"legacy-preserved","title":"Exercício 15.1 — Testando ContaBancaria","prompt":"Escreva uma classe de teste completa para ContaBancaria (capítulo 04) cobrindo: depósito válido, saque válido, saque recusado por saldo insuficiente, e saldo inicial negativo virando zero. Use @BeforeEach para criar a conta antes de cada teste.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 15.1 — Testando ContaBancariamédio Escreva uma classe de teste completa para ContaBancaria (capítulo 04) cobrindo: depósito válido, saque válido, saque recusado por saldo insuficiente, e saldo inicial negativo virando zero. Use @BeforeEach para criar a conta antes de cada teste. Ver solução class ContaBancariaTest { private ContaBancaria conta; @BeforeEach void setup() { conta = new ContaBancaria(\"Ana\", 100); } @Test void deveDepositarCorretamente() { conta.depositar(50); assertEquals(150, conta.getSaldo()); } @Test void deveSacarQuandoHaSaldo() { assertTrue(conta.sacar(30)); assertEquals(70, conta.getSaldo()); } @Test void deveRecusarSaqueSemSaldo() { assertFalse(conta.sacar(500)); assertEquals(100, conta.getSaldo()); } @Test void deveZerarSaldoInicialNegativo() { ContaBancaria outra = new ContaBancaria(\"Bruno\", -50); assertEquals(0, outra.getSaldo()); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 15.1 — Testando ContaBancaria</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva uma classe de teste completa para <code>ContaBancaria</code> (capítulo 04) cobrindo: depósito válido, saque válido, saque recusado por saldo insuficiente, e saldo inicial negativo virando zero. Use <code>@BeforeEach</code> para criar a conta antes de cada teste.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">class</span> <span class=\"cls\">BankAccountTest</span> {\n    <span class=\"kw\">private</span> <span class=\"cls\">BankAccount</span> account;\n\n    <span class=\"annotation\">@BeforeEach</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">setup</span>() { account = <span class=\"kw\">new</span> <span class=\"cls\">BankAccount</span>(<span class=\"str\">\"Ana\"</span>, 100); }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldDepositCorrectly</span>() {\n        account.deposit(50);\n        assertEquals(150, account.getBalance());\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldWithdrawWhenHaBalance</span>() {\n        assertTrue(account.withdraw(30));\n        assertEquals(70, account.getBalance());\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldRejectWithdrawalWithoutBalance</span>() {\n        assertFalse(account.withdraw(500));\n        assertEquals(100, account.getBalance());\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldResetBalanceInitialNegative</span>() {\n        <span class=\"cls\">BankAccount</span> another = <span class=\"kw\">new</span> <span class=\"cls\">BankAccount</span>(<span class=\"str\">\"Bruno\"</span>, -50);\n        assertEquals(0, another.getBalance());\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"testes-exercise-20","type":"exercise","authorship":"legacy-preserved","title":"Exercício 15.2 — TDD na prática","prompt":"Pratique o ciclo Red-Green-Refactor: escreva primeiro o teste (que vai falhar, pois a classe nem existe) para uma classe Fila<T> com enfileirar(T item) e desenfileirar() (FIFO — o primeiro que entra é o primeiro que sai), verificando que desenfileirar uma fila vazia lança NoSuchElementException. Só depois implemente a classe até o teste passar.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 15.2 — TDD na práticadifícil Pratique o ciclo Red-Green-Refactor: escreva primeiro o teste (que vai falhar, pois a classe nem existe) para uma classe Fila<T> com enfileirar(T item) e desenfileirar() (FIFO — o primeiro que entra é o primeiro que sai), verificando que desenfileirar uma fila vazia lança NoSuchElementException. Só depois implemente a classe até o teste passar. Ver solução // 1) RED -- o teste, escrito primeiro: class FilaTest { @Test void deveDesenfileirarNaOrdemDeEntrada() { Fila<String> fila = new Fila<>(); fila.enfileirar(\"a\"); fila.enfileirar(\"b\"); assertEquals(\"a\", fila.desenfileirar()); assertEquals(\"b\", fila.desenfileirar()); } @Test void deveLancarExcecaoQuandoVazia() { Fila<String> fila = new Fila<>(); assertThrows(java.util.NoSuchElementException.class, fila::desenfileirar); } } // 2) GREEN -- implementação mínima para passar: public class Fila<T> { private final java.util.LinkedList<T> itens = new java.util.LinkedList<>(); public void enfileirar(T item) { itens.addLast(item); } public T desenfileirar() { if (itens.isEmpty()) throw new java.util.NoSuchElementException(\"Fila vazia\"); return itens.removeFirst(); } } // 3) REFACTOR -- com os testes verdes, dá pra trocar a implementação interna // (ex: usar um array circular) sem medo de quebrar o comportamento.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 15.2 — TDD na prática</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Pratique o ciclo Red-Green-Refactor: escreva primeiro o teste (que vai falhar, pois a classe nem existe) para uma classe <code>Fila&lt;T&gt;</code> com <code>enfileirar(T item)</code> e <code>desenfileirar()</code> (FIFO — o primeiro que entra é o primeiro que sai), verificando que desenfileirar uma fila vazia lança <code>NoSuchElementException</code>. Só depois implemente a classe até o teste passar.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"com\">// 1) RED -- o teste, escrito primeiro:</span>\n<span class=\"kw\">class</span> <span class=\"cls\">QueueTest</span> {\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldDequeueInOrderOfEntrada</span>() {\n        <span class=\"cls\">Queue</span>&lt;<span class=\"kw\">String</span>&gt; queue = <span class=\"kw\">new</span> <span class=\"cls\">Queue</span>&lt;&gt;();\n        queue.enfileirar(<span class=\"str\">\"a\"</span>); queue.enfileirar(<span class=\"str\">\"b\"</span>);\n        assertEquals(<span class=\"str\">\"a\"</span>, queue.dequeue());\n        assertEquals(<span class=\"str\">\"b\"</span>, queue.dequeue());\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldThrowExceptionWhenEmpty</span>() {\n        <span class=\"cls\">Queue</span>&lt;<span class=\"kw\">String</span>&gt; queue = <span class=\"kw\">new</span> <span class=\"cls\">Queue</span>&lt;&gt;();\n        assertThrows(java.util.InSuchElementException.<span class=\"kw\">class</span>, queue::dequeue);\n    }\n}\n\n<span class=\"com\">// 2) GREEN -- implementação mínima para passar:</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Queue</span>&lt;T&gt; {\n    <span class=\"kw\">private final</span> java.util.LinkedList&lt;T&gt; items = <span class=\"kw\">new</span> java.util.LinkedList&lt;&gt;();\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">enfileirar</span>(T item) { items.addLast(item); }\n\n    <span class=\"kw\">public</span> T <span class=\"fn\">dequeue</span>() {\n        <span class=\"kw\">if</span> (items.isEmpty()) <span class=\"kw\">throw new</span> java.util.InSuchElementException(<span class=\"str\">\"Queue empty\"</span>);\n        <span class=\"kw\">return</span> items.removeFirst();\n    }\n}\n<span class=\"com\">// 3) REFACTOR -- com os testes verdes, dá pra trocar a implementação interna\n// (ex: usar um array circular) sem medo de quebrar o comportamento.</span></pre>\n        </div>\n      </div>"},{"id":"testes-model","type":"mental-model","authorship":"authored","title":"AAA sem teatro","body":"Arrange prepara cenário mínimo, Act executa uma ação observável e Assert verifica resultado e efeito colateral esperado.","flow":["criar entradas e dependências","executar um comportamento","verificar retorno/estado/exceção","nomear o caso de negócio"],"ownership":["teste owns os dados do cenário","produção owns a regra","build owns a execução reproduzível"]},{"id":"testes-quiz","type":"quiz","authorship":"authored","conceptId":"teste-aaa-first","prompt":"Qual teste é mais unitário e confiável?","options":[{"id":"testes-q-a","label":"Um teste rápido que prepara uma Conta, executa saque e verifica retorno e saldo sem depender de outro teste.","correct":true,"explanation":"Ele é isolado, auto-verificável e focado em um comportamento."},{"id":"testes-q-b","label":"Um teste que depende de outro rodar antes para criar estado global.","correct":false,"explanation":"Dependência de ordem viola independência e torna falhas frágeis."},{"id":"testes-q-c","label":"Um teste que só imprime o resultado e pede inspeção manual.","correct":false,"explanation":"Teste precisa falhar ou passar automaticamente."}]}],"resources":[{"id":"testes-junit-user-guide","type":"reference","title":"JUnit 5 User Guide","url":"https://junit.org/junit5/docs/current/user-guide/","reinforces":"Documenta Jupiter, annotations, assertions, lifecycle e testes parametrizados.","language":"en","publisher":"JUnit","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"testes-maven-surefire","type":"reference","title":"Maven Surefire Plugin","url":"https://maven.apache.org/surefire/maven-surefire-plugin/","reinforces":"Mostra como Maven executa testes unitários no ciclo de build.","language":"en","publisher":"Apache Maven","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A tests operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a tests operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this tests chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"import org.junit.jupiter.api.Test;","instruction":"A tests operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this tests chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mockito","moduleId":"testing-engineering","order":7,"title":"Mockito — Mocks & Dublês de Teste","summary":"O capítulo 22 (exercício 22.1) já criou um \"dublê\" de teste manualmente — uma classe NotificadorFalso escrita à mão. Mockito automatiza completamente essa criação, gerando dublês dinamicamente (via proxy, capítulo 20) sem você precisar escrever uma única classe extra.","objectives":["Diferenciar dublês de teste sem depender de framework","Usar Mockito para fronteiras externas simples","Separar assert de resultado e verify de interação","Evitar mockar domínio e detalhes internos"],"whyItExists":"Antes de subir bancos reais em Testcontainers, o aluno precisa saber quando uma dependência deve ser substituída por dublê. Mockito entra como ferramenta para colaboração externa, não como ritual de todo teste.","prerequisiteChapterIds":["testes","interfaces"],"conceptIds":["o-vocabulario-completo-de-dubles-de-teste","mock-injectmocks-e-a-anotacao-que-dispara-tudo","when-thenreturn-programando-o-comportamento-do-mock","verify-confirmando-interacoes-nao-so-valores-de-retorno"],"introducedConceptIds":["test-double-vocabulary","mockito-behavior-verification"],"usedConceptIds":["teste-aaa-first","interface-contrato"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"mockito-intuition","type":"intuition","authorship":"authored","title":"Mock não é objeto falso qualquer","body":"Um dublê de teste substitui uma colaboração para controlar entrada, falha ou interação. Use mock quando a pergunta do teste envolve conversa com uma fronteira; use objeto real quando a regra é simples e barata.","analogyLimit":"Ator substituto ajuda a imaginar dublê, mas testes precisam observar contrato, efeito e risco de acoplamento."},{"id":"mockito-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Testes</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#testes\">15 · Testes unitários</a>, <a class=\"prereq-tag\" href=\"#di\">22 · Injeção de dependência</a>, <a class=\"prereq-tag\" href=\"#padroes\">23 · Padrões de projeto</a></div>\n      </div>","fidelityText":"Testes Dificuldade: Avançado ⏱ ~2h30 de estudo + prática Pré-requisitos: 15 · Testes unitários, 22 · Injeção de dependência, 23 · Padrões de projeto"},{"id":"mockito-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 22 (exercício 22.1) já criou um \"dublê\" de teste manualmente — uma classe <code>NotificadorFalso</code> escrita à mão. <strong>Mockito</strong> automatiza completamente essa criação, gerando dublês dinamicamente (via proxy, capítulo 20) sem você precisar escrever uma única classe extra.</p>","fidelityText":"O capítulo 22 (exercício 22.1) já criou um \"dublê\" de teste manualmente — uma classe NotificadorFalso escrita à mão. Mockito automatiza completamente essa criação, gerando dublês dinamicamente (via proxy, capítulo 20) sem você precisar escrever uma única classe extra."},{"id":"mockito-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>O vocabulário completo de dublês de teste</h2>","fidelityText":"O vocabulário completo de dublês de teste"},{"id":"mockito-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Tipo</th><th>O que faz</th><th>Exemplo</th></tr>\n        <tr><td><strong>Dummy</strong></td><td>Só preenche um parâmetro obrigatório, nunca é realmente usado</td><td>Um objeto passado só porque o construtor exige, mas o teste não interage com ele</td></tr>\n        <tr><td><strong>Stub</strong></td><td>Retorna respostas pré-programadas fixas, não verifica interação</td><td>\"Sempre retorne este valor quando chamado\"</td></tr>\n        <tr><td><strong>Mock</strong></td><td>Além de retornar respostas programadas, permite <strong>verificar</strong> se e como foi chamado</td><td>\"Confirme que <code>enviar()</code> foi chamado exatamente uma vez\"</td></tr>\n        <tr><td><strong>Fake</strong></td><td>Implementação funcional simplificada, com lógica real (mas mais simples que a de produção)</td><td>O <code>LivroRepositorioEmMemoria</code> do capítulo 23 é um Fake</td></tr>\n        <tr><td><strong>Spy</strong></td><td>Envolve um objeto <strong>real</strong>, registrando chamadas, mas deixando o comportamento real acontecer (a menos que você sobrescreva partes específicas)</td><td>Útil para verificar interações em código legado difícil de refatorar</td></tr>\n      </tbody></table>","fidelityText":"TipoO que fazExemplo DummySó preenche um parâmetro obrigatório, nunca é realmente usadoUm objeto passado só porque o construtor exige, mas o teste não interage com ele StubRetorna respostas pré-programadas fixas, não verifica interação\"Sempre retorne este valor quando chamado\" MockAlém de retornar respostas programadas, permite verificar se e como foi chamado\"Confirme que enviar() foi chamado exatamente uma vez\" FakeImplementação funcional simplificada, com lógica real (mas mais simples que a de produção)O LivroRepositorioEmMemoria do capítulo 23 é um Fake SpyEnvolve um objeto real, registrando chamadas, mas deixando o comportamento real acontecer (a menos que você sobrescreva partes específicas)Útil para verificar interações em código legado difícil de refatorar"},{"id":"mockito-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">\"Mock\" virou o termo genérico popular para todo esse vocabulário (as pessoas dizem \"mockar\" para qualquer tipo de dublê), mas tecnicamente, no vocabulário original de Gerard Meszaros que cunhou esses termos, cada palavra descreve um comportamento distinto e específico. Entender a diferença ajuda a escolher a ferramenta certa: um <code>Stub</code> simples não precisa da sobrecarga de verificação de um <code>Mock</code> completo, e a biblioteca Mockito, apesar do nome, gera tanto Stubs quanto Mocks verdadeiros, dependendo de como você a usa.</div>","fidelityText":"\"Mock\" virou o termo genérico popular para todo esse vocabulário (as pessoas dizem \"mockar\" para qualquer tipo de dublê), mas tecnicamente, no vocabulário original de Gerard Meszaros que cunhou esses termos, cada palavra descreve um comportamento distinto e específico. Entender a diferença ajuda a escolher a ferramenta certa: um Stub simples não precisa da sobrecarga de verificação de um Mock completo, e a biblioteca Mockito, apesar do nome, gera tanto Stubs quanto Mocks verdadeiros, dependendo de como você a usa."},{"id":"mockito-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>@Mock, @InjectMocks e a anotação que dispara tudo</h2>","fidelityText":"@Mock, @InjectMocks e a anotação que dispara tudo"},{"id":"mockito-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"@ExtendWith(MockitoExtension.class) // integra Mockito com o ciclo de vida JUnit 5 (capítulo 15)\nclass LibraryTest {\n\n    @Mock // Mockito CRIA um dublê dinâmico da interface -- sem você escrever\n              // NENHUMA classe NotificadorFalso manual, como no capítulo 22\n    private NotifierLoan notifierMock;\n\n    @InjectMocks // Mockito cria uma Biblioteca REAL, injetando os @Mock\n                    // declarados acima automaticamente no construtor\n    private Library library;\n\n    @Test\n    void shouldNotifyOnBorrow() throws ItemUnavailableException {\n        library.add(new Book(\"Clean Code\", \"L001\", \"Robert Martin\"));\n\n        library.borrow(\"L001\");\n\n        verify(notifierMock).notify(any()); // CONFIRMA que o método FOI chamado\n    }\n}","fidelityText":"@ExtendWith(MockitoExtension.class) // integra Mockito com o ciclo de vida JUnit 5 (capítulo 15) class BibliotecaTest { @Mock // Mockito CRIA um dublê dinâmico da interface -- sem você escrever // NENHUMA classe NotificadorFalso manual, como no capítulo 22 private NotificadorEmprestimo notificadorMock; @InjectMocks // Mockito cria uma Biblioteca REAL, injetando os @Mock // declarados acima automaticamente no construtor private Biblioteca biblioteca; @Test void deveNotificarAoEmprestar() throws ItemIndisponivelException { biblioteca.adicionar(new Livro(\"Clean Code\", \"L001\", \"Robert Martin\")); biblioteca.emprestar(\"L001\"); verify(notificadorMock).notificar(any()); // CONFIRMA que o método FOI chamado } }","highlightedHtml":"<span class=\"annotation\">@ExtendWith</span>(MockitoExtension.<span class=\"kw\">class</span>) <span class=\"com\">// integra Mockito com o ciclo de vida JUnit 5 (capítulo 15)</span>\n<span class=\"kw\">class</span> <span class=\"cls\">LibraryTest</span> {\n\n    <span class=\"annotation\">@Mock</span> <span class=\"com\">// Mockito CRIA um dublê dinâmico da interface -- sem você escrever\n              // NENHUMA classe NotificadorFalso manual, como no capítulo 22</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">NotifierLoan</span> notifierMock;\n\n    <span class=\"annotation\">@InjectMocks</span> <span class=\"com\">// Mockito cria uma Biblioteca REAL, injetando os @Mock\n                    // declarados acima automaticamente no construtor</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">Library</span> library;\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldNotifyOnBorrow</span>() <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        library.add(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Clean Code\"</span>, <span class=\"str\">\"L001\"</span>, <span class=\"str\">\"Robert Martin\"</span>));\n\n        library.borrow(<span class=\"str\">\"L001\"</span>);\n\n        verify(notifierMock).notify(<span class=\"kw\">any</span>()); <span class=\"com\">// CONFIRMA que o método FOI chamado</span>\n    }\n}","caption":"Exemplo executável de mockito.","explanation":["MockitoExtension integra criação de mocks ao ciclo JUnit 5.","@Mock cria dublê dinâmico para uma dependência externa ao objeto testado."],"commonMistakes":["Mockar a própria classe sob teste","Usar mock onde um objeto real simples bastaria"]},{"id":"mockito-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>when().thenReturn() — programando o comportamento do mock</h2>","fidelityText":"when().thenReturn() — programando o comportamento do mock"},{"id":"mockito-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"@Mock private BookRepository repositoryMock;\n@InjectMocks private BookService service;\n\n@Test\nvoid shouldReturnBookWhenExists() {\n    Book bookFake = new Book(1L, \"1984\", \"Orwell\");\n\n    // programando: \"QUANDO buscarPorId(1L) for chamado, RETORNE isso\"\n    when(repositoryMock.findById(1L)).thenReturn(Optional.of(bookFake));\n\n    BookDTO result = service.findById(1L);\n\n    assertEquals(\"1984\", result.title());\n    // o servico NUNCA tocou em um banco de dados real -- repositorioMock\n    // simplesmente devolveu o valor programado, instantaneamente\n}\n\n@Test\nvoid shouldThrowExceptionWhenNotFound() {\n    when(repositoryMock.findById(999L)).thenReturn(Optional.empty());\n\n    assertThrows(BookNotFoundException.class, () -> service.findById(999L));\n}","fidelityText":"@Mock private LivroRepositorio repositorioMock; @InjectMocks private LivroServico servico; @Test void deveRetornarLivroQuandoExiste() { Livro livroFalso = new Livro(1L, \"1984\", \"Orwell\"); // programando: \"QUANDO buscarPorId(1L) for chamado, RETORNE isso\" when(repositorioMock.buscarPorId(1L)).thenReturn(Optional.of(livroFalso)); LivroDTO resultado = servico.buscarPorId(1L); assertEquals(\"1984\", resultado.titulo()); // o servico NUNCA tocou em um banco de dados real -- repositorioMock // simplesmente devolveu o valor programado, instantaneamente } @Test void deveLancarExcecaoQuandoNaoEncontrado() { when(repositorioMock.buscarPorId(999L)).thenReturn(Optional.empty()); assertThrows(LivroNaoEncontradoException.class, () -> servico.buscarPorId(999L)); }","highlightedHtml":"<span class=\"annotation\">@Mock</span> <span class=\"kw\">private</span> <span class=\"cls\">BookRepository</span> repositoryMock;\n<span class=\"annotation\">@InjectMocks</span> <span class=\"kw\">private</span> <span class=\"cls\">BookService</span> service;\n\n<span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldReturnBookWhenExists</span>() {\n    <span class=\"cls\">Book</span> bookFake = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(1L, <span class=\"str\">\"1984\"</span>, <span class=\"str\">\"Orwell\"</span>);\n\n    <span class=\"com\">// programando: \"QUANDO buscarPorId(1L) for chamado, RETORNE isso\"</span>\n    when(repositoryMock.findById(1L)).thenReturn(Optional.of(bookFake));\n\n    <span class=\"cls\">BookDTO</span> result = service.findById(1L);\n\n    assertEquals(<span class=\"str\">\"1984\"</span>, result.title());\n    <span class=\"com\">// o servico NUNCA tocou em um banco de dados real -- repositorioMock\n    // simplesmente devolveu o valor programado, instantaneamente</span>\n}\n\n<span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldThrowExceptionWhenNotFound</span>() {\n    when(repositoryMock.findById(999L)).thenReturn(Optional.empty());\n\n    assertThrows(<span class=\"cls\">BookNotFoundException</span>.<span class=\"kw\">class</span>, () -&gt; service.findById(999L));\n}","caption":"Exemplo executável de mockito.","explanation":["when().thenReturn() programa resposta controlada para uma colaboração.","@InjectMocks monta o serviço com o mock para testar a regra ao redor da porta."],"commonMistakes":["Programar comportamento que nunca é usado","Testar implementação do mock em vez da regra do serviço"]},{"id":"mockito-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>verify() — confirmando interações, não só valores de retorno</h2>","fidelityText":"verify() — confirmando interações, não só valores de retorno"},{"id":"mockito-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"verify(repositoryMock).save(any(Book.class));        // foi chamado, exatamente 1 vez (padrão)\nverify(repositoryMock, times(2)).findById(any()); // foi chamado exatamente 2 vezes\nverify(repositoryMock, never()).delete(any());       // NUNCA foi chamado\nverify(notifierMock, atLeastOnce()).notify(any()); // pelo menos 1 vez\n\n// capturando o ARGUMENTO exato passado, para inspecionar seu conteúdo:\nArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);\nverify(notifierMock).notify(captor.capture());\nassertTrue(captor.getValue().contains(\"Clean Code\")); // confere o CONTEÚDO da mensagem","fidelityText":"verify(repositorioMock).salvar(any(Livro.class)); // foi chamado, exatamente 1 vez (padrão) verify(repositorioMock, times(2)).buscarPorId(any()); // foi chamado exatamente 2 vezes verify(repositorioMock, never()).deletar(any()); // NUNCA foi chamado verify(notificadorMock, atLeastOnce()).notificar(any()); // pelo menos 1 vez // capturando o ARGUMENTO exato passado, para inspecionar seu conteúdo: ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); verify(notificadorMock).notificar(captor.capture()); assertTrue(captor.getValue().contains(\"Clean Code\")); // confere o CONTEÚDO da mensagem","highlightedHtml":"verify(repositoryMock).save(<span class=\"kw\">any</span>(<span class=\"cls\">Book</span>.<span class=\"kw\">class</span>));        <span class=\"com\">// foi chamado, exatamente 1 vez (padrão)</span>\nverify(repositoryMock, times(2)).findById(<span class=\"kw\">any</span>()); <span class=\"com\">// foi chamado exatamente 2 vezes</span>\nverify(repositoryMock, never()).delete(<span class=\"kw\">any</span>());       <span class=\"com\">// NUNCA foi chamado</span>\nverify(notifierMock, atLeastOnce()).notify(<span class=\"kw\">any</span>()); <span class=\"com\">// pelo menos 1 vez</span>\n\n<span class=\"com\">// capturando o ARGUMENTO exato passado, para inspecionar seu conteúdo:</span>\nArgumentCaptor&lt;<span class=\"kw\">String</span>&gt; captor = ArgumentCaptor.forClass(<span class=\"kw\">String</span>.<span class=\"kw\">class</span>);\nverify(notifierMock).notify(captor.capture());\nassertTrue(captor.getValue().contains(<span class=\"str\">\"Clean Code\"</span>)); <span class=\"com\">// confere o CONTEÚDO da mensagem</span>","caption":"Exemplo executável de mockito.","explanation":["verify() confirma que determinada interação ocorreu na quantidade esperada.","Use para efeitos colaterais relevantes, não para cada chamada interna."],"commonMistakes":["Verificar getter/setter irrelevante","Usar times como substituto de contrato de domínio"]},{"id":"mockito-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Mockito não substitui testes de integração (capítulo 54).</b> Um mock <strong>sempre</strong> retorna exatamente o que você programou — ele nunca prova que sua query SQL real funciona, que seu mapeamento JPA está correto, ou que a integração de verdade entre as camadas se comporta como esperado. A regra prática: use Mockito para isolar a <strong>lógica de negócio</strong> de suas dependências externas em testes unitários rápidos; use Testcontainers para validar que a integração real entre as camadas funciona de fato.</div>","fidelityText":"Mockito não substitui testes de integração (capítulo 54). Um mock sempre retorna exatamente o que você programou — ele nunca prova que sua query SQL real funciona, que seu mapeamento JPA está correto, ou que a integração de verdade entre as camadas se comporta como esperado. A regra prática: use Mockito para isolar a lógica de negócio de suas dependências externas em testes unitários rápidos; use Testcontainers para validar que a integração real entre as camadas funciona de fato."},{"id":"mockito-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um mock é como um ator substituto (dublê) em uma cena de ação perigosa — ele reproduz exatamente os movimentos combinados no roteiro (o comportamento programado), sem realmente correr o risco genuíno que o ator principal correria (sem realmente bater no banco de dados, sem realmente enviar um e-mail). Isso é ótimo para testar a \"coreografia\" da cena (a lógica de negócio) repetidamente e rapidamente, sem o custo e risco reais — mas em algum momento você precisa filmar pelo menos algumas cenas com o ator de verdade (o teste de integração) para confirmar que tudo funciona no mundo real.</div>","fidelityText":"Um mock é como um ator substituto (dublê) em uma cena de ação perigosa — ele reproduz exatamente os movimentos combinados no roteiro (o comportamento programado), sem realmente correr o risco genuíno que o ator principal correria (sem realmente bater no banco de dados, sem realmente enviar um e-mail). Isso é ótimo para testar a \"coreografia\" da cena (a lógica de negócio) repetidamente e rapidamente, sem o custo e risco reais — mas em algum momento você precisa filmar pelo menos algumas cenas com o ator de verdade (o teste de integração) para confirmar que tudo funciona no mundo real."},{"id":"mockito-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Use a regra F.I.R.S.T. do capítulo 15 como bússola para decidir entre mock e integração: se o teste precisa ser extremamente rápido e testar uma decisão de lógica isolada, mock. Se o teste precisa confirmar que uma integração real (SQL, serialização, configuração) funciona de fato, Testcontainers. Um projeto maduro tem os dois tipos coexistindo, em proporções diferentes — muitos testes unitários rápidos com mock, poucos testes de integração mais lentos mas mais realistas nos pontos críticos.</div>","fidelityText":"Use a regra F.I.R.S.T. do capítulo 15 como bússola para decidir entre mock e integração: se o teste precisa ser extremamente rápido e testar uma decisão de lógica isolada, mock. Se o teste precisa confirmar que uma integração real (SQL, serialização, configuração) funciona de fato, Testcontainers. Um projeto maduro tem os dois tipos coexistindo, em proporções diferentes — muitos testes unitários rápidos com mock, poucos testes de integração mais lentos mas mais realistas nos pontos críticos."},{"id":"mockito-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 90.1 — Testando com mocks completos","prompt":"Reescreva o teste do exercício 22.1 (que usava a classe NotificadorFalso escrita manualmente) usando Mockito: @Mock para NotificadorEmprestimo, @InjectMocks para Biblioteca, e verify() para confirmar que notificar() foi chamado exatamente uma vez com uma mensagem contendo o título do livro (usando ArgumentCaptor).","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 90.1 — Testando com mocks completosdifícil Reescreva o teste do exercício 22.1 (que usava a classe NotificadorFalso escrita manualmente) usando Mockito: @Mock para NotificadorEmprestimo, @InjectMocks para Biblioteca, e verify() para confirmar que notificar() foi chamado exatamente uma vez com uma mensagem contendo o título do livro (usando ArgumentCaptor). Ver solução @ExtendWith(MockitoExtension.class) class BibliotecaMockitoTest { @Mock private NotificadorEmprestimo notificadorMock; @InjectMocks private Biblioteca biblioteca; @Test void deveNotificarComTituloCorretoAoEmprestar() throws ItemIndisponivelException { biblioteca.adicionar(new Livro(\"Clean Code\", \"L001\", \"Robert Martin\")); biblioteca.emprestar(\"L001\"); ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); verify(notificadorMock, times(1)).notificar(captor.capture()); assertTrue(captor.getValue().contains(\"Clean Code\")); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 90.1 — Testando com mocks completos</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Reescreva o teste do exercício 22.1 (que usava a classe <code>NotificadorFalso</code> escrita manualmente) usando Mockito: <code>@Mock</code> para <code>NotificadorEmprestimo</code>, <code>@InjectMocks</code> para <code>Biblioteca</code>, e <code>verify()</code> para confirmar que <code>notificar()</code> foi chamado exatamente uma vez com uma mensagem contendo o título do livro (usando <code>ArgumentCaptor</code>).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@ExtendWith</span>(MockitoExtension.<span class=\"kw\">class</span>)\n<span class=\"kw\">class</span> <span class=\"cls\">LibraryMockitoTest</span> {\n\n    <span class=\"annotation\">@Mock</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">NotifierLoan</span> notifierMock;\n\n    <span class=\"annotation\">@InjectMocks</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">Library</span> library;\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldNotifyWithTitleCorrectOnBorrow</span>() <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        library.add(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Clean Code\"</span>, <span class=\"str\">\"L001\"</span>, <span class=\"str\">\"Robert Martin\"</span>));\n\n        library.borrow(<span class=\"str\">\"L001\"</span>);\n\n        ArgumentCaptor&lt;<span class=\"kw\">String</span>&gt; captor = ArgumentCaptor.forClass(<span class=\"kw\">String</span>.<span class=\"kw\">class</span>);\n        verify(notifierMock, times(1)).notify(captor.capture());\n        assertTrue(captor.getValue().contains(<span class=\"str\">\"Clean Code\"</span>));\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"mockito-quiz","type":"quiz","authorship":"authored","conceptId":"mockito-behavior-verification","prompt":"Quando `verify()` é uma boa ideia?","options":[{"id":"mock-a","label":"Quando a interação com uma porta externa faz parte do comportamento que precisa ser garantido.","correct":true,"explanation":"Exemplo: garantir que uma notificação foi solicitada após uma regra aprovada."},{"id":"mock-b","label":"Em todo teste, para confirmar cada método interno chamado.","correct":false,"explanation":"Isso acopla o teste à implementação e dificulta refatoração."},{"id":"mock-c","label":"Para substituir asserts de resultado observável.","correct":false,"explanation":"Interação e resultado respondem perguntas diferentes."}]}],"resources":[{"id":"mockito-site","type":"reference","title":"Mockito documentation","url":"https://site.mockito.org/","reinforces":"Documentação oficial do Mockito, objetivos e exemplos de uso.","language":"en","publisher":"Mockito","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"mockito-javadoc","type":"reference","title":"Mockito API reference","url":"https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html","reinforces":"API de mock, stubbing, verify e boas práticas descritas na referência.","language":"en","publisher":"Mockito","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A mockito operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a mockito operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this mockito chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"@ExtendWith(MockitoExtension.class) // integra Mockito com o ciclo de vida JUnit 5 (capítulo 15)","instruction":"A mockito operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this mockito chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mini-reservas-testadas","moduleId":"testing-engineering","order":8,"title":"Mini-projeto: motor de reservas guiado por testes","summary":"Implemente reservas de salas com conflitos de horário, cancelamento e política de antecedência. Comece pelos testes das regras, não pela interface.","objectives":["Transformar regra de reserva em matriz de comportamentos","Proteger capacidade e indisponibilidade com invariantes","Usar Mockito apenas em fronteiras externas","Entregar evidências reproduzíveis sem depender de banco ou concorrência avançada"],"whyItExists":"Depois de testes, Mockito e engenharia inicial, o aluno precisa de um projeto pequeno em que teste guia regra de domínio. O motor de reservas força casos-limite: capacidade, duplicidade, cancelamento, período inválido e dependências externas simuladas.","prerequisiteChapterIds":["mockito","testes","colecoes","encapsulamento"],"conceptIds":["matriz-minima","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["behavior-test-matrix","reservation-invariant-capacity","test-double-boundary"],"usedConceptIds":["teste-aaa-first","mockito-behavior-verification","encapsulamento-invariante","contrato-collection-map"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"mini-reservas-testadas-intuition","type":"intuition","authorship":"authored","title":"Reserva é promessa de capacidade, não só cadastro com data","body":"Um motor de reservas existe para impedir estados impossíveis: mais reservas que capacidade, período inválido, cancelamento duplicado, cliente inexistente ou confirmação sem disponibilidade. Testes bons nomeiam essas promessas como comportamento observável.","analogyLimit":"Agenda ajuda, mas o sistema precisa de invariantes, erros explícitos e evidência reproduzível."},{"id":"mini-reservas-testadas-matrix","type":"table","authorship":"authored","title":"Matriz mínima de comportamento","headers":["Cenário","Evidência esperada","Dublê permitido"],"rows":[["capacidade disponível","reserva confirmada e estoque reduzido","nenhum"],["capacidade esgotada","erro de domínio sem alterar estado","nenhum"],["notificação externa falha","reserva segue regra definida e falha é observável","mock/stub da notificação"],["cancelamento duplicado","operação idempotente ou erro explícito","nenhum"],["disputa simultânea futura","uma vencedora e uma rejeitada em teste de integração/concurrency","não simular lock como prova final"]]},{"id":"mini-reservas-testadas-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>testes e arquitetura</b></div><div class=\"time-est\">Tempo: <b>10–16 horas</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#mockito\">Mockito</a></div></div>","fidelityText":"Objetivo: testes e arquiteturaTempo: 10–16 horasPré-requisito: Mockito"},{"id":"mini-reservas-testadas-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Implemente reservas de salas com conflitos de horário, cancelamento e política de antecedência. Comece pelos testes das regras, não pela interface.</p>","fidelityText":"Implemente reservas de salas com conflitos de horário, cancelamento e política de antecedência. Comece pelos testes das regras, não pela interface."},{"id":"mini-reservas-testadas-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Matriz mínima</h2>","fidelityText":"Matriz mínima"},{"id":"mini-reservas-testadas-checklist-4","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-reservas-testadas-checklist-0","label":"Reservas adjacentes permitidas; intervalos sobrepostos recusados."},{"id":"mini-reservas-testadas-checklist-1","label":"Intervalo invertido e duração zero recusados."},{"id":"mini-reservas-testadas-checklist-2","label":"Relógio injetável para testar regras dependentes de “agora”."},{"id":"mini-reservas-testadas-checklist-3","label":"Mocks apenas nas fronteiras externas; entidades testadas sem mocks."},{"id":"mini-reservas-testadas-checklist-4","label":"README justificando arquitetura e trade-offs."}]},{"id":"mini-reservas-testadas-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Meta de qualidade</h2><ul><li>Testes descrevem comportamento, não detalhes internos.</li><li>Uma refatoração interna não quebra testes desnecessariamente.</li><li>Casos-limite estão nomeados e reproduzíveis.</li></ul></div>","fidelityText":"Meta de qualidadeTestes descrevem comportamento, não detalhes internos.Uma refatoração interna não quebra testes desnecessariamente.Casos-limite estão nomeados e reproduzíveis."},{"id":"mini-reservas-testadas-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-reservas-testadas:0","type":"quiz","authorship":"legacy-preserved","conceptId":"qual-teste-comprova-uma-disputa-concorrente","prompt":"Qual teste comprova uma disputa concorrente?","options":[{"id":"mini-reservas-testadas:0:option:0","label":"Duas transações reais sincronizadas no ponto de conflito e uma única vencedora.","correct":true,"explanation":"Quando a disputa concorrente for estudada, a prova exige sincronizar duas operações no ponto de conflito e observar uma única vencedora."},{"id":"mini-reservas-testadas:0:option:1","label":"Chamar o método duas vezes sequencialmente.","correct":false,"explanation":"Duas chamadas sequenciais provam repetição, mas não interleaving/conflito simultâneo."},{"id":"mini-reservas-testadas:0:option:2","label":"Mockar o lock e assumir que funcionou.","correct":false,"explanation":"Mockar o lock prova apenas que o mock foi chamado; não prova segurança da regra."}],"sourceIndex":7},{"id":"mini-reservas-testadas:1","type":"quiz","authorship":"legacy-preserved","conceptId":"uma-reserva-termina-as-14-00-e-outra-comeca-as-14-00-na-mesma-sala-segun","prompt":"Uma reserva termina às 14:00 e outra começa às 14:00 na mesma sala. Segundo a matriz mínima do projeto, essas reservas são adjacentes ou sobrepostas?","options":[{"id":"mini-reservas-testadas:1:option:0","label":"Adjacentes e permitidas: o intervalo [início, fim) não compartilha nenhum instante com o próximo, desde que o fim de uma seja exatamente o início da outra.","correct":true,"explanation":"Um intervalo meio-aberto [início, fim) trata o instante de término como não incluído, então duas reservas encostadas nesse ponto não competem pelo mesmo instante."},{"id":"mini-reservas-testadas:1:option:1","label":"Sobrepostas e recusadas, porque os dois horários mencionam 14:00.","correct":false,"explanation":"O horário coincidente no limite é exatamente o caso de adjacência permitida, não de sobreposição."},{"id":"mini-reservas-testadas:1:option:2","label":"Depende de qual reserva foi criada primeiro no sistema.","correct":false,"explanation":"A ordem de criação não determina se dois intervalos de tempo se sobrepõem -- isso depende apenas dos horários em si."}],"sourceIndex":8},{"id":"mini-reservas-testadas:2","type":"quiz","authorship":"legacy-preserved","conceptId":"uma-regra-recusa-reservas-com-menos-de-2-horas-de-antecedencia-a-partir-","prompt":"Uma regra recusa reservas com menos de 2 horas de antecedência a partir de \"agora\". O teste chama LocalDateTime.now() diretamente dentro do método de validação. Qual é o problema?","options":[{"id":"mini-reservas-testadas:2:option:0","label":"O teste se torna não determinístico e dependente do momento real de execução; um relógio injetável (Clock ou similar) permite controlar \"agora\" no teste.","correct":true,"explanation":"Um relógio injetável é o que torna o teste determinístico, permitindo fixar \"agora\" e testar a fronteira exata das 2 horas de antecedência."},{"id":"mini-reservas-testadas:2:option:1","label":"Nenhum problema, já que LocalDateTime.now() sempre retorna o mesmo valor durante os testes.","correct":false,"explanation":"LocalDateTime.now() lê o relógio do sistema a cada chamada -- o valor muda a cada execução, tornando o teste frágil."},{"id":"mini-reservas-testadas:2:option:2","label":"O problema só existe se o teste rodar depois da meia-noite.","correct":false,"explanation":"O problema é a dependência do relógio real em si, não um horário específico do dia."}],"sourceIndex":9},{"id":"mini-reservas-testadas:3","type":"quiz","authorship":"legacy-preserved","conceptId":"um-teste-faz-mock-da-propria-classe-de-dominio-reservaservice-configuran","prompt":"Um teste faz mock da própria classe de domínio ReservaService, configurando-a para sempre retornar sucesso, e afirma que isso \"prova\" que a regra de capacidade funciona. O que há de errado?","options":[{"id":"mini-reservas-testadas:3:option:0","label":"O teste verifica o comportamento configurado no mock, não a regra real de domínio -- mock deveria isolar fronteiras externas, não substituir o próprio objeto sob teste.","correct":true,"explanation":"Mockar a própria classe sob teste faz o teste validar a configuração do mock, não o comportamento real da regra de capacidade."},{"id":"mini-reservas-testadas:3:option:1","label":"Nada; Mockito é a ferramenta recomendada para testar qualquer classe, incluindo a que está sob teste.","correct":false,"explanation":"Mockito serve para isolar dependências externas da classe sob teste, não para substituir a própria classe sendo testada."},{"id":"mini-reservas-testadas:3:option:2","label":"O problema é só de nomenclatura do teste, não de cobertura real.","correct":false,"explanation":"O problema é estrutural: o teste não exercita nenhuma lógica real de domínio, independentemente do nome que receba."}],"sourceIndex":10},{"id":"mini-reservas-testadas-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"mini-reservas-testadas-exercise-matriz","type":"exercise","authorship":"authored","title":"Antes de codificar: matriz de comportamento por escrito","prompt":"Antes de escrever a primeira classe, escreva a matriz de comportamento completa do motor de reservas: para cada cenário (capacidade disponível, capacidade esgotada, período inválido, cancelamento duplicado, notificação externa indisponível, reservas adjacentes vs sobrepostas), declare a entrada, o resultado esperado e se algum dublê (mock/stub) é permitido nesse cenário. Depois, escreva o nome de um teste (estilo deveria_X_quando_Y) para dois desses cenários.","difficulty":"intermediate","criteria":["A matriz cobre pelo menos os seis cenários listados, sem casos implícitos.","Cada linha declara explicitamente se dublê é permitido, coerente com a regra de mock só na fronteira externa.","O caso de reservas adjacentes é distinguido do caso de reservas sobrepostas.","Os dois nomes de teste descrevem comportamento observável, não detalhe de implementação interna."]},{"id":"mini-reservas-testadas-project","type":"project","authorship":"authored","title":"Motor de reservas guiado por testes","brief":"Construa um domínio de reservas em memória com capacidade, período, cancelamento e notificação externa simulada. Use testes para guiar comportamento antes de refatorar estrutura interna.","requirements":["API de domínio com criar, consultar e cancelar reserva","Capacidade nunca fica negativa e nunca ultrapassa limite","Período inválido e duplicidade têm erro explícito","Mockito apenas para fronteiras externas, como notificação ou relógio","README com matriz de comportamento e comandos de teste"],"guidance":"bounded","acceptanceCriteria":["Testes cobrem caminho feliz, limite zero, capacidade cheia, cancelamento duplicado e falha externa.","Refatoração interna não quebra testes de comportamento.","Nenhum teste depende de ordem aleatória ou estado compartilhado entre casos."],"knowledgeMatrix":[{"requirement":"Invariante de capacidade","conceptIds":["reservation-invariant-capacity","encapsulamento-invariante"],"chapterIds":["encapsulamento","mini-reservas-testadas"],"expectedEvidence":"Teste prova que capacidade não ultrapassa limite nem fica negativa."},{"requirement":"Matriz de comportamento","conceptIds":["behavior-test-matrix","teste-aaa-first"],"chapterIds":["testes","mini-reservas-testadas"],"expectedEvidence":"README e testes nomeiam comportamento, limite e erro sem acoplar a método privado."},{"requirement":"Dublê na fronteira correta","conceptIds":["test-double-boundary","mockito-behavior-verification"],"chapterIds":["mockito","mini-reservas-testadas"],"expectedEvidence":"Mockito aparece em notificação/relógio, não substitui regra de reserva."}]},{"id":"mini-reservas-testadas-quiz","type":"quiz","authorship":"authored","conceptId":"test-double-boundary","prompt":"Quando Mockito ajuda nesse projeto?","options":[{"id":"mrt-a","label":"Ao isolar uma fronteira externa, como notificação ou relógio, preservando a regra real em teste.","correct":true,"explanation":"Dublê é útil quando remove dependência externa sem trocar o comportamento que você quer provar."},{"id":"mrt-b","label":"Ao mockar a própria classe de reserva para ela sempre retornar sucesso.","correct":false,"explanation":"Isso testa o mock, não a regra de domínio."},{"id":"mrt-c","label":"Ao eliminar todos os testes de caso-limite.","correct":false,"explanation":"Mockito não substitui matriz de comportamento."}]}],"resources":[{"id":"junit-user-guide-phase18-reservas","type":"official-docs","title":"JUnit 5 User Guide","url":"https://junit.org/junit5/docs/current/user-guide/","reinforces":"Estrutura de testes, assertions, lifecycle e boas práticas para casos de comportamento.","language":"en","publisher":"JUnit","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"mockito-javadoc-phase18-reservas","type":"official-docs","title":"Mockito API documentation","url":"https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html","reinforces":"Stubbing, verification e limites do uso de mocks em fronteiras externas.","language":"en","publisher":"Mockito","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A test-driven reservation engine operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a test-driven reservation engine operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this test-driven reservation engine chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"// Observe how names reveal the test-driven reservation engine contract.","instruction":"A test-driven reservation engine operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this test-driven reservation engine chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["adjacencia-vs-sobreposicao-de-intervalos","relogio-injetavel-para-regras-temporais","dublê-apenas-em-fronteira-externa","matriz-de-comportamento-nomeada","evidencia-de-disputa-concorrente-real"],"evidenceBlocks":{"adjacencia-vs-sobreposicao-de-intervalos":["mini-reservas-testadas-checklist-4","mini-reservas-testadas:1","mini-reservas-testadas-exercise-matriz"],"relogio-injetavel-para-regras-temporais":["mini-reservas-testadas-checklist-4","mini-reservas-testadas:2","mini-reservas-testadas-project"],"dublê-apenas-em-fronteira-externa":["mini-reservas-testadas:3","mini-reservas-testadas-quiz","mini-reservas-testadas-project"],"matriz-de-comportamento-nomeada":["mini-reservas-testadas-matrix","mini-reservas-testadas-exercise-matriz","mini-reservas-testadas-project"],"evidencia-de-disputa-concorrente-real":["mini-reservas-testadas:0","mini-reservas-testadas-matrix"]},"primarySources":["JUnit 5 User Guide -- https://junit.org/junit5/docs/current/user-guide/","Mockito API documentation -- https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"projeto","moduleId":"testing-engineering","order":9,"title":"Projeto final — sistema de biblioteca","summary":"Este projeto integra todos os tópicos do curso: pilares da POO, exceções customizadas, coleções, generics, streams e um teste unitário. A ideia: um pequeno sistema de empréstimo de livros de biblioteca.","objectives":["Integrar fundamentos, POO, coleções, I/O simples e testes num sistema de biblioteca","Entregar fatias verticais pequenas e verificáveis","Documentar comandos, decisões e limites no README","Preparar o aluno para projetos profissionais posteriores sem exigir framework"],"whyItExists":"O sistema de biblioteca é o projeto integrador intermediário: grande o bastante para exigir modelo, relações, regras e testes; pequeno o bastante para não depender de Spring, banco ou deploy. Ele consolida o que já foi ensinado antes dos módulos profissionais finais.","prerequisiteChapterIds":["mini-biblioteca-cli","colecoes","java-io","mini-reservas-testadas"],"conceptIds":["execucao-guiada-e-evidencias"],"introducedConceptIds":["library-domain-slice","project-evidence-readme"],"usedConceptIds":["associacao-direcao","cardinalidade-objeto","contrato-collection-map","try-resource-lifecycle","behavior-test-matrix"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"projeto-biblioteca-intuition","type":"intuition","authorship":"authored","title":"Projeto intermediário bom prova uma fatia completa antes de crescer","body":"Biblioteca parece simples até você exigir regras: exemplar disponível, empréstimo ativo, usuário bloqueado, devolução atrasada, busca, persistência simples e relatório. A meta é construir uma fatia por vez com teste e evidência, não empilhar classes sem comportamento verificável.","analogyLimit":"Biblioteca real ajuda no domínio, mas o projeto deve caber no repertório atual: Java, POO, coleções, arquivos e testes."},{"id":"projeto-biblioteca-slices","type":"table","authorship":"authored","title":"Fatias mínimas do projeto","headers":["Fatia","Regra central","Evidência"],"rows":[["Catálogo","Livro tem identidade e dados válidos","teste de cadastro/busca"],["Exemplar","Disponível, emprestado ou indisponível","teste de transição de estado"],["Empréstimo","Usuário só pega exemplar disponível","teste de bloqueio e sucesso"],["Devolução","Fecha empréstimo e libera exemplar","teste de devolução duplicada"],["Persistência simples","Arquivo preserva estado entre execuções","teste com diretório temporário ou fixture"]]},{"id":"projeto-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: todos os capítulos anteriores</div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: todos os capítulos anteriores"},{"id":"projeto-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Este projeto integra <strong>todos</strong> os tópicos do curso: pilares da POO, exceções customizadas, coleções, generics, streams e um teste unitário. A ideia: um pequeno sistema de empréstimo de livros de biblioteca.</p>","fidelityText":"Este projeto integra todos os tópicos do curso: pilares da POO, exceções customizadas, coleções, generics, streams e um teste unitário. A ideia: um pequeno sistema de empréstimo de livros de biblioteca."},{"id":"projeto-checklist-3","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"projeto-checklist-0","label":"Crie uma classe abstrata ItemAcervo com private titulo, codigo, um construtor, getters, e um método abstrato descricaoCompleta()."},{"id":"projeto-checklist-1","label":"Crie Livro e Revista estendendo ItemAcervo, cada uma implementando descricaoCompleta() à sua maneira (polimorfismo)."},{"id":"projeto-checklist-2","label":"Crie uma interface Emprestavel com void emprestar() e void devolver(), e um método default boolean disponivelParaEmprestimo()."},{"id":"projeto-checklist-3","label":"Crie uma exceção checked ItemIndisponivelException."},{"id":"projeto-checklist-4","label":"Crie Biblioteca com um List<ItemAcervo> interno (generics + coleções): métodos adicionar(ItemAcervo item), buscarPorTitulo(String parte) usando streams (filter + collect), e emprestar(String codigo) que lança ItemIndisponivelException se o item já estiver emprestado."},{"id":"projeto-checklist-5","label":"Adicione um contador static em ItemAcervo contando quantos itens já foram cadastrados no total."},{"id":"projeto-checklist-6","label":"Escreva pelo menos 3 testes JUnit cobrindo: empréstimo bem-sucedido, exceção ao emprestar item indisponível, e busca por título."}]},{"id":"projeto-exercise-4","type":"exercise","authorship":"legacy-preserved","title":"Gabarito completo do projeto","prompt":"Uma possível solução, juntando fundamentos, pilares e tópicos avançados:","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Gabarito completo do projetoprojeto Uma possível solução, juntando fundamentos, pilares e tópicos avançados: Ver solução completa public class ItemIndisponivelException extends Exception { public ItemIndisponivelException(String msg) { super(msg); } } public interface Emprestavel { void emprestar(); void devolver(); boolean isEmprestado(); default boolean disponivelParaEmprestimo() { return !isEmprestado(); } } public abstract class ItemAcervo implements Emprestavel { private static int totalItens = 0; private final String titulo; private final String codigo; private boolean emprestado = false; protected ItemAcervo(String titulo, String codigo) { this.titulo = titulo; this.codigo = codigo; totalItens++; } public abstract String descricaoCompleta(); public String getTitulo() { return titulo; } public String getCodigo() { return codigo; } @Override public void emprestar() { emprestado = true; } @Override public void devolver() { emprestado = false; } @Override public boolean isEmprestado() { return emprestado; } public static int getTotalItens() { return totalItens; } } public class Livro extends ItemAcervo { private final String autor; public Livro(String titulo, String codigo, String autor) { super(titulo, codigo); this.autor = autor; } @Override public String descricaoCompleta() { return getTitulo() + \" (livro) — \" + autor; } } public class Revista extends ItemAcervo { private final int edicao; public Revista(String titulo, String codigo, int edicao) { super(titulo, codigo); this.edicao = edicao; } @Override public String descricaoCompleta() { return getTitulo() + \" (revista) — edição \" + edicao; } } public class Biblioteca { private final List<ItemAcervo> acervo = new ArrayList<>(); public void adicionar(ItemAcervo item) { acervo.add(item); } public List<ItemAcervo> buscarPorTitulo(String parte) { return acervo.stream() .filter(i -> i.getTitulo().toLowerCase().contains(parte.toLowerCase())) .collect(Collectors.toList()); } public void emprestar(String codigo) throws ItemIndisponivelException { ItemAcervo item = acervo.stream() .filter(i -> i.getCodigo().equals(codigo)) .findFirst() .orElseThrow(() -> new ItemIndisponivelException(\"Item não encontrado\")); if (!item.disponivelParaEmprestimo()) { throw new ItemIndisponivelException(\"Item já emprestado: \" + item.getTitulo()); } item.emprestar(); } } // --- teste JUnit --- class BibliotecaTest { private Biblioteca biblioteca; private Livro livro; @BeforeEach void setup() { biblioteca = new Biblioteca(); livro = new Livro(\"Clean Code\", \"L001\", \"Robert Martin\"); biblioteca.adicionar(livro); } @Test void deveEmprestarItemDisponivel() throws ItemIndisponivelException { biblioteca.emprestar(\"L001\"); assertTrue(livro.isEmprestado()); } @Test void deveLancarExcecaoAoEmprestarItemJaEmprestado() throws ItemIndisponivelException { biblioteca.emprestar(\"L001\"); assertThrows(ItemIndisponivelException.class, () -> biblioteca.emprestar(\"L001\")); } @Test void deveBuscarPorTituloParcial() { List<ItemAcervo> resultado = biblioteca.buscarPorTitulo(\"clean\"); assertEquals(1, resultado.size()); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Gabarito completo do projeto</h2><span class=\"exercise-tag d\">projeto</span></div>\n        <p>Uma possível solução, juntando fundamentos, pilares e tópicos avançados:</p>\n        <button class=\"reveal-btn\">Ver solução completa</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">ItemUnavailableException</span> <span class=\"kw\">extends</span> <span class=\"cls\">Exception</span> {\n    <span class=\"kw\">public</span> <span class=\"fn\">ItemUnavailableException</span>(<span class=\"kw\">String</span> msg) { <span class=\"kw\">super</span>(msg); }\n}\n\n<span class=\"kw\">public interface</span> <span class=\"cls\">Emprestavel</span> {\n    <span class=\"kw\">void</span> <span class=\"fn\">borrow</span>();\n    <span class=\"kw\">void</span> <span class=\"fn\">return</span>();\n    <span class=\"kw\">boolean</span> <span class=\"fn\">isBorrowed</span>();\n    <span class=\"kw\">default boolean</span> <span class=\"fn\">availableForLoan</span>() { <span class=\"kw\">return</span> !isBorrowed(); }\n}\n\n<span class=\"kw\">public abstract class</span> <span class=\"cls\">ItemCatalog</span> <span class=\"kw\">implements</span> <span class=\"cls\">Emprestavel</span> {\n    <span class=\"kw\">private static int</span> totalItems = 0;\n\n    <span class=\"kw\">private final String</span> title;\n    <span class=\"kw\">private final String</span> code;\n    <span class=\"kw\">private boolean</span> borrowed = <span class=\"kw\">false</span>;\n\n    <span class=\"kw\">protected</span> <span class=\"fn\">ItemCatalog</span>(<span class=\"kw\">String</span> title, <span class=\"kw\">String</span> code) {\n        <span class=\"kw\">this</span>.title = title;\n        <span class=\"kw\">this</span>.code = code;\n        totalItems++;\n    }\n\n    <span class=\"kw\">public abstract String</span> <span class=\"fn\">descriptionComplete</span>();\n\n    <span class=\"kw\">public String</span> <span class=\"fn\">getTitle</span>() { <span class=\"kw\">return</span> title; }\n    <span class=\"kw\">public String</span> <span class=\"fn\">getCode</span>() { <span class=\"kw\">return</span> code; }\n\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">borrow</span>() { borrowed = <span class=\"kw\">true</span>; }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">return</span>() { borrowed = <span class=\"kw\">false</span>; }\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public boolean</span> <span class=\"fn\">isBorrowed</span>() { <span class=\"kw\">return</span> borrowed; }\n\n    <span class=\"kw\">public static int</span> <span class=\"fn\">getTotalItems</span>() { <span class=\"kw\">return</span> totalItems; }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Book</span> <span class=\"kw\">extends</span> <span class=\"cls\">ItemCatalog</span> {\n    <span class=\"kw\">private final String</span> author;\n    <span class=\"kw\">public</span> <span class=\"fn\">Book</span>(<span class=\"kw\">String</span> title, <span class=\"kw\">String</span> code, <span class=\"kw\">String</span> author) {\n        <span class=\"kw\">super</span>(title, code); <span class=\"kw\">this</span>.author = author;\n    }\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public String</span> <span class=\"fn\">descriptionComplete</span>() { <span class=\"kw\">return</span> getTitle() + <span class=\"str\">\" (book) — \"</span> + author; }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Revista</span> <span class=\"kw\">extends</span> <span class=\"cls\">ItemCatalog</span> {\n    <span class=\"kw\">private final int</span> edicao;\n    <span class=\"kw\">public</span> <span class=\"fn\">Revista</span>(<span class=\"kw\">String</span> title, <span class=\"kw\">String</span> code, <span class=\"kw\">int</span> edicao) {\n        <span class=\"kw\">super</span>(title, code); <span class=\"kw\">this</span>.edicao = edicao;\n    }\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public String</span> <span class=\"fn\">descriptionComplete</span>() { <span class=\"kw\">return</span> getTitle() + <span class=\"str\">\" (revista) — edicao \"</span> + edicao; }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">Library</span> {\n    <span class=\"kw\">private final</span> List&lt;<span class=\"cls\">ItemCatalog</span>&gt; catalog = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">add</span>(<span class=\"cls\">ItemCatalog</span> item) { catalog.add(item); }\n\n    <span class=\"kw\">public</span> List&lt;<span class=\"cls\">ItemCatalog</span>&gt; <span class=\"fn\">findByTitle</span>(<span class=\"kw\">String</span> parte) {\n        <span class=\"kw\">return</span> catalog.stream()\n            .filter(i -&gt; i.getTitle().toLowerCase().contains(parte.toLowerCase()))\n            .collect(Collectors.toList());\n    }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">borrow</span>(<span class=\"kw\">String</span> code) <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        <span class=\"cls\">ItemCatalog</span> item = catalog.stream()\n            .filter(i -&gt; i.getCode().equals(code))\n            .findFirst()\n            .orElseThrow(() -&gt; <span class=\"kw\">new</span> <span class=\"cls\">ItemUnavailableException</span>(<span class=\"str\">\"Item not found\"</span>));\n\n        <span class=\"kw\">if</span> (!item.availableForLoan()) {\n            <span class=\"kw\">throw new</span> <span class=\"cls\">ItemUnavailableException</span>(<span class=\"str\">\"Item already borrowed: \"</span> + item.getTitle());\n        }\n        item.borrow();\n    }\n}\n\n<span class=\"com\">// --- teste JUnit ---</span>\n<span class=\"kw\">class</span> <span class=\"cls\">LibraryTest</span> {\n    <span class=\"kw\">private</span> <span class=\"cls\">Library</span> library;\n    <span class=\"kw\">private</span> <span class=\"cls\">Book</span> book;\n\n    <span class=\"annotation\">@BeforeEach</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">setup</span>() {\n        library = <span class=\"kw\">new</span> <span class=\"cls\">Library</span>();\n        book = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Clean Code\"</span>, <span class=\"str\">\"L001\"</span>, <span class=\"str\">\"Robert Martin\"</span>);\n        library.add(book);\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldBorrowItemAvailable</span>() <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        library.borrow(<span class=\"str\">\"L001\"</span>);\n        assertTrue(book.isBorrowed());\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldThrowExceptionOnBorrowItemAlreadyBorrowed</span>() <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        library.borrow(<span class=\"str\">\"L001\"</span>);\n        assertThrows(<span class=\"cls\">ItemUnavailableException</span>.<span class=\"kw\">class</span>, () -&gt; library.borrow(<span class=\"str\">\"L001\"</span>));\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldFindByTitlePartial</span>() {\n        List&lt;<span class=\"cls\">ItemCatalog</span>&gt; result = library.findByTitle(<span class=\"str\">\"clean\"</span>);\n        assertEquals(1, result.size());\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"projeto-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"projeto-biblioteca-project","type":"project","authorship":"authored","title":"Sistema de biblioteca em Java puro","brief":"Construa uma aplicação de biblioteca sem framework, usando domínio orientado a objetos, coleções, persistência simples em arquivo e testes automatizados para regras centrais.","requirements":["Modelo com Livro, Exemplar, Usuário e Empréstimo ou equivalentes justificados","Operações de cadastro, busca, empréstimo, devolução e relatório simples","Regras de disponibilidade e devolução duplicada testadas","Persistência simples em arquivo com tratamento de erro","README com comandos, decisões e limitações"],"guidance":"bounded","acceptanceCriteria":["O projeto roda por comando documentado e os testes passam em ambiente limpo.","Pelo menos uma fatia vertical completa é demonstrada do input ao estado persistido.","Erros de domínio são explícitos e não dependem de stack trace para o usuário entender.","O README explica o que foi deixado fora e por quê."],"knowledgeMatrix":[{"requirement":"Modelo de relações","conceptIds":["library-domain-slice","associacao-direcao","cardinalidade-objeto"],"chapterIds":["associacoes-cardinalidade","mini-biblioteca-cli","projeto"],"expectedEvidence":"Classes e testes mostram usuário, exemplar e empréstimo com cardinalidade coerente."},{"requirement":"Coleções e busca","conceptIds":["contrato-collection-map","equals-hashcode-contrato"],"chapterIds":["colecoes","projeto"],"expectedEvidence":"Coleções escolhidas por contrato; busca e igualdade são testadas."},{"requirement":"Persistência simples e evidência","conceptIds":["try-resource-lifecycle","project-evidence-readme"],"chapterIds":["java-io","projeto"],"expectedEvidence":"Arquivo é fechado corretamente e README mostra comandos/fixtures de validação."},{"requirement":"Testes de comportamento","conceptIds":["behavior-test-matrix","teste-aaa-first"],"chapterIds":["testes","mini-reservas-testadas"],"expectedEvidence":"Casos felizes, limites e erros principais aparecem em testes automatizados."}]},{"id":"projeto-biblioteca-quiz","type":"quiz","authorship":"authored","conceptId":"library-domain-slice","prompt":"Qual é a melhor forma de começar o sistema de biblioteca?","options":[{"id":"pb-a","label":"Escolher uma fatia vertical pequena, como empréstimo de exemplar disponível, e provar regra, estado e teste.","correct":true,"explanation":"Fatia vertical reduz risco e obriga comportamento observável."},{"id":"pb-b","label":"Criar todas as classes possíveis antes de executar qualquer cenário.","correct":false,"explanation":"Isso aumenta inventário de código sem provar regra."},{"id":"pb-c","label":"Começar por Spring e banco antes de modelar regra de empréstimo.","correct":false,"explanation":"Framework e banco são ensinados depois; aqui o foco é domínio, coleções, arquivo e teste."}]}],"resources":[{"id":"oracle-files-api-phase18-library","type":"official-docs","title":"Java Files API","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/file/Files.html","reinforces":"Persistência simples, leitura/escrita e tratamento de arquivos no projeto.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"google-java-style-phase18-library","type":"reference","title":"Google Java Style Guide","url":"https://google.github.io/styleguide/javaguide.html","reinforces":"Organização legível de classes, nomes e estrutura de projeto Java.","language":"en","publisher":"Google","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A project operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a project operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this project chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"public class ItemUnavailableException extends Exception {","instruction":"A project operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this project chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"autenticacao-conceitos","moduleId":"api-security-quality","order":0,"title":"Autenticação: sessão vs JWT vs OAuth2","summary":"Antes de configurar Spring Security (próximo capítulo), é essencial entender o que existe para decidir — sem esse pano de fundo, JWT vira \"decoreba de configuração\" em vez de escolha consciente.","objectives":["Distinguir autenticação, autorização e identidade","Comparar sessão, JWT e OAuth2 sem falsa equivalência","Entender expiração, revogação e armazenamento","Reconhecer o que cada mecanismo não protege"],"whyItExists":"Depois de HTTP, JSON e contratos de API, o aluno já consegue discutir identidade como troca de mensagens, estado e risco. Autenticação entra antes de Spring Security para que o framework não pareça magia.","prerequisiteChapterIds":["http","json","anotacoes"],"conceptIds":["sessao-tradicional-cookie-estado-no-servidor","jwt-json-web-token-o-token-que-carrega-a-propria-informacao","oauth2-delegando-autorizacao-para-terceiros"],"introducedConceptIds":["auth-session-cookie","jwt-claims-signature","oauth2-delegated-authorization"],"usedConceptIds":["http-header-body-negociacao","json-formato-contrato"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"auth-intuition","type":"intuition","authorship":"authored","title":"Autenticar é reconhecer; autorizar é permitir","body":"Login responde quem está falando. Autorização responde o que essa pessoa pode fazer agora. Sessão, JWT e OAuth2 são formas diferentes de carregar confiança entre cliente, servidor e provedores.","analogyLimit":"Documento de identidade ajuda a imaginar autenticação, mas sistemas também precisam expiração, revogação, assinatura e contexto."},{"id":"autenticacao-conceitos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-security\">Segurança</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; REST</a>, <a class=\"prereq-tag\" href=\"#json\">25 · JSON &amp; serialização</a>, <a class=\"prereq-tag\" href=\"#anotacoes\">20 · Anotações &amp; Reflection</a></div>\n      </div>","fidelityText":"Segurança Dificuldade: Avançado ⏱ ~2h de estudo Pré-requisitos: 26 · HTTP & REST, 25 · JSON & serialização, 20 · Anotações & Reflection"},{"id":"autenticacao-conceitos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Antes de configurar Spring Security (próximo capítulo), é essencial entender o que existe para <em>decidir</em> — sem esse pano de fundo, JWT vira \"decoreba de configuração\" em vez de escolha consciente.</p>","fidelityText":"Antes de configurar Spring Security (próximo capítulo), é essencial entender o que existe para decidir — sem esse pano de fundo, JWT vira \"decoreba de configuração\" em vez de escolha consciente."},{"id":"autenticacao-conceitos-content-3","type":"html","authorship":"legacy-preserved","html":"<p>HTTP, por natureza, é <strong>stateless</strong> — cada requisição chega sem memória de nenhuma requisição anterior. \"Fazer login\" é, na prática, um jeito de fingir que existe estado, usando um dos padrões abaixo.</p>","fidelityText":"HTTP, por natureza, é stateless — cada requisição chega sem memória de nenhuma requisição anterior. \"Fazer login\" é, na prática, um jeito de fingir que existe estado, usando um dos padrões abaixo."},{"id":"autenticacao-conceitos-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Sessão tradicional (cookie + estado no servidor)</h2>","fidelityText":"Sessão tradicional (cookie + estado no servidor)"},{"id":"autenticacao-conceitos-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">É como pegar uma pulseira em um festival: você mostra documento uma vez na entrada (login), recebe uma pulseira (cookie de sessão), e o staff do evento só precisa checar a pulseira depois — mas quem <em>sabe</em> quem é você continua sendo o sistema de controle do festival (o servidor), não a pulseira em si (que só tem um número).</div>","fidelityText":"É como pegar uma pulseira em um festival: você mostra documento uma vez na entrada (login), recebe uma pulseira (cookie de sessão), e o staff do evento só precisa checar a pulseira depois — mas quem sabe quem é você continua sendo o sistema de controle do festival (o servidor), não a pulseira em si (que só tem um número)."},{"id":"autenticacao-conceitos-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"1. Customer sends login/password\n2. Server validates, cria uma session, guarda em memory/Redis: session_abc123 -> user_42\n3. Server devolve um cookie: Set-Cookie: JSESSIONID=abc123\n4. Toda request seguinte, o browser sends o cookie automatically\n5. Server query \"abc123 -> user_42\" para saber quem esta fazendo a request","fidelityText":"1. Cliente envia login/senha 2. Servidor valida, cria uma sessão, guarda em memória/Redis: sessao_abc123 -> usuario_42 3. Servidor devolve um cookie: Set-Cookie: JSESSIONID=abc123 4. Toda requisição seguinte, o navegador manda o cookie automaticamente 5. Servidor consulta \"abc123 -> usuario_42\" para saber quem está fazendo a requisição","highlightedHtml":"<span class=\"com\">1. Cliente envia login/senha\n2. Servidor valida, cria uma sessão, guarda em memória/Redis: sessao_abc123 -&gt; usuario_42\n3. Servidor devolve um cookie: Set-Cookie: JSESSIONID=abc123\n4. Toda requisição seguinte, o navegador manda o cookie automaticamente\n5. Servidor consulta \"abc123 -&gt; usuario_42\" para saber quem está fazendo a requisição</span>","caption":"Exemplo executável de autenticacao-conceitos.","explanation":["O fluxo de sessão usa cookie com identificador opaco e estado no servidor.","Revogação é natural porque o servidor controla a sessão armazenada."],"commonMistakes":["Guardar dados sensíveis no cookie de sessão","Ignorar SameSite, Secure e expiração"]},{"id":"autenticacao-conceitos-content-7","type":"html","authorship":"legacy-preserved","html":"<p>O servidor guarda o estado (\"quem é abc123\") — por isso <strong>não é</strong> verdadeiramente stateless. Escalar isso para múltiplos servidores exige compartilhar essa sessão entre eles (ex: guardando no Redis, capítulo 38).</p>","fidelityText":"O servidor guarda o estado (\"quem é abc123\") — por isso não é verdadeiramente stateless. Escalar isso para múltiplos servidores exige compartilhar essa sessão entre eles (ex: guardando no Redis, capítulo 38)."},{"id":"autenticacao-conceitos-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>JWT (JSON Web Token) — o token que carrega a própria informação</h2>","fidelityText":"JWT (JSON Web Token) — o token que carrega a própria informação"},{"id":"autenticacao-conceitos-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">JWT é como um crachá de visitante com foto, nome e validade impressos nele mesmo — quem verifica o crachá não precisa ligar para a recepção perguntando \"esse crachá é válido?\", porque a própria assinatura no verso (holograma) já garante que ninguém falsificou aquela informação. O servidor não precisa \"lembrar\" de nada — o próprio token já prova quem é o portador.</div>","fidelityText":"JWT é como um crachá de visitante com foto, nome e validade impressos nele mesmo — quem verifica o crachá não precisa ligar para a recepção perguntando \"esse crachá é válido?\", porque a própria assinatura no verso (holograma) já garante que ninguém falsificou aquela informação. O servidor não precisa \"lembrar\" de nada — o próprio token já prova quem é o portador."},{"id":"autenticacao-conceitos-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"1. Customer sends login/password\n2. Server validates e gera um JWT signed, contendo: { \"sub\": \"user_42\", \"exp\": 1234567890 }\n3. Customer guarda o token e sends em toda request: Authorization: Bearer eyJhbGc...\n4. Server apenas VERIFICA A SIGNATURE -- não precisa consultar banco/sessão nenhuma\n   para saber quem e o user (a menos que precise revogar o token before da expiracao)","fidelityText":"1. Cliente envia login/senha 2. Servidor valida e gera um JWT assinado, contendo: { \"sub\": \"usuario_42\", \"exp\": 1234567890 } 3. Cliente guarda o token e manda em toda requisição: Authorization: Bearer eyJhbGc... 4. Servidor apenas VERIFICA A ASSINATURA -- não precisa consultar banco/sessão nenhuma para saber quem é o usuário (a menos que precise revogar o token antes da expiração)","highlightedHtml":"<span class=\"com\">1. Cliente envia login/senha\n2. Servidor valida e gera um JWT assinado, contendo: { \"sub\": \"usuario_42\", \"exp\": 1234567890 }\n3. Cliente guarda o token e manda em toda requisição: Authorization: Bearer eyJhbGc...\n4. Servidor apenas VERIFICA A ASSINATURA -- não precisa consultar banco/sessão nenhuma\n   para saber quem é o usuário (a menos que precise revogar o token antes da expiração)</span>","caption":"Exemplo executável de autenticacao-conceitos.","explanation":["O fluxo JWT carrega claims assinadas e enviadas como Bearer token.","O servidor precisa validar assinatura, expiração e claims antes de confiar."],"commonMistakes":["Achar que o payload está secreto","Aceitar token expirado ou de issuer errado"]},{"id":"autenticacao-conceitos-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Um JWT tem três partes separadas por ponto: <code>header.payload.signature</code>. O <code>payload</code> é só Base64 (legível por qualquer um, <strong>nunca</strong> coloque senha ali!) — a segurança vem inteiramente da <code>signature</code>, que prova que o servidor foi quem gerou aquele token e ninguém alterou o conteúdo no meio do caminho.</p>","fidelityText":"Um JWT tem três partes separadas por ponto: header.payload.signature. O payload é só Base64 (legível por qualquer um, nunca coloque senha ali!) — a segurança vem inteiramente da signature, que prova que o servidor foi quem gerou aquele token e ninguém alterou o conteúdo no meio do caminho."},{"id":"autenticacao-conceitos-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>JWT não é criptografado por padrão, só assinado.</b> Qualquer pessoa pode decodificar o payload de um JWT em jwt.io e ler seu conteúdo — a assinatura só garante que ninguém <em>alterou</em> o conteúdo sem ser detectado, não que o conteúdo seja secreto. Nunca guarde dados sensíveis (senha, número de cartão) dentro do payload de um JWT.</div>","fidelityText":"JWT não é criptografado por padrão, só assinado. Qualquer pessoa pode decodificar o payload de um JWT em jwt.io e ler seu conteúdo — a assinatura só garante que ninguém alterou o conteúdo sem ser detectado, não que o conteúdo seja secreto. Nunca guarde dados sensíveis (senha, número de cartão) dentro do payload de um JWT."},{"id":"autenticacao-conceitos-content-13","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Sessão</th><th>JWT</th></tr>\n        <tr><td>Estado</td><td>No servidor (stateful)</td><td>No próprio token (stateless)</td></tr>\n        <tr><td>Escalar múltiplos servidores</td><td>Precisa de sessão compartilhada (Redis)</td><td>Trivial — qualquer servidor com a chave pode validar</td></tr>\n        <tr><td>Revogar antes de expirar</td><td>Fácil — apaga a sessão no servidor</td><td>Difícil — o token continua \"válido\" até expirar, a menos que mantenha uma lista de revogação</td></tr>\n        <tr><td>Uso comum</td><td>Aplicações web tradicionais, monolito com front renderizado no servidor</td><td>API + front separados (SPA), mobile, microsserviços</td></tr>\n      </tbody></table>","fidelityText":"SessãoJWT EstadoNo servidor (stateful)No próprio token (stateless) Escalar múltiplos servidoresPrecisa de sessão compartilhada (Redis)Trivial — qualquer servidor com a chave pode validar Revogar antes de expirarFácil — apaga a sessão no servidorDifícil — o token continua \"válido\" até expirar, a menos que mantenha uma lista de revogação Uso comumAplicações web tradicionais, monolito com front renderizado no servidorAPI + front separados (SPA), mobile, microsserviços"},{"id":"autenticacao-conceitos-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>OAuth2 — delegando autorização para terceiros</h2>","fidelityText":"OAuth2 — delegando autorização para terceiros"},{"id":"autenticacao-conceitos-content-15","type":"html","authorship":"legacy-preserved","html":"<p>OAuth2 é um framework de <strong>autorização</strong>, não de autenticação: ele resolve \"esta aplicação pode acessar tal recurso, com tal permissão\", sem a aplicação nunca precisar ver a senha do usuário. Isso é uma distinção importante, fácil de errar: OAuth2 sozinho não define um jeito padrão de responder \"quem é esse usuário\" — ele só prova que um acesso foi autorizado.</p>","fidelityText":"OAuth2 é um framework de autorização, não de autenticação: ele resolve \"esta aplicação pode acessar tal recurso, com tal permissão\", sem a aplicação nunca precisar ver a senha do usuário. Isso é uma distinção importante, fácil de errar: OAuth2 sozinho não define um jeito padrão de responder \"quem é esse usuário\" — ele só prova que um acesso foi autorizado."},{"id":"autenticacao-conceitos-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>\"Entrar com Google/GitHub\" não é OAuth2 puro — é OAuth2 + uma camada de identidade em cima.</b> Quem de fato padroniza \"esse usuário se autenticou com sucesso, aqui está o e-mail dele\" é o <strong>OpenID Connect (OIDC)</strong>, tema do próximo capítulo: uma extensão construída sobre o OAuth2 especificamente para resolver autenticação e identidade, devolvendo um token de identidade além do token de autorização. Chamar OAuth2 de \"protocolo de autenticação\" mistura as duas camadas e é um erro comum mesmo em material técnico publicado.</div>","fidelityText":"\"Entrar com Google/GitHub\" não é OAuth2 puro — é OAuth2 + uma camada de identidade em cima. Quem de fato padroniza \"esse usuário se autenticou com sucesso, aqui está o e-mail dele\" é o OpenID Connect (OIDC), tema do próximo capítulo: uma extensão construída sobre o OAuth2 especificamente para resolver autenticação e identidade, devolvendo um token de identidade além do token de autorização. Chamar OAuth2 de \"protocolo de autenticação\" mistura as duas camadas e é um erro comum mesmo em material técnico publicado."},{"id":"autenticacao-conceitos-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Isso é o mesmo princípio de injeção de dependência do capítulo 22, só que aplicado à confiança: em vez da sua aplicação implementar (e ser responsável por) toda a lógica de autorização — e, com OIDC por cima, de identidade —, ela <strong>delega</strong> essa responsabilidade para um provedor especializado (Google, GitHub), assim como <code>PedidoServico</code> delegava a criação do <code>Notificador</code> para quem \"montava\" a aplicação. Você confia na abstração, não precisa reimplementar a parte difícil.</div>","fidelityText":"Isso é o mesmo princípio de injeção de dependência do capítulo 22, só que aplicado à confiança: em vez da sua aplicação implementar (e ser responsável por) toda a lógica de autorização — e, com OIDC por cima, de identidade —, ela delega essa responsabilidade para um provedor especializado (Google, GitHub), assim como PedidoServico delegava a criação do Notificador para quem \"montava\" a aplicação. Você confia na abstração, não precisa reimplementar a parte difícil."},{"id":"autenticacao-conceitos-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Para o primeiro projeto real, comece com JWT simples (usuário/senha próprios, sem OAuth2/OIDC) — é suficiente para entender o fluxo completo de autenticação sem a complexidade extra de configurar um provedor externo. Adicione OAuth2 + OpenID Connect depois, quando o fluxo básico já estiver sólido.</div>","fidelityText":"Para o primeiro projeto real, comece com JWT simples (usuário/senha próprios, sem OAuth2/OIDC) — é suficiente para entender o fluxo completo de autenticação sem a complexidade extra de configurar um provedor externo. Adicione OAuth2 + OpenID Connect depois, quando o fluxo básico já estiver sólido."},{"id":"autenticacao-conceitos-exercise-19","type":"exercise","authorship":"legacy-preserved","title":"Exercício 51.1 — Decodificando um JWT","prompt":"Pegue um JWT de exemplo (pode gerar um em jwt.io, usando o payload {\"sub\":\"usuario_42\",\"exp\":1999999999}) e identifique as três partes separadas por ponto. Decodifique manualmente a parte do meio (payload) usando Base64 e confirme que consegue ler o conteúdo sem precisar de nenhuma chave secreta — reforçando por que dados sensíveis nunca deveriam ir ali.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 51.1 — Decodificando um JWTfácil Pegue um JWT de exemplo (pode gerar um em jwt.io, usando o payload {\"sub\":\"usuario_42\",\"exp\":1999999999}) e identifique as três partes separadas por ponto. Decodifique manualmente a parte do meio (payload) usando Base64 e confirme que consegue ler o conteúdo sem precisar de nenhuma chave secreta — reforçando por que dados sensíveis nunca deveriam ir ali. Ver solução Um JWT tem o formato header.payload.signature. Copiando só a parte do meio e decodificando de Base64 (qualquer decodificador online ou echo \"...\" | base64 -d no terminal), o conteúdo {\"sub\":\"usuario_42\",\"exp\":1999999999} aparece em texto legível, sem exigir nenhuma chave — confirmando que JWT é assinado, não criptografado, e por isso nunca deve carregar informação sensível.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 51.1 — Decodificando um JWT</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Pegue um JWT de exemplo (pode gerar um em jwt.io, usando o payload <code>{\"sub\":\"usuario_42\",\"exp\":1999999999}</code>) e identifique as três partes separadas por ponto. Decodifique manualmente a parte do meio (payload) usando Base64 e confirme que consegue ler o conteúdo sem precisar de nenhuma chave secreta — reforçando por que dados sensíveis nunca deveriam ir ali.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Um JWT tem o formato <code>header.payload.signature</code>. Copiando só a parte do meio e decodificando de Base64 (qualquer decodificador online ou <code>echo \"...\" | base64 -d</code> no terminal), o conteúdo <code>{\"sub\":\"usuario_42\",\"exp\":1999999999}</code> aparece em texto legível, sem exigir nenhuma chave — confirmando que JWT é <strong>assinado</strong>, não <strong>criptografado</strong>, e por isso nunca deve carregar informação sensível.</p>\n        </div>\n      </div>"},{"id":"auth-quiz","type":"quiz","authorship":"authored","conceptId":"jwt-claims-signature","prompt":"Qual afirmação sobre JWT está correta?","options":[{"id":"auth-a","label":"O payload pode ser lido; segurança vem da validação de assinatura, expiração e claims esperadas.","correct":true,"explanation":"JWT normalmente é codificado, não criptografado por padrão."},{"id":"auth-b","label":"Decodificar o payload já prova que o token é confiável.","correct":false,"explanation":"Decodificar só lê bytes; validação exige assinatura e política."},{"id":"auth-c","label":"OAuth2 e JWT são sempre a mesma coisa.","correct":false,"explanation":"OAuth2 é fluxo/protocolo de autorização; JWT é um formato possível de token."}]}],"resources":[{"id":"jwt-rfc7519","type":"reference","title":"RFC 7519: JSON Web Token","url":"https://www.rfc-editor.org/rfc/rfc7519","reinforces":"Formato JWT, claims, assinatura e validação.","language":"en","publisher":"IETF","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"oauth2-rfc6749","type":"reference","title":"RFC 6749: OAuth 2.0 Authorization Framework","url":"https://www.rfc-editor.org/rfc/rfc6749","reinforces":"Papéis, grants e modelo de autorização delegada.","language":"en","publisher":"IETF","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The authentication concepts component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to authentication concepts. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible authentication concepts failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"1. Customer sends login/password","instruction":"The authentication concepts component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible authentication concepts failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"spring-security","moduleId":"api-security-quality","order":1,"title":"Spring Security na prática","summary":"Spring Security é notoriamente a parte do ecossistema Spring com curva de aprendizado mais íngreme — não porque seja mal desenhado, mas porque resolve um problema genuinamente complexo (autenticação + autorização) de forma extremamente configurável.","objectives":["Entender Filter Chain antes do controller","Configurar autenticação stateless com intenção","Aplicar autorização por endpoint e método","Armazenar senha com hash apropriado"],"whyItExists":"Agora que identidade e tokens foram explicados sem framework, Spring Security entra como a implementação industrial da fronteira de segurança em APIs Spring.","prerequisiteChapterIds":["autenticacao-conceitos","spring-mvc","anotacoes"],"conceptIds":["anatomia-real-da-requisicao-ao-securitycontext","userdetailsservice-e-daoauthenticationprovider-o-login-de-verdade","emitindo-e-validando-o-jwt-com-resource-server-caminho-recomendado","laboratorio-avancado-escrevendo-seu-proprio-filtro-jwt","autorizacao-com-preauthorize-agora-com-method-security-habilitada","codificando-senhas-nunca-em-texto-puro","csrf-a-pergunta-certa-antes-de-desabilitar","401-vs-403-authenticationentrypoint-e-accessdeniedhandler","sessao-fixacao-e-logout-mesmo-numa-aplicacao-majoritariamente-stateless","testando-seguranca-spring-security-test-e-mockmvc"],"introducedConceptIds":["password-hash-verification","security-filter-chain","spring-security-authorization"],"usedConceptIds":["jwt-claims-signature","mvc-controller-binding","spring-component-scan-bean"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"spring-security-intuition","type":"intuition","authorship":"authored","title":"Segurança acontece antes do controller","body":"A cadeia de filtros recebe a requisição, tenta autenticar, monta o contexto de segurança e só então deixa o controller rodar. O controller não deve ser a primeira barreira de proteção.","analogyLimit":"Portaria ajuda a imaginar filtro, mas regras de autorização também dependem de método, recurso, token, sessão e contexto."},{"id":"spring-security-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-security\">Spring Security</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~4h30</b> de estudo + prática (o mais denso do curso — respire fundo)</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#autenticacao-conceitos\">51 · Autenticação conceitos</a>, <a class=\"prereq-tag\" href=\"#spring-mvc\">44 · Spring Web/MVC</a>, <a class=\"prereq-tag\" href=\"#anotacoes\">20 · Anotações &amp; Reflection</a> (dynamic proxy)</div>\n      </div>","fidelityText":"Spring Security Dificuldade: Avançado ⏱ ~4h30 de estudo + prática (o mais denso do curso — respire fundo) Pré-requisitos: 51 · Autenticação conceitos, 44 · Spring Web/MVC, 20 · Anotações & Reflection (dynamic proxy)"},{"id":"spring-security-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Spring Security é notoriamente a parte do ecossistema Spring com curva de aprendizado mais íngreme — não porque seja mal desenhado, mas porque resolve um problema genuinamente complexo (autenticação + autorização) de forma extremamente configurável.</p>","fidelityText":"Spring Security é notoriamente a parte do ecossistema Spring com curva de aprendizado mais íngreme — não porque seja mal desenhado, mas porque resolve um problema genuinamente complexo (autenticação + autorização) de forma extremamente configurável."},{"id":"spring-security-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense no Spring Security como um segurança de prédio em duas etapas: primeiro ele confere seu documento na entrada (<strong>autenticação</strong> — \"quem é você?\"), depois confere se seu crachá te dá acesso àquele andar específico (<strong>autorização</strong> — \"você pode estar aqui?\"). Mas um segurança de verdade não decide isso sozinho, na hora, olhando pra sua cara: ele segue um processo — confere o documento com um sistema central, recebe de volta uma confirmação com seu nível de acesso, e só então libera a catraca. É exatamente esse processo, com peças que têm nome, que a próxima seção desmonta.</div>","fidelityText":"Pense no Spring Security como um segurança de prédio em duas etapas: primeiro ele confere seu documento na entrada (autenticação — \"quem é você?\"), depois confere se seu crachá te dá acesso àquele andar específico (autorização — \"você pode estar aqui?\"). Mas um segurança de verdade não decide isso sozinho, na hora, olhando pra sua cara: ele segue um processo — confere o documento com um sistema central, recebe de volta uma confirmação com seu nível de acesso, e só então libera a catraca. É exatamente esse processo, com peças que têm nome, que a próxima seção desmonta."},{"id":"spring-security-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Anatomia real: da requisição ao SecurityContext</h2>","fidelityText":"Anatomia real: da requisição ao SecurityContext"},{"id":"spring-security-content-5","type":"html","authorship":"legacy-preserved","html":"<p>É tentador tratar Spring Security como uma caixa-preta que \"só funciona\" depois de configurada — mas os quatro erros mais comuns em produção (exemplo copiado que não compila, <code>@PreAuthorize</code> ignorado, JWT verificado do jeito errado, autorização que vaza informação) vêm exatamente de pular esta parte. O fluxo completo, do primeiro byte da requisição até a decisão de autorizar ou não:</p>","fidelityText":"É tentador tratar Spring Security como uma caixa-preta que \"só funciona\" depois de configurada — mas os quatro erros mais comuns em produção (exemplo copiado que não compila, @PreAuthorize ignorado, JWT verificado do jeito errado, autorização que vaza informação) vêm exatamente de pular esta parte. O fluxo completo, do primeiro byte da requisição até a decisão de autorizar ou não:"},{"id":"spring-security-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"1. Request HTTP arrives no servlet container (Tomcat embedded)\n2. DelegatingFilterProxy delivers a request para o FilterChainProxy do Spring Security\n3. FilterChainProxy roda a chain de filters de security, em order fixa e documentada\n4. Um filter de authentication extrai as credenciais da request\n   (formulario, header Authorization: Bearer, ou seu own filter customizado)\n   e assembles um Authentication ainda NAO authenticated (so principal + credentials)\n5. O filter chama authenticationManager.authenticate(authentication)\n6. AuthenticationManager (implementation padrao: ProviderManager) percorre uma list\n   de AuthenticationProvider e delega ao first que suporta that type de Authentication\n7. DaoAuthenticationProvider chama UserDetailsService.loadUserByUsername(...)\n   para load o user, e compara a password via PasswordEncoder.matches(...)\n8. Se valid: devolve um Authentication AUTHENTICATED, agora com authorities\n   (GrantedAuthority) filled -- se inválido: lança AuthenticationException\n9. O Authentication authenticated e guardado no SecurityContext,\n   inside do SecurityContextHolder (por padrao, um ThreadLocal por request)\n10. O remaining da chain (e depois o controller) le esse SecurityContext\n    para decide autorizacao -- esse contexto alimenta authentication.principal\n    usado em @PreAuthorize(\"#id == authentication.principal.id\")","fidelityText":"1. Requisição HTTP chega no servlet container (Tomcat embarcado) 2. DelegatingFilterProxy entrega a requisição para o FilterChainProxy do Spring Security 3. FilterChainProxy roda a cadeia de filtros de segurança, em ordem fixa e documentada 4. Um filtro de autenticação extrai as credenciais da requisição (formulário, cabeçalho Authorization: Bearer, ou seu próprio filtro customizado) e monta um Authentication ainda NÃO autenticado (só principal + credentials) 5. O filtro chama authenticationManager.authenticate(authentication) 6. AuthenticationManager (implementação padrão: ProviderManager) percorre uma lista de AuthenticationProvider e delega ao primeiro que suporta aquele tipo de Authentication 7. DaoAuthenticationProvider chama UserDetailsService.loadUserByUsername(...) para carregar o usuário, e compara a senha via PasswordEncoder.matches(...) 8. Se válido: devolve um Authentication AUTENTICADO, agora com authorities (GrantedAuthority) preenchidas -- se inválido: lança AuthenticationException 9. O Authentication autenticado é guardado no SecurityContext, dentro do SecurityContextHolder (por padrão, um ThreadLocal por requisição) 10. O restante da cadeia (e depois o controller) lê esse SecurityContext para decidir autorização -- esse contexto alimenta authentication.principal usado em @PreAuthorize(\"#id == authentication.principal.id\")","highlightedHtml":"<span class=\"com\">1. Requisição HTTP chega no servlet container (Tomcat embarcado)\n2. DelegatingFilterProxy entrega a requisição para o FilterChainProxy do Spring Security\n3. FilterChainProxy roda a cadeia de filtros de segurança, em ordem fixa e documentada\n4. Um filtro de autenticação extrai as credenciais da requisição\n   (formulário, cabeçalho Authorization: Bearer, ou seu próprio filtro customizado)\n   e monta um Authentication ainda NÃO autenticado (só principal + credentials)\n5. O filtro chama authenticationManager.authenticate(authentication)\n6. AuthenticationManager (implementação padrão: ProviderManager) percorre uma lista\n   de AuthenticationProvider e delega ao primeiro que suporta aquele tipo de Authentication\n7. DaoAuthenticationProvider chama UserDetailsService.loadUserByUsername(...)\n   para carregar o usuário, e compara a senha via PasswordEncoder.matches(...)\n8. Se válido: devolve um Authentication AUTENTICADO, agora com authorities\n   (GrantedAuthority) preenchidas -- se inválido: lança AuthenticationException\n9. O Authentication autenticado é guardado no SecurityContext,\n   dentro do SecurityContextHolder (por padrão, um ThreadLocal por requisição)\n10. O restante da cadeia (e depois o controller) lê esse SecurityContext\n    para decidir autorização -- esse contexto alimenta authentication.principal\n    usado em @PreAuthorize(\"#id == authentication.principal.id\")</span>","caption":"Exemplo executável de spring-security.","explanation":["A requisição passa por DelegatingFilterProxy/FilterChainProxy antes do controller; um filtro monta um Authentication não autenticado e delega a validação ao AuthenticationManager.","ProviderManager só delega: quem valida de fato é um AuthenticationProvider específico (DaoAuthenticationProvider chamando UserDetailsService e PasswordEncoder).","O Authentication autenticado (com authorities) fica no SecurityContext dentro do SecurityContextHolder -- é dali que @PreAuthorize lê authentication.principal."],"commonMistakes":["Achar que o Spring valida a senha magicamente, sem nomear as peças (AuthenticationManager/ProviderManager/AuthenticationProvider)","Tratar SecurityContextHolder como um detalhe interno em vez do lugar de onde a autorização é decidida"]},{"id":"spring-security-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Duas peças merecem destaque porque aparecem sozinhas, sem explicação, em código copiado da internet: o <strong><code>AuthenticationManager</code></strong> é a interface com um único método (<code>authenticate</code>); <strong><code>ProviderManager</code></strong> é a implementação padrão do Spring Security para ela, e só delega — quem de fato valida é um <strong><code>AuthenticationProvider</code></strong> específico (o mais comum sendo <code>DaoAuthenticationProvider</code>, que por sua vez depende de um <code>UserDetailsService</code> seu). Sem esse encadeamento nomeado, é fácil achar que \"o Spring valida a senha magicamente\" — na verdade, cada elo é uma peça substituível, e é exatamente essa substituibilidade que permite plugar JWT, OAuth2, LDAP ou qualquer outro mecanismo sem reescrever o resto.</p>","fidelityText":"Duas peças merecem destaque porque aparecem sozinhas, sem explicação, em código copiado da internet: o AuthenticationManager é a interface com um único método (authenticate); ProviderManager é a implementação padrão do Spring Security para ela, e só delega — quem de fato valida é um AuthenticationProvider específico (o mais comum sendo DaoAuthenticationProvider, que por sua vez depende de um UserDetailsService seu). Sem esse encadeamento nomeado, é fácil achar que \"o Spring valida a senha magicamente\" — na verdade, cada elo é uma peça substituível, e é exatamente essa substituibilidade que permite plugar JWT, OAuth2, LDAP ou qualquer outro mecanismo sem reescrever o resto."},{"id":"spring-security-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Login implementado direto no controller, sem passar por esse fluxo, é o erro mais comum em projetos que \"funcionam mas não são Spring Security de verdade\":</b> chamar <code>usuarioRepository.findByEmail(...)</code> e <code>passwordEncoder.matches(...)</code> manualmente dentro do controller até autentica o usuário, mas nunca cria um <code>Authentication</code> real, nunca populam authorities corretamente para <code>@PreAuthorize</code>, e duplicam uma lógica que o framework já resolve — testada, mantida e revisada por milhares de projetos. A seção seguinte mostra o jeito correto: implementar <code>UserDetailsService</code> e deixar o <code>AuthenticationManager</code> fazer o trabalho.</div>","fidelityText":"Login implementado direto no controller, sem passar por esse fluxo, é o erro mais comum em projetos que \"funcionam mas não são Spring Security de verdade\": chamar usuarioRepository.findByEmail(...) e passwordEncoder.matches(...) manualmente dentro do controller até autentica o usuário, mas nunca cria um Authentication real, nunca populam authorities corretamente para @PreAuthorize, e duplicam uma lógica que o framework já resolve — testada, mantida e revisada por milhares de projetos. A seção seguinte mostra o jeito correto: implementar UserDetailsService e deixar o AuthenticationManager fazer o trabalho."},{"id":"spring-security-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>UserDetailsService e DaoAuthenticationProvider: o login de verdade</h2>","fidelityText":"UserDetailsService e DaoAuthenticationProvider: o login de verdade"},{"id":"spring-security-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class LibraryUserDetailsService implements UserDetailsService {\n    private final UserRepository userRepository;\n\n    public LibraryUserDetailsService(UserRepository userRepository) {\n        this.userRepository = userRepository;\n    }\n\n    @Override\n    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {\n        User user = userRepository.findByEmail(email)\n            .orElseThrow(() -> new UsernameNotFoundException(\"email not found: \" + email));\n        return User.builder()\n            .username(user.getEmail())\n            .password(user.getPassword()) // já é o hash BCrypt -- nunca a senha crua\n            .authorities(\"ROLE_\" + user.getRole())\n            .build();\n    }\n}","fidelityText":"@Service public class BibliotecaUserDetailsService implements UserDetailsService { private final UsuarioRepository usuarioRepository; public BibliotecaUserDetailsService(UsuarioRepository usuarioRepository) { this.usuarioRepository = usuarioRepository; } @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Usuario usuario = usuarioRepository.findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException(\"email não encontrado: \" + email)); return User.builder() .username(usuario.getEmail()) .password(usuario.getSenha()) // já é o hash BCrypt -- nunca a senha crua .authorities(\"ROLE_\" + usuario.getRole()) .build(); } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">LibraryUserDetailsService</span> <span class=\"kw\">implements</span> UserDetailsService {\n    <span class=\"kw\">private final</span> <span class=\"cls\">UserRepository</span> userRepository;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">LibraryUserDetailsService</span>(<span class=\"cls\">UserRepository</span> userRepository) {\n        <span class=\"kw\">this</span>.userRepository = userRepository;\n    }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public</span> UserDetails <span class=\"fn\">loadUserByUsername</span>(<span class=\"kw\">String</span> email) <span class=\"kw\">throws</span> UsernameNotFoundException {\n        <span class=\"cls\">User</span> user = userRepository.findByEmail(email)\n            .orElseThrow(() -&gt; <span class=\"kw\">new</span> UsernameNotFoundException(<span class=\"str\">\"email not found: \"</span> + email));\n        <span class=\"kw\">return</span> User.builder()\n            .username(user.getEmail())\n            .password(user.getPassword()) <span class=\"com\">// já é o hash BCrypt -- nunca a senha crua</span>\n            .authorities(<span class=\"str\">\"ROLE_\"</span> + user.getRole())\n            .build();\n    }\n}","caption":"Exemplo executável de spring-security.","explanation":["UserDetailsService.loadUserByUsername carrega o usuário e devolve um UserDetails com o hash de senha já salvo, nunca a senha crua.","As authorities (aqui, o prefixo ROLE_) vêm do papel do usuário e são o que @PreAuthorize/hasRole avalia depois.","Com este bean e um PasswordEncoder presentes, o Spring Boot auto-configura um DaoAuthenticationProvider que os conecta -- não é preciso declará-lo manualmente."],"commonMistakes":["Guardar a senha crua no UserDetails em vez do hash já persistido","Esquecer de prefixar authorities com ROLE_ ao usar hasRole(...)"]},{"id":"spring-security-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Com um bean <code>UserDetailsService</code> e um bean <code>PasswordEncoder</code> presentes, o Spring Boot <strong>auto-configura</strong> um <code>DaoAuthenticationProvider</code> que os conecta — na maioria dos casos você nunca precisa declarar esse provider manualmente. O que ainda precisa de um bean explícito é o <code>AuthenticationManager</code>, para poder injetá-lo onde for autenticar de fato:</p>","fidelityText":"Com um bean UserDetailsService e um bean PasswordEncoder presentes, o Spring Boot auto-configura um DaoAuthenticationProvider que os conecta — na maioria dos casos você nunca precisa declarar esse provider manualmente. O que ainda precisa de um bean explícito é o AuthenticationManager, para poder injetá-lo onde for autenticar de fato:"},{"id":"spring-security-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"@Bean\npublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {\n    return config.getAuthenticationManager(); // devolve o ProviderManager já montado com o DaoAuthenticationProvider\n}","fidelityText":"@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); // devolve o ProviderManager já montado com o DaoAuthenticationProvider }","highlightedHtml":"<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> AuthenticationManager <span class=\"fn\">authenticationManager</span>(AuthenticationConfiguration config) <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n    <span class=\"kw\">return</span> config.getAuthenticationManager(); <span class=\"com\">// devolve o ProviderManager já montado com o DaoAuthenticationProvider</span>\n}","caption":"Exemplo executável de spring-security.","explanation":["AuthenticationConfiguration#getAuthenticationManager() devolve o ProviderManager já montado com o DaoAuthenticationProvider auto-configurado.","Expor esse bean é o que permite injetar AuthenticationManager em qualquer lugar que precise autenticar explicitamente (como o endpoint de login)."],"commonMistakes":["Tentar instanciar um AuthenticationManager na mão em vez de obtê-lo da AuthenticationConfiguration","Confundir este bean com o AuthenticationProvider, que é uma peça interna dele"]},{"id":"spring-security-content-13","type":"html","authorship":"legacy-preserved","html":"<p>E o endpoint de login passa a autenticar de verdade, em vez de comparar hash manualmente:</p>","fidelityText":"E o endpoint de login passa a autenticar de verdade, em vez de comparar hash manualmente:"},{"id":"spring-security-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"@PostMapping(\"/auth/login\")\npublic ResponseEntity<TokenDTO> login(@RequestBody LoginDTO dto) {\n    Authentication authenticated = authenticationManager.authenticate(\n        new UsernamePasswordAuthenticationToken(dto.email(), dto.password())\n    ); // lança BadCredentialsException se inválido -- não retorna boolean\n    return ResponseEntity.ok(new TokenDTO(jwtService.generateToken(authenticated)));\n}","fidelityText":"@PostMapping(\"/auth/login\") public ResponseEntity<TokenDTO> login(@RequestBody LoginDTO dto) { Authentication autenticado = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(dto.email(), dto.senha()) ); // lança BadCredentialsException se inválido -- não retorna boolean return ResponseEntity.ok(new TokenDTO(jwtServico.gerarToken(autenticado))); }","highlightedHtml":"<span class=\"annotation\">@PostMapping</span>(<span class=\"str\">\"/auth/login\"</span>)\n<span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"cls\">TokenDTO</span>&gt; <span class=\"fn\">login</span>(<span class=\"annotation\">@RequestBody</span> <span class=\"cls\">LoginDTO</span> dto) {\n    <span class=\"cls\">Authentication</span> authenticated = authenticationManager.authenticate(\n        <span class=\"kw\">new</span> UsernamePasswordAuthenticationToken(dto.email(), dto.password())\n    ); <span class=\"com\">// lança BadCredentialsException se inválido -- não retorna boolean</span>\n    <span class=\"kw\">return</span> ResponseEntity.ok(<span class=\"kw\">new</span> <span class=\"cls\">TokenDTO</span>(jwtService.generateToken(authenticated)));\n}","caption":"Exemplo executável de spring-security.","explanation":["O login chama authenticationManager.authenticate(...) em vez de comparar hash manualmente -- é o AuthenticationManager, não o controller, quem decide se as credenciais são válidas.","authenticate(...) lança AuthenticationException (ex.: BadCredentialsException) em caso de falha, em vez de devolver um boolean.","O JWT é gerado a partir do Authentication autenticado devolvido, não da entidade Usuario diretamente."],"commonMistakes":["Continuar chamando passwordEncoder.matches(...) manualmente no controller em vez de delegar ao AuthenticationManager","Tratar authenticate(...) como se devolvesse true/false em vez de lançar exceção"]},{"id":"spring-security-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Note a diferença de contrato: o <code>authenticationManager.authenticate(...)</code> não devolve <code>true</code>/<code>false</code> como o <code>passwordEncoder.matches(...)</code> manual — ele devolve um <code>Authentication</code> autenticado em caso de sucesso, ou <strong>lança</strong> <code>AuthenticationException</code> (por exemplo <code>BadCredentialsException</code>) em caso de falha. Isso significa que credenciais inválidas viram um erro tratado por um <code>@ExceptionHandler</code> (ou pelo <code>AuthenticationEntryPoint</code>, visto adiante) — não um <code>if</code> manual no controller.</div>","fidelityText":"Note a diferença de contrato: o authenticationManager.authenticate(...) não devolve true/false como o passwordEncoder.matches(...) manual — ele devolve um Authentication autenticado em caso de sucesso, ou lança AuthenticationException (por exemplo BadCredentialsException) em caso de falha. Isso significa que credenciais inválidas viram um erro tratado por um @ExceptionHandler (ou pelo AuthenticationEntryPoint, visto adiante) — não um if manual no controller."},{"id":"spring-security-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>Emitindo e validando o JWT com Resource Server (caminho recomendado)</h2>","fidelityText":"Emitindo e validando o JWT com Resource Server (caminho recomendado)"},{"id":"spring-security-content-17","type":"html","authorship":"legacy-preserved","html":"<p>O <code>JwtServico</code> agora emite o token a partir do <code>Authentication</code> devolvido pelo <code>AuthenticationManager</code>, não a partir da entidade <code>Usuario</code> diretamente — reforçando que quem autoriza é o <code>SecurityContext</code>, não o banco de dados direto:</p>","fidelityText":"O JwtServico agora emite o token a partir do Authentication devolvido pelo AuthenticationManager, não a partir da entidade Usuario diretamente — reforçando que quem autoriza é o SecurityContext, não o banco de dados direto:"},{"id":"spring-security-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class JwtService {\n    private final String keySecret = \"...\"; // NUNCA hardcoded de verdade -- vem de variável de ambiente (capítulo 27!)\n\n    public String generateToken(Authentication authenticated) {\n        String authorities = authenticated.getAuthorities().stream()\n            .map(GrantedAuthority::getAuthority)\n            .collect(Collectors.joining(\",\"));\n        return Jwts.builder()\n            .subject(authenticated.getName())\n            .claim(\"role\", authorities)\n            .issuedAt(new Date())\n            .expiration(new Date(System.currentTimeMillis() + 3600_000)) // 1 hora\n            .signWith(keyAsBytes())\n            .compact();\n    }\n}","fidelityText":"@Service public class JwtServico { private final String chaveSecreta = \"...\"; // NUNCA hardcoded de verdade -- vem de variável de ambiente (capítulo 27!) public String gerarToken(Authentication autenticado) { String authorities = autenticado.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(\",\")); return Jwts.builder() .subject(autenticado.getName()) .claim(\"role\", authorities) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + 3600_000)) // 1 hora .signWith(chaveComoBytes()) .compact(); } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">JwtService</span> {\n    <span class=\"kw\">private final</span> <span class=\"kw\">String</span> keySecret = <span class=\"str\">\"...\"</span>; <span class=\"com\">// NUNCA hardcoded de verdade -- vem de variável de ambiente (capítulo 27!)</span>\n\n    <span class=\"kw\">public</span> <span class=\"kw\">String</span> <span class=\"fn\">generateToken</span>(<span class=\"cls\">Authentication</span> authenticated) {\n        <span class=\"kw\">String</span> authorities = authenticated.getAuthorities().stream()\n            .map(GrantedAuthority::getAuthority)\n            .collect(Collectors.joining(<span class=\"str\">\",\"</span>));\n        <span class=\"kw\">return</span> Jwts.builder()\n            .subject(authenticated.getName())\n            .claim(<span class=\"str\">\"role\"</span>, authorities)\n            .issuedAt(<span class=\"kw\">new</span> Date())\n            .expiration(<span class=\"kw\">new</span> Date(System.currentTimeMillis() + 3600_000)) <span class=\"com\">// 1 hora</span>\n            .signWith(keyAsBytes())\n            .compact();\n    }\n}","caption":"Exemplo executável de spring-security.","explanation":["O token carrega o subject (usuário) e as authorities do Authentication já validado, não dados sensíveis.","A expiração curta (1 hora) limita o estrago se o token vazar -- combine com refresh token para renovar sem exigir login constante.","A chave de assinatura nunca é hardcoded de verdade: vem de variável de ambiente."],"commonMistakes":["Colocar dado sensível (senha, cartão) dentro do claim do JWT","Gerar token sem expiração ou com expiração longa demais"]},{"id":"spring-security-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Para <strong>validar</strong> esse token nas requisições seguintes, o caminho recomendado não é escrever seu próprio filtro (isso vem no laboratório avançado, adiante) — é usar o suporte nativo de <strong>OAuth2 Resource Server</strong> do Spring Security, o mesmo mecanismo battle-tested que valida tokens emitidos por provedores externos no capítulo de OIDC:</p>","fidelityText":"Para validar esse token nas requisições seguintes, o caminho recomendado não é escrever seu próprio filtro (isso vem no laboratório avançado, adiante) — é usar o suporte nativo de OAuth2 Resource Server do Spring Security, o mesmo mecanismo battle-tested que valida tokens emitidos por provedores externos no capítulo de OIDC:"},{"id":"spring-security-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"@Bean\npublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n    return http\n        .csrf(csrf -> csrf.disable()) // ver seção CSRF abaixo -- a razão certa, não \"porque é API\"\n        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))\n        .authorizeHttpRequests(auth -> auth\n            .requestMatchers(\"/auth/login\", \"/auth/register\").permitAll()\n            .requestMatchers(HttpMethod.GET, \"/books/**\").permitAll()\n            .requestMatchers(\"/admin/**\").hasRole(\"ADMIN\")\n            .anyRequest().authenticated()\n        )\n        .exceptionHandling(ex -> ex\n            .authenticationEntryPoint((req, res, e) -> res.sendError(HttpStatus.UNAUTHORIZED.value()))\n            .accessDeniedHandler((req, res, e) -> res.sendError(HttpStatus.FORBIDDEN.value()))\n        )\n        .oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt\n            .decoder(jwtDecoder())\n            .jwtAuthenticationConverter(jwtAuthenticationConverter())\n        ))\n        .build();\n}\n\n@Bean\npublic JwtDecoder jwtDecoder() {\n    // HMAC (chave simétrica) só faz sentido aqui porque ESTA aplicação\n    // emite e valida o próprio token. Quando o emissor é externo (capítulo\n    // de OIDC), a validação usa chaves assimétricas publicadas via JWK Set --\n    // o mecanismo de assinatura muda, mas o encaixe na filter chain, via\n    // oauth2ResourceServer().jwt(...), é o mesmo dos dois lados.\n    return NimbusJwtDecoder.withSecretKey(keyAsBytes()).build();\n}\n\n@Bean\npublic JwtAuthenticationConverter jwtAuthenticationConverter() {\n    var authoritiesConverter = new JwtGrantedAuthoritiesConverter();\n    authoritiesConverter.setAuthorityPrefix(\"ROLE_\");\n    authoritiesConverter.setAuthoritiesClaimName(\"role\"); // mesmo nome de claim usado em gerarToken\n    var converter = new JwtAuthenticationConverter();\n    converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);\n    return converter;\n}","fidelityText":"@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) // ver seção CSRF abaixo -- a razão certa, não \"porque é API\" .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers(\"/auth/login\", \"/auth/registrar\").permitAll() .requestMatchers(HttpMethod.GET, \"/livros/**\").permitAll() .requestMatchers(\"/admin/**\").hasRole(\"ADMIN\") .anyRequest().authenticated() ) .exceptionHandling(ex -> ex .authenticationEntryPoint((req, res, e) -> res.sendError(HttpStatus.UNAUTHORIZED.value())) .accessDeniedHandler((req, res, e) -> res.sendError(HttpStatus.FORBIDDEN.value())) ) .oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt .decoder(jwtDecoder()) .jwtAuthenticationConverter(jwtAuthenticationConverter()) )) .build(); } @Bean public JwtDecoder jwtDecoder() { // HMAC (chave simétrica) só faz sentido aqui porque ESTA aplicação // emite e valida o próprio token. Quando o emissor é externo (capítulo // de OIDC), a validação usa chaves assimétricas publicadas via JWK Set -- // o mecanismo de assinatura muda, mas o encaixe na filter chain, via // oauth2ResourceServer().jwt(...), é o mesmo dos dois lados. return NimbusJwtDecoder.withSecretKey(chaveComoBytes()).build(); } @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { var authoritiesConverter = new JwtGrantedAuthoritiesConverter(); authoritiesConverter.setAuthorityPrefix(\"ROLE_\"); authoritiesConverter.setAuthoritiesClaimName(\"role\"); // mesmo nome de claim usado em gerarToken var converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); return converter; }","highlightedHtml":"<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> SecurityFilterChain <span class=\"fn\">filterChain</span>(HttpSecurity http) <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n    <span class=\"kw\">return</span> http\n        .csrf(csrf -&gt; csrf.disable()) <span class=\"com\">// ver seção CSRF abaixo -- a razão certa, não \"porque é API\"</span>\n        .sessionManagement(s -&gt; s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))\n        .authorizeHttpRequests(auth -&gt; auth\n            .requestMatchers(<span class=\"str\">\"/auth/login\"</span>, <span class=\"str\">\"/auth/register\"</span>).permitAll()\n            .requestMatchers(HttpMethod.GET, <span class=\"str\">\"/books/**\"</span>).permitAll()\n            .requestMatchers(<span class=\"str\">\"/admin/**\"</span>).hasRole(<span class=\"str\">\"ADMIN\"</span>)\n            .anyRequest().authenticated()\n        )\n        .exceptionHandling(ex -&gt; ex\n            .authenticationEntryPoint((req, res, e) -&gt; res.sendError(HttpStatus.UNAUTHORIZED.value()))\n            .accessDeniedHandler((req, res, e) -&gt; res.sendError(HttpStatus.FORBIDDEN.value()))\n        )\n        .oauth2ResourceServer(oauth -&gt; oauth.jwt(jwt -&gt; jwt\n            .decoder(jwtDecoder())\n            .jwtAuthenticationConverter(jwtAuthenticationConverter())\n        ))\n        .build();\n}\n\n<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> JwtDecoder <span class=\"fn\">jwtDecoder</span>() {\n    <span class=\"com\">// HMAC (chave simétrica) só faz sentido aqui porque ESTA aplicação\n    // emite e valida o próprio token. Quando o emissor é externo (capítulo\n    // de OIDC), a validação usa chaves assimétricas publicadas via JWK Set --\n    // o mecanismo de assinatura muda, mas o encaixe na filter chain, via\n    // oauth2ResourceServer().jwt(...), é o mesmo dos dois lados.</span>\n    <span class=\"kw\">return</span> NimbusJwtDecoder.withSecretKey(keyAsBytes()).build();\n}\n\n<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> JwtAuthenticationConverter <span class=\"fn\">jwtAuthenticationConverter</span>() {\n    <span class=\"kw\">var</span> authoritiesConverter = <span class=\"kw\">new</span> JwtGrantedAuthoritiesConverter();\n    authoritiesConverter.setAuthorityPrefix(<span class=\"str\">\"ROLE_\"</span>);\n    authoritiesConverter.setAuthoritiesClaimName(<span class=\"str\">\"role\"</span>); <span class=\"com\">// mesmo nome de claim usado em gerarToken</span>\n    <span class=\"kw\">var</span> converter = <span class=\"kw\">new</span> JwtAuthenticationConverter();\n    converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);\n    <span class=\"kw\">return</span> converter;\n}","caption":"Exemplo executável de spring-security.","explanation":["oauth2ResourceServer().jwt(...) é o caminho recomendado para validar o JWT nas requisições seguintes -- substitui o filtro customizado por um mecanismo testado e mantido pelo Spring Security.","authenticationEntryPoint responde 401 quando ninguém está autenticado; accessDeniedHandler responde 403 quando o autenticado não tem permissão -- são cenários diferentes.","O JwtDecoder usa HMAC (chave simétrica) porque esta aplicação emite e valida o próprio token; quando o emissor é externo (capítulo de OIDC), a validação usa chaves assimétricas via JWK Set, mas o encaixe na filter chain é o mesmo.","JwtAuthenticationConverter mapeia o claim role do token para GrantedAuthority, usando o mesmo nome de claim definido ao gerar o token."],"commonMistakes":["Desabilitar CSRF só por ser uma API, sem checar se as credenciais são enviadas automaticamente pelo navegador","Escrever um filtro JWT customizado em vez de configurar oauth2ResourceServer(), perdendo validações que ele já faz de fábrica"]},{"id":"spring-security-content-21","type":"html","authorship":"legacy-preserved","html":"<p>Usar <code>oauth2ResourceServer(...)</code> em vez de um filtro escrito à mão traz de graça: validação de expiração e assinatura já testada, conversão de claims em <code>GrantedAuthority</code> configurável sem reescrever nada, e o mesmo modelo mental do capítulo seguinte de OIDC — trocar apenas o <code>JwtDecoder</code> (de chave simétrica para JWK Set assíncrono) é o suficiente para migrar de \"emito meu próprio token\" para \"confio num provedor de identidade externo\", sem tocar no resto da configuração.</p>","fidelityText":"Usar oauth2ResourceServer(...) em vez de um filtro escrito à mão traz de graça: validação de expiração e assinatura já testada, conversão de claims em GrantedAuthority configurável sem reescrever nada, e o mesmo modelo mental do capítulo seguinte de OIDC — trocar apenas o JwtDecoder (de chave simétrica para JWK Set assíncrono) é o suficiente para migrar de \"emito meu próprio token\" para \"confio num provedor de identidade externo\", sem tocar no resto da configuração."},{"id":"spring-security-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>Laboratório avançado: escrevendo seu próprio filtro JWT</h2>","fidelityText":"Laboratório avançado: escrevendo seu próprio filtro JWT"},{"id":"spring-security-content-23","type":"html","authorship":"legacy-preserved","html":"<p>Entender <em>como</em> um filtro de autenticação funciona por dentro vale a pena — mas o código abaixo é material de estudo, não o caminho recomendado para produção (a seção anterior já resolve o problema de forma testada e mantida pelo próprio Spring Security):</p>","fidelityText":"Entender como um filtro de autenticação funciona por dentro vale a pena — mas o código abaixo é material de estudo, não o caminho recomendado para produção (a seção anterior já resolve o problema de forma testada e mantida pelo próprio Spring Security):"},{"id":"spring-security-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"public class JwtAuthFilter extends OncePerRequestFilter {\n    @Override\n    protected void ofFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)\n            throws ServletException, IOException {\n        String header = req.getHeader(\"Authorization\");\n        if (header != null && header.startsWith(\"Bearer \")) {\n            String token = header.substring(7);\n            if (jwtService.validate(token)) {\n                // aqui você mesmo montaria o Authentication e o colocaria\n                // no SecurityContextHolder -- exatamente o passo que\n                // oauth2ResourceServer().jwt() já faz por você\n            }\n        }\n        chain.ofFilter(req, res);\n    }\n}\n// registro manual, se optar por este caminho em vez do Resource Server:\n// .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)","fidelityText":"public class JwtAuthFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException { String header = req.getHeader(\"Authorization\"); if (header != null && header.startsWith(\"Bearer \")) { String token = header.substring(7); if (jwtServico.validar(token)) { // aqui você mesmo montaria o Authentication e o colocaria // no SecurityContextHolder -- exatamente o passo que // oauth2ResourceServer().jwt() já faz por você } } chain.doFilter(req, res); } } // registro manual, se optar por este caminho em vez do Resource Server: // .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">JwtAuthFilter</span> <span class=\"kw\">extends</span> OncePerRequestFilter {\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">protected void</span> <span class=\"fn\">ofFilterInternal</span>(HttpServletRequest req, HttpServletResponse res, FilterChain chain)\n            <span class=\"kw\">throws</span> ServletException, IOException {\n        <span class=\"kw\">String</span> header = req.getHeader(<span class=\"str\">\"Authorization\"</span>);\n        <span class=\"kw\">if</span> (header != <span class=\"kw\">null</span> &amp;&amp; header.startsWith(<span class=\"str\">\"Bearer \"</span>)) {\n            <span class=\"kw\">String</span> token = header.substring(<span class=\"str\">7</span>);\n            <span class=\"kw\">if</span> (jwtService.validate(token)) {\n                <span class=\"com\">// aqui você mesmo montaria o Authentication e o colocaria\n                // no SecurityContextHolder -- exatamente o passo que\n                // oauth2ResourceServer().jwt() já faz por você</span>\n            }\n        }\n        chain.ofFilter(req, res);\n    }\n}\n<span class=\"com\">// registro manual, se optar por este caminho em vez do Resource Server:\n// .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)</span>","caption":"Exemplo executável de spring-security.","explanation":["Este filtro é material de estudo (laboratório avançado): mostra o que oauth2ResourceServer().jwt() já resolve por trás das cenas.","Um filtro escrito à mão não valida issuer, audience nem algoritmo de assinatura por padrão -- cada checagem esquecida é uma vulnerabilidade real.","Registrar este filtro manualmente (addFilterBefore) é uma alternativa ao Resource Server, não o caminho recomendado para produção."],"commonMistakes":["Copiar este filtro para produção em vez de usar oauth2ResourceServer()","Esquecer de validar issuer/audience/algoritmo ao escrever um filtro do zero"]},{"id":"spring-security-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\">Um filtro customizado escrito à mão não valida issuer, audience nem algoritmo de assinatura por padrão — cada uma dessas checagens fica por sua conta, e esquecer qualquer uma delas é uma vulnerabilidade real. É exatamente esse tipo de detalhe que o <code>oauth2ResourceServer().jwt()</code> resolve de fábrica.</div>","fidelityText":"Um filtro customizado escrito à mão não valida issuer, audience nem algoritmo de assinatura por padrão — cada uma dessas checagens fica por sua conta, e esquecer qualquer uma delas é uma vulnerabilidade real. É exatamente esse tipo de detalhe que o oauth2ResourceServer().jwt() resolve de fábrica."},{"id":"spring-security-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>Autorização com @PreAuthorize (agora com method security habilitada)</h2>","fidelityText":"Autorização com @PreAuthorize (agora com method security habilitada)"},{"id":"spring-security-code-27","type":"code","authorship":"legacy-preserved","language":"java","source":"@Configuration\n@EnableWebSecurity\n@EnableMethodSecurity // sem esta anotação, @PreAuthorize é ignorado silenciosamente -- erro clássico\npublic class SecurityConfig { /* beans da seção anterior */ }","fidelityText":"@Configuration @EnableWebSecurity @EnableMethodSecurity // sem esta anotação, @PreAuthorize é ignorado silenciosamente -- erro clássico public class SecurityConfig { /* beans da seção anterior */ }","highlightedHtml":"<span class=\"annotation\">@Configuration</span>\n<span class=\"annotation\">@EnableWebSecurity</span>\n<span class=\"annotation\">@EnableMethodSecurity</span> <span class=\"com\">// sem esta anotação, @PreAuthorize é ignorado silenciosamente -- erro clássico</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">SecurityConfig</span> { <span class=\"com\">/* beans da seção anterior */</span> }","caption":"Exemplo executável de spring-security.","explanation":["@EnableMethodSecurity é obrigatória para @PreAuthorize funcionar -- sem ela, a anotação é ignorada silenciosamente, sem erro de compilação nem exceção em runtime.","@EnableWebSecurity e @EnableMethodSecurity resolvem problemas diferentes: a primeira habilita a filter chain HTTP, a segunda habilita autorização por método."],"commonMistakes":["Copiar exemplos com @PreAuthorize sem declarar @EnableMethodSecurity e não entender por que a regra nunca é aplicada"]},{"id":"spring-security-code-28","type":"code","authorship":"legacy-preserved","language":"java","source":"@PreAuthorize(\"hasRole('ADMIN')\") // checa ANTES do método executar -- via dynamic proxy, capítulo 20!\n@DeleteMapping(\"/books/{id}\")\npublic ResponseEntity<Void> delete(@PathVariable Long id) { ... }\n\n@PreAuthorize(\"#id == authentication.principal.id\") // só o PRÓPRIO usuário pode editar seu perfil\n@PutMapping(\"/users/{id}\")\npublic ResponseEntity<Void> update(@PathVariable Long id, ...) { ... }","fidelityText":"@PreAuthorize(\"hasRole('ADMIN')\") // checa ANTES do método executar -- via dynamic proxy, capítulo 20! @DeleteMapping(\"/livros/{id}\") public ResponseEntity<Void> deletar(@PathVariable Long id) { ... } @PreAuthorize(\"#id == authentication.principal.id\") // só o PRÓPRIO usuário pode editar seu perfil @PutMapping(\"/usuarios/{id}\") public ResponseEntity<Void> atualizar(@PathVariable Long id, ...) { ... }","highlightedHtml":"<span class=\"annotation\">@PreAuthorize</span>(<span class=\"str\">\"hasRole('ADMIN')\"</span>) <span class=\"com\">// checa ANTES do método executar -- via dynamic proxy, capítulo 20!</span>\n<span class=\"annotation\">@DeleteMapping</span>(<span class=\"str\">\"/books/{id}\"</span>)\n<span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"kw\">Void</span>&gt; <span class=\"fn\">delete</span>(<span class=\"annotation\">@PathVariable</span> <span class=\"kw\">Long</span> id) { ... }\n\n<span class=\"annotation\">@PreAuthorize</span>(<span class=\"str\">\"#id == authentication.principal.id\"</span>) <span class=\"com\">// só o PRÓPRIO usuário pode editar seu perfil</span>\n<span class=\"annotation\">@PutMapping</span>(<span class=\"str\">\"/users/{id}\"</span>)\n<span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"kw\">Void</span>&gt; <span class=\"fn\">update</span>(<span class=\"annotation\">@PathVariable</span> <span class=\"kw\">Long</span> id, ...) { ... }","caption":"Exemplo executável de spring-security.","explanation":["@PreAuthorize é avaliado antes do método executar, via o mesmo mecanismo de proxy dinâmico usado por @Transactional e @Cacheable.","A expressão pode misturar papel (hasRole) e ownership do recurso (#id == authentication.principal.id) -- role sozinho não cobre regras de dono do recurso.","Por ser baseado em proxy, self-invocation (chamar o método de dentro da própria classe) ignora a checagem silenciosamente."],"commonMistakes":["Usar apenas hasRole quando a regra depende de quem é o dono do recurso","Chamar o método anotado a partir de outro método da mesma classe e assumir que a autorização ainda se aplica"]},{"id":"spring-security-content-29","type":"html","authorship":"legacy-preserved","html":"<p><code>@PreAuthorize</code> é implementado com o mesmo mecanismo de proxy dinâmico usado por <code>@Transactional</code> e <code>@Cacheable</code> — e por isso herda a mesma limitação: chamar o método anotado de <em>dentro</em> da mesma classe (self-invocation) passa por cima do proxy e ignora a checagem de autorização silenciosamente. O capítulo de Spring AOP detalha esse mecanismo com profundidade; aqui basta lembrar que <code>@PreAuthorize</code> só protege chamadas que passam pelo proxy, ou seja, chamadas vindas de fora da classe.</p>","fidelityText":"@PreAuthorize é implementado com o mesmo mecanismo de proxy dinâmico usado por @Transactional e @Cacheable — e por isso herda a mesma limitação: chamar o método anotado de dentro da mesma classe (self-invocation) passa por cima do proxy e ignora a checagem de autorização silenciosamente. O capítulo de Spring AOP detalha esse mecanismo com profundidade; aqui basta lembrar que @PreAuthorize só protege chamadas que passam pelo proxy, ou seja, chamadas vindas de fora da classe."},{"id":"spring-security-content-30","type":"html","authorship":"legacy-preserved","html":"<h2>Codificando senhas — nunca em texto puro</h2>","fidelityText":"Codificando senhas — nunca em texto puro"},{"id":"spring-security-code-31","type":"code","authorship":"legacy-preserved","language":"java","source":"@Bean\npublic PasswordEncoder passwordEncoder() {\n    return new BCryptPasswordEncoder(); // hash unidirecional com \"salt\" -- nunca reversível\n}\n\n// ao registrar:\nuser.setPassword(passwordEncoder.encode(passwordDigitada));","fidelityText":"@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // hash unidirecional com \"salt\" -- nunca reversível } // ao registrar: usuario.setSenha(passwordEncoder.encode(senhaDigitada));","highlightedHtml":"<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> PasswordEncoder <span class=\"fn\">passwordEncoder</span>() {\n    <span class=\"kw\">return new</span> BCryptPasswordEncoder(); <span class=\"com\">// hash unidirecional com \"salt\" -- nunca reversível</span>\n}\n\n<span class=\"com\">// ao registrar:</span>\nuser.setPassword(passwordEncoder.encode(passwordDigitada));","caption":"Exemplo executável de spring-security.","explanation":["BCryptPasswordEncoder gera hash unidirecional com salt -- nunca é possível reverter para a senha original.","O work factor do BCrypt é configurável (new BCryptPasswordEncoder(strength)) exatamente para poder encarecer o custo de força bruta conforme o hardware disponível."],"commonMistakes":["Guardar senha em texto puro ou apenas criptografada de forma reversível","Citar um número absoluto de tentativas/segundo do BCrypt como se fosse garantia universal, em vez de medir no próprio hardware"]},{"id":"spring-security-content-32","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">BCrypt é deliberadamente <strong>lento</strong> — e isso é uma característica de segurança, não um defeito de performance. Um hash rápido (como MD5 ou SHA-1 puro) permite testar um volume enorme de senhas por segundo contra um vazamento de banco; o <strong>work factor</strong> (fator de custo) do BCrypt existe justamente para encarecer cada tentativa o suficiente para inviabilizar força bruta em massa. Quantas tentativas por segundo isso permite depende inteiramente do hardware do atacante e do work factor escolhido — nunca cite um número absoluto como verdade universal (hardware evolui, GPUs e ASICs mudam a conta). Se precisar decidir o work factor certo para seu caso, <strong>meça</strong> o custo no seu próprio hardware (<code>new BCryptPasswordEncoder(strength)</code>, padrão <code>10</code>) em vez de confiar numa cifra genérica.</div>","fidelityText":"BCrypt é deliberadamente lento — e isso é uma característica de segurança, não um defeito de performance. Um hash rápido (como MD5 ou SHA-1 puro) permite testar um volume enorme de senhas por segundo contra um vazamento de banco; o work factor (fator de custo) do BCrypt existe justamente para encarecer cada tentativa o suficiente para inviabilizar força bruta em massa. Quantas tentativas por segundo isso permite depende inteiramente do hardware do atacante e do work factor escolhido — nunca cite um número absoluto como verdade universal (hardware evolui, GPUs e ASICs mudam a conta). Se precisar decidir o work factor certo para seu caso, meça o custo no seu próprio hardware (new BCryptPasswordEncoder(strength), padrão 10) em vez de confiar numa cifra genérica."},{"id":"spring-security-content-33","type":"html","authorship":"legacy-preserved","html":"<h2>CSRF: a pergunta certa antes de desabilitar</h2>","fidelityText":"CSRF: a pergunta certa antes de desabilitar"},{"id":"spring-security-content-34","type":"html","authorship":"legacy-preserved","html":"<p>CSRF (<em>Cross-Site Request Forgery</em>) explora o fato de que o navegador envia certas credenciais <strong>automaticamente</strong>, mesmo quando a requisição parte de outra origem — induzindo uma ação indesejada em nome de um usuário já autenticado. A pergunta certa antes de desabilitar a proteção não é \"minha API é stateless?\" — é: <strong>\"as credenciais desta requisição são enviadas automaticamente pelo navegador?\"</strong></p>","fidelityText":"CSRF (Cross-Site Request Forgery) explora o fato de que o navegador envia certas credenciais automaticamente, mesmo quando a requisição parte de outra origem — induzindo uma ação indesejada em nome de um usuário já autenticado. A pergunta certa antes de desabilitar a proteção não é \"minha API é stateless?\" — é: \"as credenciais desta requisição são enviadas automaticamente pelo navegador?\""},{"id":"spring-security-content-35","type":"html","authorship":"legacy-preserved","html":"<p>Um token no cabeçalho <code>Authorization: Bearer ...</code> precisa ser colocado ali <em>explicitamente</em> por código seu (JavaScript, app mobile); o navegador nunca o anexa sozinho como faz com cookies. Por isso esse cenário — exatamente o desta configuração — não é vulnerável a CSRF, e <code>csrf().disable()</code> é seguro aqui. Mas no momento em que o token passa a viver num cookie (por exemplo, para reduzir exposição a XSS via <code>localStorage</code>) ou a aplicação volta a usar sessão tradicional, a resposta muda: cookies <strong>são</strong> enviados automaticamente, e desabilitar CSRF nessa configuração reabre exatamente o buraco que a proteção existia para fechar.</p>","fidelityText":"Um token no cabeçalho Authorization: Bearer ... precisa ser colocado ali explicitamente por código seu (JavaScript, app mobile); o navegador nunca o anexa sozinho como faz com cookies. Por isso esse cenário — exatamente o desta configuração — não é vulnerável a CSRF, e csrf().disable() é seguro aqui. Mas no momento em que o token passa a viver num cookie (por exemplo, para reduzir exposição a XSS via localStorage) ou a aplicação volta a usar sessão tradicional, a resposta muda: cookies são enviados automaticamente, e desabilitar CSRF nessa configuração reabre exatamente o buraco que a proteção existia para fechar."},{"id":"spring-security-content-36","type":"html","authorship":"legacy-preserved","html":"<h2>401 vs 403: AuthenticationEntryPoint e AccessDeniedHandler</h2>","fidelityText":"401 vs 403: AuthenticationEntryPoint e AccessDeniedHandler"},{"id":"spring-security-content-37","type":"html","authorship":"legacy-preserved","html":"<p>Os dois handlers configurados na <code>SecurityFilterChain</code> acima respondem perguntas diferentes, e confundi-los é um erro comum: <code>AuthenticationEntryPoint</code> responde quando <strong>ninguém está autenticado</strong> — 401 Unauthorized, \"eu não sei quem você é\". <code>AccessDeniedHandler</code> responde quando alguém <strong>está</strong> autenticado, mas não tem permissão suficiente — 403 Forbidden, \"eu sei quem você é, e a resposta é não\". Devolver 403 para uma requisição sem token nenhum vaza a existência de um recurso protegido para quem nem deveria saber que ele existe; devolver 401 para um usuário autenticado mas sem a role certa é tecnicamente incorreto, porque ele se autenticou com sucesso — o problema é autorização, não identidade.</p>","fidelityText":"Os dois handlers configurados na SecurityFilterChain acima respondem perguntas diferentes, e confundi-los é um erro comum: AuthenticationEntryPoint responde quando ninguém está autenticado — 401 Unauthorized, \"eu não sei quem você é\". AccessDeniedHandler responde quando alguém está autenticado, mas não tem permissão suficiente — 403 Forbidden, \"eu sei quem você é, e a resposta é não\". Devolver 403 para uma requisição sem token nenhum vaza a existência de um recurso protegido para quem nem deveria saber que ele existe; devolver 401 para um usuário autenticado mas sem a role certa é tecnicamente incorreto, porque ele se autenticou com sucesso — o problema é autorização, não identidade."},{"id":"spring-security-content-38","type":"html","authorship":"legacy-preserved","html":"<h2>Sessão, fixação e logout — mesmo numa aplicação majoritariamente stateless</h2>","fidelityText":"Sessão, fixação e logout — mesmo numa aplicação majoritariamente stateless"},{"id":"spring-security-content-39","type":"html","authorship":"legacy-preserved","html":"<p>Nem toda aplicação Spring Security é JWT puro; é comum conviver com sessão tradicional em partes da aplicação (um painel administrativo renderizado no servidor, por exemplo). Dois cuidados mínimos quando há sessão: <strong>fixação de sessão</strong> — um atacante tenta fazer a vítima autenticar usando um identificador de sessão que ele já conhece de antemão; o Spring Security já troca o identificador de sessão automaticamente após autenticação bem-sucedida por padrão, mas vale saber que a configuração existe (<code>sessionManagement().sessionFixation().changeSessionId()</code>). E <strong>logout</strong> precisa invalidar a sessão de fato, não só esquecer o token no cliente:</p>","fidelityText":"Nem toda aplicação Spring Security é JWT puro; é comum conviver com sessão tradicional em partes da aplicação (um painel administrativo renderizado no servidor, por exemplo). Dois cuidados mínimos quando há sessão: fixação de sessão — um atacante tenta fazer a vítima autenticar usando um identificador de sessão que ele já conhece de antemão; o Spring Security já troca o identificador de sessão automaticamente após autenticação bem-sucedida por padrão, mas vale saber que a configuração existe (sessionManagement().sessionFixation().changeSessionId()). E logout precisa invalidar a sessão de fato, não só esquecer o token no cliente:"},{"id":"spring-security-code-40","type":"code","authorship":"legacy-preserved","language":"java","source":".logout(logout -> logout\n    .logoutUrl(\"/auth/logout\")\n    .invalidateHttpSession(true) // destrói a sessão no servidor, não só remove o cookie no navegador\n)","fidelityText":".logout(logout -> logout .logoutUrl(\"/auth/logout\") .invalidateHttpSession(true) // destrói a sessão no servidor, não só remove o cookie no navegador )","highlightedHtml":".logout(logout -&gt; logout\n    .logoutUrl(<span class=\"str\">\"/auth/logout\"</span>)\n    .invalidateHttpSession(<span class=\"kw\">true</span>) <span class=\"com\">// destrói a sessão no servidor, não só remove o cookie no navegador</span>\n)","caption":"Exemplo executável de spring-security.","explanation":["invalidateHttpSession(true) destrói a sessão no servidor -- só remover o cookie no navegador não revoga nada do lado do servidor.","Mesmo numa aplicação majoritariamente stateless com JWT, partes que usam sessão tradicional precisam de logout que invalide de fato."],"commonMistakes":["Achar que apenas descartar o token no cliente equivale a fazer logout de uma sessão de verdade"]},{"id":"spring-security-content-41","type":"html","authorship":"legacy-preserved","html":"<h2>Testando segurança: spring-security-test e MockMvc</h2>","fidelityText":"Testando segurança: spring-security-test e MockMvc"},{"id":"spring-security-content-42","type":"html","authorship":"legacy-preserved","html":"<p>Um módulo de segurança sem teste automatizado é uma promessa não verificada — e o Spring oferece <code>spring-security-test</code> (escopo <code>test</code>) exatamente para simular autenticação sem precisar de senha real, banco ou token JWT válido em cada teste:</p>","fidelityText":"Um módulo de segurança sem teste automatizado é uma promessa não verificada — e o Spring oferece spring-security-test (escopo test) exatamente para simular autenticação sem precisar de senha real, banco ou token JWT válido em cada teste:"},{"id":"spring-security-code-43","type":"code","authorship":"legacy-preserved","language":"java","source":"@WebMvcTest(BookController.class)\n@Import(SecurityConfig.class)\nclass BookControllerSecurityTest {\n\n    @Autowired MockMvc mockMvc;\n\n    @Test\n    void shouldPermitirReadingWithoutAuthentication() throws Exception {\n        mockMvc.perform(get(\"/books/1\"))\n            .andExpect(status().isOk());\n    }\n\n    @Test\n    void shouldRejectExclusaoWithoutAuthentication() throws Exception {\n        mockMvc.perform(delete(\"/books/1\"))\n            .andExpect(status().isUnauthorized()); // 401: ninguém autenticado\n    }\n\n    @Test\n    @WithMockUser(roles = \"LEITOR\")\n    void shouldRejectExclusaoWithoutRoleAdmin() throws Exception {\n        mockMvc.perform(delete(\"/books/1\").with(csrf()))\n            .andExpect(status().isForbidden()); // 403: autenticado, mas sem permissão\n    }\n\n    @Test\n    @WithMockUser(roles = \"ADMIN\")\n    void shouldPermitirExclusaoWithRoleAdmin() throws Exception {\n        mockMvc.perform(delete(\"/books/1\").with(csrf()))\n            .andExpect(status().isInContent());\n    }\n}","fidelityText":"@WebMvcTest(LivroController.class) @Import(SecurityConfig.class) class LivroControllerSecurityTest { @Autowired MockMvc mockMvc; @Test void devePermitirLeituraSemAutenticacao() throws Exception { mockMvc.perform(get(\"/livros/1\")) .andExpect(status().isOk()); } @Test void deveRecusarExclusaoSemAutenticacao() throws Exception { mockMvc.perform(delete(\"/livros/1\")) .andExpect(status().isUnauthorized()); // 401: ninguém autenticado } @Test @WithMockUser(roles = \"LEITOR\") void deveRecusarExclusaoSemRoleAdmin() throws Exception { mockMvc.perform(delete(\"/livros/1\").with(csrf())) .andExpect(status().isForbidden()); // 403: autenticado, mas sem permissão } @Test @WithMockUser(roles = \"ADMIN\") void devePermitirExclusaoComRoleAdmin() throws Exception { mockMvc.perform(delete(\"/livros/1\").with(csrf())) .andExpect(status().isNoContent()); } }","highlightedHtml":"<span class=\"annotation\">@WebMvcTest</span>(BookController.<span class=\"kw\">class</span>)\n<span class=\"annotation\">@Import</span>(SecurityConfig.<span class=\"kw\">class</span>)\n<span class=\"kw\">class</span> <span class=\"cls\">BookControllerSecurityTest</span> {\n\n    <span class=\"annotation\">@Autowired</span> MockMvc mockMvc;\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldPermitirReadingWithoutAuthentication</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n        mockMvc.perform(get(<span class=\"str\">\"/books/1\"</span>))\n            .andExpect(status().isOk());\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldRejectExclusaoWithoutAuthentication</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n        mockMvc.perform(delete(<span class=\"str\">\"/books/1\"</span>))\n            .andExpect(status().isUnauthorized()); <span class=\"com\">// 401: ninguém autenticado</span>\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"annotation\">@WithMockUser</span>(roles = <span class=\"str\">\"LEITOR\"</span>)\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldRejectExclusaoWithoutRoleAdmin</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n        mockMvc.perform(delete(<span class=\"str\">\"/books/1\"</span>).with(csrf()))\n            .andExpect(status().isForbidden()); <span class=\"com\">// 403: autenticado, mas sem permissão</span>\n    }\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"annotation\">@WithMockUser</span>(roles = <span class=\"str\">\"ADMIN\"</span>)\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldPermitirExclusaoWithRoleAdmin</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n        mockMvc.perform(delete(<span class=\"str\">\"/books/1\"</span>).with(csrf()))\n            .andExpect(status().isInContent());\n    }\n}","caption":"Exemplo executável de spring-security.","explanation":["@WithMockUser injeta um Authentication já autenticado direto no SecurityContext do teste, sem passar pelo AuthenticationManager nem por senha real.","Os quatro testes distinguem os cenários que AuthenticationEntryPoint/AccessDeniedHandler e @PreAuthorize foram configurados para diferenciar: público, não autenticado (401), autenticado sem permissão (403) e autorizado.","with(csrf()) é necessário nos testes autenticados porque a requisição de teste simula um cliente com estado; sem isso, POST/DELETE seriam rejeitados pela proteção CSRF do MockMvc."],"commonMistakes":["Usar o mesmo status esperado para não autenticado e para autenticado-sem-permissão, escondendo a diferença entre 401 e 403","Escrever apenas o caminho feliz e nunca testar a negação de acesso"]},{"id":"spring-security-content-44","type":"html","authorship":"legacy-preserved","html":"<p><code>@WithMockUser</code> injeta um <code>Authentication</code> já autenticado direto no <code>SecurityContext</code> do teste, sem passar pelo <code>AuthenticationManager</code> nem por uma senha real — é isso que torna esses testes rápidos e determinísticos. Note como os quatro testes acima exercitam exatamente os quatro cenários que o <code>AuthenticationEntryPoint</code>/<code>AccessDeniedHandler</code> e o <code>@PreAuthorize</code> foram configurados para diferenciar: público, não autenticado, autenticado sem permissão, e autorizado.</p>","fidelityText":"@WithMockUser injeta um Authentication já autenticado direto no SecurityContext do teste, sem passar pelo AuthenticationManager nem por uma senha real — é isso que torna esses testes rápidos e determinísticos. Note como os quatro testes acima exercitam exatamente os quatro cenários que o AuthenticationEntryPoint/AccessDeniedHandler e o @PreAuthorize foram configurados para diferenciar: público, não autenticado, autenticado sem permissão, e autorizado."},{"id":"spring-security-content-45","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Nunca guarde senha em texto puro, nem \"só criptografada\" (reversível) — sempre hash unidirecional com BCrypt ou equivalente, e nunca cite um número absoluto de tentativas/segundo como se fosse garantia — meça no seu hardware.</li>\n          <li>Login autentica de verdade através do <code>AuthenticationManager</code> (via <code>UserDetailsService</code>/<code>DaoAuthenticationProvider</code>) — nunca compare hash manualmente dentro do controller.</li>\n          <li><code>@EnableMethodSecurity</code> é obrigatório para <code>@PreAuthorize</code> funcionar — sem ela, a anotação é silenciosamente ignorada.</li>\n          <li>Prefira <code>oauth2ResourceServer().jwt(...)</code> a um filtro JWT escrito à mão — ele já valida assinatura, algoritmo e expiração de forma testada.</li>\n          <li>A chave secreta do JWT nunca vai no código-fonte — sempre variável de ambiente (capítulo 27), diferente entre dev e produção.</li>\n          <li><code>csrf().disable()</code> só é seguro quando as credenciais NÃO são enviadas automaticamente pelo navegador (Bearer token) — nunca desabilite CSRF numa aplicação que usa cookie de sessão.</li>\n          <li>Teste 401 (não autenticado) e 403 (autenticado sem permissão) explicitamente e em separado — são cenários diferentes, não intercambiáveis.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Nunca guarde senha em texto puro, nem \"só criptografada\" (reversível) — sempre hash unidirecional com BCrypt ou equivalente, e nunca cite um número absoluto de tentativas/segundo como se fosse garantia — meça no seu hardware. Login autentica de verdade através do AuthenticationManager (via UserDetailsService/DaoAuthenticationProvider) — nunca compare hash manualmente dentro do controller. @EnableMethodSecurity é obrigatório para @PreAuthorize funcionar — sem ela, a anotação é silenciosamente ignorada. Prefira oauth2ResourceServer().jwt(...) a um filtro JWT escrito à mão — ele já valida assinatura, algoritmo e expiração de forma testada. A chave secreta do JWT nunca vai no código-fonte — sempre variável de ambiente (capítulo 27), diferente entre dev e produção. csrf().disable() só é seguro quando as credenciais NÃO são enviadas automaticamente pelo navegador (Bearer token) — nunca desabilite CSRF numa aplicação que usa cookie de sessão. Teste 401 (não autenticado) e 403 (autenticado sem permissão) explicitamente e em separado — são cenários diferentes, não intercambiáveis."},{"id":"spring-security-content-46","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Spring Security tem uma curva de aprendizado real — não se cobre entender tudo na primeira leitura. Comece implementando só o fluxo básico (registrar → hash BCrypt → login via <code>AuthenticationManager</code> → gerar JWT → validar via <code>oauth2ResourceServer</code>) funcionando ponta a ponta em um projeto de brinquedo, antes de se preocupar com <code>@PreAuthorize</code> refinado, sessões ou OAuth2. Segurança se aprende construindo camada por camada, exatamente como você fez com o resto deste curso.</div>","fidelityText":"Spring Security tem uma curva de aprendizado real — não se cobre entender tudo na primeira leitura. Comece implementando só o fluxo básico (registrar → hash BCrypt → login via AuthenticationManager → gerar JWT → validar via oauth2ResourceServer) funcionando ponta a ponta em um projeto de brinquedo, antes de se preocupar com @PreAuthorize refinado, sessões ou OAuth2. Segurança se aprende construindo camada por camada, exatamente como você fez com o resto deste curso."},{"id":"spring-security-exercise-47","type":"exercise","authorship":"legacy-preserved","title":"Exercício 52.1 — Fluxo de autenticação completo","prompt":"Desenhe (em pseudo-código ou texto estruturado) o fluxo completo de registro e login para o sistema de biblioteca: endpoint de registro que faz hash da senha com BCrypt antes de salvar; endpoint de login que autentica via AuthenticationManager e gera um JWT a partir do Authentication resultante; e a configuração de SecurityFilterChain liberando /auth/** publicamente e exigindo autenticação para /livros/** em métodos que não sejam GET.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 52.1 — Fluxo de autenticação completodifícil Desenhe (em pseudo-código ou texto estruturado) o fluxo completo de registro e login para o sistema de biblioteca: endpoint de registro que faz hash da senha com BCrypt antes de salvar; endpoint de login que autentica via AuthenticationManager e gera um JWT a partir do Authentication resultante; e a configuração de SecurityFilterChain liberando /auth/** publicamente e exigindo autenticação para /livros/** em métodos que não sejam GET. Ver solução @PostMapping(\"/auth/registrar\") public ResponseEntity<Void> registrar(@RequestBody RegistroDTO dto) { Usuario usuario = new Usuario(dto.email(), passwordEncoder.encode(dto.senha())); usuarioRepository.save(usuario); return ResponseEntity.status(HttpStatus.CREATED).build(); } @PostMapping(\"/auth/login\") public ResponseEntity<TokenDTO> login(@RequestBody LoginDTO dto) { Authentication autenticado = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(dto.email(), dto.senha()) ); // AuthenticationException (ex: BadCredentialsException) se inválido return ResponseEntity.ok(new TokenDTO(jwtServico.gerarToken(autenticado))); } .authorizeHttpRequests(auth -> auth .requestMatchers(\"/auth/**\").permitAll() .requestMatchers(HttpMethod.GET, \"/livros/**\").permitAll() .anyRequest().authenticated() )","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 52.1 — Fluxo de autenticação completo</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Desenhe (em pseudo-código ou texto estruturado) o fluxo completo de registro e login para o sistema de biblioteca: endpoint de registro que faz hash da senha com BCrypt antes de salvar; endpoint de login que autentica via <code>AuthenticationManager</code> e gera um JWT a partir do <code>Authentication</code> resultante; e a configuração de <code>SecurityFilterChain</code> liberando <code>/auth/**</code> publicamente e exigindo autenticação para <code>/livros/**</code> em métodos que não sejam GET.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@PostMapping</span>(<span class=\"str\">\"/auth/register\"</span>)\n<span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"kw\">Void</span>&gt; <span class=\"fn\">register</span>(<span class=\"annotation\">@RequestBody</span> <span class=\"cls\">RecordDTO</span> dto) {\n    <span class=\"cls\">User</span> user = <span class=\"kw\">new</span> <span class=\"cls\">User</span>(dto.email(), passwordEncoder.encode(dto.password()));\n    userRepository.save(user);\n    <span class=\"kw\">return</span> ResponseEntity.status(HttpStatus.CREATED).build();\n}\n\n<span class=\"annotation\">@PostMapping</span>(<span class=\"str\">\"/auth/login\"</span>)\n<span class=\"kw\">public</span> ResponseEntity&lt;<span class=\"cls\">TokenDTO</span>&gt; <span class=\"fn\">login</span>(<span class=\"annotation\">@RequestBody</span> <span class=\"cls\">LoginDTO</span> dto) {\n    <span class=\"cls\">Authentication</span> authenticated = authenticationManager.authenticate(\n        <span class=\"kw\">new</span> UsernamePasswordAuthenticationToken(dto.email(), dto.password())\n    ); <span class=\"com\">// AuthenticationException (ex: BadCredentialsException) se inválido</span>\n    <span class=\"kw\">return</span> ResponseEntity.ok(<span class=\"kw\">new</span> <span class=\"cls\">TokenDTO</span>(jwtService.generateToken(authenticated)));\n}</pre>\n<pre class=\"code\">.authorizeHttpRequests(auth -&gt; auth\n    .requestMatchers(<span class=\"str\">\"/auth/**\"</span>).permitAll()\n    .requestMatchers(HttpMethod.GET, <span class=\"str\">\"/books/**\"</span>).permitAll()\n    .anyRequest().authenticated()\n)</pre>\n        </div>\n      </div>"},{"id":"spring-security-exercise-48","type":"exercise","authorship":"legacy-preserved","title":"Exercício 52.2 — Testando 401 e 403 separadamente","prompt":"Escreva os testes de MockMvc para o endpoint DELETE /livros/{id} (protegido por @PreAuthorize(\"hasRole('ADMIN')\")): um caso sem nenhuma autenticação, um caso autenticado com @WithMockUser(roles = \"LEITOR\"), e um caso autenticado com @WithMockUser(roles = \"ADMIN\"). Explique por que os dois primeiros casos não podem compartilhar o mesmo código de status esperado.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 52.2 — Testando 401 e 403 separadamentedifícil Escreva os testes de MockMvc para o endpoint DELETE /livros/{id} (protegido por @PreAuthorize(\"hasRole('ADMIN')\")): um caso sem nenhuma autenticação, um caso autenticado com @WithMockUser(roles = \"LEITOR\"), e um caso autenticado com @WithMockUser(roles = \"ADMIN\"). Explique por que os dois primeiros casos não podem compartilhar o mesmo código de status esperado. Ver solução Sem autenticação, o AuthenticationEntryPoint responde 401 — a requisição nem chega a ser avaliada por @PreAuthorize, porque não há Authentication nenhuma no SecurityContext. Com @WithMockUser(roles = \"LEITOR\"), existe autenticação, mas @PreAuthorize(\"hasRole('ADMIN')\") nega o acesso — o AccessDeniedHandler responde 403. Compartilhar o mesmo status para os dois casos esconderia a diferença real entre \"eu não sei quem você é\" e \"eu sei quem você é, e não pode\" — exatamente a distinção que a seção de 401 vs 403 deste capítulo formaliza.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 52.2 — Testando 401 e 403 separadamente</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Escreva os testes de <code>MockMvc</code> para o endpoint <code>DELETE /livros/{id}</code> (protegido por <code>@PreAuthorize(\"hasRole('ADMIN')\")</code>): um caso sem nenhuma autenticação, um caso autenticado com <code>@WithMockUser(roles = \"LEITOR\")</code>, e um caso autenticado com <code>@WithMockUser(roles = \"ADMIN\")</code>. Explique por que os dois primeiros casos não podem compartilhar o mesmo código de status esperado.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Sem autenticação, o <code>AuthenticationEntryPoint</code> responde <strong>401</strong> — a requisição nem chega a ser avaliada por <code>@PreAuthorize</code>, porque não há <code>Authentication</code> nenhuma no <code>SecurityContext</code>. Com <code>@WithMockUser(roles = \"LEITOR\")</code>, existe autenticação, mas <code>@PreAuthorize(\"hasRole('ADMIN')\")</code> nega o acesso — o <code>AccessDeniedHandler</code> responde <strong>403</strong>. Compartilhar o mesmo status para os dois casos esconderia a diferença real entre \"eu não sei quem você é\" e \"eu sei quem você é, e não pode\" — exatamente a distinção que a seção de 401 vs 403 deste capítulo formaliza.</p>\n        </div>\n      </div>"},{"id":"spring-security-quiz","type":"quiz","authorship":"authored","conceptId":"security-filter-chain","prompt":"Por que Spring Security usa uma cadeia de filtros antes do controller?","options":[{"id":"sec-a","label":"Para autenticar e autorizar a requisição antes que a regra HTTP do controller seja executada.","correct":true,"explanation":"O controller recebe uma requisição já avaliada pelo contexto de segurança."},{"id":"sec-b","label":"Para trocar todos os DTOs por Entities automaticamente.","correct":false,"explanation":"DTO/Entity é boundary de API/persistência, não função da filter chain."},{"id":"sec-c","label":"Para garantir que qualquer usuário autenticado possa executar qualquer método.","correct":false,"explanation":"Autenticação não implica autorização irrestrita."}]}],"resources":[{"id":"spring-security-servlet-auth","type":"reference","title":"Spring Security: Servlet Authentication Architecture","url":"https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html","reinforces":"Filtros, AuthenticationManager e SecurityContext.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-security-password-storage","type":"reference","title":"Spring Security: Password Storage","url":"https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html","reinforces":"PasswordEncoder, BCrypt e armazenamento seguro de senha.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The spring security component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to spring security. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible spring security failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"1. Request HTTP arrives no servlet container (Tomcat embedded)","instruction":"The spring security component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible spring security failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"security-oidc","moduleId":"api-security-quality","order":2,"title":"OAuth 2.0, OpenID Connect e Resource Server seguros","summary":"OAuth 2.0 delega autorização; OpenID Connect acrescenta autenticação e identidade. Um access token autoriza uma API, um ID token descreve a autenticação ao cliente e um refresh token permite obter novos tokens. Trocar esses papéis abre vulnerabilidades.","objectives":["Separar OAuth2 e OpenID Connect","Entender Authorization Code com PKCE","Validar JWT em Resource Server","Reconhecer riscos de navegador, issuer, audience e escopo"],"whyItExists":"Depois de JWT local e Spring Security, o aluno precisa sair do login caseiro e entender provedores de identidade, tokens emitidos por terceiros e validação de resource server.","prerequisiteChapterIds":["spring-security","http"],"conceptIds":["quem-e-quem-no-fluxo-de-identidade","authorization-code-com-pkce","protecoes-do-navegador-usadas-no-exemplo","quatro-mecanismos-que-nao-sao-equivalentes","cookies-e-tokens-mudam-o-risco","ameacas-que-o-capitulo-basico-nao-cobre-sozinho"],"introducedConceptIds":["oidc-id-token-claims","pkce-authorization-code","resource-server-jwt-validation"],"usedConceptIds":["oauth2-delegated-authorization","jwt-claims-signature","security-filter-chain"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"oidc-intuition","type":"intuition","authorship":"authored","title":"OIDC adiciona identidade ao OAuth2","body":"OAuth2 autoriza acesso. OpenID Connect padroniza identidade com ID Token. Uma API Resource Server normalmente valida access token, não usa ID token como passe livre.","analogyLimit":"Crachá e permissão ajudam, mas tokens têm issuer, audience, escopos, chaves e expiração verificáveis."},{"id":"security-oidc-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-security\">Segurança</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~6h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-security\">Spring Security</a>, <a class=\"prereq-tag\" href=\"#http\">HTTP</a></div></div>","fidelityText":"SegurançaDificuldade: Avançado⏱ ~6h de estudo e práticaPré-requisitos: Spring Security, HTTP"},{"id":"security-oidc-content-2","type":"html","authorship":"legacy-preserved","html":"<p>OAuth 2.0 delega autorização; OpenID Connect acrescenta autenticação e identidade. Um access token autoriza uma API, um ID token descreve a autenticação ao cliente e um refresh token permite obter novos tokens. Trocar esses papéis abre vulnerabilidades.</p>","fidelityText":"OAuth 2.0 delega autorização; OpenID Connect acrescenta autenticação e identidade. Um access token autoriza uma API, um ID token descreve a autenticação ao cliente e um refresh token permite obter novos tokens. Trocar esses papéis abre vulnerabilidades."},{"id":"security-oidc-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Quem é quem no fluxo de identidade</h2></div>\n    <p>Não decore siglas antes de identificar os participantes e o propósito de cada artefato.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Pessoa/Resource Owner</dt><dd>Dona dos dados que autoriza determinado acesso.</dd></div><div class=\"concept-card\"><dt>Client</dt><dd>Aplicação que solicita autorização; pode ser navegador, app móvel ou backend.</dd></div><div class=\"concept-card\"><dt>Authorization Server</dt><dd>Sistema que autentica, obtém consentimento e emite tokens. Também é chamado de provedor de identidade em cenários OIDC.</dd></div><div class=\"concept-card\"><dt>Resource Server</dt><dd>API que protege recursos e valida o access token antes de atender.</dd></div><div class=\"concept-card\"><dt>OAuth 2.0</dt><dd>Framework de autorização delegada: permite conceder acesso limitado sem entregar a senha ao cliente.</dd></div><div class=\"concept-card\"><dt>OpenID Connect (OIDC)</dt><dd>Camada de identidade sobre OAuth 2.0 que padroniza autenticação do usuário e ID token.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoQuem é quem no fluxo de identidade Não decore siglas antes de identificar os participantes e o propósito de cada artefato. Pessoa/Resource OwnerDona dos dados que autoriza determinado acesso.ClientAplicação que solicita autorização; pode ser navegador, app móvel ou backend.Authorization ServerSistema que autentica, obtém consentimento e emite tokens. Também é chamado de provedor de identidade em cenários OIDC.Resource ServerAPI que protege recursos e valida o access token antes de atender.OAuth 2.0Framework de autorização delegada: permite conceder acesso limitado sem entregar a senha ao cliente.OpenID Connect (OIDC)Camada de identidade sobre OAuth 2.0 que padroniza autenticação do usuário e ID token."},{"id":"security-oidc-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Authorization Code com PKCE</h2>","fidelityText":"Authorization Code com PKCE"},{"id":"security-oidc-content-5","type":"html","authorship":"legacy-preserved","html":"<ol><li>O cliente cria <code>code_verifier</code>, deriva o challenge e redireciona o usuário ao authorization server.</li><li>Após autenticação e consentimento, recebe um código curto.</li><li>Troca código e verifier por tokens diretamente no endpoint seguro.</li><li>A API valida assinatura, algoritmo permitido, issuer, audience, expiração e escopos.</li></ol>","fidelityText":"O cliente cria code_verifier, deriva o challenge e redireciona o usuário ao authorization server.Após autenticação e consentimento, recebe um código curto.Troca código e verifier por tokens diretamente no endpoint seguro.A API valida assinatura, algoritmo permitido, issuer, audience, expiração e escopos."},{"id":"security-oidc-content-6","type":"html","authorship":"legacy-preserved","html":"<p><strong>PKCE</strong> (<em>Proof Key for Code Exchange</em>) faz o cliente criar um segredo temporário para cada tentativa de login. O servidor recebe primeiro um derivado e exige o valor original na troca; assim, um código de autorização interceptado não basta. O navegador não deve receber <em>client secret</em> permanente embutido.</p>","fidelityText":"PKCE (Proof Key for Code Exchange) faz o cliente criar um segredo temporário para cada tentativa de login. O servidor recebe primeiro um derivado e exige o valor original na troca; assim, um código de autorização interceptado não basta. O navegador não deve receber client secret permanente embutido."},{"id":"security-oidc-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Proteções do navegador usadas no exemplo</h2>","fidelityText":"Proteções do navegador usadas no exemplo"},{"id":"security-oidc-content-8","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Quatro mecanismos que não são equivalentes</h2></div>\n    <p>O código abaixo menciona CSRF e CORS; conheça seu papel antes de interpretar por que uma proteção pode ou não ser desativada.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Cookie HttpOnly</dt><dd>Cookie inacessível ao JavaScript da página, embora ainda seja enviado automaticamente pelo navegador.</dd></div><div class=\"concept-card\"><dt>CSRF</dt><dd><em>Cross-Site Request Forgery</em>: outra página induz o navegador autenticado a enviar uma ação não desejada.</dd></div><div class=\"concept-card\"><dt>CORS</dt><dd><em>Cross-Origin Resource Sharing</em>: política do navegador que controla quais origens podem ler respostas; não autentica o chamador.</dd></div><div class=\"concept-card\"><dt>XSS</dt><dd><em>Cross-Site Scripting</em>: execução de JavaScript não confiável na origem da aplicação.</dd></div><div class=\"concept-card\"><dt>CSP</dt><dd><em>Content Security Policy</em>: política que restringe de onde scripts e outros recursos podem ser carregados.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoQuatro mecanismos que não são equivalentes O código abaixo menciona CSRF e CORS; conheça seu papel antes de interpretar por que uma proteção pode ou não ser desativada. Cookie HttpOnlyCookie inacessível ao JavaScript da página, embora ainda seja enviado automaticamente pelo navegador.CSRFCross-Site Request Forgery: outra página induz o navegador autenticado a enviar uma ação não desejada.CORSCross-Origin Resource Sharing: política do navegador que controla quais origens podem ler respostas; não autentica o chamador.XSSCross-Site Scripting: execução de JavaScript não confiável na origem da aplicação.CSPContent Security Policy: política que restringe de onde scripts e outros recursos podem ser carregados."},{"id":"security-oidc-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"@Bean\nSecurityFilterChain api(HttpSecurity http) throws Exception {\n    return http\n        .csrf(csrf -> csrf.disable()) // somente para API Bearer stateless; não copie para autenticação por cookie\n        .cors(Customizer.withDefaults())\n        .authorizeHttpRequests(auth -> auth\n            .requestMatchers(\"/actuator/health\").permitAll()\n            .requestMatchers(HttpMethod.POST, \"/orders/**\").hasAuthority(\"scope_orders:write\")\n            .anyRequest().authenticated())\n        .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))\n        .build();\n}","fidelityText":"@Bean SecurityFilterChain api(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf.disable()) // somente para API Bearer stateless; não copie para autenticação por cookie .cors(Customizer.withDefaults()) .authorizeHttpRequests(auth -> auth .requestMatchers(\"/actuator/health\").permitAll() .requestMatchers(HttpMethod.POST, \"/pedidos/**\").hasAuthority(\"SCOPE_pedidos:write\") .anyRequest().authenticated()) .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults())) .build(); }","highlightedHtml":"<span class=\"annotation\">@Bean</span>\nSecurityFilterChain api(HttpSecurity http) <span class=\"kw\">throws</span> Exception {\n    <span class=\"kw\">return</span> http\n        .csrf(csrf -&gt; csrf.disable()) <span class=\"com\">// somente para API Bearer stateless; não copie para autenticação por cookie</span>\n        .cors(Customizer.withDefaults())\n        .authorizeHttpRequests(auth -&gt; auth\n            .requestMatchers(<span class=\"str\">\"/actuator/health\"</span>).permitAll()\n            .requestMatchers(HttpMethod.POST, <span class=\"str\">\"/orders/**\"</span>).hasAuthority(<span class=\"str\">\"scope_orders:write\"</span>)\n            .anyRequest().authenticated())\n        .oauth2ResourceServer(oauth -&gt; oauth.jwt(Customizer.withDefaults()))\n        .build();\n}","caption":"Exemplo executável de security-oidc.","explanation":["oauth2ResourceServer().jwt() configura a API para validar Bearer JWT como resource server.","Regras de requestMatchers e authorities devem refletir endpoints públicos e protegidos."],"commonMistakes":["Usar ID token para chamar API","Não validar issuer/audience"]},{"id":"security-oidc-content-10","type":"html","authorship":"legacy-preserved","html":"<p>O <strong>issuer</strong> identifica quem emitiu o token; a <strong>audience</strong> identifica para qual API ele foi criado. Issuer, audience, expiração e escopo são exemplos de <strong>claims</strong> — as afirmações que o token carrega sobre si mesmo, dentro do payload assinado (o mesmo <code>payload</code> Base64 já visto no capítulo de autenticação conceitos). <strong>JWK Set</strong> é o conjunto publicado de chaves com que a API verifica assinaturas, inclusive durante rotação. Uma <strong>allowlist</strong> é a lista explícita de algoritmos aceitos. Rejeite token de outro emissor, destinado a outra API ou assinado por algoritmo não autorizado.</p>","fidelityText":"O issuer identifica quem emitiu o token; a audience identifica para qual API ele foi criado. Issuer, audience, expiração e escopo são exemplos de claims — as afirmações que o token carrega sobre si mesmo, dentro do payload assinado (o mesmo payload Base64 já visto no capítulo de autenticação conceitos). JWK Set é o conjunto publicado de chaves com que a API verifica assinaturas, inclusive durante rotação. Uma allowlist é a lista explícita de algoritmos aceitos. Rejeite token de outro emissor, destinado a outra API ou assinado por algoritmo não autorizado."},{"id":"security-oidc-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Repare a diferença de mecanismo de assinatura em relação ao <code>JwtServico</code> do capítulo de Spring Security: lá, a mesma aplicação emite <strong>e</strong> valida o próprio token, então uma chave simétrica (HMAC) compartilhada faz sentido. Aqui, com um Authorization Server separado do Resource Server, a verificação usa chaves <strong>assimétricas</strong> publicadas via JWK Set — qualquer Resource Server consegue validar a assinatura usando só a chave pública, sem nunca conhecer (nem precisar confiar) um segredo compartilhado com o emissor. O encaixe na filter chain via <code>oauth2ResourceServer().jwt(...)</code> é idêntico nos dois casos; só o <code>JwtDecoder</code> por trás muda.</div>","fidelityText":"Repare a diferença de mecanismo de assinatura em relação ao JwtServico do capítulo de Spring Security: lá, a mesma aplicação emite e valida o próprio token, então uma chave simétrica (HMAC) compartilhada faz sentido. Aqui, com um Authorization Server separado do Resource Server, a verificação usa chaves assimétricas publicadas via JWK Set — qualquer Resource Server consegue validar a assinatura usando só a chave pública, sem nunca conhecer (nem precisar confiar) um segredo compartilhado com o emissor. O encaixe na filter chain via oauth2ResourceServer().jwt(...) é idêntico nos dois casos; só o JwtDecoder por trás muda."},{"id":"security-oidc-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Cookies e tokens mudam o risco</h2>","fidelityText":"Cookies e tokens mudam o risco"},{"id":"security-oidc-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Cookies <code>HttpOnly</code> reduzem roubo por JavaScript, mas são enviados automaticamente e portanto exigem proteção CSRF. Tokens no <code>localStorage</code> não são enviados automaticamente, porém ficam acessíveis a XSS. Não existe armazenamento mágico: reduza XSS com escaping, CSP e dependências auditadas; use cookies <code>Secure</code>, <code>SameSite</code> e CSRF conforme a arquitetura.</p>","fidelityText":"Cookies HttpOnly reduzem roubo por JavaScript, mas são enviados automaticamente e portanto exigem proteção CSRF. Tokens no localStorage não são enviados automaticamente, porém ficam acessíveis a XSS. Não existe armazenamento mágico: reduza XSS com escaping, CSP e dependências auditadas; use cookies Secure, SameSite e CSRF conforme a arquitetura."},{"id":"security-oidc-content-14","type":"html","authorship":"legacy-preserved","html":"<p>CORS não autentica ninguém. O wildcard de origem não pode ser combinado com credenciais pelo navegador. Como o preflight ocorre antes da autenticação, CORS deve ser processado antes da cadeia de segurança e configurado por origens explícitas.</p>","fidelityText":"CORS não autentica ninguém. O wildcard de origem não pode ser combinado com credenciais pelo navegador. Como o preflight ocorre antes da autenticação, CORS deve ser processado antes da cadeia de segurança e configurado por origens explícitas."},{"id":"security-oidc-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Ameaças que o capítulo básico não cobre sozinho</h2>","fidelityText":"Ameaças que o capítulo básico não cobre sozinho"},{"id":"security-oidc-content-16","type":"html","authorship":"legacy-preserved","html":"<ul><li><strong>SSRF — Server-Side Request Forgery:</strong> entrada controlada pelo atacante faz o servidor acessar destinos indevidos; restrinja endereços, protocolos, DNS e redirecionamentos.</li><li><strong>Mass assignment:</strong> vinculação automática aceita campos que o cliente não deveria controlar; use DTOs com allowlist.</li><li><strong>Broken access control:</strong> autorização ausente ou incompleta; confira propriedade e escopo do recurso, não apenas papel global.</li><li><strong>Session fixation:</strong> atacante tenta fazer a vítima usar uma sessão já conhecida; rotacione o identificador ao autenticar e defina logout/revogação.</li><li><strong>PII — informação pessoal identificável:</strong> dados capazes de identificar uma pessoa; não registre tokens, senhas nem requisições inteiras.</li><li><strong>Supply chain:</strong> risco herdado de dependências, plugins e processo de build; inventarie componentes e priorize vulnerabilidades realmente alcançáveis.</li></ul>","fidelityText":"SSRF — Server-Side Request Forgery: entrada controlada pelo atacante faz o servidor acessar destinos indevidos; restrinja endereços, protocolos, DNS e redirecionamentos.Mass assignment: vinculação automática aceita campos que o cliente não deveria controlar; use DTOs com allowlist.Broken access control: autorização ausente ou incompleta; confira propriedade e escopo do recurso, não apenas papel global.Session fixation: atacante tenta fazer a vítima usar uma sessão já conhecida; rotacione o identificador ao autenticar e defina logout/revogação.PII — informação pessoal identificável: dados capazes de identificar uma pessoa; não registre tokens, senhas nem requisições inteiras.Supply chain: risco herdado de dependências, plugins e processo de build; inventarie componentes e priorize vulnerabilidades realmente alcançáveis."},{"id":"security-oidc-exercise-17","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — matriz de autorização","prompt":"Crie testes para endpoint público, token ausente, expirado, issuer errado, audience errada, escopo insuficiente, dono correto e usuário tentando acessar recurso alheio.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — matriz de autorizaçãodifícilCrie testes para endpoint público, token ausente, expirado, issuer errado, audience errada, escopo insuficiente, dono correto e usuário tentando acessar recurso alheio.Ver critériosInclua testes em nível HTTP e em método. Habilite method security explicitamente quando usar @PreAuthorize. Cada negação deve retornar 401 ou 403 conforme o caso, sem revelar a existência de recurso sensível.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — matriz de autorização</h2><span class=\"exercise-tag d\">difícil</span></div><p>Crie testes para endpoint público, token ausente, expirado, issuer errado, audience errada, escopo insuficiente, dono correto e usuário tentando acessar recurso alheio.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>Inclua testes em nível HTTP e em método. Habilite method security explicitamente quando usar <code>@PreAuthorize</code>. Cada negação deve retornar 401 ou 403 conforme o caso, sem revelar a existência de recurso sensível.</p></div></div>"},{"id":"oidc-quiz","type":"quiz","authorship":"authored","conceptId":"resource-server-jwt-validation","prompt":"O que uma API Resource Server deve validar em um JWT recebido?","options":[{"id":"oidc-a","label":"Assinatura, issuer, audience, expiração e escopos/authorities necessários.","correct":true,"explanation":"Sem essas validações, o payload legível vira confiança falsa."},{"id":"oidc-b","label":"Apenas se o token tem três partes separadas por ponto.","correct":false,"explanation":"Formato não prova origem nem permissão."},{"id":"oidc-c","label":"Apenas se o frontend disse que o usuário está logado.","correct":false,"explanation":"Estado do frontend não autoriza backend."}]}],"resources":[{"id":"oidc-core","type":"reference","title":"OpenID Connect Core 1.0","url":"https://openid.net/specs/openid-connect-core-1_0.html","reinforces":"ID Token, claims, fluxos e relação com OAuth2.","language":"en","publisher":"OpenID Foundation","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-resource-server-jwt","type":"reference","title":"Spring Security: OAuth2 Resource Server JWT","url":"https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html","reinforces":"Configuração de Resource Server, JWT decoder, issuer e authorities.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The security oidc component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to security oidc. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible security oidc failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"SecurityFilterChain api(HttpSecurity http) throws Exception {","instruction":"The security oidc component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible security oidc failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"https","moduleId":"api-security-quality","order":3,"title":"HTTPS & certificados","summary":"Todo JWT, toda senha, todo dado sensível que você protegeu nos últimos dois capítulos viaja pela rede como texto simples se a conexão não for HTTPS — qualquer um na mesma rede Wi-Fi poderia interceptar e ler tudo. HTTPS é o que torna todo o resto deste curso realmente seguro na prática.","objectives":["Entender o que HTTPS protege e o que não protege","Ligar certificado, domínio e cadeia de confiança","Reconhecer riscos de mixed content e certificado inválido","Relacionar TLS com cookies, OAuth/OIDC e APIs"],"whyItExists":"Segurança de identidade depende de transporte confiável. HTTPS entra aqui para mostrar que token correto em canal inseguro continua sendo um desastre operacional.","prerequisiteChapterIds":["autenticacao-conceitos"],"conceptIds":["como-funciona-em-conceito","o-handshake-como-cliente-e-servidor-combinam-uma-chave-sem-nunca-envia-l","cadeia-de-confianca-por-que-seu-navegador-confia-num-certificado-que-nun","self-signed-vs-certificado-de-ca-real","hsts-fechando-a-porta-do-primeiro-acesso-em-http-puro","onde-o-tls-termina-load-balancer-proxy-vs-aplicacao","onde-https-se-conecta-com-o-resto-do-que-voce-ja-aprendeu"],"introducedConceptIds":["tls-certificate-chain"],"usedConceptIds":["http-mensagem-recurso","jwt-claims-signature"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"https-intuition","type":"intuition","authorship":"authored","title":"HTTPS protege o caminho, não corrige a regra","body":"TLS reduz espionagem e alteração da comunicação em trânsito e ajuda o cliente a autenticar o servidor pelo certificado. Ele não decide se o usuário pode apagar um pedido.","analogyLimit":"Envelope lacrado ajuda a imaginar confidencialidade, mas certificado, CA, SNI, hostname e expiração são detalhes técnicos reais."},{"id":"https-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-security\">Segurança</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#autenticacao-conceitos\">51 · Autenticação conceitos</a></div>\n      </div>","fidelityText":"Segurança Dificuldade: Intermediário ⏱ ~2h de estudo Pré-requisitos: 51 · Autenticação conceitos"},{"id":"https-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Todo JWT, toda senha, todo dado sensível que você protegeu nos últimos dois capítulos viaja pela rede como texto simples se a conexão não for <strong>HTTPS</strong> — qualquer um na mesma rede Wi-Fi poderia interceptar e ler tudo. HTTPS é o que torna todo o resto deste curso realmente seguro na prática.</p>","fidelityText":"Todo JWT, toda senha, todo dado sensível que você protegeu nos últimos dois capítulos viaja pela rede como texto simples se a conexão não for HTTPS — qualquer um na mesma rede Wi-Fi poderia interceptar e ler tudo. HTTPS é o que torna todo o resto deste curso realmente seguro na prática."},{"id":"https-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">HTTP sem HTTPS é enviar um cartão postal — qualquer pessoa que manuseie a carta no caminho consegue ler o conteúdo. HTTPS é colocar essa mesma mensagem dentro de um envelope lacrado, cifrado, que só o destinatário certo consegue abrir — mesmo que alguém intercepte fisicamente o envelope no caminho, o conteúdo continua ilegível.</div>","fidelityText":"HTTP sem HTTPS é enviar um cartão postal — qualquer pessoa que manuseie a carta no caminho consegue ler o conteúdo. HTTPS é colocar essa mesma mensagem dentro de um envelope lacrado, cifrado, que só o destinatário certo consegue abrir — mesmo que alguém intercepte fisicamente o envelope no caminho, o conteúdo continua ilegível."},{"id":"https-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Como funciona, em conceito</h2>","fidelityText":"Como funciona, em conceito"},{"id":"https-content-5","type":"html","authorship":"legacy-preserved","html":"<p>HTTPS usa <strong>TLS</strong> (Transport Layer Security) para criptografar a comunicação entre cliente e servidor. Um <strong>certificado digital</strong> — emitido por uma Autoridade Certificadora (CA) confiável — prova que o servidor é realmente quem diz ser, evitando que alguém finja ser sua API (ataque conhecido como <em>man-in-the-middle</em>).</p>","fidelityText":"HTTPS usa TLS (Transport Layer Security) para criptografar a comunicação entre cliente e servidor. Um certificado digital — emitido por uma Autoridade Certificadora (CA) confiável — prova que o servidor é realmente quem diz ser, evitando que alguém finja ser sua API (ataque conhecido como man-in-the-middle)."},{"id":"https-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>O handshake: como cliente e servidor combinam uma chave sem nunca enviá-la pela rede</h2>","fidelityText":"O handshake: como cliente e servidor combinam uma chave sem nunca enviá-la pela rede"},{"id":"https-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Criptografia assimétrica (chave pública/privada) é cara demais para cifrar toda a conversa — por isso o TLS só usa isso no início, para negociar com segurança uma chave simétrica bem mais rápida que vai proteger o resto da sessão:</p>","fidelityText":"Criptografia assimétrica (chave pública/privada) é cara demais para cifrar toda a conversa — por isso o TLS só usa isso no início, para negociar com segurança uma chave simétrica bem mais rápida que vai proteger o resto da sessão:"},{"id":"https-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"1. Customer sends ClientHello: versions de TLS supported, algorithms de cipher\n   proposed e um number random\n2. Server responds com ServerHello: version/algorithm chosen, other\n   number random, e o CERTIFICATE do server (contem a key public dele)\n3. Customer validates o certificate (next section -- cadeia de confianca) e usa\n   information exchanged para as duas partes derive a SAME key symmetric,\n   sem que essa key em si never travels pela network\n4. O remaining da communication usa essa key symmetric -- rapida o\n   sufficient para encrypt cada request/response sem custo noticeable","fidelityText":"1. Cliente envia ClientHello: versoes de TLS suportadas, algoritmos de cifra propostos e um numero aleatorio 2. Servidor responde com ServerHello: versao/algoritmo escolhidos, outro numero aleatorio, e o CERTIFICADO do servidor (contem a chave publica dele) 3. Cliente valida o certificado (proxima secao -- cadeia de confianca) e usa informacoes trocadas para as duas partes derivarem a MESMA chave simetrica, sem que essa chave em si jamais trafegue pela rede 4. O restante da comunicacao usa essa chave simetrica -- rapida o suficiente para cifrar cada request/response sem custo perceptivel","highlightedHtml":"<span class=\"com\">1. Cliente envia ClientHello: versoes de TLS suportadas, algoritmos de cifra\n   propostos e um numero aleatorio\n2. Servidor responde com ServerHello: versao/algoritmo escolhidos, outro\n   numero aleatorio, e o CERTIFICADO do servidor (contem a chave publica dele)\n3. Cliente valida o certificado (proxima secao -- cadeia de confianca) e usa\n   informacoes trocadas para as duas partes derivarem a MESMA chave simetrica,\n   sem que essa chave em si jamais trafegue pela rede\n4. O restante da comunicacao usa essa chave simetrica -- rapida o\n   suficiente para cifrar cada request/response sem custo perceptivel</span>","caption":"Exemplo executável de https.","explanation":["A criptografia assimétrica (certificado/chave pública) só serve para o handshake: provar identidade e combinar com segurança uma chave simétrica, que nunca trafega pela rede.","A partir da chave simétrica derivada, toda a comunicação usa essa chave -- rápida o suficiente para não ter custo perceptível por requisição.","TLS 1.3 reduziu esse handshake para uma única ida-e-volta (1-RTT)."],"commonMistakes":["Achar que toda a sessão TLS usa criptografia assimétrica (cara demais para isso)","Não saber por que a chave simétrica nunca é enviada pela rede em texto"]},{"id":"https-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Note a divisão de trabalho: a parte assimétrica (certificado, chave pública) só serve para provar identidade e combinar uma chave com segurança; a parte simétrica é quem de fato cifra o tráfego. É a mesma divisão de papéis do <code>JwtDecoder</code> do capítulo 52 — HMAC (simétrico) é rápido mas exige segredo compartilhado; assinatura assimétrica (JWK) é mais cara, mas prova identidade sem compartilhar segredo nenhum. TLS 1.3 (padrão atual) reduziu esse handshake para uma única ida-e-volta (1-RTT), tornando a conexão inicial mais rápida que nas versões anteriores.</p>","fidelityText":"Note a divisão de trabalho: a parte assimétrica (certificado, chave pública) só serve para provar identidade e combinar uma chave com segurança; a parte simétrica é quem de fato cifra o tráfego. É a mesma divisão de papéis do JwtDecoder do capítulo 52 — HMAC (simétrico) é rápido mas exige segredo compartilhado; assinatura assimétrica (JWK) é mais cara, mas prova identidade sem compartilhar segredo nenhum. TLS 1.3 (padrão atual) reduziu esse handshake para uma única ida-e-volta (1-RTT), tornando a conexão inicial mais rápida que nas versões anteriores."},{"id":"https-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Cadeia de confiança: por que seu navegador confia num certificado que nunca viu antes</h2>","fidelityText":"Cadeia de confiança: por que seu navegador confia num certificado que nunca viu antes"},{"id":"https-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Uma CA não assina o certificado do seu servidor diretamente com a chave raiz dela — essa chave raiz fica guardada offline, isolada, porque se vazasse comprometeria a confiança de <em>todos</em> os certificados que aquela CA já emitiu. Em vez disso, a cadeia funciona em camadas:</p>","fidelityText":"Uma CA não assina o certificado do seu servidor diretamente com a chave raiz dela — essa chave raiz fica guardada offline, isolada, porque se vazasse comprometeria a confiança de todos os certificados que aquela CA já emitiu. Em vez disso, a cadeia funciona em camadas:"},{"id":"https-content-12","type":"html","authorship":"legacy-preserved","html":"<ul style=\"color:var(--ink-dim)\">\n        <li><strong>Root CA</strong> — a chave mais protegida, raramente usada diretamente; seu certificado já vem pré-instalado no sistema operacional/navegador como confiável.</li>\n        <li><strong>Intermediate CA</strong> — certificado assinado pela root, usado no dia a dia para assinar certificados de servidores. Se um intermediate for comprometido, só ele (não a root) precisa ser revogado.</li>\n        <li><strong>Leaf (seu servidor)</strong> — o certificado do seu domínio, assinado por um intermediate.</li>\n      </ul>","fidelityText":"Root CA — a chave mais protegida, raramente usada diretamente; seu certificado já vem pré-instalado no sistema operacional/navegador como confiável. Intermediate CA — certificado assinado pela root, usado no dia a dia para assinar certificados de servidores. Se um intermediate for comprometido, só ele (não a root) precisa ser revogado. Leaf (seu servidor) — o certificado do seu domínio, assinado por um intermediate."},{"id":"https-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Quando o cliente recebe o certificado do servidor (etapa 2 do handshake), ele sobe essa cadeia — verifica que o leaf foi assinado por um intermediate confiável, que por sua vez foi assinado por uma root que já está na lista de confiança do sistema — até fechar a cadeia num ponto em que já confia. Se qualquer elo dessa cadeia não puder ser validado, o navegador mostra o aviso de \"conexão não é segura\" que todo mundo já viu ao menos uma vez.</p>","fidelityText":"Quando o cliente recebe o certificado do servidor (etapa 2 do handshake), ele sobe essa cadeia — verifica que o leaf foi assinado por um intermediate confiável, que por sua vez foi assinado por uma root que já está na lista de confiança do sistema — até fechar a cadeia num ponto em que já confia. Se qualquer elo dessa cadeia não puder ser validado, o navegador mostra o aviso de \"conexão não é segura\" que todo mundo já viu ao menos uma vez."},{"id":"https-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Self-signed vs. certificado de CA real</h2>","fidelityText":"Self-signed vs. certificado de CA real"},{"id":"https-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Um certificado <strong>self-signed</strong> é assinado pela própria chave privada do servidor, sem nenhuma CA envolvida — não existe cadeia até uma root confiável, então clientes rejeitam por padrão (o aviso de \"certificado inválido\"). Isso não é um bug do navegador: sem CA, não há como um terceiro provar que aquele servidor é realmente quem diz ser. Self-signed serve para desenvolvimento local (<code>localhost</code>) e testes internos onde você mesmo controla os dois lados da conexão — nunca para tráfego público de produção.</p>","fidelityText":"Um certificado self-signed é assinado pela própria chave privada do servidor, sem nenhuma CA envolvida — não existe cadeia até uma root confiável, então clientes rejeitam por padrão (o aviso de \"certificado inválido\"). Isso não é um bug do navegador: sem CA, não há como um terceiro provar que aquele servidor é realmente quem diz ser. Self-signed serve para desenvolvimento local (localhost) e testes internos onde você mesmo controla os dois lados da conexão — nunca para tráfego público de produção."},{"id":"https-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A boa notícia: você quase nunca precisa configurar certificados manualmente hoje em dia. Provedores modernos de deploy (Railway, Render, Vercel — que você vai usar em capítulos futuros) já entregam HTTPS automaticamente, renovando certificados sozinhos via Let's Encrypt (uma CA gratuita e automatizada, sem intervenção manual), de graça. A configuração manual de certificado só volta a ser sua responsabilidade em cenários de infraestrutura própria (VPS, servidor dedicado).</div>","fidelityText":"A boa notícia: você quase nunca precisa configurar certificados manualmente hoje em dia. Provedores modernos de deploy (Railway, Render, Vercel — que você vai usar em capítulos futuros) já entregam HTTPS automaticamente, renovando certificados sozinhos via Let's Encrypt (uma CA gratuita e automatizada, sem intervenção manual), de graça. A configuração manual de certificado só volta a ser sua responsabilidade em cenários de infraestrutura própria (VPS, servidor dedicado)."},{"id":"https-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>HSTS: fechando a porta do primeiro acesso em HTTP puro</h2>","fidelityText":"HSTS: fechando a porta do primeiro acesso em HTTP puro"},{"id":"https-content-18","type":"html","authorship":"legacy-preserved","html":"<p>Mesmo com HTTPS configurado, um usuário que digita <code>meusite.com</code> (sem <code>https://</code>) no navegador faz a primeira requisição em HTTP puro — um atacante na mesma rede poderia interceptar esse primeiro pedido e forçar a conversa a continuar em HTTP (um <em>downgrade attack</em>), mesmo que o servidor sempre redirecione para HTTPS depois. O cabeçalho <code>Strict-Transport-Security</code> (HSTS) resolve isso:</p>","fidelityText":"Mesmo com HTTPS configurado, um usuário que digita meusite.com (sem https://) no navegador faz a primeira requisição em HTTP puro — um atacante na mesma rede poderia interceptar esse primeiro pedido e forçar a conversa a continuar em HTTP (um downgrade attack), mesmo que o servidor sempre redirecione para HTTPS depois. O cabeçalho Strict-Transport-Security (HSTS) resolve isso:"},{"id":"https-code-19","type":"code","authorship":"legacy-preserved","language":"java","source":"Strict-Transport-Security: max-age=31536000; includeSubDomains","fidelityText":"Strict-Transport-Security: max-age=31536000; includeSubDomains","highlightedHtml":"Strict-Transport-Security: max-age=<span class=\"num\">31536000</span>; includeSubDomains","caption":"Exemplo executável de https.","explanation":["Strict-Transport-Security instrui o navegador a nunca mais tentar HTTP puro para aquele domínio durante o max-age, mesmo que o usuário digite http:// manualmente.","O redirecionamento acontece localmente no navegador -- sem chegar a fazer a requisição insegura -- por isso protege contra downgrade attack no primeiro acesso.","includeSubDomains estende a mesma regra a todos os subdomínios."],"commonMistakes":["Achar que HSTS substitui a necessidade de configurar HTTPS -- ele só reforça o uso depois que já existe","Esquecer includeSubDomains e deixar subdomínios vulneráveis ao downgrade"]},{"id":"https-content-20","type":"html","authorship":"legacy-preserved","html":"<p>Depois da <strong>primeira</strong> visita HTTPS bem-sucedida, o navegador memoriza esse cabeçalho e nunca mais tenta HTTP puro para aquele domínio (nem se o usuário digitar <code>http://</code> ou clicar um link antigo) durante o <code>max-age</code> informado — o redirecionamento acontece localmente no navegador, sem nem chegar a fazer a requisição insegura. <code>includeSubDomains</code> estende a mesma regra a todos os subdomínios.</p>","fidelityText":"Depois da primeira visita HTTPS bem-sucedida, o navegador memoriza esse cabeçalho e nunca mais tenta HTTP puro para aquele domínio (nem se o usuário digitar http:// ou clicar um link antigo) durante o max-age informado — o redirecionamento acontece localmente no navegador, sem nem chegar a fazer a requisição insegura. includeSubDomains estende a mesma regra a todos os subdomínios."},{"id":"https-content-21","type":"html","authorship":"legacy-preserved","html":"<h2>Onde o TLS termina: load balancer/proxy vs. aplicação</h2>","fidelityText":"Onde o TLS termina: load balancer/proxy vs. aplicação"},{"id":"https-content-22","type":"html","authorship":"legacy-preserved","html":"<p>Na maioria dos deploys em produção, sua aplicação Spring Boot <strong>não</strong> é quem descriptografa o tráfego TLS — quem faz isso é uma camada na borda (load balancer, reverse proxy, API gateway, CDN). Esse padrão se chama <strong>terminação de TLS</strong>:</p>","fidelityText":"Na maioria dos deploys em produção, sua aplicação Spring Boot não é quem descriptografa o tráfego TLS — quem faz isso é uma camada na borda (load balancer, reverse proxy, API gateway, CDN). Esse padrão se chama terminação de TLS:"},{"id":"https-code-23","type":"code","authorship":"legacy-preserved","language":"java","source":"Customer --HTTPS (encrypted)--> Load Balancer/Proxy --HTTP (text plain)--> Application\n                              (TLS ends aqui)      (network internal/private)","fidelityText":"Cliente --HTTPS (cifrado)--> Load Balancer/Proxy --HTTP (texto puro)--> Aplicacao (TLS termina aqui) (rede interna/privada)","highlightedHtml":"<span class=\"com\">Cliente --HTTPS (cifrado)--&gt; Load Balancer/Proxy --HTTP (texto puro)--&gt; Aplicacao\n                              (TLS termina aqui)      (rede interna/privada)</span>","caption":"Exemplo executável de https.","explanation":["Na maioria dos deploys, o TLS termina no load balancer/proxy -- a aplicação recebe o tráfego já descriptografado como HTTP puro dentro da rede privada.","Terminação centralizada simplifica certificado (um lugar só) e tira o custo de criptografia da aplicação, mas exige confiar no segmento de rede interno.","Sem o cabeçalho X-Forwarded-Proto, a aplicação não sabe que a requisição original era HTTPS -- daí a necessidade de server.forward-headers-strategy."],"commonMistakes":["Assumir que a aplicação sempre vê o esquema HTTPS original sem configurar forward-headers-strategy","Confundir terminação de TLS na borda com ausência de segurança -- o segmento interno só é aceitável por estar numa rede privada/VPC"]},{"id":"https-content-24","type":"html","authorship":"legacy-preserved","html":"<p>O tráfego chega cifrado até o load balancer; dali para a aplicação, dentro da rede privada/VPC, costuma seguir como HTTP simples — o que só é aceitável porque esse segmento interno não está exposto à internet pública. Essa terminação centralizada tem vantagens reais: um único lugar para configurar e renovar certificado (em vez de um por instância da aplicação), e o trabalho pesado de criptografia/descriptografia sai da sua aplicação. A alternativa é <strong>TLS ponta a ponta</strong> (a aplicação também termina TLS, ou o proxy só repassa o tráfego cifrado sem descriptografar — <em>TLS passthrough</em>): mais defesa em profundidade, mas exige gerenciar certificado em cada instância da aplicação também.</p>","fidelityText":"O tráfego chega cifrado até o load balancer; dali para a aplicação, dentro da rede privada/VPC, costuma seguir como HTTP simples — o que só é aceitável porque esse segmento interno não está exposto à internet pública. Essa terminação centralizada tem vantagens reais: um único lugar para configurar e renovar certificado (em vez de um por instância da aplicação), e o trabalho pesado de criptografia/descriptografia sai da sua aplicação. A alternativa é TLS ponta a ponta (a aplicação também termina TLS, ou o proxy só repassa o tráfego cifrado sem descriptografar — TLS passthrough): mais defesa em profundidade, mas exige gerenciar certificado em cada instância da aplicação também."},{"id":"https-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\">Se o TLS termina antes da sua aplicação, o Spring Boot recebe a requisição como HTTP puro — sem o cabeçalho <code>X-Forwarded-Proto: https</code> (que o proxy adiciona informando o esquema original), seu código pode achar erroneamente que a conexão nunca foi HTTPS, gerando redirecionamentos incorretos ou cookies <code>Secure</code> nunca enviados. Configure <code>server.forward-headers-strategy=native</code> (ou <code>framework</code>) para o Spring Boot confiar nesses cabeçalhos <code>X-Forwarded-*</code> vindos do proxy.</div>","fidelityText":"Se o TLS termina antes da sua aplicação, o Spring Boot recebe a requisição como HTTP puro — sem o cabeçalho X-Forwarded-Proto: https (que o proxy adiciona informando o esquema original), seu código pode achar erroneamente que a conexão nunca foi HTTPS, gerando redirecionamentos incorretos ou cookies Secure nunca enviados. Configure server.forward-headers-strategy=native (ou framework) para o Spring Boot confiar nesses cabeçalhos X-Forwarded-* vindos do proxy."},{"id":"https-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>Onde HTTPS se conecta com o resto do que você já aprendeu</h2>","fidelityText":"Onde HTTPS se conecta com o resto do que você já aprendeu"},{"id":"https-content-27","type":"html","authorship":"legacy-preserved","html":"<ul style=\"color:var(--ink-dim)\">\n        <li>Sem HTTPS, o <code>Authorization: Bearer eyJ...</code> do capítulo 52 viaja em texto puro — qualquer um na rede pode roubar o token e se passar pelo usuário.</li>\n        <li>O CORS do capítulo 50 e o cookie <code>Secure</code> (que só é enviado sobre HTTPS) andam juntos em produção séria.</li>\n        <li>Certificados expirados são uma causa clássica de incidente em produção — parte do capítulo de observabilidade (mais adiante) cobre monitorar isso.</li>\n      </ul>","fidelityText":"Sem HTTPS, o Authorization: Bearer eyJ... do capítulo 52 viaja em texto puro — qualquer um na rede pode roubar o token e se passar pelo usuário. O CORS do capítulo 50 e o cookie Secure (que só é enviado sobre HTTPS) andam juntos em produção séria. Certificados expirados são uma causa clássica de incidente em produção — parte do capítulo de observabilidade (mais adiante) cobre monitorar isso."},{"id":"https-content-28","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não é necessário estudar criptografia assimétrica a fundo para usar HTTPS corretamente no dia a dia — o importante é internalizar a regra prática: <strong>toda</strong> API que lida com autenticação ou dado sensível deveria estar atrás de HTTPS, sem exceção, mesmo em ambientes internos. \"É só a rede interna, não precisa\" é uma das frases mais caras já ditas em incidentes de segurança reais.</div>","fidelityText":"Não é necessário estudar criptografia assimétrica a fundo para usar HTTPS corretamente no dia a dia — o importante é internalizar a regra prática: toda API que lida com autenticação ou dado sensível deveria estar atrás de HTTPS, sem exceção, mesmo em ambientes internos. \"É só a rede interna, não precisa\" é uma das frases mais caras já ditas em incidentes de segurança reais."},{"id":"https-exercise-29","type":"exercise","authorship":"legacy-preserved","title":"Exercício 53.1 — Identificando o problema","prompt":"Explique, em texto, por que enviar um JWT (capítulo 52) sobre uma conexão HTTP simples (não HTTPS) anula boa parte da segurança que o BCrypt e a assinatura do token oferecem, mesmo que a senha nunca tenha sido enviada em texto puro no login.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 53.1 — Identificando o problemafácil Explique, em texto, por que enviar um JWT (capítulo 52) sobre uma conexão HTTP simples (não HTTPS) anula boa parte da segurança que o BCrypt e a assinatura do token oferecem, mesmo que a senha nunca tenha sido enviada em texto puro no login. Ver solução Mesmo com senha protegida por BCrypt no banco e o JWT assinado corretamente, se a conexão não for HTTPS, o token em si viaja em texto puro em toda requisição subsequente — qualquer pessoa capturando o tráfego de rede (ex: em um Wi-Fi público) pode copiar esse token e reutilizá-lo para se passar pelo usuário até o token expirar, sem nunca precisar saber a senha. HTTPS protege o transporte; BCrypt e assinatura JWT protegem o conteúdo — os dois são necessários, um não substitui o outro.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 53.1 — Identificando o problema</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Explique, em texto, por que enviar um JWT (capítulo 52) sobre uma conexão HTTP simples (não HTTPS) anula boa parte da segurança que o BCrypt e a assinatura do token oferecem, mesmo que a senha nunca tenha sido enviada em texto puro no login.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Mesmo com senha protegida por BCrypt no banco e o JWT assinado corretamente, se a <strong>conexão</strong> não for HTTPS, o token em si viaja em texto puro em toda requisição subsequente — qualquer pessoa capturando o tráfego de rede (ex: em um Wi-Fi público) pode copiar esse token e reutilizá-lo para se passar pelo usuário até o token expirar, sem nunca precisar saber a senha. HTTPS protege o <em>transporte</em>; BCrypt e assinatura JWT protegem o <em>conteúdo</em> — os dois são necessários, um não substitui o outro.</p>\n        </div>\n      </div>"},{"id":"https-exercise-30","type":"exercise","authorship":"legacy-preserved","title":"Exercício 53.2 — Diagnosticando um redirecionamento que nunca termina","prompt":"Sua aplicação Spring Boot está atrás de um load balancer que termina TLS e repassa as requisições como HTTP puro. Você configurou um filtro que redireciona toda requisição HTTP para HTTPS. Em produção, os usuários relatam um loop infinito de redirecionamento. Explique a causa mais provável e a correção.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 53.2 — Diagnosticando um redirecionamento que nunca terminamédio Sua aplicação Spring Boot está atrás de um load balancer que termina TLS e repassa as requisições como HTTP puro. Você configurou um filtro que redireciona toda requisição HTTP para HTTPS. Em produção, os usuários relatam um loop infinito de redirecionamento. Explique a causa mais provável e a correção. Ver solução Como o TLS termina no load balancer, a aplicação sempre recebe a requisição como HTTP puro — sem confiar no cabeçalho X-Forwarded-Proto, o filtro de redirecionamento acha que toda requisição (mesmo as que chegaram como HTTPS no cliente) precisa ser redirecionada, criando um loop. A correção é configurar server.forward-headers-strategy=native para o Spring Boot reconhecer X-Forwarded-Proto: https enviado pelo load balancer e tratar a requisição como já estando em HTTPS.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 53.2 — Diagnosticando um redirecionamento que nunca termina</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Sua aplicação Spring Boot está atrás de um load balancer que termina TLS e repassa as requisições como HTTP puro. Você configurou um filtro que redireciona toda requisição HTTP para HTTPS. Em produção, os usuários relatam um loop infinito de redirecionamento. Explique a causa mais provável e a correção.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Como o TLS termina no load balancer, a aplicação sempre recebe a requisição como HTTP puro — sem confiar no cabeçalho <code>X-Forwarded-Proto</code>, o filtro de redirecionamento acha que <strong>toda</strong> requisição (mesmo as que chegaram como HTTPS no cliente) precisa ser redirecionada, criando um loop. A correção é configurar <code>server.forward-headers-strategy=native</code> para o Spring Boot reconhecer <code>X-Forwarded-Proto: https</code> enviado pelo load balancer e tratar a requisição como já estando em HTTPS.</p>\n        </div>\n      </div>"},{"id":"https-quiz","type":"quiz","authorship":"authored","conceptId":"tls-certificate-chain","prompt":"Qual problema HTTPS resolve diretamente?","options":[{"id":"https-a","label":"Reduzir leitura/alteração da comunicação em trânsito e autenticar o servidor pelo certificado.","correct":true,"explanation":"TLS protege canal e identidade do servidor quando validado corretamente."},{"id":"https-b","label":"Garantir que todo usuário logado tem permissão de admin.","correct":false,"explanation":"Autorização continua sendo regra da aplicação."},{"id":"https-c","label":"Impedir qualquer XSS dentro da aplicação.","correct":false,"explanation":"HTTPS não sanitiza HTML nem corrige injeção no frontend."}]}],"resources":[{"id":"rfc8446-tls13","type":"reference","title":"RFC 8446: TLS 1.3","url":"https://www.rfc-editor.org/rfc/rfc8446","reinforces":"Protocolo TLS moderno, handshake e objetivos de segurança.","language":"en","publisher":"IETF","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"mdn-https","type":"guide","title":"MDN: HTTPS","url":"https://developer.mozilla.org/en-US/docs/Glossary/HTTPS","reinforces":"Visão prática de HTTPS, certificados e proteção de transporte.","language":"en","publisher":"MDN","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The https component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to https. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible https failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"1. Customer sends ClientHello: versions de TLS supported, algorithms de cipher","instruction":"The https component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible https failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mini-auth-rbac","moduleId":"api-security-quality","order":5,"title":"Mini-projeto: autenticação e autorização por papéis","summary":"Adicione identidade e autorização ao help desk. Papéis mínimos: cliente, técnico e administrador. Autenticação não basta: cada recurso precisa validar propriedade e permissão.","objectives":["Implementar autenticação e autorização por papéis","Modelar ownership além de ROLE_ADMIN/ROLE_USER","Testar endpoints protegidos e negados","Registrar evidências de ataque e defesa"],"whyItExists":"Depois de Spring Security, OIDC e HTTPS, o aluno precisa provar segurança em uma API pequena. O projeto RBAC transforma conceitos em endpoints testáveis, não em configuração decorativa.","prerequisiteChapterIds":["spring-security","security-oidc","https"],"conceptIds":["cenarios-de-ataque-e-defesa","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["rbac-permission-boundary"],"usedConceptIds":["spring-security-authorization","password-hash-verification","resource-server-jwt-validation"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"rbac-intuition","type":"intuition","authorship":"authored","title":"Papel é ponto de partida; regra de recurso é o fechamento","body":"Um atendente pode ver chamados do setor; um cliente vê os próprios chamados; um admin audita. Essas diferenças não cabem só em autenticado/não autenticado.","analogyLimit":"Cargos ajudam a imaginar RBAC, mas sistemas reais também exigem ownership, escopo, auditoria e negação explícita."},{"id":"mini-auth-rbac-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>Spring Security</b></div><div class=\"time-est\">Tempo: <b>12–20 horas</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#spring-security\">Spring Security</a></div></div>","fidelityText":"Objetivo: Spring SecurityTempo: 12–20 horasPré-requisito: Spring Security"},{"id":"mini-auth-rbac-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Adicione identidade e autorização ao help desk. Papéis mínimos: cliente, técnico e administrador. Autenticação não basta: cada recurso precisa validar propriedade e permissão.</p>","fidelityText":"Adicione identidade e autorização ao help desk. Papéis mínimos: cliente, técnico e administrador. Autenticação não basta: cada recurso precisa validar propriedade e permissão."},{"id":"mini-auth-rbac-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Cenários de ataque e defesa</h2>","fidelityText":"Cenários de ataque e defesa"},{"id":"mini-auth-rbac-checklist-4","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-auth-rbac-checklist-0","label":"Senha armazenada com hash apropriado, nunca reversível."},{"id":"mini-auth-rbac-checklist-1","label":"Cliente não acessa chamado de outro cliente alterando o ID."},{"id":"mini-auth-rbac-checklist-2","label":"Tokens expirados, ausentes e adulterados retornam respostas coerentes."},{"id":"mini-auth-rbac-checklist-3","label":"Segredos ficam fora do repositório e possuem configuração documentada."},{"id":"mini-auth-rbac-checklist-4","label":"Testes de autorização negativos para cada endpoint sensível."}]},{"id":"mini-auth-rbac-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Proibido declarar segurança concluída porque o login funciona.</b> A maior parte dos erros está na autorização de cada operação e no tratamento do ciclo de credenciais.</div>","fidelityText":"Proibido declarar segurança concluída porque o login funciona. A maior parte dos erros está na autorização de cada operação e no tratamento do ciclo de credenciais."},{"id":"mini-auth-rbac-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-auth-rbac:0","type":"quiz","authorship":"legacy-preserved","conceptId":"autenticacao-por-papel-e-suficiente-para-proteger-um-chamado","prompt":"Autenticação por papel é suficiente para proteger um chamado?","options":[{"id":"mini-auth-rbac:0:option:0","label":"Não; também é preciso verificar propriedade/escopo do recurso.","correct":true,"explanation":"Role ajuda, mas ownership e regra de domínio fecham a autorização."},{"id":"mini-auth-rbac:0:option:1","label":"Sim; qualquer usuário com papel USER acessa tudo.","correct":false,"explanation":"Proteção real precisa estar no servidor e ser testada."},{"id":"mini-auth-rbac:0:option:2","label":"Sim, desde que o JWT seja longo.","correct":false,"explanation":"Autenticação responde quem é; autorização responde o que pode fazer."}],"sourceIndex":7},{"id":"mini-auth-rbac:1","type":"quiz","authorship":"legacy-preserved","conceptId":"um-endpoint-get-chamados-id-verifica-apenas-se-o-usuario-esta-autenticad","prompt":"Um endpoint GET /chamados/{id} verifica apenas se o usuário está autenticado, sem checar se o chamado pertence a ele. Um cliente autenticado troca o {id} na URL e acessa o chamado de outro cliente. Que tipo de vulnerabilidade é essa?","options":[{"id":"mini-auth-rbac:1:option:0","label":"IDOR (Insecure Direct Object Reference) / quebra de controle de acesso: autenticação sozinha não garante que o recurso pertence a quem pede.","correct":true,"explanation":"Checar apenas autenticação e não ownership é exatamente o padrão de IDOR: o ID na URL vira a única barreira, e ela é trivial de contornar."},{"id":"mini-auth-rbac:1:option:1","label":"Não é uma vulnerabilidade, já que o usuário está autenticado no sistema.","correct":false,"explanation":"Estar autenticado responde quem é o usuário, não se o recurso pedido pertence a ele."},{"id":"mini-auth-rbac:1:option:2","label":"É um problema de desempenho, não de segurança.","correct":false,"explanation":"IDOR é uma falha de controle de acesso, não de desempenho -- o sintoma é acesso indevido a dado, não lentidão."}],"sourceIndex":8},{"id":"mini-auth-rbac:2","type":"quiz","authorship":"legacy-preserved","conceptId":"uma-requisicao-chega-com-um-jwt-adulterado-assinatura-invalida-qual-resp","prompt":"Uma requisição chega com um JWT adulterado (assinatura inválida). Qual resposta é coerente com o contrato de segurança do projeto?","options":[{"id":"mini-auth-rbac:2:option:0","label":"401 com mensagem genérica de falha de autenticação, sem revelar detalhes internos da validação (por exemplo, qual parte da assinatura falhou).","correct":true,"explanation":"Uma mensagem genérica de 401 nega o acesso sem ensinar a um atacante qual parte da validação de assinatura falhou."},{"id":"mini-auth-rbac:2:option:1","label":"200 com um corpo vazio, para não assustar o cliente.","correct":false,"explanation":"Responder 200 para uma assinatura inválida esconde uma falha de autenticação real como se fosse sucesso."},{"id":"mini-auth-rbac:2:option:2","label":"500, já que é um erro inesperado do servidor.","correct":false,"explanation":"Assinatura inválida é uma falha de autenticação esperada e tratável, não um erro inesperado do servidor."}],"sourceIndex":9},{"id":"mini-auth-rbac:3","type":"quiz","authorship":"legacy-preserved","conceptId":"o-frontend-esconde-o-botao-excluir-usuario-para-quem-nao-e-admin-o-endpo","prompt":"O frontend esconde o botão \"Excluir usuário\" para quem não é admin. O endpoint DELETE /usuarios/{id} no backend não verifica o papel do chamador. Isso é seguro?","options":[{"id":"mini-auth-rbac:3:option:0","label":"Não. Esconder um botão no frontend não impede uma chamada direta ao endpoint; a autorização precisa ser aplicada no servidor.","correct":true,"explanation":"Autorização precisa ser aplicada no servidor -- o frontend só melhora a experiência, nunca substitui a checagem real."},{"id":"mini-auth-rbac:3:option:1","label":"Sim, porque nenhum usuário comum saberia a URL exata do endpoint.","correct":false,"explanation":"A URL de um endpoint não é segredo; qualquer cliente HTTP pode chamá-la diretamente sem passar pela UI."},{"id":"mini-auth-rbac:3:option:2","label":"Sim, desde que o frontend seja servido por HTTPS.","correct":false,"explanation":"HTTPS protege o transporte da requisição, não decide se o chamador tem permissão para a operação."}],"sourceIndex":10},{"id":"mini-auth-rbac-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"rbac-exercise-matriz","type":"exercise","authorship":"authored","title":"Antes de codificar: matriz de autorização do help desk","prompt":"Antes de implementar os endpoints, escreva a matriz de autorização completa: para cada papel (cliente, técnico, administrador) e cada operação (criar chamado, ver chamado, listar chamados, encerrar chamado, ver todos os chamados), diga se é permitido, negado, ou permitido apenas quando há ownership. Depois, para dois casos negados da matriz, escreva o teste que prova a negação (401 ou 403, dependendo do caso).","difficulty":"advanced","criteria":["A matriz cobre os três papéis e todas as operações listadas, sem casos implícitos ('assume-se que...').","Casos de ownership (cliente só vê o próprio chamado) aparecem como uma condição distinta de papel puro.","Os dois testes escritos diferenciam claramente 401 (não autenticado) de 403 (autenticado mas sem permissão/ownership).","A resposta não usa esconder elementos de UI como argumento de segurança."]},{"id":"rbac-project","type":"project","authorship":"authored","title":"API de autenticação e autorização por papéis","brief":"Evolua a API de helpdesk para login, senha com hash, papéis, ownership de chamados e testes de acesso permitido/negado.","requirements":["Cadastro/login com senha armazenada por PasswordEncoder","Endpoint público mínimo e endpoints protegidos por role/authority","Regra de ownership para cliente acessar apenas seus chamados","Admin com permissão explícita de auditoria","Testes para 401, 403, acesso permitido e acesso negado por ownership","Documentação de ameaças: token expirado, papel insuficiente, usuário inexistente e tentativa de acessar recurso de outro usuário"],"guidance":"bounded","acceptanceCriteria":["Nenhuma senha ou secret real aparece em código, logs ou README.","Testes diferenciam não autenticado, autenticado sem permissão e recurso inexistente.","Frontend esconder botão não é usado como única proteção.","A matriz de autorização está documentada e coberta por teste."],"knowledgeMatrix":[{"requirement":"Senha e identidade","conceptIds":["password-hash-verification","auth-session-cookie","jwt-claims-signature"],"chapterIds":["autenticacao-conceitos","spring-security"],"expectedEvidence":"Senha é hash; token/sessão tem expiração; login falho não revela detalhes sensíveis."},{"requirement":"Autorização antes do método","conceptIds":["security-filter-chain","spring-security-authorization"],"chapterIds":["spring-security"],"expectedEvidence":"Endpoints negam requisição antes da regra executar quando credenciais/permissões faltam."},{"requirement":"Ownership do recurso","conceptIds":["rbac-permission-boundary","jpa-entity-identity"],"chapterIds":["mini-auth-rbac","spring-jpa"],"expectedEvidence":"Teste prova que cliente não acessa chamado de outro cliente."},{"requirement":"Resource Server seguro","conceptIds":["resource-server-jwt-validation","oidc-id-token-claims"],"chapterIds":["security-oidc"],"expectedEvidence":"Issuer/audience/expiração/escopo são tratados como contrato."}],"englishSpecification":{"title":"RBAC help desk API","brief":"Implement authentication and role-based authorization with ownership checks and explicit negative tests.","requirements":["Password hashing","Protected endpoints","Role and ownership rules","401/403 tests","Threat notes"],"acceptanceCriteria":["No plaintext password is stored.","Tests prove allowed and denied access.","Authorization is enforced server-side."]}},{"id":"rbac-quiz","type":"quiz","authorship":"authored","conceptId":"rbac-permission-boundary","prompt":"Por que RBAC simples pode ser insuficiente em chamados?","options":[{"id":"rbac-a","label":"Porque além do papel, a regra pode depender de quem é dono do recurso específico.","correct":true,"explanation":"Cliente autenticado não deve ver chamado de outro cliente."},{"id":"rbac-b","label":"Porque o frontend sempre protege melhor que o backend.","correct":false,"explanation":"O backend deve aplicar a regra; frontend só melhora experiência."},{"id":"rbac-c","label":"Porque senha com hash elimina necessidade de autorização.","correct":false,"explanation":"Hash protege credencial armazenada, não decide permissão."}]}],"resources":[{"id":"spring-method-security","type":"reference","title":"Spring Security: Method Security","url":"https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html","reinforces":"@PreAuthorize, authorities e autorização em método.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"owasp-authz-cheatsheet","type":"guide","title":"OWASP Authorization Cheat Sheet","url":"https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html","reinforces":"Princípios de deny by default, least privilege e validação server-side.","language":"en","publisher":"OWASP","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The role-based access control project component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to role-based access control project. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible role-based access control project failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// Observe how names reveal the role-based access control project contract.","instruction":"The role-based access control project component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible role-based access control project failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["ownership-alem-de-role","idor-e-controle-de-acesso-quebrado","hash-de-senha-e-ciclo-de-credenciais","tratamento-coerente-de-token-invalido","frontend-nao-substitui-autorizacao-no-servidor"],"evidenceBlocks":{"ownership-alem-de-role":["mini-auth-rbac:0","mini-auth-rbac:1","rbac-project"],"idor-e-controle-de-acesso-quebrado":["mini-auth-rbac:1","mini-auth-rbac-checklist-4","rbac-exercise-matriz"],"hash-de-senha-e-ciclo-de-credenciais":["mini-auth-rbac-checklist-4","rbac-project"],"tratamento-coerente-de-token-invalido":["mini-auth-rbac-checklist-4","mini-auth-rbac:2","rbac-project"],"frontend-nao-substitui-autorizacao-no-servidor":["mini-auth-rbac:3","mini-auth-rbac-content-5","rbac-exercise-matriz"]},"primarySources":["Spring Security: Method Security -- https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html","OWASP Authorization Cheat Sheet -- https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"docker-conceitos","moduleId":"containers-integration-data","order":0,"title":"Docker — conceitos & instalação","summary":"\"Na minha máquina funciona\" é o problema que Docker resolve. Um container empacota sua aplicação junto com tudo que ela precisa para rodar (runtime Java, bibliotecas, variáveis de ambiente) em uma unidade isolada e portátil, idêntica em qualquer máquina.","objectives":["Separar imagem, container, layer e processo","Entender volumes como persistência deliberada","Executar serviços locais sem confundir com produção","Ler logs, portas e variáveis como contrato operacional"],"whyItExists":"Depois de build, Git, banco e Spring, Docker entra para reproduzir ambiente e dependências. O objetivo é entender artefato e processo antes de Compose, Testcontainers ou deploy.","prerequisiteChapterIds":["git","build"],"conceptIds":["imagem-vs-container-a-distincao-fundamental","como-o-isolamento-funciona-de-verdade-namespaces-e-cgroups","volumes-persistindo-dados-de-verdade","volume-nomeado-vs-bind-mount"],"introducedConceptIds":["container-image-layer","docker-volume-persistence"],"usedConceptIds":["build-lifecycle","path-filesystem"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"docker-intuition","type":"intuition","authorship":"authored","title":"Container é processo isolado a partir de uma imagem","body":"A imagem é o pacote imutável. O container é uma execução dessa imagem. Se você remove o container, perde o estado gravado nele, a menos que tenha colocado esse estado em volume.","analogyLimit":"Forma e bolo ajudam a imaginar imagem/container, mas rede, filesystem, usuário e sinais são contratos técnicos reais."},{"id":"docker-conceitos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-docker\">Docker</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#git\">29 · Git</a>, <a class=\"prereq-tag\" href=\"#build\">19 · Maven &amp; Gradle</a></div>\n      </div>","fidelityText":"Docker Dificuldade: Intermediário ⏱ ~2h de estudo + prática Pré-requisitos: 29 · Git, 19 · Maven & Gradle"},{"id":"docker-conceitos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>\"Na minha máquina funciona\" é o problema que Docker resolve. Um <strong>container</strong> empacota sua aplicação junto com tudo que ela precisa para rodar (runtime Java, bibliotecas, variáveis de ambiente) em uma unidade isolada e portátil, idêntica em qualquer máquina.</p>","fidelityText":"\"Na minha máquina funciona\" é o problema que Docker resolve. Um container empacota sua aplicação junto com tudo que ela precisa para rodar (runtime Java, bibliotecas, variáveis de ambiente) em uma unidade isolada e portátil, idêntica em qualquer máquina."},{"id":"docker-conceitos-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"install-box\">\n        <h2>Instalação (Docker Desktop / Engine)</h2>\n        <div class=\"install-tabs\" role=\"tablist\">\n          <button type=\"button\" class=\"install-tab active\" role=\"tab\" aria-selected=\"true\">Windows</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">macOS</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">Linux</button>\n        </div>\n        <div class=\"install-panel active\"><pre class=\"code\"><span class=\"com\"># baixe o Docker Desktop em docker.com (exige WSL2 habilitado)</span>\nwsl --install     <span class=\"com\"># se ainda não tiver o WSL2</span>\n<span class=\"com\"># depois instale o Docker Desktop pelo site e reinicie</span></pre></div>\n        <div class=\"install-panel\"><pre class=\"code\">brew install --cask docker\n<span class=\"com\"># abra o Docker Desktop uma vez para iniciar o daemon</span></pre></div>\n        <div class=\"install-panel\"><pre class=\"code\"><span class=\"com\"># Ubuntu/Debian -- use o repositório apt oficial documentado em docs.docker.com.\n# Evite executar scripts remotos diretamente com \"curl | sh\".\n# Depois de instalar docker-ce e os plugins, valide:</span>\nsudo systemctl status docker\ndocker version</pre></div>\n        <pre class=\"code\">docker --version\ndocker run hello-world   <span class=\"com\"># confirma que tudo está funcionando</span></pre>\n      </div>","fidelityText":"Instalação (Docker Desktop / Engine) Windows macOS Linux # baixe o Docker Desktop em docker.com (exige WSL2 habilitado) wsl --install # se ainda não tiver o WSL2 # depois instale o Docker Desktop pelo site e reinicie brew install --cask docker # abra o Docker Desktop uma vez para iniciar o daemon # Ubuntu/Debian -- use o repositório apt oficial documentado em docs.docker.com. # Evite executar scripts remotos diretamente com \"curl | sh\". # Depois de instalar docker-ce e os plugins, valide: sudo systemctl status docker docker version docker --version docker run hello-world # confirma que tudo está funcionando"},{"id":"docker-conceitos-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Imagem vs Container — a distinção fundamental</h2>","fidelityText":"Imagem vs Container — a distinção fundamental"},{"id":"docker-conceitos-content-5","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Conceito</th><th>O que é</th><th>Analogia</th></tr>\n        <tr><td><strong>Imagem</strong></td><td>Um \"molde\" somente leitura, com o sistema de arquivos e configuração da aplicação</td><td>A classe (capítulo 01)</td></tr>\n        <tr><td><strong>Container</strong></td><td>Uma instância em execução dessa imagem, isolada</td><td>O objeto (capítulo 01)</td></tr>\n        <tr><td><strong>Volume</strong></td><td>Armazenamento persistente fora do ciclo de vida do container</td><td>Um HD externo plugado no container</td></tr>\n      </tbody></table>","fidelityText":"ConceitoO que éAnalogia ImagemUm \"molde\" somente leitura, com o sistema de arquivos e configuração da aplicaçãoA classe (capítulo 01) ContainerUma instância em execução dessa imagem, isoladaO objeto (capítulo 01) VolumeArmazenamento persistente fora do ciclo de vida do containerUm HD externo plugado no container"},{"id":"docker-conceitos-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"docker pull postgres:16          # baixa a imagem do Docker Hub\ndocker run -d --name my-postgres -e POSTGRES_PASSWORD=password123 -p 5432:5432 postgres:16\n# -d: roda em background (detached)\n# -e: variável de ambiente\n# -p host:container: mapeia a porta 5432 da sua máquina para a 5432 do container\n\ndocker ps                         # containers rodando\ndocker logs my-postgres          # ver a saída/log do container\ndocker exec -it my-postgres bash # abre um terminal DENTRO do container\ndocker stop my-postgres\ndocker rm my-postgres            # remove o container (a imagem continua no disco)","fidelityText":"docker pull postgres:16 # baixa a imagem do Docker Hub docker run -d --name meu-postgres -e POSTGRES_PASSWORD=senha123 -p 5432:5432 postgres:16 # -d: roda em background (detached) # -e: variável de ambiente # -p host:container: mapeia a porta 5432 da sua máquina para a 5432 do container docker ps # containers rodando docker logs meu-postgres # ver a saída/log do container docker exec -it meu-postgres bash # abre um terminal DENTRO do container docker stop meu-postgres docker rm meu-postgres # remove o container (a imagem continua no disco)","highlightedHtml":"docker pull postgres:16          <span class=\"com\"># baixa a imagem do Docker Hub</span>\ndocker run -d --name my-postgres -e POSTGRES_PASSWORD=password123 -p 5432:5432 postgres:16\n<span class=\"com\"># -d: roda em background (detached)\n# -e: variável de ambiente\n# -p host:container: mapeia a porta 5432 da sua máquina para a 5432 do container</span>\n\ndocker ps                         <span class=\"com\"># containers rodando</span>\ndocker logs my-postgres          <span class=\"com\"># ver a saída/log do container</span>\ndocker exec -it my-postgres bash <span class=\"com\"># abre um terminal DENTRO do container</span>\ndocker stop my-postgres\ndocker rm my-postgres            <span class=\"com\"># remove o container (a imagem continua no disco)</span>","caption":"Exemplo executável de docker-conceitos.","explanation":["docker pull baixa uma imagem; docker run cria e inicia um container a partir dela.","Portas e variáveis configuram a execução, não alteram a imagem original."],"commonMistakes":["Tratar container como VM permanente","Esquecer de nomear e observar logs"]},{"id":"docker-conceitos-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Containers são <strong>efêmeros por natureza</strong> — qualquer dado gravado dentro dele some ao removê-lo, a menos que você use um <strong>volume</strong>.</li>\n          <li>Nunca rode processos como <code>root</code> dentro de um container em produção sem necessidade — é uma superfície de ataque desnecessária.</li>\n          <li>Uma imagem deveria fazer <strong>uma coisa</strong> — evite meter banco + API + front na mesma imagem; use containers separados conectados por rede (é para isso que existe o Docker Compose, próximo capítulo).</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Containers são efêmeros por natureza — qualquer dado gravado dentro dele some ao removê-lo, a menos que você use um volume. Nunca rode processos como root dentro de um container em produção sem necessidade — é uma superfície de ataque desnecessária. Uma imagem deveria fazer uma coisa — evite meter banco + API + front na mesma imagem; use containers separados conectados por rede (é para isso que existe o Docker Compose, próximo capítulo)."},{"id":"docker-conceitos-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Como o isolamento funciona de verdade: namespaces e cgroups</h2>","fidelityText":"Como o isolamento funciona de verdade: namespaces e cgroups"},{"id":"docker-conceitos-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Um container não é uma VM — não existe um kernel próprio rodando dentro dele. O que faz um processo \"parecer\" isolado são dois mecanismos do kernel Linux que o Docker apenas orquestra:</p>","fidelityText":"Um container não é uma VM — não existe um kernel próprio rodando dentro dele. O que faz um processo \"parecer\" isolado são dois mecanismos do kernel Linux que o Docker apenas orquestra:"},{"id":"docker-conceitos-content-10","type":"html","authorship":"legacy-preserved","html":"<ul>\n        <li><strong>Namespaces</strong> — isolam o que o processo <em>enxerga</em>. O namespace de PID faz o processo dentro do container se achar o PID 1 (mesmo tendo outro PID visto do host); o namespace de rede dá a ele sua própria interface e tabela de rotas; o namespace de mount isola o sistema de arquivos que ele vê. É por isso que <code>docker exec -it meu-postgres bash</code> te dá um shell \"dentro\" de outro mundo — sem namespace, seria só mais um processo comum rodando junto com todos os outros do seu Linux.</li>\n        <li><strong>cgroups (control groups)</strong> — limitam <em>quanto</em> de CPU, memória e I/O o processo pode consumir. Sem cgroups, um container com vazamento de memória poderia consumir a RAM inteira da máquina host; com <code>docker run --memory 512m</code>, é o kernel quem mata o processo (OOM) antes que isso aconteça — o container não decide sozinho respeitar o limite, é forçado a respeitar.</li>\n      </ul>","fidelityText":"Namespaces — isolam o que o processo enxerga. O namespace de PID faz o processo dentro do container se achar o PID 1 (mesmo tendo outro PID visto do host); o namespace de rede dá a ele sua própria interface e tabela de rotas; o namespace de mount isola o sistema de arquivos que ele vê. É por isso que docker exec -it meu-postgres bash te dá um shell \"dentro\" de outro mundo — sem namespace, seria só mais um processo comum rodando junto com todos os outros do seu Linux. cgroups (control groups) — limitam quanto de CPU, memória e I/O o processo pode consumir. Sem cgroups, um container com vazamento de memória poderia consumir a RAM inteira da máquina host; com docker run --memory 512m, é o kernel quem mata o processo (OOM) antes que isso aconteça — o container não decide sozinho respeitar o limite, é forçado a respeitar."},{"id":"docker-conceitos-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Isso explica por que containers Linux sobem em segundos (compartilham o kernel do host, não bootam um sistema operacional próprio) e por que \"rodar Docker no Windows/macOS\" na prática significa rodar uma VM Linux leve por baixo (WSL2 no Windows, uma VM enxuta no macOS) — os containers em si continuam sendo processos Linux isolados por namespace/cgroup, só que dentro dessa VM.</p>","fidelityText":"Isso explica por que containers Linux sobem em segundos (compartilham o kernel do host, não bootam um sistema operacional próprio) e por que \"rodar Docker no Windows/macOS\" na prática significa rodar uma VM Linux leve por baixo (WSL2 no Windows, uma VM enxuta no macOS) — os containers em si continuam sendo processos Linux isolados por namespace/cgroup, só que dentro dessa VM."},{"id":"docker-conceitos-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Volumes — persistindo dados de verdade</h2>","fidelityText":"Volumes — persistindo dados de verdade"},{"id":"docker-conceitos-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"docker run -d --name my-postgres -e POSTGRES_PASSWORD=password123 \\\n  -v data_postgres:/var/lib/postgresql/data \\\n  -p 5432:5432 postgres:16\n# \"data_postgres\" is a named volume, managed by Docker --\n# even removing and recreating the container, the database data survives\n\ndocker volume ls\ndocker volume rm data_postgres  # only then is the data actually deleted","fidelityText":"docker run -d --name meu-postgres -e POSTGRES_PASSWORD=senha123 \\ -v dados_postgres:/var/lib/postgresql/data \\ -p 5432:5432 postgres:16 # \"dados_postgres\" é um volume nomeado, gerenciado pelo Docker -- # mesmo removendo e recriando o container, os dados do banco sobrevivem docker volume ls docker volume rm dados_postgres # só aí os dados são realmente apagados","highlightedHtml":"docker run -d --name my-postgres -e POSTGRES_PASSWORD=password123 \\\n  -v data_postgres:/var/lib/postgresql/data \\\n  -p 5432:5432 postgres:16\n<span class=\"com\"># \"data_postgres\" is a named volume, managed by Docker --\n# even removing and recreating the container, the database data survives</span>\n\ndocker volume ls\ndocker volume rm data_postgres  <span class=\"com\"># só aí os dados são realmente apagados</span>","caption":"Exemplo executável de docker-conceitos.","explanation":["-v liga um volume ao caminho de dados do Postgres para persistir fora do container.","Isso permite recriar o container sem perder o banco local de estudo."],"commonMistakes":["Guardar dados no container sem volume","Montar volume no caminho errado"]},{"id":"docker-conceitos-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Volume nomeado vs. bind mount</h2>","fidelityText":"Volume nomeado vs. bind mount"},{"id":"docker-conceitos-content-15","type":"html","authorship":"legacy-preserved","html":"<p>O exemplo acima usa um <strong>volume nomeado</strong> — mas existe uma segunda forma de montar armazenamento, útil por razões bem diferentes:</p>","fidelityText":"O exemplo acima usa um volume nomeado — mas existe uma segunda forma de montar armazenamento, útil por razões bem diferentes:"},{"id":"docker-conceitos-content-16","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Volume nomeado</th><th>Bind mount</th></tr>\n        <tr><td>Quem gerencia o armazenamento</td><td>O Docker (fica dentro da área interna dele no disco)</td><td>Você — é um caminho real do seu sistema de arquivos</td></tr>\n        <tr><td>Uso típico</td><td>Persistir dados de um banco, em dev ou produção</td><td>Montar seu código-fonte dentro do container para hot-reload em dev</td></tr>\n        <tr><td>Portabilidade entre máquinas</td><td>Alta — não depende de estrutura de pastas do host</td><td>Baixa — amarra o container a um caminho específico do host</td></tr>\n      </tbody></table>","fidelityText":"Volume nomeadoBind mount Quem gerencia o armazenamentoO Docker (fica dentro da área interna dele no disco)Você — é um caminho real do seu sistema de arquivos Uso típicoPersistir dados de um banco, em dev ou produçãoMontar seu código-fonte dentro do container para hot-reload em dev Portabilidade entre máquinasAlta — não depende de estrutura de pastas do hostBaixa — amarra o container a um caminho específico do host"},{"id":"docker-conceitos-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"docker run -v $(pwd)/src:/app/src my-image\n# bind mount: mudar um arquivo fora do container muda o mesmo arquivo dentro, sem rebuild da imagem","fidelityText":"docker run -v $(pwd)/src:/app/src minha-imagem # bind mount: mudar um arquivo fora do container muda o mesmo arquivo dentro, sem rebuild da imagem","highlightedHtml":"docker run -v $(pwd)/src:/app/src my-image\n<span class=\"com\"># bind mount: mudar um arquivo fora do container muda o mesmo arquivo dentro, sem rebuild da imagem</span>","caption":"Exemplo executável de docker-conceitos.","explanation":["Bind mount liga um caminho real do host ao filesystem do container, ao contrário do volume nomeado (gerenciado pelo Docker).","Serve para hot-reload de código em dev; não é a escolha certa para persistir dado de banco."],"commonMistakes":["Usar bind mount para dado de banco em vez de volume nomeado","Depender de estrutura de pasta do host, quebrando portabilidade entre máquinas"]},{"id":"docker-conceitos-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Todo tutorial de Spring Boot moderno assume que você sabe subir um Postgres com um comando <code>docker run</code> em vez de instalar o banco na máquina. Isso não é só conveniência: significa que seu ambiente de desenvolvimento é <strong>descartável e reproduzível</strong> — se algo quebrar, você apaga o container e sobe outro do zero em segundos, sem \"desinstalar e reinstalar\" nada no seu sistema operacional.</div>","fidelityText":"Todo tutorial de Spring Boot moderno assume que você sabe subir um Postgres com um comando docker run em vez de instalar o banco na máquina. Isso não é só conveniência: significa que seu ambiente de desenvolvimento é descartável e reproduzível — se algo quebrar, você apaga o container e sobe outro do zero em segundos, sem \"desinstalar e reinstalar\" nada no seu sistema operacional."},{"id":"docker-conceitos-exercise-19","type":"exercise","authorship":"legacy-preserved","title":"Exercício 30.1 — Subindo um Postgres com volume","prompt":"Suba um container Postgres 16 com senha biblioteca123, expondo a porta 5432, com um volume nomeado db_biblioteca para persistência. Confirme que está rodando com docker ps, entre no container com docker exec -it e rode psql -U postgres para confirmar acesso ao banco.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 30.1 — Subindo um Postgres com volumemédio Suba um container Postgres 16 com senha biblioteca123, expondo a porta 5432, com um volume nomeado db_biblioteca para persistência. Confirme que está rodando com docker ps, entre no container com docker exec -it e rode psql -U postgres para confirmar acesso ao banco. Ver solução docker run -d --name pg-biblioteca \\ -e POSTGRES_PASSWORD=biblioteca123 \\ -v db_biblioteca:/var/lib/postgresql/data \\ -p 5432:5432 \\ postgres:16 docker ps docker exec -it pg-biblioteca psql -U postgres","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 30.1 — Subindo um Postgres com volume</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Suba um container Postgres 16 com senha <code>biblioteca123</code>, expondo a porta 5432, com um volume nomeado <code>db_biblioteca</code> para persistência. Confirme que está rodando com <code>docker ps</code>, entre no container com <code>docker exec -it</code> e rode <code>psql -U postgres</code> para confirmar acesso ao banco.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">docker run -d --name pg-library \\\n  -e POSTGRES_PASSWORD=library123 \\\n  -v db_library:/var/lib/postgresql/data \\\n  -p 5432:5432 \\\n  postgres:16\n\ndocker ps\ndocker exec -it pg-library psql -U postgres</pre>\n        </div>\n      </div>"},{"id":"docker-quiz","type":"quiz","authorship":"authored","conceptId":"container-image-layer","prompt":"O que acontece com dados gravados apenas dentro do filesystem do container removido?","options":[{"id":"dock-a","label":"Eles desaparecem com o container, salvo se estiverem em volume ou bind mount configurado.","correct":true,"explanation":"Container é execução descartável; persistência precisa ser explícita."},{"id":"dock-b","label":"Eles são copiados automaticamente para a imagem original.","correct":false,"explanation":"Imagem não muda por escrita em container comum."},{"id":"dock-c","label":"Eles ficam sempre seguros no Docker Hub.","correct":false,"explanation":"Docker Hub armazena imagens enviadas, não estado local do container."}]}],"resources":[{"id":"docker-overview","type":"reference","title":"Docker overview","url":"https://docs.docker.com/get-started/docker-overview/","reinforces":"Define imagem, container, engine e fluxo básico do Docker.","language":"en","publisher":"Docker","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"docker-volumes","type":"reference","title":"Docker volumes","url":"https://docs.docker.com/engine/storage/volumes/","reinforces":"Persistência de dados fora do ciclo de vida do container.","language":"en","publisher":"Docker","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The docker concepts component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to docker concepts. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible docker concepts failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"# baixe o Docker Desktop em docker.com (exige WSL2 habilitado)","instruction":"The docker concepts component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible docker concepts failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"dockerfile","moduleId":"containers-integration-data","order":1,"title":"Dockerfile & multi-stage build","summary":"Um Dockerfile é a receita que descreve como construir sua própria imagem — no nosso caso, a imagem da aplicação Java que você escreveu ao longo do curso.","objectives":["Construir imagem de aplicação Java com contexto controlado","Entender cache/layers e arquivos copiados","Usar multi-stage para separar build e runtime","Evitar secrets, target e artefatos desnecessários na imagem final"],"whyItExists":"Depois de entender imagem e container, o aluno precisa empacotar a própria aplicação. Dockerfile aparece como receita reproduzível de build, não como lugar para improvisar ambiente manual.","prerequisiteChapterIds":["docker-conceitos","build"],"conceptIds":["multi-stage-build-a-solucao-real","dockerignore-parando-de-copiar-lixo-pro-build","a-jvm-ja-entende-cgroups-mas-confirme-o-tamanho-da-heap"],"introducedConceptIds":["dockerfile-build-context","multistage-runtime-image"],"usedConceptIds":["container-image-layer","build-lifecycle"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"dockerfile-intuition","type":"intuition","authorship":"authored","title":"Dockerfile transforma código em artefato executável","body":"Cada instrução monta parte da imagem. Multi-stage deixa ferramentas pesadas no estágio de build e copia só o necessário para rodar. A imagem final deve ser pequena, previsível e sem segredo.","analogyLimit":"Receita ajuda, mas cache, contexto, usuário e filesystem final precisam ser verificáveis."},{"id":"dockerfile-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-docker\">Docker</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#docker-conceitos\">30 · Docker conceitos</a>, <a class=\"prereq-tag\" href=\"#build\">19 · Maven &amp; Gradle</a></div>\n      </div>","fidelityText":"Docker Dificuldade: Avançado ⏱ ~2h30 de estudo + prática Pré-requisitos: 30 · Docker conceitos, 19 · Maven & Gradle"},{"id":"dockerfile-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Um <strong>Dockerfile</strong> é a receita que descreve como construir sua própria imagem — no nosso caso, a imagem da aplicação Java que você escreveu ao longo do curso.</p>","fidelityText":"Um Dockerfile é a receita que descreve como construir sua própria imagem — no nosso caso, a imagem da aplicação Java que você escreveu ao longo do curso."},{"id":"dockerfile-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"# Dockerfile ingênuo -- funciona, mas é PROBLEMÁTICO:\nFROM eclipse-temurin:21-jdk\nWORKDIR /app\nCOPY . .\nRUN ./mvnw package -DskipTests\nCMD [\"java\", \"-jar\", \"target/library-1.0.0.jar\"]","fidelityText":"# Dockerfile ingênuo -- funciona, mas é PROBLEMÁTICO: FROM eclipse-temurin:21-jdk WORKDIR /app COPY . . RUN ./mvnw package -DskipTests CMD [\"java\", \"-jar\", \"target/biblioteca-1.0.0.jar\"]","highlightedHtml":"<span class=\"com\"># Dockerfile ingênuo -- funciona, mas é PROBLEMÁTICO:</span>\nFROM eclipse-temurin:21-jdk\nWORKDIR /app\nCOPY . .\nRUN ./mvnw package -DskipTests\nCMD [\"java\", \"-jar\", \"target/library-1.0.0.jar\"]","caption":"Exemplo executável de dockerfile.","explanation":["A versão ingênua copia tudo e compila dentro de uma imagem que também vira runtime.","Funciona para estudo, mas tende a gerar imagem grande e com arquivos desnecessários."],"commonMistakes":["Copiar .git, target antigo ou secrets","Usar imagem de build como produção sem motivo"]},{"id":"dockerfile-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>O problema:</b> essa imagem final carrega o JDK inteiro (compilador incluso) + todo o código-fonte + o Maven + o cache de dependências de build — tudo isso é peso morto em produção, onde você só precisa do <code>.jar</code> já compilado rodando. Imagens assim facilmente passam de 500MB-1GB.</div>","fidelityText":"O problema: essa imagem final carrega o JDK inteiro (compilador incluso) + todo o código-fonte + o Maven + o cache de dependências de build — tudo isso é peso morto em produção, onde você só precisa do .jar já compilado rodando. Imagens assim facilmente passam de 500MB-1GB."},{"id":"dockerfile-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Multi-stage build — a solução real</h2>","fidelityText":"Multi-stage build — a solução real"},{"id":"dockerfile-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"# --- ESTÁGIO 1: build (usa o JDK completo, mas é DESCARTADO no final) ---\nFROM eclipse-temurin:21-jdk AS build\nWORKDIR /app\nCOPY .mvn .mvn\nCOPY mvnw pom.xml ./\nRUN ./mvnw -B -DskipTests dependency:go-offline\nCOPY src ./src\nRUN ./mvnw -B package -DskipTests\n\n# --- ESTÁGIO 2: runtime (imagem final, enxuta, só com o necessário) ---\nFROM eclipse-temurin:21-jre\nWORKDIR /app\nCOPY --from=build /app/target/library-1.0.0.jar app.jar\nEXPOSE 8080\nRUN useradd --system --uid 10001 app\nUSER 10001\nENTRYPOINT [\"java\", \"-jar\", \"app.jar\"]","fidelityText":"# --- ESTÁGIO 1: build (usa o JDK completo, mas é DESCARTADO no final) --- FROM eclipse-temurin:21-jdk AS build WORKDIR /app COPY .mvn .mvn COPY mvnw pom.xml ./ RUN ./mvnw -B -DskipTests dependency:go-offline COPY src ./src RUN ./mvnw -B package -DskipTests # --- ESTÁGIO 2: runtime (imagem final, enxuta, só com o necessário) --- FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=build /app/target/biblioteca-1.0.0.jar app.jar EXPOSE 8080 RUN useradd --system --uid 10001 app USER 10001 ENTRYPOINT [\"java\", \"-jar\", \"app.jar\"]","highlightedHtml":"<span class=\"com\"># --- ESTÁGIO 1: build (usa o JDK completo, mas é DESCARTADO no final) ---</span>\nFROM eclipse-temurin:21-jdk AS build\nWORKDIR /app\nCOPY .mvn .mvn\nCOPY mvnw pom.xml ./\nRUN ./mvnw -B -DskipTests dependency:go-offline\nCOPY src ./src\nRUN ./mvnw -B package -DskipTests\n\n<span class=\"com\"># --- ESTÁGIO 2: runtime (imagem final, enxuta, só com o necessário) ---</span>\nFROM eclipse-temurin:21-jre\nWORKDIR /app\nCOPY --from=build /app/target/library-1.0.0.jar app.jar\nEXPOSE 8080\nRUN useradd --system --uid 10001 app\nUSER 10001\nENTRYPOINT [\"java\", \"-jar\", \"app.jar\"]","caption":"Exemplo executável de dockerfile.","explanation":["O primeiro stage compila; o stage final copia apenas o jar/artefato.","Isso reduz dependências e torna o runtime mais previsível."],"commonMistakes":["Copiar diretório inteiro do build stage","Não fixar versão base compatível"]},{"id":"dockerfile-content-7","type":"html","authorship":"legacy-preserved","html":"<p>O estágio final usa apenas o runtime Java e copia <strong>somente o <code>.jar</code> pronto</strong>. A aplicação roda como usuário sem privilégios. O tamanho exato depende da imagem e das camadas; meça em vez de assumir. Imagens Alpine usam musl e podem ter diferenças de compatibilidade, portanto não devem ser escolhidas apenas pelo tamanho.</p>","fidelityText":"O estágio final usa apenas o runtime Java e copia somente o .jar pronto. A aplicação roda como usuário sem privilégios. O tamanho exato depende da imagem e das camadas; meça em vez de assumir. Imagens Alpine usam musl e podem ter diferenças de compatibilidade, portanto não devem ser escolhidas apenas pelo tamanho."},{"id":"dockerfile-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Comando</th><th>Para que serve</th></tr>\n        <tr><td><code>FROM</code></td><td>Imagem base a partir da qual construir</td></tr>\n        <tr><td><code>WORKDIR</code></td><td>Diretório de trabalho dentro do container</td></tr>\n        <tr><td><code>COPY</code></td><td>Copia arquivos do host para dentro da imagem</td></tr>\n        <tr><td><code>RUN</code></td><td>Executa um comando durante o <em>build</em> da imagem</td></tr>\n        <tr><td><code>EXPOSE</code></td><td>Documenta qual porta a aplicação usa (não abre a porta sozinho — isso é o <code>-p</code> do <code>docker run</code>)</td></tr>\n        <tr><td><code>ENTRYPOINT</code></td><td>Comando fixo executado quando o container inicia</td></tr>\n        <tr><td><code>CMD</code></td><td>Argumentos padrão — sozinho define o comando, mas junto de <code>ENTRYPOINT</code> vira só os argumentos dele</td></tr>\n      </tbody></table>","fidelityText":"ComandoPara que serve FROMImagem base a partir da qual construir WORKDIRDiretório de trabalho dentro do container COPYCopia arquivos do host para dentro da imagem RUNExecuta um comando durante o build da imagem EXPOSEDocumenta qual porta a aplicação usa (não abre a porta sozinho — isso é o -p do docker run) ENTRYPOINTComando fixo executado quando o container inicia CMDArgumentos padrão — sozinho define o comando, mas junto de ENTRYPOINT vira só os argumentos dele"},{"id":"dockerfile-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>ENTRYPOINT</code> e <code>CMD</code> não são sinônimos.</b> Se o Dockerfile só tem <code>CMD [\"java\", \"-jar\", \"app.jar\"]</code>, rodar <code>docker run minha-imagem outra-coisa</code> <strong>substitui</strong> o comando inteiro por <code>outra-coisa</code>. Se o Dockerfile tem <code>ENTRYPOINT [\"java\", \"-jar\", \"app.jar\"]</code>, o mesmo comando <strong>anexa</strong> <code>outra-coisa</code> como argumento extra do <code>java -jar</code> — o binário fixo nunca muda. Use <code>ENTRYPOINT</code> quando a imagem representa um único executável (como a nossa API); use <code>CMD</code> sozinho quando quer um comando padrão fácil de sobrescrever inteiro.</div>","fidelityText":"ENTRYPOINT e CMD não são sinônimos. Se o Dockerfile só tem CMD [\"java\", \"-jar\", \"app.jar\"], rodar docker run minha-imagem outra-coisa substitui o comando inteiro por outra-coisa. Se o Dockerfile tem ENTRYPOINT [\"java\", \"-jar\", \"app.jar\"], o mesmo comando anexa outra-coisa como argumento extra do java -jar — o binário fixo nunca muda. Use ENTRYPOINT quando a imagem representa um único executável (como a nossa API); use CMD sozinho quando quer um comando padrão fácil de sobrescrever inteiro."},{"id":"dockerfile-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"docker build -t library-api:1.0 .   # constrói a imagem a partir do Dockerfile na pasta atual\ndocker run -d -p 8080:8080 library-api:1.0\ndocker images                          # lista imagens locais, compare o tamanho antes/depois do multi-stage","fidelityText":"docker build -t biblioteca-api:1.0 . # constrói a imagem a partir do Dockerfile na pasta atual docker run -d -p 8080:8080 biblioteca-api:1.0 docker images # lista imagens locais, compare o tamanho antes/depois do multi-stage","highlightedHtml":"docker build -t library-api:1.0 .   <span class=\"com\"># constrói a imagem a partir do Dockerfile na pasta atual</span>\ndocker run -d -p 8080:8080 library-api:1.0\ndocker images                          <span class=\"com\"># lista imagens locais, compare o tamanho antes/depois do multi-stage</span>","caption":"Exemplo executável de dockerfile.","explanation":["docker build cria a imagem local com tag; docker run executa um container dessa imagem.","Tag ajuda a rastrear versão do artefato testado."],"commonMistakes":["Rodar imagem antiga sem rebuild","Usar latest como se fosse versão auditável"]},{"id":"dockerfile-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A ordem das instruções aproveita o cache: wrapper e <code>pom.xml</code> são copiados, então <code>dependency:go-offline</code> cria uma camada antes de copiar <code>src</code>. Se apenas o código mudar, a camada de dependências pode ser reaproveitada. Copiar <code>src</code> antes de baixar as dependências invalidaria essa vantagem.</div>","fidelityText":"A ordem das instruções aproveita o cache: wrapper e pom.xml são copiados, então dependency:go-offline cria uma camada antes de copiar src. Se apenas o código mudar, a camada de dependências pode ser reaproveitada. Copiar src antes de baixar as dependências invalidaria essa vantagem."},{"id":"dockerfile-content-12","type":"html","authorship":"legacy-preserved","html":"<h2><code>.dockerignore</code> — parando de copiar lixo pro build</h2>","fidelityText":".dockerignore — parando de copiar lixo pro build"},{"id":"dockerfile-content-13","type":"html","authorship":"legacy-preserved","html":"<p>O Dockerfile ingênuo do início deste capítulo tinha <code>COPY . .</code> — sem um <code>.dockerignore</code>, isso copia <strong>tudo</strong> da pasta do projeto pro contexto de build: <code>.git</code> (histórico inteiro), <code>target/</code> (build anterior), arquivos de IDE, e qualquer <code>.env</code> com segredo que você tenha deixado na raiz por descuido. Além do risco de vazamento, cada arquivo novo ou modificado nessas pastas invalida o cache de camadas sem necessidade nenhuma — o Docker recalcula o hash do que foi copiado a cada <code>COPY</code>.</p>","fidelityText":"O Dockerfile ingênuo do início deste capítulo tinha COPY . . — sem um .dockerignore, isso copia tudo da pasta do projeto pro contexto de build: .git (histórico inteiro), target/ (build anterior), arquivos de IDE, e qualquer .env com segredo que você tenha deixado na raiz por descuido. Além do risco de vazamento, cada arquivo novo ou modificado nessas pastas invalida o cache de camadas sem necessidade nenhuma — o Docker recalcula o hash do que foi copiado a cada COPY."},{"id":"dockerfile-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"# .dockerignore\n.git\ntarget/\n*.iml\n.idea/\n.env\nDockerfile\ndocker-compose.yml","fidelityText":"# .dockerignore .git target/ *.iml .idea/ .env Dockerfile docker-compose.yml","highlightedHtml":"<span class=\"com\"># .dockerignore</span>\n.git\ntarget/\n*.iml\n.idea/\n.env\nDockerfile\ndocker-compose.yml","caption":"Exemplo executável de dockerfile.","explanation":["Sem .dockerignore, COPY . . copia .git, target/ e possíveis segredos para o contexto de build.","Cada arquivo extra também invalida cache de camada sem necessidade, tornando o build mais lento."],"commonMistakes":["Deixar .env ou credenciais alcançáveis pelo contexto de build","Esquecer de ignorar target/ e reconstruir a partir de build anterior sujo"]},{"id":"dockerfile-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Funciona como um <code>.gitignore</code>: qualquer caminho listado nunca chega a sair do host, então nunca aparece na imagem final nem no cache de build.</p>","fidelityText":"Funciona como um .gitignore: qualquer caminho listado nunca chega a sair do host, então nunca aparece na imagem final nem no cache de build."},{"id":"dockerfile-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>A JVM já entende cgroups (mas confirme o tamanho da heap)</h2>","fidelityText":"A JVM já entende cgroups (mas confirme o tamanho da heap)"},{"id":"dockerfile-content-17","type":"html","authorship":"legacy-preserved","html":"<p>Desde o Java 10 (retroportado para versões LTS anteriores, e válido na baseline Java 21 do curso), a JVM lê o limite de memória do <strong>cgroup</strong> do container automaticamente — sem isso, versões antigas liam a RAM total da <em>máquina host</em> e podiam calcular uma heap maior do que o container tem permissão para usar, sendo mortas pelo OOM killer do kernel. Hoje o padrão (<code>-XX:+UseContainerSupport</code>, ativado por padrão) já respeita <code>docker run --memory</code>, mas o valor padrão de heap ainda é uma fração do limite do container (<code>-XX:MaxRAMPercentage</code>, 25% por padrão) — pensado para sobrar espaço para threads, stack e metaspace fora da heap. Se sua aplicação estiver sendo morta com <code>OOMKilled</code> mesmo parecendo \"caber\", meça com <code>-Xlog:gc</code> antes de simplesmente aumentar o limite do container.</p>","fidelityText":"Desde o Java 10 (retroportado para versões LTS anteriores, e válido na baseline Java 21 do curso), a JVM lê o limite de memória do cgroup do container automaticamente — sem isso, versões antigas liam a RAM total da máquina host e podiam calcular uma heap maior do que o container tem permissão para usar, sendo mortas pelo OOM killer do kernel. Hoje o padrão (-XX:+UseContainerSupport, ativado por padrão) já respeita docker run --memory, mas o valor padrão de heap ainda é uma fração do limite do container (-XX:MaxRAMPercentage, 25% por padrão) — pensado para sobrar espaço para threads, stack e metaspace fora da heap. Se sua aplicação estiver sendo morta com OOMKilled mesmo parecendo \"caber\", meça com -Xlog:gc antes de simplesmente aumentar o limite do container."},{"id":"dockerfile-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Exercício 31.1 — Multi-stage do zero","prompt":"Escreva um Dockerfile multi-stage para o projeto da biblioteca: estágio 1 com JDK compilando pelo Maven Wrapper, estágio 2 com JRE copiando só o .jar e executando sem privilégios. Construa, rode, inspecione usuário e tamanho e compare com uma versão single-stage.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 31.1 — Multi-stage do zerodifícil Escreva um Dockerfile multi-stage para o projeto da biblioteca: estágio 1 com JDK compilando pelo Maven Wrapper, estágio 2 com JRE copiando só o .jar e executando sem privilégios. Construa, rode, inspecione usuário e tamanho e compare com uma versão single-stage. Ver solução FROM eclipse-temurin:21-jdk AS build WORKDIR /app COPY .mvn .mvn COPY mvnw pom.xml ./ RUN ./mvnw -B -DskipTests dependency:go-offline COPY src ./src RUN ./mvnw -B package -DskipTests FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 RUN useradd --system --uid 10001 app USER 10001 ENTRYPOINT [\"java\", \"-jar\", \"app.jar\"] docker build -t biblioteca-api . docker run -d -p 8080:8080 --name biblioteca biblioteca-api docker images | grep biblioteca","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 31.1 — Multi-stage do zero</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Escreva um Dockerfile multi-stage para o projeto da biblioteca: estágio 1 com JDK compilando pelo Maven Wrapper, estágio 2 com JRE copiando só o <code>.jar</code> e executando sem privilégios. Construa, rode, inspecione usuário e tamanho e compare com uma versão single-stage.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">FROM eclipse-temurin:21-jdk AS build\nWORKDIR /app\nCOPY .mvn .mvn\nCOPY mvnw pom.xml ./\nRUN ./mvnw -B -DskipTests dependency:go-offline\nCOPY src ./src\nRUN ./mvnw -B package -DskipTests\n\nFROM eclipse-temurin:21-jre\nWORKDIR /app\nCOPY --from=build /app/target/*.jar app.jar\nEXPOSE 8080\nRUN useradd --system --uid 10001 app\nUSER 10001\nENTRYPOINT [\"java\", \"-jar\", \"app.jar\"]</pre>\n<pre class=\"code\">docker build -t library-api .\ndocker run -d -p 8080:8080 --name library library-api\ndocker images | grep library</pre>\n        </div>\n      </div>"},{"id":"dockerfile-quiz","type":"quiz","authorship":"authored","conceptId":"multistage-runtime-image","prompt":"Qual é o benefício central de multi-stage build?","options":[{"id":"df-a","label":"Separar ferramentas de build da imagem final de execução, reduzindo tamanho e superfície.","correct":true,"explanation":"O estágio final recebe apenas o artefato necessário para rodar."},{"id":"df-b","label":"Fazer o container virar banco de dados gerenciado.","correct":false,"explanation":"Multi-stage fala de build de imagem, não operação de banco."},{"id":"df-c","label":"Impedir qualquer necessidade de testes automatizados.","correct":false,"explanation":"Imagem reproduzível não prova comportamento da aplicação."}]}],"resources":[{"id":"dockerfile-reference","type":"reference","title":"Dockerfile reference","url":"https://docs.docker.com/reference/dockerfile/","reinforces":"Instruções, contexto, COPY, RUN, CMD e semântica do Dockerfile.","language":"en","publisher":"Docker","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"docker-multistage","type":"reference","title":"Multi-stage builds","url":"https://docs.docker.com/build/building/multi-stage/","reinforces":"Separação de estágios de build e runtime para imagens menores.","language":"en","publisher":"Docker","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The dockerfile component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to dockerfile. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible dockerfile failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"# Dockerfile ingênuo -- funciona, mas é PROBLEMÁTICO:","instruction":"The dockerfile component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible dockerfile failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"compose","moduleId":"containers-integration-data","order":2,"title":"Docker Compose — app + banco juntos","summary":"Um sistema real não é um container sozinho — é a API, o banco, talvez um Redis e um Kafka, todos rodando juntos e conversando entre si. Docker Compose descreve essa \"orquestra\" inteira em um único arquivo YAML.","objectives":["Subir aplicação e banco como serviços locais","Entender rede, nomes de serviço, portas e logs","Separar dependência iniciada de dependência pronta","Usar volumes e variáveis sem vazar produção"],"whyItExists":"Depois de construir imagem, Compose junta serviços para estudo e desenvolvimento local. Ele reduz atrito, mas não transforma ambiente local em produção nem garante readiness automaticamente.","prerequisiteChapterIds":["dockerfile"],"conceptIds":["healthcheck-provando-pronto-em-vez-de-assumir"],"introducedConceptIds":["compose-service-network"],"usedConceptIds":["container-image-layer","docker-volume-persistence"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"compose-intuition","type":"intuition","authorship":"authored","title":"Compose descreve uma pequena topologia local","body":"A API, o banco e outros serviços entram na mesma rede do projeto. Dentro dessa rede, a API fala com `postgres` pelo nome do serviço, não com o localhost da sua máquina.","analogyLimit":"Maquete de ambiente ajuda, mas readiness, credenciais, volumes e logs continuam sendo contratos operacionais."},{"id":"compose-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-docker\">Docker</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#dockerfile\">31 · Dockerfile</a></div>\n      </div>","fidelityText":"Docker Dificuldade: Intermediário ⏱ ~1h30 de estudo + prática Pré-requisitos: 31 · Dockerfile"},{"id":"compose-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Um sistema real não é um container sozinho — é a API, o banco, talvez um Redis e um Kafka, todos rodando juntos e conversando entre si. <strong>Docker Compose</strong> descreve essa \"orquestra\" inteira em um único arquivo YAML.</p>","fidelityText":"Um sistema real não é um container sozinho — é a API, o banco, talvez um Redis e um Kafka, todos rodando juntos e conversando entre si. Docker Compose descreve essa \"orquestra\" inteira em um único arquivo YAML."},{"id":"compose-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"# docker-compose.yml\nservices:\n  api:\n    build: .                    # usa o Dockerfile da pasta atual\n    ports:\n      - \"8080:8080\"\n    environment:\n      DB_URL: jdbc:postgresql://db:5432/biblioteca  # \"db\" -- o NOME do serviço vira hostname!\n      DB_USER: postgres\n      DB_PASSWORD: password123\n    depends_on:\n      - db\n\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_DB: library\n      POSTGRES_PASSWORD: password123\n    volumes:\n      - db_data:/var/lib/postgresql/data\n    ports:\n      - \"5432:5432\"\n\nvolumes:\n  db_data:","fidelityText":"# docker-compose.yml services: api: build: . # usa o Dockerfile da pasta atual ports: - \"8080:8080\" environment: DB_URL: jdbc:postgresql://db:5432/biblioteca # \"db\" -- o NOME do serviço vira hostname! DB_USER: postgres DB_PASSWORD: senha123 depends_on: - db db: image: postgres:16 environment: POSTGRES_DB: biblioteca POSTGRES_PASSWORD: senha123 volumes: - db_data:/var/lib/postgresql/data ports: - \"5432:5432\" volumes: db_data:","highlightedHtml":"<span class=\"com\"># docker-compose.yml</span>\nservices:\n  api:\n    build: .                    <span class=\"com\"># usa o Dockerfile da pasta atual</span>\n    ports:\n      - \"8080:8080\"\n    environment:\n      DB_URL: jdbc:postgresql://db:5432/biblioteca  <span class=\"com\"># \"db\" -- o NOME do serviço vira hostname!</span>\n      DB_USER: postgres\n      DB_PASSWORD: password123\n    depends_on:\n      - db\n\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_DB: library\n      POSTGRES_PASSWORD: password123\n    volumes:\n      - db_data:/var/lib/postgresql/data\n    ports:\n      - \"5432:5432\"\n\nvolumes:\n  db_data:","caption":"Exemplo executável de compose.","explanation":["services define API e banco como unidades executáveis relacionadas.","Variáveis e portas configuram o ambiente local; volumes preservam estado quando necessário."],"commonMistakes":["Usar localhost entre containers","Versionar senha real de produção no compose"]},{"id":"compose-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"docker compose up -d        # sobe TODOS os serviços definidos\ndocker compose logs -f api  # acompanha o log só do serviço \"api\"\ndocker compose down         # derruba tudo (mas mantém os volumes por padrão)\ndocker compose down -v      # derruba tudo E apaga os volumes (cuidado!)","fidelityText":"docker compose up -d # sobe TODOS os serviços definidos docker compose logs -f api # acompanha o log só do serviço \"api\" docker compose down # derruba tudo (mas mantém os volumes por padrão) docker compose down -v # derruba tudo E apaga os volumes (cuidado!)","highlightedHtml":"docker compose up -d        <span class=\"com\"># sobe TODOS os serviços definidos</span>\ndocker compose logs -f api  <span class=\"com\"># acompanha o log só do serviço \"api\"</span>\ndocker compose down         <span class=\"com\"># derruba tudo (mas mantém os volumes por padrão)</span>\ndocker compose down -v      <span class=\"com\"># derruba tudo E apaga os volumes (cuidado!)</span>","caption":"Exemplo executável de compose.","explanation":["docker compose up sobe os serviços; logs acompanha saída por serviço.","Logs e health/readiness ajudam a diagnosticar inicialização e dependência."],"commonMistakes":["Achar que depends_on espera aplicação pronta","Ignorar logs do serviço que falhou"]},{"id":"compose-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Dentro da rede interna que o Compose cria automaticamente, os serviços se enxergam <strong>pelo nome</strong> — é por isso que a URL do banco no exemplo usa <code>db</code> (o nome do serviço), não <code>localhost</code>. Esse é o ponto que mais confunde quem começa: de dentro do container da API, <code>localhost</code> se refere ao <em>próprio container da API</em>, não ao container do banco ao lado.</div>","fidelityText":"Dentro da rede interna que o Compose cria automaticamente, os serviços se enxergam pelo nome — é por isso que a URL do banco no exemplo usa db (o nome do serviço), não localhost. Esse é o ponto que mais confunde quem começa: de dentro do container da API, localhost se refere ao próprio container da API, não ao container do banco ao lado."},{"id":"compose-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>depends_on não espera o banco \"estar pronto\"</b> — só espera o container <em>ter iniciado</em>, o que não é o mesmo que o Postgres já aceitar conexões. Isso resolve-se de verdade com um <code>healthcheck</code> explícito, não só com retry na aplicação.</div>","fidelityText":"depends_on não espera o banco \"estar pronto\" — só espera o container ter iniciado, o que não é o mesmo que o Postgres já aceitar conexões. Isso resolve-se de verdade com um healthcheck explícito, não só com retry na aplicação."},{"id":"compose-content-7","type":"html","authorship":"legacy-preserved","html":"<h2><code>healthcheck</code> — provando \"pronto\" em vez de assumir</h2>","fidelityText":"healthcheck — provando \"pronto\" em vez de assumir"},{"id":"compose-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"services:\n  api:\n    build: .\n    ports:\n      - \"8080:8080\"\n    environment:\n      DB_URL: jdbc:postgresql://db:5432/biblioteca\n    depends_on:\n      db:\n        condition: service_healthy   # só sobe a API quando o healthcheck do db passar\n\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_DB: library\n      POSTGRES_PASSWORD: password123\n    volumes:\n      - db_data:/var/lib/postgresql/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U postgres\"]\n      interval: 5s\n      timeout: 3s\n      retries: 5\n\nvolumes:\n  db_data:","fidelityText":"services: api: build: . ports: - \"8080:8080\" environment: DB_URL: jdbc:postgresql://db:5432/biblioteca depends_on: db: condition: service_healthy # só sobe a API quando o healthcheck do db passar db: image: postgres:16 environment: POSTGRES_DB: biblioteca POSTGRES_PASSWORD: senha123 volumes: - db_data:/var/lib/postgresql/data healthcheck: test: [\"CMD-SHELL\", \"pg_isready -U postgres\"] interval: 5s timeout: 3s retries: 5 volumes: db_data:","highlightedHtml":"services:\n  api:\n    build: .\n    ports:\n      - \"8080:8080\"\n    environment:\n      DB_URL: jdbc:postgresql://db:5432/biblioteca\n    depends_on:\n      db:\n        condition: service_healthy   <span class=\"com\"># só sobe a API quando o healthcheck do db passar</span>\n\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_DB: library\n      POSTGRES_PASSWORD: password123\n    volumes:\n      - db_data:/var/lib/postgresql/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U postgres\"]\n      interval: 5s\n      timeout: 3s\n      retries: 5\n\nvolumes:\n  db_data:","caption":"Exemplo executável de compose.","explanation":["healthcheck prova prontidão de verdade rodando um teste real (pg_isready) em intervalo, não só checa se o processo iniciou.","condition: service_healthy faz depends_on esperar o healthcheck passar antes de subir o serviço dependente."],"commonMistakes":["Confiar em depends_on sozinho, sem healthcheck, para prontidão real","Escolher um teste de healthcheck que não reflete o serviço realmente aceitando conexões"]},{"id":"compose-content-9","type":"html","authorship":"legacy-preserved","html":"<p>O Compose roda <code>test</code> repetidamente a cada <code>interval</code>; o serviço só é considerado <code>healthy</code> depois de passar, e é isso que <code>condition: service_healthy</code> espera antes de subir a <code>api</code> — sem esse par (healthcheck + condition), <code>depends_on</code> sozinho só garante ordem de inicialização dos containers, nunca prontidão real do serviço lá dentro.</p>","fidelityText":"O Compose roda test repetidamente a cada interval; o serviço só é considerado healthy depois de passar, e é isso que condition: service_healthy espera antes de subir a api — sem esse par (healthcheck + condition), depends_on sozinho só garante ordem de inicialização dos containers, nunca prontidão real do serviço lá dentro."},{"id":"compose-exercise-10","type":"exercise","authorship":"legacy-preserved","title":"Exercício 32.1 — Ambiente completo com Compose","prompt":"Escreva um docker-compose.yml com dois serviços: api (build a partir do Dockerfile do capítulo 31, porta 8080) e db (Postgres 16, com volume nomeado e variáveis de ambiente para nome do banco, usuário e senha). Suba tudo com um único comando e confirme com docker compose ps que os dois serviços estão de pé.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 32.1 — Ambiente completo com Composemédio Escreva um docker-compose.yml com dois serviços: api (build a partir do Dockerfile do capítulo 31, porta 8080) e db (Postgres 16, com volume nomeado e variáveis de ambiente para nome do banco, usuário e senha). Suba tudo com um único comando e confirme com docker compose ps que os dois serviços estão de pé. Ver solução services: api: build: . ports: - \"8080:8080\" environment: DB_URL: jdbc:postgresql://db:5432/biblioteca DB_USER: postgres DB_PASSWORD: biblioteca123 depends_on: - db db: image: postgres:16 environment: POSTGRES_DB: biblioteca POSTGRES_PASSWORD: biblioteca123 volumes: - db_data:/var/lib/postgresql/data ports: - \"5432:5432\" volumes: db_data: docker compose up -d docker compose ps","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 32.1 — Ambiente completo com Compose</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva um <code>docker-compose.yml</code> com dois serviços: <code>api</code> (build a partir do Dockerfile do capítulo 31, porta 8080) e <code>db</code> (Postgres 16, com volume nomeado e variáveis de ambiente para nome do banco, usuário e senha). Suba tudo com um único comando e confirme com <code>docker compose ps</code> que os dois serviços estão de pé.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">services:\n  api:\n    build: .\n    ports:\n      - \"8080:8080\"\n    environment:\n      DB_URL: jdbc:postgresql://db:5432/biblioteca\n      DB_USER: postgres\n      DB_PASSWORD: library123\n    depends_on:\n      - db\n\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_DB: library\n      POSTGRES_PASSWORD: library123\n    volumes:\n      - db_data:/var/lib/postgresql/data\n    ports:\n      - \"5432:5432\"\n\nvolumes:\n  db_data:</pre>\n<pre class=\"code\">docker compose up -d\ndocker compose ps</pre>\n        </div>\n      </div>"},{"id":"compose-quiz","type":"quiz","authorship":"authored","conceptId":"compose-service-network","prompt":"Dentro de um container da API, por que `localhost:5432` geralmente não aponta para o Postgres do Compose?","options":[{"id":"cmp-a","label":"Porque localhost é o próprio container da API; o banco deve ser acessado pelo nome do serviço na rede Compose.","correct":true,"explanation":"Compose cria DNS interno para nomes de serviço."},{"id":"cmp-b","label":"Porque Docker proíbe qualquer conexão TCP entre containers.","correct":false,"explanation":"Containers na mesma rede podem se comunicar quando configurados."},{"id":"cmp-c","label":"Porque Postgres só funciona fora de containers.","correct":false,"explanation":"Postgres funciona em container, desde que estado e configuração sejam tratados."}]}],"resources":[{"id":"compose-overview","type":"reference","title":"Docker Compose overview","url":"https://docs.docker.com/compose/","reinforces":"Modelo de serviços, redes e volumes do Compose.","language":"en","publisher":"Docker","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"compose-file-reference","type":"reference","title":"Compose file reference","url":"https://docs.docker.com/reference/compose-file/","reinforces":"Campos de services, networks, volumes, depends_on e healthcheck.","language":"en","publisher":"Docker","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The compose component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to compose. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible compose failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"# docker-compose.yml","instruction":"The compose component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible compose failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"testcontainers","moduleId":"containers-integration-data","order":3,"title":"Testcontainers & banco de teste isolado","summary":"Os testes do capítulo 15 usavam mocks — rápidos, mas cegos para o comportamento real do banco. Uma query JOIN FETCH mal escrita (capítulo 46) pode passar perfeitamente em um teste com mock e quebrar completamente contra um Postgres real. Testcontainers fecha essa lacuna.","objectives":["Rodar banco real isolado em testes","Injetar configuração dinâmica no Spring","Evitar banco de dev/produção em teste automatizado","Preparar estado e limpeza de forma determinística"],"whyItExists":"Com Docker e Compose compreendidos, Testcontainers aparece como forma de tornar integração real e reprodutível. Ele não existe para testar Docker; existe para testar sua aplicação contra dependências reais descartáveis.","prerequisiteChapterIds":["testes","docker-conceitos","spring-jpa"],"conceptIds":["como-funciona-um-banco-real-descartavel-por-execucao","o-que-acontece-entre-new-postgresqlcontainer-e-o-primeiro-teste-rodar","por-que-nunca-testar-contra-o-banco-de-dev-ou-producao"],"introducedConceptIds":["testcontainers-disposable-dependency","dynamic-test-configuration"],"usedConceptIds":["compose-service-network","teste-aaa-first","springdata-repository-contract"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"testcontainers-intuition","type":"intuition","authorship":"authored","title":"Teste de integração precisa de dependência real e estado descartável","body":"Um container de Postgres por execução dá SQL real, constraints reais e isolamento. O teste ainda precisa controlar dados, migrations, portas e tempo de inicialização.","analogyLimit":"Laboratório descartável ajuda, mas readiness, imagem, rede e fixture determinam confiabilidade."},{"id":"testcontainers-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Testes</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#testes\">15 · Testes unitários</a>, <a class=\"prereq-tag\" href=\"#docker-conceitos\">30 · Docker conceitos</a>, <a class=\"prereq-tag\" href=\"#spring-jpa\">45 · Spring Data JPA</a></div>\n      </div>","fidelityText":"Testes Dificuldade: Avançado ⏱ ~2h de estudo + prática Pré-requisitos: 15 · Testes unitários, 30 · Docker conceitos, 45 · Spring Data JPA"},{"id":"testcontainers-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Os testes do capítulo 15 usavam mocks — rápidos, mas cegos para o comportamento real do banco. Uma query <code>JOIN FETCH</code> mal escrita (capítulo 46) pode passar perfeitamente em um teste com mock e quebrar completamente contra um Postgres real. <strong>Testcontainers</strong> fecha essa lacuna.</p>","fidelityText":"Os testes do capítulo 15 usavam mocks — rápidos, mas cegos para o comportamento real do banco. Uma query JOIN FETCH mal escrita (capítulo 46) pode passar perfeitamente em um teste com mock e quebrar completamente contra um Postgres real. Testcontainers fecha essa lacuna."},{"id":"testcontainers-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um mock de banco de dados é como treinar direção só no simulador — útil e rápido, mas não prova que você sabe estacionar de verdade. Testcontainers é pegar o carro de verdade para um teste em um estacionamento vazio: ainda controlado e seguro, mas com física real, não simulada. Ao final do teste, o \"carro\" (container) é descartado — você nunca acumula sujeira de testes anteriores.<p></p>\n      </div>","fidelityText":"Um mock de banco de dados é como treinar direção só no simulador — útil e rápido, mas não prova que você sabe estacionar de verdade. Testcontainers é pegar o carro de verdade para um teste em um estacionamento vazio: ainda controlado e seguro, mas com física real, não simulada. Ao final do teste, o \"carro\" (container) é descartado — você nunca acumula sujeira de testes anteriores."},{"id":"testcontainers-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Como funciona: um banco real, descartável, por execução</h2>","fidelityText":"Como funciona: um banco real, descartável, por execução"},{"id":"testcontainers-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"@SpringBootTest\n@Testcontainers\nclass BookRepositoryTest {\n\n    @Container\n    @ServiceConnection // Spring Boot conecta o datasource sozinho, automaticamente\n    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(\"postgres:16\");\n\n    @Autowired\n    private BookRepository bookRepository;\n\n    @Test\n    void shouldSaveAndFindBook() {\n        Book book = new Book(\"Duna\", 688);\n        bookRepository.save(book);\n\n        Optional<Book> found = bookRepository.findById(book.getId());\n        assertTrue(found.isPresent());\n    }\n}\n// esse Postgres sobe do zero antes da classe rodar, e é destruído depois --\n// as migrations do Flyway (capítulo 35) rodam automaticamente nele, exatamente\n// como rodariam em produção","fidelityText":"@SpringBootTest @Testcontainers class LivroRepositoryTest { @Container @ServiceConnection // Spring Boot conecta o datasource sozinho, automaticamente static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(\"postgres:16\"); @Autowired private LivroRepository livroRepository; @Test void deveSalvarEBuscarLivro() { Livro livro = new Livro(\"Duna\", 688); livroRepository.save(livro); Optional<Livro> encontrado = livroRepository.findById(livro.getId()); assertTrue(encontrado.isPresent()); } } // esse Postgres sobe do zero antes da classe rodar, e é destruído depois -- // as migrations do Flyway (capítulo 35) rodam automaticamente nele, exatamente // como rodariam em produção","highlightedHtml":"<span class=\"annotation\">@SpringBootTest</span>\n<span class=\"annotation\">@Testcontainers</span>\n<span class=\"kw\">class</span> <span class=\"cls\">BookRepositoryTest</span> {\n\n    <span class=\"annotation\">@Container</span>\n    <span class=\"annotation\">@ServiceConnection</span> <span class=\"com\">// Spring Boot conecta o datasource sozinho, automaticamente</span>\n    <span class=\"kw\">static</span> PostgreSQLContainer&lt;?&gt; postgres = <span class=\"kw\">new</span> PostgreSQLContainer&lt;&gt;(<span class=\"str\">\"postgres:16\"</span>);\n\n    <span class=\"annotation\">@Autowired</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">BookRepository</span> bookRepository;\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldSaveAndFindBook</span>() {\n        <span class=\"cls\">Book</span> book = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Duna\"</span>, 688);\n        bookRepository.save(book);\n\n        Optional&lt;<span class=\"cls\">Book</span>&gt; found = bookRepository.findById(book.getId());\n        assertTrue(found.isPresent());\n    }\n}\n<span class=\"com\">// esse Postgres sobe do zero antes da classe rodar, e é destruído depois --\n// as migrations do Flyway (capítulo 35) rodam automaticamente nele, exatamente\n// como rodariam em produção</span>","caption":"Exemplo executável de testcontainers.","explanation":["@Container declara o ciclo de vida do Postgres real usado no teste; @Testcontainers (extensão JUnit 5) chama start()/stop() nele.","@ServiceConnection reconhece o tipo do container e registra a conexão do datasource sozinho, incluindo a porta aleatória escolhida a cada execução."],"commonMistakes":["Assumir porta fixa","Compartilhar banco de dev em CI"]},{"id":"testcontainers-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>@ServiceConnection</code> (Spring Boot 3.1+, disponível na baseline 3.5 do curso) reconhece o tipo do container — aqui, <code>PostgreSQLContainer</code> — e registra <code>spring.datasource.url/username/password</code> sozinho, incluindo a porta aleatória que o Testcontainers escolhe a cada execução (evitando conflito com outro Postgres na porta padrão 5432, como o do capítulo 30). Antes do Spring Boot 3.1, ou para um container que o Spring não reconhece nativamente, o caminho era registrar cada propriedade manualmente com <code>@DynamicPropertySource</code>:</div>","fidelityText":"@ServiceConnection (Spring Boot 3.1+, disponível na baseline 3.5 do curso) reconhece o tipo do container — aqui, PostgreSQLContainer — e registra spring.datasource.url/username/password sozinho, incluindo a porta aleatória que o Testcontainers escolhe a cada execução (evitando conflito com outro Postgres na porta padrão 5432, como o do capítulo 30). Antes do Spring Boot 3.1, ou para um container que o Spring não reconhece nativamente, o caminho era registrar cada propriedade manualmente com @DynamicPropertySource:"},{"id":"testcontainers-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"@DynamicPropertySource\nstatic void configureProperties(DynamicPropertyRegistry registry) {\n    registry.add(\"spring.datasource.url\", postgres::getJdbcUrl);\n    registry.add(\"spring.datasource.username\", postgres::getUsername);\n    registry.add(\"spring.datasource.password\", postgres::getPassword);\n}\n// o @DynamicPropertySource ainda importa -- mecanismo do @ServiceConnection,\n// o unico caminho para um container sem suporte do Spring Boot","fidelityText":"@DynamicPropertySource static void configurarPropriedades(DynamicPropertyRegistry registry) { registry.add(\"spring.datasource.url\", postgres::getJdbcUrl); registry.add(\"spring.datasource.username\", postgres::getUsername); registry.add(\"spring.datasource.password\", postgres::getPassword); } // o @DynamicPropertySource ainda importa -- mecanismo do @ServiceConnection, // o unico caminho para um container sem suporte do Spring Boot","highlightedHtml":"<span class=\"annotation\">@DynamicPropertySource</span>\n<span class=\"kw\">static void</span> <span class=\"fn\">configureProperties</span>(DynamicPropertyRegistry registry) {\n    registry.add(<span class=\"str\">\"spring.datasource.url\"</span>, postgres::getJdbcUrl);\n    registry.add(<span class=\"str\">\"spring.datasource.username\"</span>, postgres::getUsername);\n    registry.add(<span class=\"str\">\"spring.datasource.password\"</span>, postgres::getPassword);\n}\n<span class=\"com\">// o @DynamicPropertySource ainda importa -- mecanismo do @ServiceConnection,\n// o unico caminho para um container sem suporte do Spring Boot</span>","caption":"Exemplo executável de testcontainers.","explanation":["@DynamicPropertySource é o mecanismo manual por trás do @ServiceConnection: registra cada propriedade de conexão explicitamente.","Ainda necessário para um container que o Spring Boot não reconhece nativamente (sem suporte a @ServiceConnection)."],"commonMistakes":["Usar @DynamicPropertySource por hábito quando @ServiceConnection já resolveria com menos código","Esquecer alguma propriedade (url/username/password) e obter erro de conexão só em runtime"]},{"id":"testcontainers-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>O que acontece entre <code>new PostgreSQLContainer(...)</code> e o primeiro teste rodar</h2>","fidelityText":"O que acontece entre new PostgreSQLContainer(...) e o primeiro teste rodar"},{"id":"testcontainers-content-9","type":"html","authorship":"legacy-preserved","html":"<p><code>@Testcontainers</code> é uma extensão JUnit 5 — é ela quem chama <code>start()</code> em todo campo anotado com <code>@Container</code> antes da classe rodar, e <code>stop()</code> depois. Sem essa extensão, as duas anotações não fariam nada sozinhas. Dois detalhes de mecanismo importam na prática:</p>","fidelityText":"@Testcontainers é uma extensão JUnit 5 — é ela quem chama start() em todo campo anotado com @Container antes da classe rodar, e stop() depois. Sem essa extensão, as duas anotações não fariam nada sozinhas. Dois detalhes de mecanismo importam na prática:"},{"id":"testcontainers-content-10","type":"html","authorship":"legacy-preserved","html":"<ul>\n        <li><strong>Readiness, não só \"processo iniciado\"</strong> — <code>start()</code> bloqueia até uma <em>wait strategy</em> ser satisfeita, não só até o processo do container existir. Para <code>PostgreSQLContainer</code>, o padrão espera a porta do Postgres aceitar conexões TCP; para uma imagem sem módulo dedicado, você mesmo declara isso com <code>.waitingFor(Wait.forListeningPort())</code> ou <code>Wait.forLogMessage(...)</code>. É por isso que o teste nunca corre risco de rodar contra um banco que ainda está inicializando.</li>\n        <li><strong>Ryuk — o motivo de containers de teste não ficarem \"esquecidos\"</strong> — o Testcontainers sobe automaticamente um container auxiliar (Ryuk) que monitora a JVM de teste e remove todo container/volume/rede criado por ela assim que o processo termina, mesmo em caso de crash ou <code>Ctrl+C</code>. Sem isso, cada execução de suíte deixaria containers Postgres órfãos acumulando na sua máquina.</li>\n      </ul>","fidelityText":"Readiness, não só \"processo iniciado\" — start() bloqueia até uma wait strategy ser satisfeita, não só até o processo do container existir. Para PostgreSQLContainer, o padrão espera a porta do Postgres aceitar conexões TCP; para uma imagem sem módulo dedicado, você mesmo declara isso com .waitingFor(Wait.forListeningPort()) ou Wait.forLogMessage(...). É por isso que o teste nunca corre risco de rodar contra um banco que ainda está inicializando. Ryuk — o motivo de containers de teste não ficarem \"esquecidos\" — o Testcontainers sobe automaticamente um container auxiliar (Ryuk) que monitora a JVM de teste e remove todo container/volume/rede criado por ela assim que o processo termina, mesmo em caso de crash ou Ctrl+C. Sem isso, cada execução de suíte deixaria containers Postgres órfãos acumulando na sua máquina."},{"id":"testcontainers-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Para uma suíte grande, subir um Postgres novo <em>por classe</em> de teste soma minutos reais de espera de inicialização. O padrão de <strong>container singleton reutilizado</strong> resolve isso: uma classe base abstrata mantém o container em um campo <code>static</code>, todas as classes de teste estendem essa base, e o mesmo container atende a suíte inteira (com <code>.withReuse(true)</code> e <code>testcontainers.reuse.enable=true</code> em <code>~/.testcontainers.properties</code>, o container sobrevive até entre execuções locais). Fica mais rápido, mas exige que cada teste limpe seus próprios dados no fim — o isolamento deixa de ser \"container novo\", passa a ser responsabilidade do teste.</div>","fidelityText":"Para uma suíte grande, subir um Postgres novo por classe de teste soma minutos reais de espera de inicialização. O padrão de container singleton reutilizado resolve isso: uma classe base abstrata mantém o container em um campo static, todas as classes de teste estendem essa base, e o mesmo container atende a suíte inteira (com .withReuse(true) e testcontainers.reuse.enable=true em ~/.testcontainers.properties, o container sobrevive até entre execuções locais). Fica mais rápido, mas exige que cada teste limpe seus próprios dados no fim — o isolamento deixa de ser \"container novo\", passa a ser responsabilidade do teste."},{"id":"testcontainers-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Por que nunca testar contra o banco de dev ou produção</h2>","fidelityText":"Por que nunca testar contra o banco de dev ou produção"},{"id":"testcontainers-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Nunca aponte testes automatizados para um banco compartilhado.</b> Se dois desenvolvedores rodam a suíte de testes simultaneamente contra o mesmo banco de dev, um teste pode ler dados que outro acabou de inserir/apagar, gerando falhas aleatórias e difíceis de reproduzir — o clássico \"passou na minha máquina, falhou no CI\". Testcontainers elimina essa classe inteira de problema ao garantir isolamento total por execução.</div>","fidelityText":"Nunca aponte testes automatizados para um banco compartilhado. Se dois desenvolvedores rodam a suíte de testes simultaneamente contra o mesmo banco de dev, um teste pode ler dados que outro acabou de inserir/apagar, gerando falhas aleatórias e difíceis de reproduzir — o clássico \"passou na minha máquina, falhou no CI\". Testcontainers elimina essa classe inteira de problema ao garantir isolamento total por execução."},{"id":"testcontainers-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Comece rodando Testcontainers localmente antes de configurar no CI/CD (capítulo mais adiante) — a única exigência é ter Docker instalado e rodando na máquina que executa os testes. Se os testes passarem localmente com Testcontainers, eles quase sempre vão passar no pipeline de CI também, desde que o ambiente de CI também tenha Docker disponível (a maioria das plataformas modernas já vem com isso pronto).</div>","fidelityText":"Comece rodando Testcontainers localmente antes de configurar no CI/CD (capítulo mais adiante) — a única exigência é ter Docker instalado e rodando na máquina que executa os testes. Se os testes passarem localmente com Testcontainers, eles quase sempre vão passar no pipeline de CI também, desde que o ambiente de CI também tenha Docker disponível (a maioria das plataformas modernas já vem com isso pronto)."},{"id":"testcontainers-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 54.1 — Teste de integração real","prompt":"Escreva um teste com Testcontainers para o LivroRepository (capítulo 45), confirmando que findByTituloContainingIgnoreCase encontra um livro mesmo com capitalização diferente do título buscado (ex: buscar \"duna\" encontra \"Duna\"). Isso é algo que um mock jamais provaria, porque depende do comportamento real do LIKE ILIKE do Postgres.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 54.1 — Teste de integração realdifícil Escreva um teste com Testcontainers para o LivroRepository (capítulo 45), confirmando que findByTituloContainingIgnoreCase encontra um livro mesmo com capitalização diferente do título buscado (ex: buscar \"duna\" encontra \"Duna\"). Isso é algo que um mock jamais provaria, porque depende do comportamento real do LIKE ILIKE do Postgres. Ver solução @Test void deveEncontrarPorTituloIgnorandoCapitalizacao() { livroRepository.save(new Livro(\"Duna\", 688)); Page<Livro> resultado = livroRepository .findByTituloContainingIgnoreCase(\"duna\", PageRequest.of(0, 10)); assertEquals(1, resultado.getTotalElements()); } // esse teste só faz sentido de verdade contra um Postgres REAL -- // um mock simplesmente retornaria o que você programasse, sem validar // se a query SQL gerada realmente ignora capitalização","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 54.1 — Teste de integração real</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Escreva um teste com Testcontainers para o <code>LivroRepository</code> (capítulo 45), confirmando que <code>findByTituloContainingIgnoreCase</code> encontra um livro mesmo com capitalização diferente do título buscado (ex: buscar \"duna\" encontra \"Duna\"). Isso é algo que um mock jamais provaria, porque depende do comportamento real do <code>LIKE ILIKE</code> do Postgres.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldFindByTitleIgnorandoCapitalizacao</span>() {\n    bookRepository.save(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Duna\"</span>, 688));\n\n    Page&lt;<span class=\"cls\">Book</span>&gt; result = bookRepository\n        .findByTitleContainingIgnoreCase(<span class=\"str\">\"duna\"</span>, PageRequest.of(0, 10));\n\n    assertEquals(1, result.getTotalElements());\n}\n<span class=\"com\">// esse teste só faz sentido de verdade contra um Postgres REAL --\n// um mock simplesmente retornaria o que você programasse, sem validar\n// se a query SQL gerada realmente ignora capitalização</span></pre>\n        </div>\n      </div>"},{"id":"testcontainers-quiz","type":"quiz","authorship":"authored","conceptId":"testcontainers-disposable-dependency","prompt":"Por que Testcontainers é melhor que testar contra o banco de desenvolvimento compartilhado?","options":[{"id":"tc-a","label":"Porque cria dependência real em estado controlado e descartável para cada execução ou suíte.","correct":true,"explanation":"Isso reduz interferência, flakiness e risco de dados reais."},{"id":"tc-b","label":"Porque transforma teste de integração em teste unitário puro.","correct":false,"explanation":"Continua sendo integração; só fica mais reprodutível."},{"id":"tc-c","label":"Porque dispensa migrations e fixtures.","correct":false,"explanation":"O estado do banco precisa ser criado de forma explícita."}]}],"resources":[{"id":"testcontainers-junit5","type":"reference","title":"Testcontainers JUnit 5 quickstart","url":"https://java.testcontainers.org/quickstart/junit_5_quickstart/","reinforces":"Integração com JUnit 5, lifecycle e containers em testes.","language":"en","publisher":"Testcontainers","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"testcontainers-postgres","type":"reference","title":"Testcontainers PostgreSQL module","url":"https://java.testcontainers.org/modules/databases/postgres/","reinforces":"Uso de Postgres real descartável em testes Java.","language":"en","publisher":"Testcontainers","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The testcontainers component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to testcontainers. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible testcontainers failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"@SpringBootTest","instruction":"The testcontainers component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible testcontainers failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"estrategia-testes","moduleId":"containers-integration-data","order":4,"title":"Estratégia de testes: slices, contratos, concorrência e qualidade","summary":"Uma suíte útil oferece feedback rápido e confiança proporcional ao risco. Testes unitários isolam regras; slices verificam uma camada com infraestrutura reduzida; integração verifica fronteiras reais; end-to-end cobre poucos fluxos críticos. Nenhuma única categoria substitui as outras.","objectives":["Escolher unit, slice, integração e contrato pelo risco","Usar mocks, MockMvc e Testcontainers no lugar certo","Evitar teste lento/flaky por falta de isolamento","Cobrir concorrência, assíncrono e regressão de contrato quando fizer sentido"],"whyItExists":"Depois de Mockito, Spring MVC, REST Docs e Testcontainers, o aluno pode montar uma estratégia de testes por camadas. O objetivo não é ter muitos testes, mas testes que falham pelo motivo certo.","prerequisiteChapterIds":["testcontainers","mockito","api-contract-evolution"],"conceptIds":["da-unidade-a-jornada-completa","assincrono-contratos-e-mutacao"],"introducedConceptIds":["test-pyramid-slice-contract","deterministic-integration-test"],"usedConceptIds":["mockito-behavior-verification","contract-test-evolution","testcontainers-disposable-dependency"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"estrategia-testes-intuition","type":"intuition","authorship":"authored","title":"Estratégia de teste é mapa de risco, não contagem de arquivos","body":"Unit testa regra isolada. Slice testa uma camada com infraestrutura parcial. Integração testa dependências reais. Contrato protege representação publicada. Escolha pelo que pode quebrar e pelo custo de feedback.","analogyLimit":"Pirâmide ajuda a lembrar proporção, mas cada sistema exige riscos, velocidade e evidências próprias."},{"id":"estrategia-testes-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-spring\">Spring</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~5h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#testcontainers\">Testcontainers</a>, <a class=\"prereq-tag\" href=\"#mockito\">Mockito</a></div></div>","fidelityText":"SpringDificuldade: Avançado⏱ ~5h de estudo e práticaPré-requisitos: Testcontainers, Mockito"},{"id":"estrategia-testes-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Uma suíte útil oferece feedback rápido e confiança proporcional ao risco. Testes unitários isolam regras; slices verificam uma camada com infraestrutura reduzida; integração verifica fronteiras reais; end-to-end cobre poucos fluxos críticos. Nenhuma única categoria substitui as outras.</p>","fidelityText":"Uma suíte útil oferece feedback rápido e confiança proporcional ao risco. Testes unitários isolam regras; slices verificam uma camada com infraestrutura reduzida; integração verifica fronteiras reais; end-to-end cobre poucos fluxos críticos. Nenhuma única categoria substitui as outras."},{"id":"estrategia-testes-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Da unidade à jornada completa</h2></div>\n    <p>O tamanho de um teste é definido pelo que ele integra, não pelo número de linhas do método.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Unitário</dt><dd>Verifica uma unidade de comportamento com dependências simples ou substituídas; deve localizar falhas rapidamente.</dd></div><div class=\"concept-card\"><dt>Slice</dt><dd>Carrega uma fatia do Spring, como MVC ou JPA, evitando o contexto completo.</dd></div><div class=\"concept-card\"><dt>Integração</dt><dd>Verifica a colaboração com fronteiras reais relevantes, como PostgreSQL ou Kafka.</dd></div><div class=\"concept-card\"><dt>Contrato</dt><dd>Verifica que consumidor e provedor concordam sobre mensagens, campos e semântica.</dd></div><div class=\"concept-card\"><dt>End-to-end (E2E)</dt><dd>Exercita uma jornada por várias camadas do sistema; é valioso, porém mais lento e difícil de diagnosticar.</dd></div><div class=\"concept-card\"><dt>Mutation testing</dt><dd>Altera deliberadamente o código e verifica se os testes detectam a mudança; mutante sobrevivente revela uma asserção fraca ou ausência de cenário.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoDa unidade à jornada completa O tamanho de um teste é definido pelo que ele integra, não pelo número de linhas do método. UnitárioVerifica uma unidade de comportamento com dependências simples ou substituídas; deve localizar falhas rapidamente.SliceCarrega uma fatia do Spring, como MVC ou JPA, evitando o contexto completo.IntegraçãoVerifica a colaboração com fronteiras reais relevantes, como PostgreSQL ou Kafka.ContratoVerifica que consumidor e provedor concordam sobre mensagens, campos e semântica.End-to-end (E2E)Exercita uma jornada por várias camadas do sistema; é valioso, porém mais lento e difícil de diagnosticar.Mutation testingAltera deliberadamente o código e verifica se os testes detectam a mudança; mutante sobrevivente revela uma asserção fraca ou ausência de cenário."},{"id":"estrategia-testes-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Ferramenta</th><th>Escopo</th></tr><tr><td>JUnit + AssertJ</td><td>regra pura, exemplos e testes parametrizados</td></tr><tr><td><code>@WebMvcTest</code></td><td>contrato MVC, JSON, validation e security web</td></tr><tr><td><code>@DataJpaTest</code></td><td>mapeamento, queries, constraints e locks</td></tr><tr><td><code>@SpringBootTest</code></td><td>integração ampla; use quando precisa do contexto completo</td></tr><tr><td>Testcontainers</td><td>serviço real descartável, não isolamento automático dos dados</td></tr></tbody></table>","fidelityText":"FerramentaEscopoJUnit + AssertJregra pura, exemplos e testes parametrizados@WebMvcTestcontrato MVC, JSON, validation e security web@DataJpaTestmapeamento, queries, constraints e locks@SpringBootTestintegração ampla; use quando precisa do contexto completoTestcontainersserviço real descartável, não isolamento automático dos dados"},{"id":"estrategia-testes-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"@WebMvcTest(OrderController.class)\nclass OrderControllerTest {\n    @Autowired MockMvc mvc;\n    @MockitoBean CreateOrder useCase;\n\n    @Test void rejectsQuantityInvalid() throws Exception {\n        mvc.perform(post(\"/orders\").contentType(APPLICATION_JSON)\n            .content(\"{\"productId\":1,\"quantity\":0}\"))\n            .andExpect(status().isBadRequest())\n            .andExpect(jsonPath(\"$.status\").value(400));\n    }\n}","fidelityText":"@WebMvcTest(PedidoController.class) class PedidoControllerTest { @Autowired MockMvc mvc; @MockitoBean CriarPedido casoDeUso; @Test void rejeitaQuantidadeInvalida() throws Exception { mvc.perform(post(\"/pedidos\").contentType(APPLICATION_JSON) .content(\"{\"produtoId\":1,\"quantidade\":0}\")) .andExpect(status().isBadRequest()) .andExpect(jsonPath(\"$.status\").value(400)); } }","highlightedHtml":"<span class=\"annotation\">@WebMvcTest</span>(OrderController.class)\n<span class=\"kw\">class</span> OrderControllerTest {\n    <span class=\"annotation\">@Autowired</span> MockMvc mvc;\n    <span class=\"annotation\">@MockitoBean</span> CreateOrder useCase;\n\n    <span class=\"annotation\">@Test</span> <span class=\"kw\">void</span> rejectsQuantityInvalid() <span class=\"kw\">throws</span> Exception {\n        mvc.perform(post(<span class=\"str\">\"/orders\"</span>).contentType(APPLICATION_JSON)\n            .content(<span class=\"str\">\"{\"productId\":1,\"quantity\":0}\"</span>))\n            .andExpect(status().isBadRequest())\n            .andExpect(jsonPath(<span class=\"str\">\"$.status\"</span>).value(400));\n    }\n}","caption":"Exemplo executável de estrategia-testes.","explanation":["@WebMvcTest testa controller e infraestrutura MVC sem subir toda aplicação.","@MockBean substitui dependência para focar binding, status, validação e resposta HTTP."],"commonMistakes":["Achar que WebMvcTest testa banco","Mockar tanto que o contrato HTTP real desaparece"]},{"id":"estrategia-testes-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Em versões atuais do Spring Boot, prefira <code>@MockitoBean</code> ao antigo <code>@MockBean</code>. Para Testcontainers integrado ao Boot, <code>@ServiceConnection</code> reduz configuração manual. Um container estático pode ser compartilhado pela classe; limpe dados, use rollback ou bancos/schemas exclusivos quando os testes exigirem isolamento.</p>","fidelityText":"Em versões atuais do Spring Boot, prefira @MockitoBean ao antigo @MockBean. Para Testcontainers integrado ao Boot, @ServiceConnection reduz configuração manual. Um container estático pode ser compartilhado pela classe; limpe dados, use rollback ou bancos/schemas exclusivos quando os testes exigirem isolamento."},{"id":"estrategia-testes-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Assíncrono, contratos e mutação</h2>","fidelityText":"Assíncrono, contratos e mutação"},{"id":"estrategia-testes-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Não use <code>Thread.sleep</code> como sincronização. Espere uma condição observável com timeout. Testes de contrato verificam compatibilidade entre consumidor e provedor; testes de migration partem da versão anterior do schema; mutation testing revela testes que executam código sem realmente distinguir comportamento.</p>","fidelityText":"Não use Thread.sleep como sincronização. Espere uma condição observável com timeout. Testes de contrato verificam compatibilidade entre consumidor e provedor; testes de migration partem da versão anterior do schema; mutation testing revela testes que executam código sem realmente distinguir comportamento."},{"id":"estrategia-testes-exercise-9","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — pirâmide de confiança","prompt":"Para o fluxo criar pedido, escreva testes de domínio, MVC, repository com PostgreSQL real, migration e evento Kafka. Introduza propositalmente uma duplicata e comprove a idempotência.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — pirâmide de confiançadifícilPara o fluxo criar pedido, escreva testes de domínio, MVC, repository com PostgreSQL real, migration e evento Kafka. Introduza propositalmente uma duplicata e comprove a idempotência.Ver critériosA suíte deve ser repetível em qualquer ordem, não depender de portas fixas, ter timeouts explícitos e produzir diagnóstico útil quando falhar.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — pirâmide de confiança</h2><span class=\"exercise-tag d\">difícil</span></div><p>Para o fluxo criar pedido, escreva testes de domínio, MVC, repository com PostgreSQL real, migration e evento Kafka. Introduza propositalmente uma duplicata e comprove a idempotência.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>A suíte deve ser repetível em qualquer ordem, não depender de portas fixas, ter timeouts explícitos e produzir diagnóstico útil quando falhar.</p></div></div>"},{"id":"estrategia-testes-quiz","type":"quiz","authorship":"authored","conceptId":"test-pyramid-slice-contract","prompt":"Qual combinação é mais coerente para uma API Spring com banco?","options":[{"id":"strat-a","label":"Unit para regra pura, WebMvcTest para contrato HTTP, Testcontainers para persistência real e contrato para JSON publicado.","correct":true,"explanation":"Cada camada responde uma pergunta diferente com custo proporcional."},{"id":"strat-b","label":"Apenas testes end-to-end subindo tudo para qualquer regra pequena.","correct":false,"explanation":"Feedback fica lento e diagnóstico piora."},{"id":"strat-c","label":"Apenas mocks de repository para provar SQL e constraints.","correct":false,"explanation":"Mocks não executam SQL real nem constraints do banco."}]}],"resources":[{"id":"fowler-test-pyramid","type":"guide","title":"Practical Test Pyramid","url":"https://martinfowler.com/articles/practical-test-pyramid.html","reinforces":"Camadas de teste, trade-offs e feedback por risco.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-boot-test-slices","type":"reference","title":"Spring Boot: Test Auto-configuration Annotations","url":"https://docs.spring.io/spring-boot/appendix/test-auto-configuration/slices.html","reinforces":"Slices como WebMvcTest, DataJpaTest e escopo de infraestrutura em testes Spring.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The strategy tests component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to strategy tests. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible strategy tests failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"@WebMvcTest(OrderController.class)","instruction":"The strategy tests component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible strategy tests failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"nosql","moduleId":"containers-integration-data","order":5,"title":"NoSQL — conceitos & quando usar","summary":"\"NoSQL\" não é um produto — é um guarda-chuva para bancos não relacionais com modelos e garantias muito diferentes. Alguns oferecem validação de schema, transações, replicação e consistência configurável; outros priorizam disponibilidade, latência ou escala. Avalie o produto e a configuração concretos, não uma suposta regra universal de NoSQL.","objectives":["Entender NoSQL por modelo de acesso, não por moda","Comparar documento, chave-valor, coluna e grafo","Relacionar consistência, consulta e evolução do dado","Decidir quando SQL ainda é a resposta melhor"],"whyItExists":"Depois de SQL/JDBC e antes de MongoDB/Redis, o aluno precisa de mapa mental. NoSQL entra como família de trade-offs, não como substituto universal de banco relacional.","prerequisiteChapterIds":["sql","json"],"conceptIds":["as-quatro-familias-principais","sql-vs-nosql-a-pergunta-certa-nao-e-qual-e-melhor"],"introducedConceptIds":["nosql-access-pattern-modeling"],"usedConceptIds":["json-formato-contrato","modelo-relacional-tabela-chave"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"nosql-intuition","type":"intuition","authorship":"authored","title":"NoSQL começa pela pergunta de leitura e consistência","body":"Não escolha NoSQL porque parece moderno. Escolha quando o padrão de acesso, volume, estrutura, latência ou flexibilidade justificam outro modelo de armazenamento.","analogyLimit":"Caixas diferentes ajudam a imaginar famílias, mas cada banco tem consulta, índice, consistência e operação próprios."},{"id":"nosql-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-nosql\">NoSQL</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#sql\">33 · SQL fundamentos</a>, <a class=\"prereq-tag\" href=\"#json\">25 · JSON &amp; serialização</a></div>\n      </div>","fidelityText":"NoSQL Dificuldade: Intermediário ⏱ ~1h30 de estudo Pré-requisitos: 33 · SQL fundamentos, 25 · JSON & serialização"},{"id":"nosql-content-2","type":"html","authorship":"legacy-preserved","html":"<p>\"NoSQL\" não é um produto — é um guarda-chuva para bancos não relacionais com modelos e garantias muito diferentes. Alguns oferecem validação de schema, transações, replicação e consistência configurável; outros priorizam disponibilidade, latência ou escala. Avalie o produto e a configuração concretos, não uma suposta regra universal de NoSQL.</p>","fidelityText":"\"NoSQL\" não é um produto — é um guarda-chuva para bancos não relacionais com modelos e garantias muito diferentes. Alguns oferecem validação de schema, transações, replicação e consistência configurável; outros priorizam disponibilidade, latência ou escala. Avalie o produto e a configuração concretos, não uma suposta regra universal de NoSQL."},{"id":"nosql-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Se um banco relacional é como uma planilha rigidamente formatada — toda linha <strong>precisa</strong> ter as mesmas colunas, nos mesmos tipos — um banco de documentos (o tipo de NoSQL mais comum) é como uma pasta cheia de fichas de papel: cada ficha pode ter campos diferentes, algumas com mais anotações que outras, sem forçar um molde único para todo mundo.<p></p>\n      </div>","fidelityText":"Se um banco relacional é como uma planilha rigidamente formatada — toda linha precisa ter as mesmas colunas, nos mesmos tipos — um banco de documentos (o tipo de NoSQL mais comum) é como uma pasta cheia de fichas de papel: cada ficha pode ter campos diferentes, algumas com mais anotações que outras, sem forçar um molde único para todo mundo."},{"id":"nosql-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>As quatro famílias principais</h2>","fidelityText":"As quatro famílias principais"},{"id":"nosql-content-5","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Família</th><th>Unidade de dado</th><th>Exemplo</th><th>Bom para</th></tr>\n        <tr><td><strong>Documento</strong></td><td>Documento JSON-like, esquema flexível</td><td>MongoDB</td><td>Dados semi-estruturados, protótipos rápidos, catálogos de produto</td></tr>\n        <tr><td><strong>Chave-valor</strong></td><td>Par chave → valor simples</td><td>Redis</td><td>Cache, sessão, contadores, filas simples</td></tr>\n        <tr><td><strong>Coluna larga</strong></td><td>Linhas com colunas dinâmicas por família</td><td>Cassandra</td><td>Escrita massiva, séries temporais em grande escala</td></tr>\n        <tr><td><strong>Grafo</strong></td><td>Nós e relacionamentos</td><td>Neo4j</td><td>Redes sociais, recomendações, dados fortemente interconectados</td></tr>\n      </tbody></table>","fidelityText":"FamíliaUnidade de dadoExemploBom para DocumentoDocumento JSON-like, esquema flexívelMongoDBDados semi-estruturados, protótipos rápidos, catálogos de produto Chave-valorPar chave → valor simplesRedisCache, sessão, contadores, filas simples Coluna largaLinhas com colunas dinâmicas por famíliaCassandraEscrita massiva, séries temporais em grande escala GrafoNós e relacionamentosNeo4jRedes sociais, recomendações, dados fortemente interconectados"},{"id":"nosql-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>SQL vs NoSQL: a pergunta certa não é \"qual é melhor\"</h2>","fidelityText":"SQL vs NoSQL: a pergunta certa não é \"qual é melhor\""},{"id":"nosql-content-7","type":"html","authorship":"legacy-preserved","html":"<p>É \"qual encaixa melhor no formato dos meus dados e no meu padrão de acesso\". Um sistema real frequentemente usa <strong>os dois</strong> — Postgres para dados transacionais estruturados (pedidos, pagamentos, usuários) e MongoDB/Redis para partes específicas onde flexibilidade ou velocidade importam mais que rigidez relacional.</p>","fidelityText":"É \"qual encaixa melhor no formato dos meus dados e no meu padrão de acesso\". Um sistema real frequentemente usa os dois — Postgres para dados transacionais estruturados (pedidos, pagamentos, usuários) e MongoDB/Redis para partes específicas onde flexibilidade ou velocidade importam mais que rigidez relacional."},{"id":"nosql-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Relacional (SQL)</th><th>Documento (NoSQL)</th></tr>\n        <tr><td>Esquema</td><td>Fixo, definido antes (migrations)</td><td>Flexível, cada documento pode variar</td></tr>\n        <tr><td>Relacionamentos</td><td>JOINs nativos e eficientes</td><td>Geralmente aninha dados ou referencia manualmente</td></tr>\n        <tr><td>Transações complexas</td><td>ACID e relacionamentos são centrais</td><td>Varia por produto; MongoDB oferece transações, mas o modelo deve evitar depender delas indiscriminadamente</td></tr>\n        <tr><td>Escala horizontal</td><td>Depende do produto, particionamento e operação</td><td>Muitos foram desenhados para distribuição, ainda com custo operacional</td></tr>\n        <tr><td>Exemplo de bom uso</td><td>Sistema financeiro, estoque, pedidos</td><td>Catálogo de produtos com atributos variáveis, logs, cache de sessão</td></tr>\n      </tbody></table>","fidelityText":"Relacional (SQL)Documento (NoSQL) EsquemaFixo, definido antes (migrations)Flexível, cada documento pode variar RelacionamentosJOINs nativos e eficientesGeralmente aninha dados ou referencia manualmente Transações complexasACID e relacionamentos são centraisVaria por produto; MongoDB oferece transações, mas o modelo deve evitar depender delas indiscriminadamente Escala horizontalDepende do produto, particionamento e operaçãoMuitos foram desenhados para distribuição, ainda com custo operacional Exemplo de bom usoSistema financeiro, estoque, pedidosCatálogo de produtos com atributos variáveis, logs, cache de sessão"},{"id":"nosql-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A \"promessa\" de NoSQL de escalar infinitamente sem esforço é um mito que já causou muita dor de cabeça em produção. Na prática, times maduros escolhem NoSQL para o <strong>problema específico</strong> que ele resolve bem (documentos flexíveis, cache ultrarrápido), e continuam usando Postgres para tudo que precisa de consistência forte e relacionamentos — não por modismo, mas porque cada ferramenta tem seu propósito.</div>","fidelityText":"A \"promessa\" de NoSQL de escalar infinitamente sem esforço é um mito que já causou muita dor de cabeça em produção. Na prática, times maduros escolhem NoSQL para o problema específico que ele resolve bem (documentos flexíveis, cache ultrarrápido), e continuam usando Postgres para tudo que precisa de consistência forte e relacionamentos — não por modismo, mas porque cada ferramenta tem seu propósito."},{"id":"nosql-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não tente decorar \"quando usar cada banco\" como uma tabela de regras fixas. Em vez disso, pergunte sempre: <em>\"meus dados têm uma estrutura previsível e relacionamentos importantes?\"</em> — se sim, SQL. <em>\"Preciso de leitura absurdamente rápida de algo simples, tipo sessão de usuário?\"</em> — Redis. <em>\"Meus documentos variam muito de formato entre si?\"</em> — MongoDB. A decisão nasce do formato real do problema, não de uma lista memorizada.</div>","fidelityText":"Não tente decorar \"quando usar cada banco\" como uma tabela de regras fixas. Em vez disso, pergunte sempre: \"meus dados têm uma estrutura previsível e relacionamentos importantes?\" — se sim, SQL. \"Preciso de leitura absurdamente rápida de algo simples, tipo sessão de usuário?\" — Redis. \"Meus documentos variam muito de formato entre si?\" — MongoDB. A decisão nasce do formato real do problema, não de uma lista memorizada."},{"id":"nosql-exercise-11","type":"exercise","authorship":"legacy-preserved","title":"Exercício 36.1 — Escolhendo a ferramenta certa","prompt":"Para cada cenário, decida se SQL (Postgres) ou um banco de documentos (MongoDB) encaixa melhor, justificando em uma frase: (a) sistema de pedidos de um e-commerce com estoque e pagamento; (b) catálogo de produtos onde cada categoria tem atributos completamente diferentes (roupas têm tamanho/cor, eletrônicos têm voltagem/garantia); (c) log de eventos de uma aplicação, gravado em alto volume e raramente consultado por relacionamento.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 36.1 — Escolhendo a ferramenta certafácil Para cada cenário, decida se SQL (Postgres) ou um banco de documentos (MongoDB) encaixa melhor, justificando em uma frase: (a) sistema de pedidos de um e-commerce com estoque e pagamento; (b) catálogo de produtos onde cada categoria tem atributos completamente diferentes (roupas têm tamanho/cor, eletrônicos têm voltagem/garantia); (c) log de eventos de uma aplicação, gravado em alto volume e raramente consultado por relacionamento. Ver solução (a) SQL/Postgres — pedidos, estoque e pagamento têm relacionamentos fortes e exigem transações consistentes (débito de estoque + criação de pedido precisam ser atômicos, como vimos no capítulo 33). (b) MongoDB — atributos variáveis por categoria de produto é exatamente o caso onde esquema flexível economiza a dor de ter colunas vazias ou tabelas de atributos genéricas complicadas. (c) MongoDB (ou um banco de coluna larga em escala maior) — logs são escritos em alto volume, raramente precisam de JOIN, e o formato pode variar entre tipos de evento; forçar isso em tabelas relacionais rígidas costuma ser desnecessariamente trabalhoso.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 36.1 — Escolhendo a ferramenta certa</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Para cada cenário, decida se SQL (Postgres) ou um banco de documentos (MongoDB) encaixa melhor, justificando em uma frase: (a) sistema de pedidos de um e-commerce com estoque e pagamento; (b) catálogo de produtos onde cada categoria tem atributos completamente diferentes (roupas têm tamanho/cor, eletrônicos têm voltagem/garantia); (c) log de eventos de uma aplicação, gravado em alto volume e raramente consultado por relacionamento.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>(a) SQL/Postgres</strong> — pedidos, estoque e pagamento têm relacionamentos fortes e exigem transações consistentes (débito de estoque + criação de pedido precisam ser atômicos, como vimos no capítulo 33). <strong>(b) MongoDB</strong> — atributos variáveis por categoria de produto é exatamente o caso onde esquema flexível economiza a dor de ter colunas vazias ou tabelas de atributos genéricas complicadas. <strong>(c) MongoDB (ou um banco de coluna larga em escala maior)</strong> — logs são escritos em alto volume, raramente precisam de JOIN, e o formato pode variar entre tipos de evento; forçar isso em tabelas relacionais rígidas costuma ser desnecessariamente trabalhoso.</p>\n        </div>\n      </div>"},{"id":"nosql-exercise","type":"exercise","authorship":"authored","title":"Escolha justificada de armazenamento","prompt":"Compare SQL, documento e Redis para catálogo de produtos, sessão de usuário e relatório financeiro. Explique leitura principal, escrita, consistência, índice e risco operacional.","difficulty":"intermediate","criteria":["Cada escolha cita padrão de acesso.","Consistência e atualização são discutidas.","A resposta reconhece quando SQL é melhor."]},{"id":"nosql-quiz","type":"quiz","authorship":"authored","conceptId":"nosql-access-pattern-modeling","prompt":"Qual pergunta vem primeiro ao escolher NoSQL?","options":[{"id":"nosql-a","label":"Como o dado será lido, atualizado, indexado e mantido consistente no caso real.","correct":true,"explanation":"Modelo de acesso e consistência guiam a escolha."},{"id":"nosql-b","label":"Qual banco está mais popular no momento.","correct":false,"explanation":"Popularidade não prova adequação ao domínio."},{"id":"nosql-c","label":"Como evitar qualquer modelagem de dados.","correct":false,"explanation":"NoSQL exige modelagem, só muda o tipo de modelagem."}]}],"resources":[{"id":"mongodb-data-modeling","type":"reference","title":"MongoDB data modeling introduction","url":"https://www.mongodb.com/docs/manual/data-modeling/","reinforces":"Modelagem orientada a documentos e padrões de acesso.","language":"en","publisher":"MongoDB","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"redis-data-types","type":"reference","title":"Redis data types","url":"https://redis.io/docs/latest/develop/data-types/","reinforces":"Família de estruturas de dados em memória e comandos associados.","language":"en","publisher":"Redis","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The nosql component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to nosql. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible nosql failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// Observe how names reveal the nosql contract.","instruction":"The nosql component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible nosql failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mongodb","moduleId":"containers-integration-data","order":6,"title":"MongoDB na prática","summary":"Em vez de linhas em uma tabela, o MongoDB guarda documentos BSON (um binário eficiente parecido com JSON) dentro de coleções (o equivalente a uma tabela, mas sem esquema fixo).","objectives":["Modelar documentos por leitura e crescimento","Decidir embedding vs referência","Consultar e agregar sem esconder custo","Relacionar índice ao formato da consulta"],"whyItExists":"Com o mapa NoSQL pronto, MongoDB aprofunda banco de documentos. O aluno já conhece JSON, agregados e SQL, então pode discutir documento como contrato de persistência, não como JSON jogado no banco.","prerequisiteChapterIds":["nosql","docker-conceitos"],"conceptIds":["documentos-a-unidade-basica","embedding-vs-referencing-a-decisao-central-de-modelagem","consultas-mais-elaboradas","spring-data-mongodb-a-mesma-ideia-do-capitulo-45-sem-tabela"],"introducedConceptIds":["document-embed-reference","mongodb-query-index-shape"],"usedConceptIds":["nosql-access-pattern-modeling","agregado-consistencia"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"mongodb-intuition","type":"intuition","authorship":"authored","title":"Documento deve refletir uma leitura útil, não a árvore inteira do mundo","body":"Embedding é ótimo quando dados são lidos juntos e crescem com limite. Referência é melhor quando o dado cresce sem limite, muda independente ou precisa ser compartilhado.","analogyLimit":"Pasta com documentos ajuda, mas cardinalidade, atualização e índice definem o custo real."},{"id":"mongodb-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-nosql\">MongoDB</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#nosql\">36 · NoSQL conceitos</a>, <a class=\"prereq-tag\" href=\"#docker-conceitos\">30 · Docker conceitos</a></div>\n      </div>","fidelityText":"MongoDB Dificuldade: Intermediário ⏱ ~2h de estudo + prática Pré-requisitos: 36 · NoSQL conceitos, 30 · Docker conceitos"},{"id":"mongodb-content-2","type":"html","authorship":"legacy-preserved","html":"<div class=\"install-box\">\n        <h2>Instalação</h2>\n        <div class=\"install-tabs\" role=\"tablist\">\n          <button type=\"button\" class=\"install-tab active\" role=\"tab\" aria-selected=\"true\">Via Docker (recomendado)</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">macOS</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">Windows</button>\n        </div>\n        <div class=\"install-panel active\"><pre class=\"code\">docker run -d --name mongo -p 27017:27017 -v mongodata:/date/db mongo:7</pre></div>\n        <div class=\"install-panel\"><pre class=\"code\">brew tap mongodb/brew\nbrew install mongodb-community@7.0\nbrew services start mongodb-community@7.0</pre></div>\n        <div class=\"install-panel\"><pre class=\"code\"><span class=\"com\"># instalador oficial em mongodb.com/try/download/community</span></pre></div>\n        <pre class=\"code\">mongosh   <span class=\"com\"># abre o shell interativo do Mongo, similar ao psql</span></pre>\n      </div>","fidelityText":"Instalação Via Docker (recomendado) macOS Windows docker run -d --name mongo -p 27017:27017 -v mongodata:/data/db mongo:7 brew tap mongodb/brew brew install mongodb-community@7.0 brew services start mongodb-community@7.0 # instalador oficial em mongodb.com/try/download/community mongosh # abre o shell interativo do Mongo, similar ao psql"},{"id":"mongodb-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Documentos — a unidade básica</h2>","fidelityText":"Documentos — a unidade básica"},{"id":"mongodb-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Em vez de linhas em uma tabela, o MongoDB guarda <strong>documentos</strong> BSON (um binário eficiente parecido com JSON) dentro de <strong>coleções</strong> (o equivalente a uma tabela, mas sem esquema fixo).</p>","fidelityText":"Em vez de linhas em uma tabela, o MongoDB guarda documentos BSON (um binário eficiente parecido com JSON) dentro de coleções (o equivalente a uma tabela, mas sem esquema fixo)."},{"id":"mongodb-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"db.books.insertOne({\n  title: \"Duna\",\n  author: \"Frank Herbert\",\n  pages: 688,\n  genres: [\"fiction cientifica\", \"aventura\"],   // array direto no documento!\n  publisher: { name: \"Aleph\", country: \"Brasil\" }     // objeto aninhado, sem precisar de JOIN\n});\n\ndb.books.find({ pages: { $gt: 300 } });        // equivalente a WHERE paginas > 300\ndb.books.find({ genres: \"aventura\" });          // busca dentro do array\ndb.books.updateOne({ title: \"Duna\" }, { $set: { available: false } });\ndb.books.deleteOne({ title: \"Duna\" });","fidelityText":"db.livros.insertOne({ titulo: \"Duna\", autor: \"Frank Herbert\", paginas: 688, generos: [\"ficção científica\", \"aventura\"], // array direto no documento! editora: { nome: \"Aleph\", pais: \"Brasil\" } // objeto aninhado, sem precisar de JOIN }); db.livros.find({ paginas: { $gt: 300 } }); // equivalente a WHERE paginas > 300 db.livros.find({ generos: \"aventura\" }); // busca dentro do array db.livros.updateOne({ titulo: \"Duna\" }, { $set: { disponivel: false } }); db.livros.deleteOne({ titulo: \"Duna\" });","highlightedHtml":"db.books.insertOne({\n  title: \"Duna\",\n  author: \"Frank Herbert\",\n  pages: 688,\n  genres: [\"fiction cientifica\", \"aventura\"],   <span class=\"com\">// array direto no documento!</span>\n  publisher: { name: \"Aleph\", country: \"Brasil\" }     <span class=\"com\">// objeto aninhado, sem precisar de JOIN</span>\n});\n\ndb.books.find({ pages: { $gt: 300 } });        <span class=\"com\">// equivalente a WHERE paginas &gt; 300</span>\ndb.books.find({ genres: \"aventura\" });          <span class=\"com\">// busca dentro do array</span>\ndb.books.updateOne({ title: \"Duna\" }, { $set: { available: false } });\ndb.books.deleteOne({ title: \"Duna\" });","caption":"Exemplo executável de mongodb.","explanation":["insertOne grava um documento BSON/JSON-like com campos e arrays.","A forma do documento deve refletir consulta e atualização esperadas."],"commonMistakes":["Inserir qualquer JSON sem contrato","Ignorar tipos e validação"]},{"id":"mongodb-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em um documento MongoDB como um objeto Java já \"achatado\" em JSON, guardado inteiro — inclusive listas e objetos aninhados, tudo junto. Em SQL você quebraria \"editora\" em uma tabela separada com chave estrangeira; em MongoDB, se a relação é \"um livro tem uma editora e você quase sempre lê os dois juntos\", faz sentido simplesmente aninhar — sem precisar de JOIN, porque tudo já vem junto na mesma leitura.</div>","fidelityText":"Pense em um documento MongoDB como um objeto Java já \"achatado\" em JSON, guardado inteiro — inclusive listas e objetos aninhados, tudo junto. Em SQL você quebraria \"editora\" em uma tabela separada com chave estrangeira; em MongoDB, se a relação é \"um livro tem uma editora e você quase sempre lê os dois juntos\", faz sentido simplesmente aninhar — sem precisar de JOIN, porque tudo já vem junto na mesma leitura."},{"id":"mongodb-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Embedding vs Referencing — a decisão central de modelagem</h2>","fidelityText":"Embedding vs Referencing — a decisão central de modelagem"},{"id":"mongodb-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"// EMBEDDING: dados aninhados dentro do mesmo documento\n// bom quando os dados são sempre lidos juntos e não crescem sem limite\n{\n  title: \"Duna\",\n  author: { name: \"Frank Herbert\", nationality: \"EUA\" }\n}\n\n// REFERENCING: guarda só o ID, como uma chave estrangeira \"manual\"\n// bom quando os dados são grandes, mudam com frequência, ou são\n// compartilhados entre muitos documentos\n{\n  title: \"Duna\",\n  authorId: ObjectId(\"64f...\")\n}","fidelityText":"// EMBEDDING: dados aninhados dentro do mesmo documento // bom quando os dados são sempre lidos juntos e não crescem sem limite { titulo: \"Duna\", autor: { nome: \"Frank Herbert\", nacionalidade: \"EUA\" } } // REFERENCING: guarda só o ID, como uma chave estrangeira \"manual\" // bom quando os dados são grandes, mudam com frequência, ou são // compartilhados entre muitos documentos { titulo: \"Duna\", autorId: ObjectId(\"64f...\") }","highlightedHtml":"<span class=\"com\">// EMBEDDING: dados aninhados dentro do mesmo documento\n// bom quando os dados são sempre lidos juntos e não crescem sem limite</span>\n{\n  title: \"Duna\",\n  author: { name: \"Frank Herbert\", nationality: \"EUA\" }\n}\n\n<span class=\"com\">// REFERENCING: guarda só o ID, como uma chave estrangeira \"manual\"\n// bom quando os dados são grandes, mudam com frequência, ou são\n// compartilhados entre muitos documentos</span>\n{\n  title: \"Duna\",\n  authorId: ObjectId(\"64f...\")\n}","caption":"Exemplo executável de mongodb.","explanation":["Embedding coloca dados relacionados dentro do mesmo documento para leitura conjunta.","Referencing separa documentos quando crescimento, compartilhamento ou atualização independente pesam mais."],"commonMistakes":["Embedar lista ilimitada","Referenciar tudo por hábito relacional"]},{"id":"mongodb-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>A armadilha mais comum de quem vem de SQL:</b> tentar \"normalizar\" tudo em MongoDB como faria em Postgres, criando referências para tudo e perdendo a vantagem principal do modelo de documento (ler tudo relacionado em uma única consulta). A regra prática: se você <strong>sempre</strong> lê os dados juntos e eles não crescem sem limite, aninhe (embedding). Se os dados são grandes, mudam independentemente, ou são compartilhados por muitos documentos diferentes, referencie.</div>","fidelityText":"A armadilha mais comum de quem vem de SQL: tentar \"normalizar\" tudo em MongoDB como faria em Postgres, criando referências para tudo e perdendo a vantagem principal do modelo de documento (ler tudo relacionado em uma única consulta). A regra prática: se você sempre lê os dados juntos e eles não crescem sem limite, aninhe (embedding). Se os dados são grandes, mudam independentemente, ou são compartilhados por muitos documentos diferentes, referencie."},{"id":"mongodb-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Consultas mais elaboradas</h2>","fidelityText":"Consultas mais elaboradas"},{"id":"mongodb-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"db.books.find({ pages: { $gte: 200, $lte: 500 } });  // entre 200 e 500\n\ndb.books.aggregate([\n  { $match: { available: true } },\n  { $group: { _id: \"$autor\", total: { $sum: 1 } } },\n  { $sort: { total: -1 } }\n]);\n// o \"aggregate\" é o equivalente ao GROUP BY + JOIN combinados do SQL","fidelityText":"db.livros.find({ paginas: { $gte: 200, $lte: 500 } }); // entre 200 e 500 db.livros.aggregate([ { $match: { disponivel: true } }, { $group: { _id: \"$autor\", total: { $sum: 1 } } }, { $sort: { total: -1 } } ]); // o \"aggregate\" é o equivalente ao GROUP BY + JOIN combinados do SQL","highlightedHtml":"db.books.find({ pages: { $gte: 200, $lte: 500 } });  <span class=\"com\">// entre 200 e 500</span>\n\ndb.books.aggregate([\n  { $match: { available: true } },\n  { $group: { _id: \"$autor\", total: { $sum: 1 } } },\n  { $sort: { total: -1 } }\n]);\n<span class=\"com\">// o \"aggregate\" é o equivalente ao GROUP BY + JOIN combinados do SQL</span>","caption":"Exemplo executável de mongodb.","explanation":["find com operadores filtra documentos; aggregate monta pipeline de transformação/agrupamento.","Índices precisam acompanhar filtro e ordenação usados com frequência."],"commonMistakes":["Criar aggregation complexa para compensar modelo ruim","Não verificar plano/índice"]},{"id":"mongodb-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">O shell <code>mongosh</code> é para MongoDB o que o <code>psql</code> é para Postgres — pratique digitando consultas direto nele antes de escrever qualquer código Java. Entender a \"forma\" da consulta em isolamento evita confundir erro de sintaxe do Mongo com erro de código Java quando você conectar as duas coisas via Spring Data MongoDB, a seguir.</div>","fidelityText":"O shell mongosh é para MongoDB o que o psql é para Postgres — pratique digitando consultas direto nele antes de escrever qualquer código Java. Entender a \"forma\" da consulta em isolamento evita confundir erro de sintaxe do Mongo com erro de código Java quando você conectar as duas coisas via Spring Data MongoDB, a seguir."},{"id":"mongodb-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Spring Data MongoDB — a mesma ideia do capítulo 45, sem tabela</h2>","fidelityText":"Spring Data MongoDB — a mesma ideia do capítulo 45, sem tabela"},{"id":"mongodb-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Se você entendeu <code>@Entity</code>/<code>JpaRepository</code> (capítulo 45), Spring Data MongoDB é o mesmo contrato — repositório com métodos derivados do nome, sem SQL escrito à mão — só que mapeando para um documento em vez de uma linha de tabela.</p>","fidelityText":"Se você entendeu @Entity/JpaRepository (capítulo 45), Spring Data MongoDB é o mesmo contrato — repositório com métodos derivados do nome, sem SQL escrito à mão — só que mapeando para um documento em vez de uma linha de tabela."},{"id":"mongodb-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"@Document(collection = \"books\")\npublic class Book {\n    @Id\n    private String id; // ObjectId do Mongo, exposto como String -- não como long/Long\n    private String title;\n    private String author;\n    private int pages;\n    private List<String> genres;\n\n    // getters, setters e construtores omitidos\n}\n\npublic interface BookRepository extends MongoRepository<Book, String> {\n    List<Book> findByGenresContaining(String genre);\n    List<Book> findByPagesGreaterThan(int pages);\n}","fidelityText":"@Document(collection = \"livros\") public class Livro { @Id private String id; // ObjectId do Mongo, exposto como String -- não como long/Long private String titulo; private String autor; private int paginas; private List<String> generos; // getters, setters e construtores omitidos } public interface LivroRepository extends MongoRepository<Livro, String> { List<Livro> findByGenerosContaining(String genero); List<Livro> findByPaginasGreaterThan(int paginas); }","highlightedHtml":"<span class=\"annotation\">@Document</span>(collection = <span class=\"str\">\"books\"</span>)\n<span class=\"kw\">public class</span> <span class=\"cls\">Book</span> {\n    <span class=\"annotation\">@Id</span>\n    <span class=\"kw\">private</span> <span class=\"kw\">String</span> id; <span class=\"com\">// ObjectId do Mongo, exposto como String -- não como long/Long</span>\n    <span class=\"kw\">private</span> <span class=\"kw\">String</span> title;\n    <span class=\"kw\">private</span> <span class=\"kw\">String</span> author;\n    <span class=\"kw\">private</span> <span class=\"kw\">int</span> pages;\n    <span class=\"kw\">private</span> List&lt;<span class=\"kw\">String</span>&gt; genres;\n\n    <span class=\"com\">// getters, setters e construtores omitidos</span>\n}\n\n<span class=\"kw\">public interface</span> <span class=\"cls\">BookRepository</span> <span class=\"kw\">extends</span> MongoRepository&lt;<span class=\"cls\">Book</span>, <span class=\"kw\">String</span>&gt; {\n    List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findByGenresContaining</span>(<span class=\"kw\">String</span> genre);\n    List&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findByPagesGreaterThan</span>(<span class=\"kw\">int</span> pages);\n}","caption":"Exemplo executável de mongodb.","explanation":["@Document + MongoRepository seguem o mesmo contrato de @Entity/JpaRepository (capítulo 45): métodos derivados do nome, sem SQL escrito à mão.","@Id aqui é um ObjectId exposto como String, não uma sequência numérica -- por isso o tipo do repositório é <Livro, String>, não <Livro, Long>."],"commonMistakes":["Tipar o repositório como MongoRepository<Livro, Long> por hábito vindo de JPA","Esperar um findBy... derivado resolver um aggregate com $group -- isso exige @Aggregation ou MongoTemplate"]},{"id":"mongodb-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>A diferença que mais pega quem vem de JPA:</b> o <code>@Id</code> aqui é um <code>ObjectId</code> do Mongo, não uma sequência numérica gerada pelo banco — por isso o tipo do repositório é <code>MongoRepository&lt;Livro, String&gt;</code>, não <code>&lt;Livro, Long&gt;</code>. E não existe JOIN: <code>findByGenerosContaining</code> gera uma query dentro do array do próprio documento, nunca uma junção entre coleções. Um <code>aggregate</code> complexo (como o <code>$group</code> visto acima) não tem tradução direta em método derivado — para esse caso, use <code>@Aggregation</code> ou injete <code>MongoTemplate</code> diretamente.</div>","fidelityText":"A diferença que mais pega quem vem de JPA: o @Id aqui é um ObjectId do Mongo, não uma sequência numérica gerada pelo banco — por isso o tipo do repositório é MongoRepository<Livro, String>, não <Livro, Long>. E não existe JOIN: findByGenerosContaining gera uma query dentro do array do próprio documento, nunca uma junção entre coleções. Um aggregate complexo (como o $group visto acima) não tem tradução direta em método derivado — para esse caso, use @Aggregation ou injete MongoTemplate diretamente."},{"id":"mongodb-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties\nspring.data.mongodb.uri=mongodb://localhost:27017/library","fidelityText":"# application.properties spring.data.mongodb.uri=mongodb://localhost:27017/biblioteca","highlightedHtml":"<span class=\"com\"># application.properties</span>\nspring.data.mongodb.uri=mongodb://localhost:27017/library","caption":"Exemplo executável de mongodb.","explanation":["spring.data.mongodb.uri configura a conexão completa (host, porta, banco) em uma única propriedade, análogo ao spring.datasource.url do JDBC."],"commonMistakes":["Hardcodar a URI de produção no application.properties versionado em vez de variável de ambiente"]},{"id":"mongodb-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Exercício 37.1 — Modelando com embedding","prompt":"Usando mongosh conectado a um MongoDB rodando via Docker, crie uma coleção livros e insira 3 documentos, cada um com titulo, autor (objeto aninhado com nome e nacionalidade) e um array generos. Escreva uma consulta que retorna só livros de ficção científica, e outra que conta quantos livros cada nacionalidade de autor tem, usando aggregate.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 37.1 — Modelando com embeddingmédio Usando mongosh conectado a um MongoDB rodando via Docker, crie uma coleção livros e insira 3 documentos, cada um com titulo, autor (objeto aninhado com nome e nacionalidade) e um array generos. Escreva uma consulta que retorna só livros de ficção científica, e outra que conta quantos livros cada nacionalidade de autor tem, usando aggregate. Ver solução db.livros.insertMany([ { titulo: \"Duna\", autor: { nome: \"Frank Herbert\", nacionalidade: \"EUA\" }, generos: [\"ficção científica\"] }, { titulo: \"Neuromancer\", autor: { nome: \"William Gibson\", nacionalidade: \"EUA\" }, generos: [\"ficção científica\", \"cyberpunk\"] }, { titulo: \"Dom Casmurro\", autor: { nome: \"Machado de Assis\", nacionalidade: \"Brasil\" }, generos: [\"romance\"] } ]); db.livros.find({ generos: \"ficção científica\" }); db.livros.aggregate([ { $group: { _id: \"$autor.nacionalidade\", total: { $sum: 1 } } } ]);","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 37.1 — Modelando com embedding</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Usando <code>mongosh</code> conectado a um MongoDB rodando via Docker, crie uma coleção <code>livros</code> e insira 3 documentos, cada um com <code>titulo</code>, <code>autor</code> (objeto aninhado com <code>nome</code> e <code>nacionalidade</code>) e um array <code>generos</code>. Escreva uma consulta que retorna só livros de ficção científica, e outra que conta quantos livros cada nacionalidade de autor tem, usando <code>aggregate</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">db.books.insertMany([\n  { title: \"Duna\", author: { name: \"Frank Herbert\", nationality: \"EUA\" }, genres: [\"fiction cientifica\"] },\n  { title: \"Neuromancer\", author: { name: \"William Gibson\", nationality: \"EUA\" }, genres: [\"fiction cientifica\", \"cyberpunk\"] },\n  { title: \"Dom Casmurro\", author: { name: \"Machado de Assis\", nationality: \"Brasil\" }, genres: [\"romance\"] }\n]);\n\ndb.books.find({ genres: \"fiction cientifica\" });\n\ndb.books.aggregate([\n  { $group: { _id: \"$autor.nationality\", total: { $sum: 1 } } }\n]);</pre>\n        </div>\n      </div>"},{"id":"mongodb-quiz","type":"quiz","authorship":"authored","conceptId":"document-embed-reference","prompt":"Quando embedding tende a ser melhor em MongoDB?","options":[{"id":"mongo-a","label":"Quando os dados são lidos juntos, têm crescimento limitado e pertencem ao mesmo agregado.","correct":true,"explanation":"Embedding favorece leitura conjunta e atomicidade dentro do documento."},{"id":"mongo-b","label":"Quando a coleção embutida cresce sem limite previsível.","correct":false,"explanation":"Documento grande e crescimento ilimitado geram problemas de custo e limite."},{"id":"mongo-c","label":"Quando o objetivo é copiar uma normalização relacional sem pensar no acesso.","correct":false,"explanation":"MongoDB exige modelagem orientada a consulta e atualização."}]}],"resources":[{"id":"mongodb-crud","type":"reference","title":"MongoDB CRUD operations","url":"https://www.mongodb.com/docs/manual/crud/","reinforces":"Operações de inserção, consulta, atualização e remoção em documentos.","language":"en","publisher":"MongoDB","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"mongodb-embedding-reference","type":"reference","title":"MongoDB embedded data models","url":"https://www.mongodb.com/docs/manual/data-modeling/concepts/embedding-vs-references/","reinforces":"Critérios para embedding e referência em modelagem documental.","language":"en","publisher":"MongoDB","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The mongodb component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to mongodb. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible mongodb failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"docker run -d --name mongo -p 27017:27017 -v mongodata:/date/db mongo:7","instruction":"The mongodb component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible mongodb failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"redis","moduleId":"containers-integration-data","order":7,"title":"Redis — cache & estruturas em memória","summary":"Redis guarda tudo em memória RAM — é por isso que é absurdamente rápido (microssegundos, não milissegundos) e por isso que não é o lugar certo para seus dados \"de verdade\" e permanentes.","objectives":["Usar Redis como estrutura de dados, não só chave-valor","Aplicar cache-aside com TTL e invalidação","Separar cache de fonte de verdade","Reconhecer riscos de lock e coordenação"],"whyItExists":"Redis aparece depois de coleções e NoSQL para aproveitar o que o aluno já sabe sobre estruturas de dados. O foco é decidir operação e risco, não decorar comandos.","prerequisiteChapterIds":["nosql","colecoes"],"conceptIds":["as-estruturas-de-dados-do-redis-nao-e-so-chave-valor-simples","cache-aside-o-padrao-mais-comum-na-pratica"],"introducedConceptIds":["redis-data-structure-choice","cache-aside-ttl-invalidation"],"usedConceptIds":["contrato-collection-map","nosql-access-pattern-modeling"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"redis-intuition","type":"intuition","authorship":"authored","title":"Redis é caixa de ferramentas de estruturas rápidas","body":"String, Hash, Set, Sorted Set e Streams servem perguntas diferentes. Como cache, Redis melhora latência, mas introduz stale data e invalidação como problemas de design.","analogyLimit":"Memória rápida ajuda a imaginar cache, mas eviction, persistência e consistência precisam de política explícita."},{"id":"redis-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-nosql\">Redis</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#nosql\">36 · NoSQL conceitos</a>, <a class=\"prereq-tag\" href=\"#colecoes\">11 · Coleções</a></div>\n      </div>","fidelityText":"Redis Dificuldade: Intermediário ⏱ ~1h30 de estudo + prática Pré-requisitos: 36 · NoSQL conceitos, 11 · Coleções"},{"id":"redis-content-2","type":"html","authorship":"legacy-preserved","html":"<p><strong>Redis</strong> guarda tudo <strong>em memória RAM</strong> — é por isso que é absurdamente rápido (microssegundos, não milissegundos) e por isso que não é o lugar certo para seus dados \"de verdade\" e permanentes.</p>","fidelityText":"Redis guarda tudo em memória RAM — é por isso que é absurdamente rápido (microssegundos, não milissegundos) e por isso que não é o lugar certo para seus dados \"de verdade\" e permanentes."},{"id":"redis-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Se Postgres é o arquivo morto da empresa — organizado, confiável, guarda tudo permanentemente — Redis é o post-it grudado no monitor: instantâneo de ler e escrever, mas você não colocaria ali informação que não pode se dar ao luxo de perder se alguém limpar a mesa. Toda consulta cara ao \"arquivo morto\" que se repete com frequência vale a pena anotar num post-it (cache), pra não ter que ir lá de novo toda vez.</div>","fidelityText":"Se Postgres é o arquivo morto da empresa — organizado, confiável, guarda tudo permanentemente — Redis é o post-it grudado no monitor: instantâneo de ler e escrever, mas você não colocaria ali informação que não pode se dar ao luxo de perder se alguém limpar a mesa. Toda consulta cara ao \"arquivo morto\" que se repete com frequência vale a pena anotar num post-it (cache), pra não ter que ir lá de novo toda vez."},{"id":"redis-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"install-box\">\n        <h2>Instalação</h2>\n        <div class=\"install-tabs\" role=\"tablist\">\n          <button type=\"button\" class=\"install-tab active\" role=\"tab\" aria-selected=\"true\">Via Docker (recomendado)</button>\n          <button type=\"button\" class=\"install-tab\" role=\"tab\" aria-selected=\"false\" tabindex=\"-1\">macOS</button>\n        </div>\n        <div class=\"install-panel active\"><pre class=\"code\">docker run -d --name redis -p 6379:6379 redis:7</pre></div>\n        <div class=\"install-panel\"><pre class=\"code\">brew install redis\nbrew services start redis</pre></div>\n        <pre class=\"code\">redis-cli   <span class=\"com\"># shell interativo</span></pre>\n      </div>","fidelityText":"Instalação Via Docker (recomendado) macOS docker run -d --name redis -p 6379:6379 redis:7 brew install redis brew services start redis redis-cli # shell interativo"},{"id":"redis-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>As estruturas de dados do Redis (não é só chave-valor simples)</h2>","fidelityText":"As estruturas de dados do Redis (não é só chave-valor simples)"},{"id":"redis-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"SET user:1:name \"Felipy\"          # string simples\nGET user:1:name\n\nSET book:42:views 0\nINCR book:42:views          # operação atômica -- lembra da race condition do capítulo 14!\n\nSET user:1:session \"token-opaco\" EX 3600 # cria a chave já com TTL atômico\nTTL user:1:session                 # quanto tempo falta para expirar\n\nLPUSH queue:emails \"send-boas-vindas@user1\"  # lista -- fila simples\nRPOP queue:emails\n\nSADD tags:book:42 \"fiction\" \"aventura\"   # set -- sem duplicatas, como o capítulo 11\n\nHSET user:1 name \"Felipy\" city \"Custodia\"  # hash -- como um objeto/Map dentro da chave\nHGET user:1 name","fidelityText":"SET usuario:1:nome \"Felipy\" # string simples GET usuario:1:nome SET livro:42:visualizacoes 0 INCR livro:42:visualizacoes # operação atômica -- lembra da race condition do capítulo 14! SET usuario:1:sessao \"token-opaco\" EX 3600 # cria a chave já com TTL atômico TTL usuario:1:sessao # quanto tempo falta para expirar LPUSH fila:emails \"enviar-boas-vindas@user1\" # lista -- fila simples RPOP fila:emails SADD tags:livro:42 \"ficção\" \"aventura\" # set -- sem duplicatas, como o capítulo 11 HSET usuario:1 nome \"Felipy\" cidade \"Custódia\" # hash -- como um objeto/Map dentro da chave HGET usuario:1 nome","highlightedHtml":"SET user:1:name \"Felipy\"          <span class=\"com\"># string simples</span>\nGET user:1:name\n\nSET book:42:views 0\nINCR book:42:views          <span class=\"com\"># operação atômica -- lembra da race condition do capítulo 14!</span>\n\nSET user:1:session \"token-opaco\" EX 3600 <span class=\"com\"># cria a chave já com TTL atômico</span>\nTTL user:1:session                 <span class=\"com\"># quanto tempo falta para expirar</span>\n\nLPUSH queue:emails \"send-boas-vindas@user1\"  <span class=\"com\"># lista -- fila simples</span>\nRPOP queue:emails\n\nSADD tags:book:42 \"fiction\" \"aventura\"   <span class=\"com\"># set -- sem duplicatas, como o capítulo 11</span>\n\nHSET user:1 name \"Felipy\" city \"Custodia\"  <span class=\"com\"># hash -- como um objeto/Map dentro da chave</span>\nHGET user:1 name","caption":"Exemplo executável de redis.","explanation":["Comandos como SET, GET e INCR operam estruturas simples com baixa latência.","A escolha da chave e da estrutura define operação, expiração e manutenção."],"commonMistakes":["Criar chaves sem namespace","Usar Redis como lixeira global sem TTL"]},{"id":"redis-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Lembra do capítulo 14, sobre a race condition de \"valor++\" não ser atômico em Java puro sem <code>synchronized</code>? O comando <code>INCR</code> do Redis <strong>é</strong> atômico por natureza — Redis processa comandos um de cada vez, em uma única thread, então nunca existe a janela de \"duas leituras antes de uma escrita\" que causava aquele bug. É por isso que Redis é usado com frequência para contadores compartilhados entre múltiplas instâncias de uma aplicação, mesmo rodando em servidores diferentes.</div>","fidelityText":"Lembra do capítulo 14, sobre a race condition de \"valor++\" não ser atômico em Java puro sem synchronized? O comando INCR do Redis é atômico por natureza — Redis processa comandos um de cada vez, em uma única thread, então nunca existe a janela de \"duas leituras antes de uma escrita\" que causava aquele bug. É por isso que Redis é usado com frequência para contadores compartilhados entre múltiplas instâncias de uma aplicação, mesmo rodando em servidores diferentes."},{"id":"redis-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Cache-aside — o padrão mais comum na prática</h2>","fidelityText":"Cache-aside — o padrão mais comum na prática"},{"id":"redis-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"public Book findBook(long id) {\n    String key = \"book:\" + id;\n    String cacheado = redis.get(key);\n\n    if (cacheado != null) {\n        return deserialize(cacheado); // achou no cache -- retorna sem tocar no banco\n    }\n\n    Book book = repository.findById(id); // não achou -- vai no Postgres\n    redis.set(key, serialize(book), Duration.ofMinutes(10)); // guarda para a PRÓXIMA vez\n    return book;\n}","fidelityText":"public Livro buscarLivro(long id) { String chave = \"livro:\" + id; String cacheado = redis.get(chave); if (cacheado != null) { return desserializar(cacheado); // achou no cache -- retorna sem tocar no banco } Livro livro = repositorio.buscarPorId(id); // não achou -- vai no Postgres redis.set(chave, serializar(livro), Duration.ofMinutes(10)); // guarda para a PRÓXIMA vez return livro; }","highlightedHtml":"<span class=\"kw\">public</span> <span class=\"cls\">Book</span> <span class=\"fn\">findBook</span>(<span class=\"kw\">long</span> id) {\n    <span class=\"kw\">String</span> key = <span class=\"str\">\"book:\"</span> + id;\n    <span class=\"kw\">String</span> cacheado = redis.get(key);\n\n    <span class=\"kw\">if</span> (cacheado != <span class=\"kw\">null</span>) {\n        <span class=\"kw\">return</span> deserialize(cacheado); <span class=\"com\">// achou no cache -- retorna sem tocar no banco</span>\n    }\n\n    <span class=\"cls\">Book</span> book = repository.findById(id); <span class=\"com\">// não achou -- vai no Postgres</span>\n    redis.set(key, serialize(book), Duration.ofMinutes(10)); <span class=\"com\">// guarda para a PRÓXIMA vez</span>\n    <span class=\"kw\">return</span> book;\n}","caption":"Exemplo executável de redis.","explanation":["Cache-aside tenta ler cache, busca no banco em miss e grava cache com política.","TTL/invalidação precisam refletir tolerância a stale data."],"commonMistakes":["Não expirar cache","Atualizar banco e esquecer invalidação"]},{"id":"redis-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Cache invalidation é a parte difícil de verdade:</b> quando o livro é atualizado no Postgres, o valor em cache no Redis fica desatualizado até expirar ou ser explicitamente removido (<code>redis.delete(chave)</code>). Esquecer de invalidar o cache ao atualizar dados é a causa mais comum de \"por que o sistema está mostrando informação errada\" em produção — e o próprio Spring tem anotações (<code>@CacheEvict</code>) para automatizar isso quando você chegar no módulo Spring.</div>","fidelityText":"Cache invalidation é a parte difícil de verdade: quando o livro é atualizado no Postgres, o valor em cache no Redis fica desatualizado até expirar ou ser explicitamente removido (redis.delete(chave)). Esquecer de invalidar o cache ao atualizar dados é a causa mais comum de \"por que o sistema está mostrando informação errada\" em produção — e o próprio Spring tem anotações (@CacheEvict) para automatizar isso quando você chegar no módulo Spring."},{"id":"redis-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não use Redis \"porque é rápido\" sem pensar em <em>quando</em> os dados em cache podem ficar errados e por quanto tempo isso é aceitável. A pergunta certa antes de cachear algo é: \"se esse dado estiver 5 minutos desatualizado, algum problema real acontece?\" Se a resposta for não, cachear com um TTL de alguns minutos já resolve 90% dos casos sem complicar a invalidação manual.</div>","fidelityText":"Não use Redis \"porque é rápido\" sem pensar em quando os dados em cache podem ficar errados e por quanto tempo isso é aceitável. A pergunta certa antes de cachear algo é: \"se esse dado estiver 5 minutos desatualizado, algum problema real acontece?\" Se a resposta for não, cachear com um TTL de alguns minutos já resolve 90% dos casos sem complicar a invalidação manual."},{"id":"redis-exercise-12","type":"exercise","authorship":"legacy-preserved","title":"Exercício 38.1 — Cache-aside na mão","prompt":"Usando o Redis rodando via Docker e a classe LivroRepositorioJdbc do capítulo 24, implemente uma classe LivroServicoComCache que primeiro tenta buscar do Redis; se não encontrar, busca do repositório JDBC e grava no Redis com TTL de 5 minutos antes de retornar. Escreva um pequeno teste manual que busca o mesmo livro duas vezes e confirma (via log) que a segunda vez veio do cache, não do banco.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 38.1 — Cache-aside na mãodifícil Usando o Redis rodando via Docker e a classe LivroRepositorioJdbc do capítulo 24, implemente uma classe LivroServicoComCache que primeiro tenta buscar do Redis; se não encontrar, busca do repositório JDBC e grava no Redis com TTL de 5 minutos antes de retornar. Escreva um pequeno teste manual que busca o mesmo livro duas vezes e confirma (via log) que a segunda vez veio do cache, não do banco. Ver solução public class LivroServicoComCache { private final LivroRepositorio repositorio; private final JedisPool redisPool; // cliente Redis (biblioteca Jedis) public LivroServicoComCache(LivroRepositorio repositorio, JedisPool redisPool) { this.repositorio = repositorio; this.redisPool = redisPool; } public Optional<Livro> buscarPorId(long id) { String chave = \"livro:\" + id; try (Jedis redis = redisPool.getResource()) { String cacheado = redis.get(chave); if (cacheado != null) { System.out.println(\"[CACHE HIT] \" + chave); return Optional.of(desserializar(cacheado)); } System.out.println(\"[CACHE MISS] \" + chave + \" -- indo ao banco\"); Optional<Livro> livro = repositorio.buscarPorId(id); livro.ifPresent(l -> redis.setex(chave, 300, serializar(l))); // 300s = 5 minutos return livro; } } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 38.1 — Cache-aside na mão</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Usando o Redis rodando via Docker e a classe <code>LivroRepositorioJdbc</code> do capítulo 24, implemente uma classe <code>LivroServicoComCache</code> que primeiro tenta buscar do Redis; se não encontrar, busca do repositório JDBC e grava no Redis com TTL de 5 minutos antes de retornar. Escreva um pequeno teste manual que busca o mesmo livro duas vezes e confirma (via log) que a segunda vez veio do cache, não do banco.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">BookServiceWithCache</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">BookRepository</span> repository;\n    <span class=\"kw\">private final</span> JedisPool redisPool; <span class=\"com\">// cliente Redis (biblioteca Jedis)</span>\n\n    <span class=\"kw\">public</span> <span class=\"fn\">BookServiceWithCache</span>(<span class=\"cls\">BookRepository</span> repository, JedisPool redisPool) {\n        <span class=\"kw\">this</span>.repository = repository;\n        <span class=\"kw\">this</span>.redisPool = redisPool;\n    }\n\n    <span class=\"kw\">public</span> Optional&lt;<span class=\"cls\">Book</span>&gt; <span class=\"fn\">findById</span>(<span class=\"kw\">long</span> id) {\n        <span class=\"kw\">String</span> key = <span class=\"str\">\"book:\"</span> + id;\n        <span class=\"kw\">try</span> (Jedis redis = redisPool.getResource()) {\n            <span class=\"kw\">String</span> cacheado = redis.get(key);\n            <span class=\"kw\">if</span> (cacheado != <span class=\"kw\">null</span>) {\n                System.out.println(<span class=\"str\">\"[CACHE HIT] \"</span> + key);\n                <span class=\"kw\">return</span> Optional.of(deserialize(cacheado));\n            }\n\n            System.out.println(<span class=\"str\">\"[CACHE MISS] \"</span> + key + <span class=\"str\">\" -- indo to the bank\"</span>);\n            Optional&lt;<span class=\"cls\">Book</span>&gt; book = repository.findById(id);\n            book.ifPresent(l -&gt; redis.setex(key, 300, serialize(l))); <span class=\"com\">// 300s = 5 minutos</span>\n            <span class=\"kw\">return</span> book;\n        }\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"redis-quiz","type":"quiz","authorship":"authored","conceptId":"cache-aside-ttl-invalidation","prompt":"Qual risco o padrão cache-aside precisa tratar?","options":[{"id":"redis-a","label":"Dado stale quando banco muda e cache não expira ou não é invalidado corretamente.","correct":true,"explanation":"Cache melhora leitura, mas pode mentir se a política for ruim."},{"id":"redis-b","label":"O banco relacional deixa de existir automaticamente.","correct":false,"explanation":"Cache-aside normalmente mantém banco como fonte de verdade."},{"id":"redis-c","label":"Toda leitura passa a ser serializável por padrão.","correct":false,"explanation":"Cache não fornece isolamento transacional automaticamente."}]}],"resources":[{"id":"redis-cache-patterns","type":"reference","title":"Redis cache patterns","url":"https://redis.io/docs/latest/develop/use-cases/cache-aside/","reinforces":"Padrão cache-aside, miss/hit e atualização de cache.","language":"en","publisher":"Redis","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"redis-data-types-redis","type":"reference","title":"Redis data types","url":"https://redis.io/docs/latest/develop/data-types/","reinforces":"Strings, Hashes, Lists, Sets, Sorted Sets, Streams e comandos associados.","language":"en","publisher":"Redis","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The redis component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to redis. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible redis failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"docker run -d --name redis -p 6379:6379 redis:7","instruction":"The redis component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible redis failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"nosql-operacional","moduleId":"containers-integration-data","order":8,"title":"MongoDB e Redis operacionais: índices, consistência, persistência e escala","summary":"NoSQL não significa ausência de schema ou transações. Significa escolher outro modelo e outro conjunto de trade-offs. MongoDB oferece validação de schema, índices, replica sets e transações; Redis possui persistência, replicação e políticas de remoção. A configuração determina a garantia real.","objectives":["Ligar modelo NoSQL a índice, consistência e escala","Planejar MongoDB por consulta e cardinalidade","Configurar Redis conforme cache, persistência e eviction","Avaliar locks distribuídos com cautela"],"whyItExists":"Depois de MongoDB e Redis básicos, o aluno precisa parar de tratar NoSQL como ferramenta local. Operação coloca custo, persistência, consistência e escala no centro da decisão.","prerequisiteChapterIds":["mongodb","redis"],"conceptIds":["do-modelo-a-operacao","mongodb-modelo-orientado-ao-acesso","redis-cache-nao-e-banco-descartavel-por-definicao","locks-e-coordenacao"],"introducedConceptIds":["mongodb-operational-indexing","redis-persistence-eviction","distributed-lock-risk"],"usedConceptIds":["mongodb-query-index-shape","cache-aside-ttl-invalidation"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"nosql-operacional-intuition","type":"intuition","authorship":"authored","title":"Operar NoSQL é assumir explicitamente o que o modelo comprou","body":"Documento rápido para uma leitura pode dificultar outra. Cache veloz pode entregar dado velho. Lock distribuído pode falhar por tempo e rede. Produção cobra essas decisões.","analogyLimit":"Ferramenta especializada ajuda, mas cada escolha cria custo operacional mensurável."},{"id":"nosql-operacional-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-nosql\">NoSQL</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~5h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#mongodb\">MongoDB</a>, <a class=\"prereq-tag\" href=\"#redis\">Redis</a></div></div>","fidelityText":"NoSQLDificuldade: Avançado⏱ ~5h de estudo e práticaPré-requisitos: MongoDB, Redis"},{"id":"nosql-operacional-content-2","type":"html","authorship":"legacy-preserved","html":"<p>NoSQL não significa ausência de schema ou transações. Significa escolher outro modelo e outro conjunto de trade-offs. MongoDB oferece validação de schema, índices, replica sets e transações; Redis possui persistência, replicação e políticas de remoção. A configuração determina a garantia real.</p>","fidelityText":"NoSQL não significa ausência de schema ou transações. Significa escolher outro modelo e outro conjunto de trade-offs. MongoDB oferece validação de schema, índices, replica sets e transações; Redis possui persistência, replicação e políticas de remoção. A configuração determina a garantia real."},{"id":"nosql-operacional-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Do modelo à operação</h2></div>\n    <p>Antes das opções específicas, separe formato, cópia, durabilidade e cache: cada decisão responde a uma falha diferente.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Documento</dt><dd>Registro hierárquico de campos e arrays armazenado como unidade no MongoDB.</dd></div><div class=\"concept-card\"><dt>Replica set</dt><dd>Grupo de instâncias MongoDB que mantém cópias e elege um primário para tolerar falhas.</dd></div><div class=\"concept-card\"><dt>Persistência</dt><dd>Mecanismo que permite reconstruir dados após reinício ou falha do processo.</dd></div><div class=\"concept-card\"><dt>Cache-aside</dt><dd>Aplicação consulta o cache; em falta, busca a fonte, devolve e armazena o resultado por um período.</dd></div><div class=\"concept-card\"><dt>TTL</dt><dd><em>Time to live</em>: duração após a qual uma chave expira.</dd></div><div class=\"concept-card\"><dt>Eviction</dt><dd>Remoção de chaves pelo Redis quando a política de memória exige liberar espaço.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoDo modelo à operação Antes das opções específicas, separe formato, cópia, durabilidade e cache: cada decisão responde a uma falha diferente. DocumentoRegistro hierárquico de campos e arrays armazenado como unidade no MongoDB.Replica setGrupo de instâncias MongoDB que mantém cópias e elege um primário para tolerar falhas.PersistênciaMecanismo que permite reconstruir dados após reinício ou falha do processo.Cache-asideAplicação consulta o cache; em falta, busca a fonte, devolve e armazena o resultado por um período.TTLTime to live: duração após a qual uma chave expira.EvictionRemoção de chaves pelo Redis quando a política de memória exige liberar espaço."},{"id":"nosql-operacional-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>MongoDB: modelo orientado ao acesso</h2>","fidelityText":"MongoDB: modelo orientado ao acesso"},{"id":"nosql-operacional-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Embuta dados que mudam e são lidos como unidade; referencie dados grandes, compartilhados ou com ciclo de vida independente. Documentos sem limite podem ultrapassar tamanho e aumentar <strong>write amplification</strong>: uma pequena mudança lógica causa uma quantidade muito maior de bytes reescritos, replicados ou indexados.</p>","fidelityText":"Embuta dados que mudam e são lidos como unidade; referencie dados grandes, compartilhados ou com ciclo de vida independente. Documentos sem limite podem ultrapassar tamanho e aumentar write amplification: uma pequena mudança lógica causa uma quantidade muito maior de bytes reescritos, replicados ou indexados."},{"id":"nosql-operacional-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"db.orders.createIndex({ customerId: 1, createdIn: -1 })\ndb.orders.find({ customerId: UUID(\"...\") })\n  .sort({ createdIn: -1 })\n  .explain(\"executionStats\")","fidelityText":"db.pedidos.createIndex({ clienteId: 1, criadoEm: -1 }) db.pedidos.find({ clienteId: UUID(\"...\") }) .sort({ criadoEm: -1 }) .explain(\"executionStats\")","highlightedHtml":"db.orders.createIndex({ customerId: 1, createdIn: -1 })\ndb.orders.find({ customerId: UUID(<span class=\"str\">\"...\"</span>) })\n  .sort({ createdIn: -1 })\n  .explain(<span class=\"str\">\"executionStats\"</span>)","caption":"Exemplo executável de nosql-operacional.","explanation":["createIndex prepara caminho de consulta por customerId e data de criação.","A ordem dos campos deve refletir filtro, ordenação e cardinalidade reais."],"commonMistakes":["Criar índice sem consultar explain/plano","Indexar campo de baixa seletividade sem necessidade"]},{"id":"nosql-operacional-content-7","type":"html","authorship":"legacy-preserved","html":"<p>A ordem do índice composto importa. Compare documentos examinados, retornados e estágio do plano. Use validação JSON Schema para impedir documentos estruturalmente inválidos. <strong>Write concern</strong> define quais confirmações de escrita são exigidas antes da resposta; <strong>read concern</strong> define a garantia de visibilidade da leitura. Não presuma consistência pela aparência da API.</p>","fidelityText":"A ordem do índice composto importa. Compare documentos examinados, retornados e estágio do plano. Use validação JSON Schema para impedir documentos estruturalmente inválidos. Write concern define quais confirmações de escrita são exigidas antes da resposta; read concern define a garantia de visibilidade da leitura. Não presuma consistência pela aparência da API."},{"id":"nosql-operacional-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Redis: cache não é banco descartável por definição</h2>","fidelityText":"Redis: cache não é banco descartável por definição"},{"id":"nosql-operacional-content-9","type":"html","authorship":"legacy-preserved","html":"<p><strong>RDB</strong> cria snapshots periódicos do conjunto de dados; <strong>AOF</strong> (<em>append-only file</em>) registra operações para reprodução. Eles oferecem trocas diferentes de durabilidade, espaço e recuperação. Replicação copia dados; <strong>Sentinel</strong> monitora e coordena failover sem particionar o conjunto; <strong>Cluster</strong> distribui chaves entre nós. Se uma chave não pode ser perdida, teste reinício, failover e restauração em vez de chamá-la apenas de cache.</p>","fidelityText":"RDB cria snapshots periódicos do conjunto de dados; AOF (append-only file) registra operações para reprodução. Eles oferecem trocas diferentes de durabilidade, espaço e recuperação. Replicação copia dados; Sentinel monitora e coordena failover sem particionar o conjunto; Cluster distribui chaves entre nós. Se uma chave não pode ser perdida, teste reinício, failover e restauração em vez de chamá-la apenas de cache."},{"id":"nosql-operacional-content-10","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Risco</th><th>O que significa</th><th>Mitigação</th></tr><tr><td>stampede</td><td>muitos clientes perdem a mesma chave e atingem a fonte ao mesmo tempo</td><td>TTL com jitter, single-flight/lock limitado e stale-while-revalidate</td></tr><tr><td>penetração</td><td>consultas repetidas por dados inexistentes atravessam o cache</td><td>validação, cache negativo curto ou filtro probabilístico</td></tr><tr><td>hot key</td><td>uma chave recebe parcela desproporcional do tráfego</td><td>particionamento lógico, réplica de leitura ou cache local controlado</td></tr><tr><td>memória cheia</td><td>novos dados excedem o limite configurado</td><td>maxmemory, eviction policy coerente e alertas</td></tr><tr><td>dados incompatíveis</td><td>versões da aplicação interpretam o valor de formas diferentes</td><td>versão no valor/chave e estratégia de migração</td></tr></tbody></table>","fidelityText":"RiscoO que significaMitigaçãostampedemuitos clientes perdem a mesma chave e atingem a fonte ao mesmo tempoTTL com jitter, single-flight/lock limitado e stale-while-revalidatepenetraçãoconsultas repetidas por dados inexistentes atravessam o cachevalidação, cache negativo curto ou filtro probabilísticohot keyuma chave recebe parcela desproporcional do tráfegoparticionamento lógico, réplica de leitura ou cache local controladomemória cheianovos dados excedem o limite configuradomaxmemory, eviction policy coerente e alertasdados incompatíveisversões da aplicação interpretam o valor de formas diferentesversão no valor/chave e estratégia de migração"},{"id":"nosql-operacional-content-11","type":"html","authorship":"legacy-preserved","html":"<p><strong>Single-flight</strong> deixa apenas uma chamada reconstruir a chave enquanto as demais aguardam. <strong>Stale-while-revalidate</strong> serve por pouco tempo uma cópia expirada enquanto a atualiza em segundo plano. Um <strong>filtro probabilístico</strong>, como Bloom filter, responde rapidamente que certos itens definitivamente não existem, aceitando alguns falsos positivos.</p>","fidelityText":"Single-flight deixa apenas uma chamada reconstruir a chave enquanto as demais aguardam. Stale-while-revalidate serve por pouco tempo uma cópia expirada enquanto a atualiza em segundo plano. Um filtro probabilístico, como Bloom filter, responde rapidamente que certos itens definitivamente não existem, aceitando alguns falsos positivos."},{"id":"nosql-operacional-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Locks e coordenação</h2>","fidelityText":"Locks e coordenação"},{"id":"nosql-operacional-content-13","type":"html","authorship":"legacy-preserved","html":"<p><code>SET chave valor NX PX tempo</code> fornece aquisição condicional com expiração, mas pausas e rede podem fazer um dono antigo continuar agindo. Para recursos críticos, use fencing token crescente aceito pelo recurso protegido. Nunca libere um lock sem verificar que o valor ainda pertence ao solicitante.</p>","fidelityText":"SET chave valor NX PX tempo fornece aquisição condicional com expiração, mas pausas e rede podem fazer um dono antigo continuar agindo. Para recursos críticos, use fencing token crescente aceito pelo recurso protegido. Nunca libere um lock sem verificar que o valor ainda pertence ao solicitante."},{"id":"nosql-operacional-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — cache sob pressão","prompt":"Implemente cache-aside com TTL aleatório, cache negativo e métricas de hit/miss. Simule 100 requisições simultâneas após expiração e impeça 100 consultas iguais ao banco. Reinicie Redis e documente o efeito da política de persistência.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — cache sob pressãodifícilImplemente cache-aside com TTL aleatório, cache negativo e métricas de hit/miss. Simule 100 requisições simultâneas após expiração e impeça 100 consultas iguais ao banco. Reinicie Redis e documente o efeito da política de persistência.Ver critériosO teste deve medir chamadas ao banco, não apenas tempo. A aplicação precisa continuar correta quando Redis estiver indisponível e não deve armazenar erro transitório como resultado válido.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — cache sob pressão</h2><span class=\"exercise-tag d\">difícil</span></div><p>Implemente cache-aside com TTL aleatório, cache negativo e métricas de hit/miss. Simule 100 requisições simultâneas após expiração e impeça 100 consultas iguais ao banco. Reinicie Redis e documente o efeito da política de persistência.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>O teste deve medir chamadas ao banco, não apenas tempo. A aplicação precisa continuar correta quando Redis estiver indisponível e não deve armazenar erro transitório como resultado válido.</p></div></div>"},{"id":"nosql-operacional-quiz","type":"quiz","authorship":"authored","conceptId":"mongodb-operational-indexing","prompt":"Por que índice precisa ser pensado junto com padrão de acesso?","options":[{"id":"nso-a","label":"Porque filtro, ordenação e cardinalidade da consulta determinam se o índice reduz custo ou só adiciona manutenção.","correct":true,"explanation":"Índice acelera caminhos específicos e custa escrita/memória."},{"id":"nso-b","label":"Porque todo índice acelera toda consulta igualmente.","correct":false,"explanation":"Índice errado pode nem ser usado."},{"id":"nso-c","label":"Porque NoSQL não tem custo de consulta.","correct":false,"explanation":"NoSQL também tem plano, cardinalidade, memória e I/O."}]}],"resources":[{"id":"mongodb-indexes","type":"reference","title":"MongoDB indexes","url":"https://www.mongodb.com/docs/manual/indexes/","reinforces":"Tipos de índice, custo e relação com consultas.","language":"en","publisher":"MongoDB","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"redis-persistence","type":"reference","title":"Redis persistence","url":"https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/","reinforces":"RDB, AOF, durabilidade e trade-offs operacionais do Redis.","language":"en","publisher":"Redis","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The nosql operational component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to nosql operational. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible nosql operational failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"db.orders.createIndex({ customerId: 1, createdIn: -1 })","instruction":"The nosql operational component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible nosql operational failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"hospedagem-db","moduleId":"containers-integration-data","order":9,"title":"Onde hospedar o banco: dev, teste & produção","summary":"Um sistema real nunca usa um único banco — usa pelo menos três \"versões\" dele, com propósitos diferentes. Confundir esses ambientes é uma das formas mais comuns de causar incidentes graves.","objectives":["Separar banco local, teste, staging e produção","Comparar container local com banco gerenciado","Planejar backup, restore, migration e rede","Evitar credenciais e dados reais em ambientes errados"],"whyItExists":"Depois de Compose, Testcontainers, SQL e NoSQL operacional, o aluno precisa saber onde cada banco deve viver. Produção não é só subir container com senha forte.","prerequisiteChapterIds":["postgres","compose","testcontainers","nosql-operacional"],"conceptIds":["banco-gerenciado-por-que-nao-simplesmente-rodar-postgres-em-um-servidor-"],"introducedConceptIds":["database-environment-boundary","managed-database-operations"],"usedConceptIds":["compose-service-network","docker-volume-persistence","migration-expand-contract"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"hospedagem-db-intuition","type":"intuition","authorship":"authored","title":"Ambiente de banco é contrato de risco","body":"Dev prioriza facilidade. Teste prioriza isolamento. Staging aproxima produção. Produção exige backup restaurável, rede, monitoramento, upgrade e acesso controlado. Misturar esses mundos cria acidente silencioso.","analogyLimit":"Cozinha de teste e restaurante ajudam a imaginar, mas banco tem dados reais, compliance, restore e janelas de manutenção."},{"id":"hospedagem-db-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Infra</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#postgres\">34 · PostgreSQL</a>, <a class=\"prereq-tag\" href=\"#compose\">32 · Docker Compose</a>, <a class=\"prereq-tag\" href=\"#build\">19 · Maven &amp; Gradle</a></div>\n      </div>","fidelityText":"Infra Dificuldade: Intermediário ⏱ ~1h de estudo Pré-requisitos: 34 · PostgreSQL, 32 · Docker Compose, 19 · Maven & Gradle"},{"id":"hospedagem-db-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Um sistema real nunca usa <strong>um único banco</strong> — usa pelo menos três \"versões\" dele, com propósitos diferentes. Confundir esses ambientes é uma das formas mais comuns de causar incidentes graves.</p>","fidelityText":"Um sistema real nunca usa um único banco — usa pelo menos três \"versões\" dele, com propósitos diferentes. Confundir esses ambientes é uma das formas mais comuns de causar incidentes graves."},{"id":"hospedagem-db-content-3","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Ambiente</th><th>Onde roda</th><th>Dados</th></tr>\n        <tr><td><strong>Desenvolvimento (dev)</strong></td><td>Docker local (capítulo 30-32), na sua máquina</td><td>Fictícios, descartáveis, recriados livremente</td></tr>\n        <tr><td><strong>Teste (automatizado)</strong></td><td>Container efêmero via Testcontainers (capítulo futuro), sobe e morre a cada execução</td><td>Gerados pelo próprio teste, isolados de tudo</td></tr>\n        <tr><td><strong>Staging/Homologação</strong></td><td>Ambiente gerenciado, similar à produção</td><td>Cópia (anonimizada) ou similar à produção, para validar antes do deploy real</td></tr>\n        <tr><td><strong>Produção</strong></td><td>Provedor gerenciado (ver abaixo)</td><td>Dados reais de usuários reais — tratamento máximo de cuidado</td></tr>\n      </tbody></table>","fidelityText":"AmbienteOnde rodaDados Desenvolvimento (dev)Docker local (capítulo 30-32), na sua máquinaFictícios, descartáveis, recriados livremente Teste (automatizado)Container efêmero via Testcontainers (capítulo futuro), sobe e morre a cada execuçãoGerados pelo próprio teste, isolados de tudo Staging/HomologaçãoAmbiente gerenciado, similar à produçãoCópia (anonimizada) ou similar à produção, para validar antes do deploy real ProduçãoProvedor gerenciado (ver abaixo)Dados reais de usuários reais — tratamento máximo de cuidado"},{"id":"hospedagem-db-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense nesses ambientes como as fases de escrever este próprio curso: primeiro você escreve e testa exemplos no seu rascunho (dev), depois roda os exercícios para confirmar que funcionam (teste automatizado), depois revisa tudo antes de publicar (staging), e só então o conteúdo vai ao ar para o público de verdade (produção). Pular etapas — testar uma mudança grande direto em produção — é como publicar um capítulo sem nunca ter revisado.</div>","fidelityText":"Pense nesses ambientes como as fases de escrever este próprio curso: primeiro você escreve e testa exemplos no seu rascunho (dev), depois roda os exercícios para confirmar que funcionam (teste automatizado), depois revisa tudo antes de publicar (staging), e só então o conteúdo vai ao ar para o público de verdade (produção). Pular etapas — testar uma mudança grande direto em produção — é como publicar um capítulo sem nunca ter revisado."},{"id":"hospedagem-db-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Banco gerenciado — por que não simplesmente rodar Postgres em um servidor você mesmo</h2>","fidelityText":"Banco gerenciado — por que não simplesmente rodar Postgres em um servidor você mesmo"},{"id":"hospedagem-db-content-6","type":"html","authorship":"legacy-preserved","html":"<p>É possível, mas significa que <strong>você</strong> vira responsável por backups, atualizações de segurança, monitoramento de disco cheio, réplicas de alta disponibilidade — tudo manualmente. Provedores de banco gerenciado assumem essa operação por você:</p>","fidelityText":"É possível, mas significa que você vira responsável por backups, atualizações de segurança, monitoramento de disco cheio, réplicas de alta disponibilidade — tudo manualmente. Provedores de banco gerenciado assumem essa operação por você:"},{"id":"hospedagem-db-content-7","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Provedor</th><th>Característica</th></tr>\n        <tr><td><strong>Supabase</strong></td><td>Postgres gerenciado + autenticação + API automática, camada gratuita generosa, ótimo para projetos pessoais/MVPs</td></tr>\n        <tr><td><strong>Neon</strong></td><td>Postgres serverless — escala a zero quando não usado, cobrança por uso real</td></tr>\n        <tr><td><strong>Railway / Render</strong></td><td>Banco + aplicação no mesmo lugar, deploy simples, bom para projetos pequenos/médios</td></tr>\n        <tr><td><strong>AWS RDS</strong></td><td>Padrão corporativo, altamente configurável, exige mais conhecimento de infraestrutura</td></tr>\n        <tr><td><strong>MongoDB Atlas</strong></td><td>Equivalente gerenciado para MongoDB</td></tr>\n        <tr><td><strong>Upstash / Redis Cloud</strong></td><td>Equivalente gerenciado para Redis (capítulo 38) — camada gratuita cobre cache de projeto pequeno; cobrado por requisição/memória, não por servidor sempre ligado</td></tr>\n      </tbody></table>","fidelityText":"ProvedorCaracterística SupabasePostgres gerenciado + autenticação + API automática, camada gratuita generosa, ótimo para projetos pessoais/MVPs NeonPostgres serverless — escala a zero quando não usado, cobrança por uso real Railway / RenderBanco + aplicação no mesmo lugar, deploy simples, bom para projetos pequenos/médios AWS RDSPadrão corporativo, altamente configurável, exige mais conhecimento de infraestrutura MongoDB AtlasEquivalente gerenciado para MongoDB Upstash / Redis CloudEquivalente gerenciado para Redis (capítulo 38) — camada gratuita cobre cache de projeto pequeno; cobrado por requisição/memória, não por servidor sempre ligado"},{"id":"hospedagem-db-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Nunca aponte seu ambiente de desenvolvimento para o banco de produção \"só para testar rápido\" — um <code>DELETE</code> sem <code>WHERE</code> nesse cenário é irreversível.</li>\n          <li>String de conexão de produção nunca vai para o código-fonte nem para o Git — sempre variável de ambiente (conecta com o capítulo 27 e com um capítulo futuro sobre secrets).</li>\n          <li>Staging deveria ter a <strong>mesma versão</strong> de Postgres/dependências que produção — divergência de versão é fonte clássica de \"funcionou no staging, quebrou em produção\".</li>\n          <li>Nunca copie dados reais de usuários para dev/staging sem anonimizar — é tanto risco de segurança quanto, em muitas jurisdições, problema legal (LGPD/GDPR).</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Nunca aponte seu ambiente de desenvolvimento para o banco de produção \"só para testar rápido\" — um DELETE sem WHERE nesse cenário é irreversível. String de conexão de produção nunca vai para o código-fonte nem para o Git — sempre variável de ambiente (conecta com o capítulo 27 e com um capítulo futuro sobre secrets). Staging deveria ter a mesma versão de Postgres/dependências que produção — divergência de versão é fonte clássica de \"funcionou no staging, quebrou em produção\". Nunca copie dados reais de usuários para dev/staging sem anonimizar — é tanto risco de segurança quanto, em muitas jurisdições, problema legal (LGPD/GDPR)."},{"id":"hospedagem-db-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Se você está construindo um projeto pessoal para portfólio, comece com Supabase ou Neon para produção (camada gratuita cobre a maioria dos projetos de aprendizado) e Docker local para desenvolvimento — exatamente a combinação que este curso usa como padrão. Só migre para AWS RDS quando o projeto realmente exigir a flexibilidade extra; começar direto por lá costuma significar mais tempo configurando infraestrutura do que escrevendo código.</div>","fidelityText":"Se você está construindo um projeto pessoal para portfólio, comece com Supabase ou Neon para produção (camada gratuita cobre a maioria dos projetos de aprendizado) e Docker local para desenvolvimento — exatamente a combinação que este curso usa como padrão. Só migre para AWS RDS quando o projeto realmente exigir a flexibilidade extra; começar direto por lá costuma significar mais tempo configurando infraestrutura do que escrevendo código."},{"id":"hospedagem-db-exercise-10","type":"exercise","authorship":"legacy-preserved","title":"Exercício 39.1 — Desenhando os ambientes do projeto biblioteca","prompt":"Para o projeto da biblioteca construído ao longo do curso, escreva (em texto) uma proposta de configuração de ambientes: onde rodaria o banco de dev, como os testes automatizados (capítulo 15/futuro Testcontainers) se conectariam a um banco, e qual provedor gerenciado você escolheria para produção, justificando a escolha.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 39.1 — Desenhando os ambientes do projeto bibliotecafácil Para o projeto da biblioteca construído ao longo do curso, escreva (em texto) uma proposta de configuração de ambientes: onde rodaria o banco de dev, como os testes automatizados (capítulo 15/futuro Testcontainers) se conectariam a um banco, e qual provedor gerenciado você escolheria para produção, justificando a escolha. Ver solução Dev: Postgres via docker compose (capítulo 32), rodando localmente, com dados fictícios recriados sempre que necessário. Testes automatizados: Testcontainers sobe um Postgres novo e isolado a cada execução da suíte de testes, garantindo que nenhum teste dependa de estado deixado por outro. Produção: Supabase ou Neon — para um projeto de biblioteca de porte pequeno/médio, a camada gratuita/inicial cobre bem, com backups automáticos incluídos, sem exigir que você mesmo administre um servidor de banco.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 39.1 — Desenhando os ambientes do projeto biblioteca</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Para o projeto da biblioteca construído ao longo do curso, escreva (em texto) uma proposta de configuração de ambientes: onde rodaria o banco de dev, como os testes automatizados (capítulo 15/futuro Testcontainers) se conectariam a um banco, e qual provedor gerenciado você escolheria para produção, justificando a escolha.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>Dev:</strong> Postgres via <code>docker compose</code> (capítulo 32), rodando localmente, com dados fictícios recriados sempre que necessário. <strong>Testes automatizados:</strong> Testcontainers sobe um Postgres novo e isolado a cada execução da suíte de testes, garantindo que nenhum teste dependa de estado deixado por outro. <strong>Produção:</strong> Supabase ou Neon — para um projeto de biblioteca de porte pequeno/médio, a camada gratuita/inicial cobre bem, com backups automáticos incluídos, sem exigir que você mesmo administre um servidor de banco.</p>\n        </div>\n      </div>"},{"id":"hospedagem-db-quiz","type":"quiz","authorship":"authored","conceptId":"database-environment-boundary","prompt":"Por que teste automatizado não deve usar o banco de desenvolvimento compartilhado?","options":[{"id":"host-a","label":"Porque estado compartilhado torna o teste não determinístico e pode corromper dados de trabalho.","correct":true,"explanation":"Teste precisa controlar setup, execução e limpeza."},{"id":"host-b","label":"Porque banco local nunca aceita conexão de teste.","correct":false,"explanation":"Aceitar conexão não torna a prática segura ou reprodutível."},{"id":"host-c","label":"Porque Testcontainers só funciona em produção.","correct":false,"explanation":"Testcontainers é justamente para testes locais/CI com dependência descartável."}]}],"resources":[{"id":"aws-rds-welcome","type":"reference","title":"Amazon RDS User Guide","url":"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html","reinforces":"Responsabilidades e recursos de banco relacional gerenciado.","language":"en","publisher":"AWS","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"postgres-backup","type":"reference","title":"PostgreSQL backup and restore","url":"https://www.postgresql.org/docs/current/backup.html","reinforces":"Backup, restore e responsabilidade operacional em bancos PostgreSQL.","language":"en","publisher":"PostgreSQL","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The hosting db component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to hosting db. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible hosting db failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// Observe how names reveal the hosting db contract.","instruction":"The hosting db component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible hosting db failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"resilience","moduleId":"resilience-observability","order":0,"title":"Resiliência com Resilience4j: Circuit Breaker, Retry, Bulkhead e Rate Limiter","summary":"Quando seu back-end depende de outro serviço (outra API, um serviço de pagamento externo), o que acontece quando esse serviço fica lento ou fora do ar? Sem proteção, sua aplicação também trava — um efeito cascata que pode derrubar um sistema inteiro por causa de uma única dependência externa instável.","objectives":["Explicar circuit breaker como máquina de estados","Combinar timeout, retry, circuit breaker e fallback sem amplificar falha","Diferenciar resiliência de mascarar erro","Medir política por sinais operacionais"],"whyItExists":"Depois que o laboratório síncrono mostra acoplamento temporal e estado desconhecido, Resilience4j entra como política para falhar melhor. Ele não conserta serviço remoto; limita dano e torna decisão explícita.","prerequisiteChapterIds":["coupled-services-lab","webclient","anotacoes"],"conceptIds":["os-tres-estados-do-circuito","configuracao-real-parametros-que-decidem-o-comportamento-nao-so-ativar","retry-como-padrao-proprio-nao-e-tentar-de-novo-ingenuo","bulkhead-isolando-o-dano-de-uma-dependencia-lenta","rate-limiter-protegendo-o-outro-lado-e-sua-propria-cota-de-voce-mesmo","classificacao-de-falha-nem-toda-excecao-deveria-contar-contra-o-circuito","composicao-na-pratica","observabilidade-veja-o-estado-do-circuito-nao-adivinhe","testando-resiliencia-forcando-o-circuito-a-abrir-sem-esperar-falhas-reai"],"introducedConceptIds":["circuit-breaker-state-machine","resilience4j-policy-composition"],"usedConceptIds":["partial-failure-unknown-state","synchronous-deadline-budget","retry-backoff-jitter","timeout-deadline-cancelamento-http"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"resilience-intuition","type":"intuition","authorship":"authored","title":"Resiliência é controlar dano, não fingir sucesso","body":"Quando uma dependência falha, continuar chamando pode piorar tudo. Circuit breaker observa falhas, abre para parar a pressão, testa recuperação e fecha quando há sinal suficiente.","analogyLimit":"Disjuntor elétrico ajuda, mas software também precisa fallback, métrica, timeout e efeito de negócio."},{"id":"resilience-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; REST</a>, <a class=\"prereq-tag\" href=\"#anotacoes\">20 · Anotações &amp; Reflection</a> (dynamic proxy)</div>\n      </div>","fidelityText":"Spring Dificuldade: Avançado ⏱ ~3h de estudo + prática Pré-requisitos: 26 · HTTP & REST, 20 · Anotações & Reflection (dynamic proxy)"},{"id":"resilience-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Quando seu back-end depende de outro serviço (outra API, um serviço de pagamento externo), o que acontece quando esse serviço fica lento ou fora do ar? Sem proteção, sua aplicação também trava — um efeito cascata que pode derrubar um sistema inteiro por causa de uma única dependência externa instável.</p>","fidelityText":"Quando seu back-end depende de outro serviço (outra API, um serviço de pagamento externo), o que acontece quando esse serviço fica lento ou fora do ar? Sem proteção, sua aplicação também trava — um efeito cascata que pode derrubar um sistema inteiro por causa de uma única dependência externa instável."},{"id":"resilience-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um <strong>circuit breaker</strong> (disjuntor) funciona exatamente como o disjuntor elétrico da sua casa: se algo começa a \"puxar corrente demais\" (o serviço externo está falhando repetidamente), o disjuntor <strong>desarma</strong> — para de tentar, evitando queimar o resto do sistema. Depois de um tempo, ele testa cautelosamente se a energia normalizou antes de religar de vez.</div>","fidelityText":"Um circuit breaker (disjuntor) funciona exatamente como o disjuntor elétrico da sua casa: se algo começa a \"puxar corrente demais\" (o serviço externo está falhando repetidamente), o disjuntor desarma — para de tentar, evitando queimar o resto do sistema. Depois de um tempo, ele testa cautelosamente se a energia normalizou antes de religar de vez."},{"id":"resilience-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class PaymentExternalService {\n\n    @CircuitBreaker(name = \"service-payment\", fallbackMethod = \"paymentUnavailable\")\n    public ResponsePayment process(Payment payment) {\n        return httpClient.callServiceExternal(payment); // pode falhar, pode demorar\n    }\n\n    // chamado automaticamente quando o circuito está \"aberto\" (desarmado)\n    public ResponsePayment paymentUnavailable(Payment payment, Throwable t) {\n        return new ResponsePayment(\"UNAVAILABLE\", \"Tente again in instantes\");\n    }\n}","fidelityText":"@Service public class PagamentoExternoServico { @CircuitBreaker(name = \"servico-pagamento\", fallbackMethod = \"pagamentoIndisponivel\") public RespostaPagamento processar(Pagamento pagamento) { return clienteHttp.chamarServicoExterno(pagamento); // pode falhar, pode demorar } // chamado automaticamente quando o circuito está \"aberto\" (desarmado) public RespostaPagamento pagamentoIndisponivel(Pagamento pagamento, Throwable t) { return new RespostaPagamento(\"INDISPONIVEL\", \"Tente novamente em instantes\"); } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">PaymentExternalService</span> {\n\n    <span class=\"annotation\">@CircuitBreaker</span>(name = <span class=\"str\">\"service-payment\"</span>, fallbackMethod = <span class=\"str\">\"paymentUnavailable\"</span>)\n    <span class=\"kw\">public</span> <span class=\"cls\">ResponsePayment</span> <span class=\"fn\">process</span>(<span class=\"cls\">Payment</span> payment) {\n        <span class=\"kw\">return</span> httpClient.callServiceExternal(payment); <span class=\"com\">// pode falhar, pode demorar</span>\n    }\n\n    <span class=\"com\">// chamado automaticamente quando o circuito está \"aberto\" (desarmado)</span>\n    <span class=\"kw\">public</span> <span class=\"cls\">ResponsePayment</span> <span class=\"fn\">paymentUnavailable</span>(<span class=\"cls\">Payment</span> payment, <span class=\"cls\">Throwable</span> t) {\n        <span class=\"kw\">return new</span> <span class=\"cls\">ResponsePayment</span>(<span class=\"str\">\"UNAVAILABLE\"</span>, <span class=\"str\">\"Tente again in instantes\"</span>);\n    }\n}","caption":"Exemplo executável de resilience.","explanation":["@CircuitBreaker(name = ...) conecta o método a uma instância de configuração nomeada -- sem configuração explícita, valores padrão raramente são os certos para o domínio real.","fallbackMethod é chamado automaticamente quando o circuito está aberto, com a mesma assinatura do método original mais um Throwable no final.","O fallback deve comunicar indisponibilidade real ao chamador, não mentir dizendo que a operação teve sucesso."],"commonMistakes":["Usar fallback que mente para o usuário dizendo que a operação teve sucesso","Não declarar configuração nenhuma e assumir que os defaults do Resilience4j servem para qualquer domínio"]},{"id":"resilience-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Os três estados do circuito</h2>","fidelityText":"Os três estados do circuito"},{"id":"resilience-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Estado</th><th>Comportamento</th></tr>\n        <tr><td><strong>Fechado</strong> (normal)</td><td>Chamadas passam normalmente; falhas são contadas</td></tr>\n        <tr><td><strong>Aberto</strong></td><td>Depois de muitas falhas seguidas, para de tentar chamar o serviço real — chama o <code>fallbackMethod</code> direto, instantaneamente</td></tr>\n        <tr><td><strong>Meio-aberto</strong></td><td>Depois de um tempo, deixa passar algumas chamadas de teste para ver se o serviço voltou a funcionar</td></tr>\n      </tbody></table>","fidelityText":"EstadoComportamento Fechado (normal)Chamadas passam normalmente; falhas são contadas AbertoDepois de muitas falhas seguidas, para de tentar chamar o serviço real — chama o fallbackMethod direto, instantaneamente Meio-abertoDepois de um tempo, deixa passar algumas chamadas de teste para ver se o serviço voltou a funcionar"},{"id":"resilience-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Assim como <code>@Transactional</code> (capítulo 33) e <code>@PreAuthorize</code> (capítulo 52), <code>@CircuitBreaker</code> funciona via dynamic proxy (capítulo 20) — o método real só é chamado através de um wrapper que monitora falhas e decide se deve ou não deixar a chamada passar. É o mesmo mecanismo de interceptação reaparecendo pela quarta vez no curso, cada vez resolvendo um problema transversal diferente (transação, autorização, resiliência) sem espalhar código repetido em cada método de negócio.</div>","fidelityText":"Assim como @Transactional (capítulo 33) e @PreAuthorize (capítulo 52), @CircuitBreaker funciona via dynamic proxy (capítulo 20) — o método real só é chamado através de um wrapper que monitora falhas e decide se deve ou não deixar a chamada passar. É o mesmo mecanismo de interceptação reaparecendo pela quarta vez no curso, cada vez resolvendo um problema transversal diferente (transação, autorização, resiliência) sem espalhar código repetido em cada método de negócio."},{"id":"resilience-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Configuração real: parâmetros que decidem o comportamento, não só \"ativar\"</h2>","fidelityText":"Configuração real: parâmetros que decidem o comportamento, não só \"ativar\""},{"id":"resilience-content-9","type":"html","authorship":"legacy-preserved","html":"<p><code>@CircuitBreaker(name = \"servico-pagamento\")</code> sozinho usa valores padrão que raramente são os certos para o seu caso. O nome da instância (<code>servico-pagamento</code>) conecta a anotação a uma configuração explícita:</p>","fidelityText":"@CircuitBreaker(name = \"servico-pagamento\") sozinho usa valores padrão que raramente são os certos para o seu caso. O nome da instância (servico-pagamento) conecta a anotação a uma configuração explícita:"},{"id":"resilience-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"resilience4j:\n  circuitbreaker:\n    instances:\n      service-payment:\n        sliding-window-type: COUNT_BASED       # ou TIME_BASED (janela por tempo, não por quantidade de chamadas)\n        sliding-window-size: 10              # quantas chamadas recentes entram na janela avaliada\n        minimum-number-of-calls: 5            # não decide nada com menos de 5 chamadas na janela\n        failure-rate-threshold: 50           # % de falhas na janela que abre o circuito\n        wait-duration-in-open-state: 30s      # quanto tempo fica aberto antes de testar de novo\n        permitted-number-of-calls-in-half-open-state: 3 # chamadas de teste no estado meio-aberto","fidelityText":"resilience4j: circuitbreaker: instances: servico-pagamento: sliding-window-type: COUNT_BASED # ou TIME_BASED (janela por tempo, não por quantidade de chamadas) sliding-window-size: 10 # quantas chamadas recentes entram na janela avaliada minimum-number-of-calls: 5 # não decide nada com menos de 5 chamadas na janela failure-rate-threshold: 50 # % de falhas na janela que abre o circuito wait-duration-in-open-state: 30s # quanto tempo fica aberto antes de testar de novo permitted-number-of-calls-in-half-open-state: 3 # chamadas de teste no estado meio-aberto","highlightedHtml":"resilience4j:\n  circuitbreaker:\n    instances:\n      service-payment:\n        sliding-window-type: COUNT_BASED       <span class=\"com\"># ou TIME_BASED (janela por tempo, não por quantidade de chamadas)</span>\n        sliding-window-size: <span class=\"num\">10</span>              <span class=\"com\"># quantas chamadas recentes entram na janela avaliada</span>\n        minimum-number-of-calls: <span class=\"num\">5</span>            <span class=\"com\"># não decide nada com menos de 5 chamadas na janela</span>\n        failure-rate-threshold: <span class=\"num\">50</span>           <span class=\"com\"># % de falhas na janela que abre o circuito</span>\n        wait-duration-in-open-state: <span class=\"num\">30</span>s      <span class=\"com\"># quanto tempo fica aberto antes de testar de novo</span>\n        permitted-number-of-calls-in-half-open-state: <span class=\"num\">3</span> <span class=\"com\"># chamadas de teste no estado meio-aberto</span>","caption":"Exemplo executável de resilience.","explanation":["sliding-window-size/minimum-number-of-calls evitam abrir o circuito com uma amostra pequena demais para ser confiável (2 falhas em 2 chamadas não é 100% de falha real).","failure-rate-threshold é a métrica que de fato decide a transição para aberto, avaliada sobre a janela configurada.","wait-duration-in-open-state e permitted-number-of-calls-in-half-open-state controlam quanto tempo o circuito fica aberto e quantas chamadas de teste são permitidas antes de fechar de novo."],"commonMistakes":["Copiar os valores padrão sem medir o volume e a criticidade reais da dependência","Configurar minimum-number-of-calls maior que sliding-window-size, o que nunca permite decisão"]},{"id":"resilience-content-11","type":"html","authorship":"legacy-preserved","html":"<p><code>sliding-window-size</code>/<code>minimum-number-of-calls</code> evitam que 2 falhas em 2 chamadas (100%!) abram o circuito precocemente — a janela precisa de uma amostra mínima antes de decidir. <code>failure-rate-threshold</code> é a métrica de decisão real: com os valores acima, o circuito só abre se pelo menos 50% das últimas 10 chamadas (com no mínimo 5 avaliadas) falharem.</p>","fidelityText":"sliding-window-size/minimum-number-of-calls evitam que 2 falhas em 2 chamadas (100%!) abram o circuito precocemente — a janela precisa de uma amostra mínima antes de decidir. failure-rate-threshold é a métrica de decisão real: com os valores acima, o circuito só abre se pelo menos 50% das últimas 10 chamadas (com no mínimo 5 avaliadas) falharem."},{"id":"resilience-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Retry como padrão próprio — não é \"tentar de novo\" ingênuo</h2>","fidelityText":"Retry como padrão próprio — não é \"tentar de novo\" ingênuo"},{"id":"resilience-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Retry merece sua própria anotação e configuração, separada do circuit breaker:</p>","fidelityText":"Retry merece sua própria anotação e configuração, separada do circuit breaker:"},{"id":"resilience-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"@Retry(name = \"service-payment\", fallbackMethod = \"paymentUnavailable\")\n@CircuitBreaker(name = \"service-payment\", fallbackMethod = \"paymentUnavailable\")\npublic ResponsePayment process(Payment payment) {\n    return httpClient.callServiceExternal(payment);\n}","fidelityText":"@Retry(name = \"servico-pagamento\", fallbackMethod = \"pagamentoIndisponivel\") @CircuitBreaker(name = \"servico-pagamento\", fallbackMethod = \"pagamentoIndisponivel\") public RespostaPagamento processar(Pagamento pagamento) { return clienteHttp.chamarServicoExterno(pagamento); }","highlightedHtml":"<span class=\"annotation\">@Retry</span>(name = <span class=\"str\">\"service-payment\"</span>, fallbackMethod = <span class=\"str\">\"paymentUnavailable\"</span>)\n<span class=\"annotation\">@CircuitBreaker</span>(name = <span class=\"str\">\"service-payment\"</span>, fallbackMethod = <span class=\"str\">\"paymentUnavailable\"</span>)\n<span class=\"kw\">public</span> <span class=\"cls\">ResponsePayment</span> <span class=\"fn\">process</span>(<span class=\"cls\">Payment</span> payment) {\n    <span class=\"kw\">return</span> httpClient.callServiceExternal(payment);\n}","caption":"Exemplo executável de resilience.","explanation":["Combinar @Retry e @CircuitBreaker na mesma instância nomeada não soma os dois de forma arbitrária -- a ordem de aplicação dos decoradores é fixa (Retry por fora, CircuitBreaker por dentro).","Se o circuito já está aberto, cada tentativa do Retry é interceptada pelo CircuitBreaker antes de chegar à chamada real."],"commonMistakes":["Achar que Retry ignora o estado do circuito ou que a ordem das anotações no código muda a ordem real de aplicação"]},{"id":"resilience-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"resilience4j:\n  retry:\n    instances:\n      service-payment:\n        max-attempts: 3\n        wait-duration: 500ms\n        enable-exponential-backoff: true       # 500ms, depois ~1s, depois ~2s -- não martela o serviço já fraco\n        exponential-backoff-multiplier: 2\n        retry-exceptions:\n          - java.net.ConnectException          # falha de rede: vale tentar de novo\n        ignore-exceptions:\n          - com.exemplo.ValidationException # erro do cliente: repetir não muda nada","fidelityText":"resilience4j: retry: instances: servico-pagamento: max-attempts: 3 wait-duration: 500ms enable-exponential-backoff: true # 500ms, depois ~1s, depois ~2s -- não martela o serviço já fraco exponential-backoff-multiplier: 2 retry-exceptions: - java.net.ConnectException # falha de rede: vale tentar de novo ignore-exceptions: - com.exemplo.ValidacaoException # erro do cliente: repetir não muda nada","highlightedHtml":"resilience4j:\n  retry:\n    instances:\n      service-payment:\n        max-attempts: <span class=\"num\">3</span>\n        wait-duration: <span class=\"num\">500</span>ms\n        enable-exponential-backoff: <span class=\"kw\">true</span>       <span class=\"com\"># 500ms, depois ~1s, depois ~2s -- não martela o serviço já fraco</span>\n        exponential-backoff-multiplier: <span class=\"num\">2</span>\n        retry-exceptions:\n          - java.net.ConnectException          <span class=\"com\"># falha de rede: vale tentar de novo</span>\n        ignore-exceptions:\n          - com.exemplo.ValidationException <span class=\"com\"># erro do cliente: repetir não muda nada</span>","caption":"Exemplo executável de resilience.","explanation":["enable-exponential-backoff espaça as tentativas progressivamente (500ms, ~1s, ~2s) em vez de martelar um serviço já com problema no mesmo intervalo fixo.","retry-exceptions/ignore-exceptions classificam qual falha vale a pena repetir (rede) e qual não muda nada repetindo (erro de validação do próprio cliente)."],"commonMistakes":["Tentar de novo uma exceção de validação que vai falhar exatamente igual toda vez","Não usar backoff exponencial e martelar uma dependência já degradada no mesmo ritmo"]},{"id":"resilience-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>A ordem de aplicação dos decoradores não é a ordem das anotações no código — é fixa e documentada pelo Resilience4j:</b> <code>Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( chamada real ) ) ) ) )</code>. <code>Retry</code> é a camada mais externa. Isso importa na prática: se o circuito está <strong>aberto</strong>, cada nova tentativa do <code>Retry</code> é barrada instantaneamente pelo <code>CircuitBreaker</code> (que já está por dentro dele) — o <code>Retry</code> não ignora o circuito aberto, ele só volta a tentar vezes suficientes para <em>observar</em> que o circuito continua aberto. Sem entender essa ordem fixa, é fácil esperar comportamento diferente do que o Resilience4j realmente executa.</div>","fidelityText":"A ordem de aplicação dos decoradores não é a ordem das anotações no código — é fixa e documentada pelo Resilience4j: Retry ( CircuitBreaker ( RateLimiter ( TimeLimiter ( Bulkhead ( chamada real ) ) ) ) ). Retry é a camada mais externa. Isso importa na prática: se o circuito está aberto, cada nova tentativa do Retry é barrada instantaneamente pelo CircuitBreaker (que já está por dentro dele) — o Retry não ignora o circuito aberto, ele só volta a tentar vezes suficientes para observar que o circuito continua aberto. Sem entender essa ordem fixa, é fácil esperar comportamento diferente do que o Resilience4j realmente executa."},{"id":"resilience-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Bulkhead: isolando o dano de uma dependência lenta</h2>","fidelityText":"Bulkhead: isolando o dano de uma dependência lenta"},{"id":"resilience-content-18","type":"html","authorship":"legacy-preserved","html":"<p>Circuit breaker reage a <em>falhas</em>. Bulkhead protege contra um problema diferente: uma dependência lenta (não necessariamente falhando) que consome tantas threads/conexões simultâneas que acaba faminta o resto da aplicação — o nome vem dos compartimentos estanques de um navio: um alagamento fica contido numa seção, sem afundar o navio inteiro.</p>","fidelityText":"Circuit breaker reage a falhas. Bulkhead protege contra um problema diferente: uma dependência lenta (não necessariamente falhando) que consome tantas threads/conexões simultâneas que acaba faminta o resto da aplicação — o nome vem dos compartimentos estanques de um navio: um alagamento fica contido numa seção, sem afundar o navio inteiro."},{"id":"resilience-code-19","type":"code","authorship":"legacy-preserved","language":"java","source":"resilience4j:\n  bulkhead:\n    instances:\n      service-payment:\n        max-concurrent-calls: 10    # no máximo 10 chamadas simultâneas a este serviço\n        max-wait-duration: 0         # chamada além do limite falha na hora, não espera fila","fidelityText":"resilience4j: bulkhead: instances: servico-pagamento: max-concurrent-calls: 10 # no máximo 10 chamadas simultâneas a este serviço max-wait-duration: 0 # chamada além do limite falha na hora, não espera fila","highlightedHtml":"resilience4j:\n  bulkhead:\n    instances:\n      service-payment:\n        max-concurrent-calls: <span class=\"num\">10</span>    <span class=\"com\"># no máximo 10 chamadas simultâneas a este serviço</span>\n        max-wait-duration: <span class=\"num\">0</span>         <span class=\"com\"># chamada além do limite falha na hora, não espera fila</span>","caption":"Exemplo executável de resilience.","explanation":["Bulkhead limita quantas chamadas simultâneas uma dependência específica pode consumir, para que ela não esgote threads/conexões que o resto da aplicação precisa.","max-wait-duration = 0 significa que uma chamada além do limite falha imediatamente, em vez de esperar numa fila."],"commonMistakes":["Confundir Bulkhead (concorrência) com Rate Limiter (taxa por período) -- resolvem problemas diferentes"]},{"id":"resilience-content-20","type":"html","authorship":"legacy-preserved","html":"<p>Existem duas implementações: <code>SemaphoreBulkhead</code> (o padrão, mostrado acima — limita concorrência sem trocar de thread) e <code>ThreadPoolBulkhead</code> (isola a chamada num pool de threads dedicado e separado do resto da aplicação, com <code>max-thread-pool-size</code>/<code>core-thread-pool-size</code>/<code>queue-capacity</code> próprios). Use <code>ThreadPoolBulkhead</code> quando quiser que uma dependência lenta nunca consiga consumir threads que o resto da aplicação precisa.</p>","fidelityText":"Existem duas implementações: SemaphoreBulkhead (o padrão, mostrado acima — limita concorrência sem trocar de thread) e ThreadPoolBulkhead (isola a chamada num pool de threads dedicado e separado do resto da aplicação, com max-thread-pool-size/core-thread-pool-size/queue-capacity próprios). Use ThreadPoolBulkhead quando quiser que uma dependência lenta nunca consiga consumir threads que o resto da aplicação precisa."},{"id":"resilience-content-21","type":"html","authorship":"legacy-preserved","html":"<h2>Rate Limiter: protegendo o outro lado (e sua própria cota) de você mesmo</h2>","fidelityText":"Rate Limiter: protegendo o outro lado (e sua própria cota) de você mesmo"},{"id":"resilience-content-22","type":"html","authorship":"legacy-preserved","html":"<p>Bulkhead limita <strong>concorrência</strong> (quantas chamadas simultâneas); Rate Limiter limita <strong>taxa</strong> (quantas chamadas por período de tempo, mesmo que nenhuma seja concorrente). É a diferença entre \"não deixo 10 pessoas entrarem juntas\" e \"não deixo mais de 100 pessoas entrarem por hora\" — proteções diferentes, para problemas diferentes. Rate Limiter é essencial quando a API externa tem cota contratual (ex.: 100 requisições/minuto) e estourá-la gera erro 429 ou até suspensão da conta:</p>","fidelityText":"Bulkhead limita concorrência (quantas chamadas simultâneas); Rate Limiter limita taxa (quantas chamadas por período de tempo, mesmo que nenhuma seja concorrente). É a diferença entre \"não deixo 10 pessoas entrarem juntas\" e \"não deixo mais de 100 pessoas entrarem por hora\" — proteções diferentes, para problemas diferentes. Rate Limiter é essencial quando a API externa tem cota contratual (ex.: 100 requisições/minuto) e estourá-la gera erro 429 ou até suspensão da conta:"},{"id":"resilience-code-23","type":"code","authorship":"legacy-preserved","language":"java","source":"resilience4j:\n  ratelimiter:\n    instances:\n      service-payment:\n        limit-for-period: 10        # no máximo 10 chamadas por período\n        limit-refresh-period: 1s      # o período se renova a cada 1 segundo\n        timeout-duration: 0           # chamada além do limite falha na hora (RequestNotPermitted)","fidelityText":"resilience4j: ratelimiter: instances: servico-pagamento: limit-for-period: 10 # no máximo 10 chamadas por período limit-refresh-period: 1s # o período se renova a cada 1 segundo timeout-duration: 0 # chamada além do limite falha na hora (RequestNotPermitted)","highlightedHtml":"resilience4j:\n  ratelimiter:\n    instances:\n      service-payment:\n        limit-for-period: <span class=\"num\">10</span>        <span class=\"com\"># no máximo 10 chamadas por período</span>\n        limit-refresh-period: <span class=\"num\">1</span>s      <span class=\"com\"># o período se renova a cada 1 segundo</span>\n        timeout-duration: <span class=\"num\">0</span>           <span class=\"com\"># chamada além do limite falha na hora (RequestNotPermitted)</span>","caption":"Exemplo executável de resilience.","explanation":["Rate Limiter limita quantas chamadas acontecem por período de tempo, mesmo que nenhuma seja concorrente -- protege contra estourar cota contratual de uma API externa.","timeout-duration = 0 significa que uma chamada além do limite falha imediatamente com RequestNotPermitted, em vez de esperar a próxima janela."],"commonMistakes":["Usar Bulkhead quando o problema real é cota de chamadas por minuto, não concorrência"]},{"id":"resilience-content-24","type":"html","authorship":"legacy-preserved","html":"<h2>Classificação de falha: nem toda exceção deveria contar contra o circuito</h2>","fidelityText":"Classificação de falha: nem toda exceção deveria contar contra o circuito"},{"id":"resilience-content-25","type":"html","authorship":"legacy-preserved","html":"<p>Se <code>@CircuitBreaker</code> conta <em>qualquer</em> exceção como falha, um cliente enviando dados inválidos repetidamente (erro do <strong>cliente</strong>, não do serviço externo) pode abrir o circuito para todo mundo — um efeito colateral injusto e perigoso. <code>ignore-exceptions</code> (visto na configuração de Retry acima) também se aplica ao circuit breaker: exceções de validação de domínio não deveriam contar como sinal de que o serviço externo está com problema. Reserve a contagem de falha para o que de fato indica indisponibilidade real: timeout, erro de conexão, <code>5xx</code> do serviço remoto.</p>","fidelityText":"Se @CircuitBreaker conta qualquer exceção como falha, um cliente enviando dados inválidos repetidamente (erro do cliente, não do serviço externo) pode abrir o circuito para todo mundo — um efeito colateral injusto e perigoso. ignore-exceptions (visto na configuração de Retry acima) também se aplica ao circuit breaker: exceções de validação de domínio não deveriam contar como sinal de que o serviço externo está com problema. Reserve a contagem de falha para o que de fato indica indisponibilidade real: timeout, erro de conexão, 5xx do serviço remoto."},{"id":"resilience-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>Composição na prática</h2>","fidelityText":"Composição na prática"},{"id":"resilience-code-27","type":"code","authorship":"legacy-preserved","language":"java","source":"@Retry(name = \"service-payment\")\n@CircuitBreaker(name = \"service-payment\", fallbackMethod = \"paymentUnavailable\")\n@Bulkhead(name = \"service-payment\")\npublic ResponsePayment process(Payment payment) {\n    return httpClient.callServiceExternal(payment);\n}","fidelityText":"@Retry(name = \"servico-pagamento\") @CircuitBreaker(name = \"servico-pagamento\", fallbackMethod = \"pagamentoIndisponivel\") @Bulkhead(name = \"servico-pagamento\") public RespostaPagamento processar(Pagamento pagamento) { return clienteHttp.chamarServicoExterno(pagamento); }","highlightedHtml":"<span class=\"annotation\">@Retry</span>(name = <span class=\"str\">\"service-payment\"</span>)\n<span class=\"annotation\">@CircuitBreaker</span>(name = <span class=\"str\">\"service-payment\"</span>, fallbackMethod = <span class=\"str\">\"paymentUnavailable\"</span>)\n<span class=\"annotation\">@Bulkhead</span>(name = <span class=\"str\">\"service-payment\"</span>)\n<span class=\"kw\">public</span> <span class=\"cls\">ResponsePayment</span> <span class=\"fn\">process</span>(<span class=\"cls\">Payment</span> payment) {\n    <span class=\"kw\">return</span> httpClient.callServiceExternal(payment);\n}","caption":"Exemplo executável de resilience.","explanation":["Retry, CircuitBreaker e Bulkhead na mesma instância nomeada resolvem três problemas diferentes (falha passageira, indisponibilidade persistente, esgotamento de recursos) e não são redundantes entre si.","Nem todo método precisa dos três -- a escolha deveria vir do problema real observado, não de aplicar tudo por precaução."],"commonMistakes":["Empilhar os três padrões em todo método sem entender qual problema cada um resolve"]},{"id":"resilience-content-28","type":"html","authorship":"legacy-preserved","html":"<p>Combinar os três não é redundância — cada um resolve um problema diferente: <code>Bulkhead</code> evita que essa dependência específica esgote threads/conexões do resto da aplicação; <code>CircuitBreaker</code> para de insistir quando fica claro que o serviço está indisponível; <code>Retry</code> absorve falhas passageiras antes de qualquer um dos outros dois entrar em ação. Nem todo método precisa dos três — comece pelo que resolve o problema real observado (ver seção de observabilidade a seguir), não por \"aplicar tudo por precaução\".</p>","fidelityText":"Combinar os três não é redundância — cada um resolve um problema diferente: Bulkhead evita que essa dependência específica esgote threads/conexões do resto da aplicação; CircuitBreaker para de insistir quando fica claro que o serviço está indisponível; Retry absorve falhas passageiras antes de qualquer um dos outros dois entrar em ação. Nem todo método precisa dos três — comece pelo que resolve o problema real observado (ver seção de observabilidade a seguir), não por \"aplicar tudo por precaução\"."},{"id":"resilience-content-29","type":"html","authorship":"legacy-preserved","html":"<h2>Observabilidade: veja o estado do circuito, não adivinhe</h2>","fidelityText":"Observabilidade: veja o estado do circuito, não adivinhe"},{"id":"resilience-content-30","type":"html","authorship":"legacy-preserved","html":"<p>Resilience4j publica métricas via Micrometer, expostas pelo Actuator (capítulo de Observabilidade aprofunda o Actuator em si):</p>","fidelityText":"Resilience4j publica métricas via Micrometer, expostas pelo Actuator (capítulo de Observabilidade aprofunda o Actuator em si):"},{"id":"resilience-code-31","type":"code","authorship":"legacy-preserved","language":"java","source":"management:\n  endpoints:\n    web:\n      exposure:\n        include: health,circuitbreakers,retries,bulkheads,ratelimiters,metrics","fidelityText":"management: endpoints: web: exposure: include: health,circuitbreakers,retries,bulkheads,ratelimiters,metrics","highlightedHtml":"management:\n  endpoints:\n    web:\n      exposure:\n        include: health,circuitbreakers,retries,bulkheads,ratelimiters,metrics","caption":"Exemplo executável de resilience.","explanation":["management.endpoints.web.exposure.include precisa listar explicitamente os endpoints do Resilience4j (circuitbreakers, retries, bulkheads, ratelimiters) além de health -- eles não ficam expostos por padrão.","Com isso, /actuator/circuitbreakers mostra o estado atual de cada instância nomeada, e métricas ficam disponíveis via Micrometer para dashboards."],"commonMistakes":["Assumir que os endpoints de resiliência aparecem automaticamente sem declarar exposure.include","Descobrir um circuito preso em aberto só por reclamação de usuário, sem monitorar o estado"]},{"id":"resilience-content-32","type":"html","authorship":"legacy-preserved","html":"<p>Com isso, <code>/actuator/circuitbreakers</code> lista o estado atual de cada instância nomeada, e métricas como <code>resilience4j.circuitbreaker.calls</code>/<code>resilience4j.circuitbreaker.state</code> ficam disponíveis para dashboards. Descobrir que um circuito está preso em \"aberto\" olhando um dashboard é muito mais rápido do que descobrir isso por reclamação de usuário.</p>","fidelityText":"Com isso, /actuator/circuitbreakers lista o estado atual de cada instância nomeada, e métricas como resilience4j.circuitbreaker.calls/resilience4j.circuitbreaker.state ficam disponíveis para dashboards. Descobrir que um circuito está preso em \"aberto\" olhando um dashboard é muito mais rápido do que descobrir isso por reclamação de usuário."},{"id":"resilience-content-33","type":"html","authorship":"legacy-preserved","html":"<h2>Testando resiliência: forçando o circuito a abrir sem esperar falhas reais</h2>","fidelityText":"Testando resiliência: forçando o circuito a abrir sem esperar falhas reais"},{"id":"resilience-code-34","type":"code","authorship":"legacy-preserved","language":"java","source":"@Test\nvoid shouldCallFallbackWhenCircuitOpen() {\n    CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(\"service-payment\");\n    circuitBreaker.transitionToOpenState(); // muda o estado direto -- o teste não precisa de falhas reais\n\n    ResponsePayment response = paymentService.process(payment);\n\n    assertThat(response.status()).isEqualTo(\"UNAVAILABLE\"); // fallback foi chamado, não o serviço real\n}","fidelityText":"@Test void deveChamarFallbackQuandoCircuitoAberto() { CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(\"servico-pagamento\"); circuitBreaker.transitionToOpenState(); // muda o estado direto -- o teste não precisa de falhas reais RespostaPagamento resposta = pagamentoServico.processar(pagamento); assertThat(resposta.status()).isEqualTo(\"INDISPONIVEL\"); // fallback foi chamado, não o serviço real }","highlightedHtml":"<span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldCallFallbackWhenCircuitOpen</span>() {\n    <span class=\"cls\">CircuitBreaker</span> circuitBreaker = circuitBreakerRegistry.circuitBreaker(<span class=\"str\">\"service-payment\"</span>);\n    circuitBreaker.transitionToOpenState(); <span class=\"com\">// muda o estado direto -- o teste não precisa de falhas reais</span>\n\n    <span class=\"cls\">ResponsePayment</span> response = paymentService.process(payment);\n\n    assertThat(response.status()).isEqualTo(<span class=\"str\">\"UNAVAILABLE\"</span>); <span class=\"com\">// fallback foi chamado, não o serviço real</span>\n}","caption":"Exemplo executável de resilience.","explanation":["CircuitBreakerRegistry.circuitBreaker(nome) obtém a mesma instância nomeada usada pela anotação, permitindo inspecionar ou forçar seu estado no teste.","transitionToOpenState() torna o teste determinístico -- não depende de gerar falhas reais repetidas para observar o circuito aberto."],"commonMistakes":["Tentar testar o estado aberto disparando falhas reais repetidas em vez de forçar o estado via registry"]},{"id":"resilience-content-35","type":"html","authorship":"legacy-preserved","html":"<p><code>CircuitBreakerRegistry</code> permite forçar o estado diretamente (<code>transitionToOpenState()</code>/<code>transitionToClosedState()</code>) — um teste determinístico não deveria depender de gerar falhas reais repetidas só para observar o circuito abrir.</p>","fidelityText":"CircuitBreakerRegistry permite forçar o estado diretamente (transitionToOpenState()/transitionToClosedState()) — um teste determinístico não deveria depender de gerar falhas reais repetidas só para observar o circuito abrir."},{"id":"resilience-content-36","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não confunda circuit breaker com retry simples. Retry tenta de novo assumindo que a falha é passageira; circuit breaker existe justamente para <strong>parar</strong> de tentar quando fica claro que o problema é persistente — tentar repetidamente contra um serviço já sobrecarregado só piora a situação dele. Comece configurando um só padrão (geralmente circuit breaker) com valores conservadores, meça o comportamento real em produção, e só então adicione retry/bulkhead/rate limiter conforme o problema observado justificar — não os quatro de uma vez em todo método.</div>","fidelityText":"Não confunda circuit breaker com retry simples. Retry tenta de novo assumindo que a falha é passageira; circuit breaker existe justamente para parar de tentar quando fica claro que o problema é persistente — tentar repetidamente contra um serviço já sobrecarregado só piora a situação dele. Comece configurando um só padrão (geralmente circuit breaker) com valores conservadores, meça o comportamento real em produção, e só então adicione retry/bulkhead/rate limiter conforme o problema observado justificar — não os quatro de uma vez em todo método."},{"id":"resilience-exercise-37","type":"exercise","authorship":"legacy-preserved","title":"Exercício 55.1 — Protegendo uma chamada externa","prompt":"Escreva uma classe ServicoFrete com um método calcularFrete(String cep) anotado com @CircuitBreaker, simulando uma chamada a uma API externa de frete. Escreva o método de fallback correspondente, retornando um valor de frete padrão fixo quando o serviço externo estiver indisponível.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 55.1 — Protegendo uma chamada externamédio Escreva uma classe ServicoFrete com um método calcularFrete(String cep) anotado com @CircuitBreaker, simulando uma chamada a uma API externa de frete. Escreva o método de fallback correspondente, retornando um valor de frete padrão fixo quando o serviço externo estiver indisponível. Ver solução @Service public class ServicoFrete { @CircuitBreaker(name = \"servico-frete\", fallbackMethod = \"freteFallback\") public double calcularFrete(String cep) { return clienteHttp.consultarFreteExterno(cep); // pode lançar exceção ou demorar demais } public double freteFallback(String cep, Throwable t) { return 25.00; // valor padrão -- melhor que travar o checkout inteiro } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 55.1 — Protegendo uma chamada externa</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva uma classe <code>ServicoFrete</code> com um método <code>calcularFrete(String cep)</code> anotado com <code>@CircuitBreaker</code>, simulando uma chamada a uma API externa de frete. Escreva o método de fallback correspondente, retornando um valor de frete padrão fixo quando o serviço externo estiver indisponível.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">ServiceShipping</span> {\n\n    <span class=\"annotation\">@CircuitBreaker</span>(name = <span class=\"str\">\"service-shipping\"</span>, fallbackMethod = <span class=\"str\">\"shippingFallback\"</span>)\n    <span class=\"kw\">public</span> <span class=\"kw\">double</span> <span class=\"fn\">calculateShipping</span>(<span class=\"kw\">String</span> cep) {\n        <span class=\"kw\">return</span> httpClient.queryShippingExternal(cep); <span class=\"com\">// pode lançar exceção ou demorar demais</span>\n    }\n\n    <span class=\"kw\">public double</span> <span class=\"fn\">shippingFallback</span>(<span class=\"kw\">String</span> cep, <span class=\"cls\">Throwable</span> t) {\n        <span class=\"kw\">return</span> 25.00; <span class=\"com\">// valor padrão -- melhor que travar o checkout inteiro</span>\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"resilience-exercise-38","type":"exercise","authorship":"legacy-preserved","title":"Exercício 55.2 — Por que o Retry não \"furou\" o circuito aberto","prompt":"ServicoFrete.calcularFrete está anotado com @Retry(name = \"servico-frete\", maxAttempts = 3) e @CircuitBreaker(name = \"servico-frete\"). O circuito já está aberto (falhas recentes ultrapassaram o threshold). Um novo pedido chama calcularFrete. Quantas vezes o serviço HTTP real de frete é efetivamente chamado, e por quê?","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 55.2 — Por que o Retry não \"furou\" o circuito abertodifícil ServicoFrete.calcularFrete está anotado com @Retry(name = \"servico-frete\", maxAttempts = 3) e @CircuitBreaker(name = \"servico-frete\"). O circuito já está aberto (falhas recentes ultrapassaram o threshold). Um novo pedido chama calcularFrete. Quantas vezes o serviço HTTP real de frete é efetivamente chamado, e por quê? Ver solução Zero vezes. A ordem fixa de decoradores do Resilience4j coloca Retry por fora e CircuitBreaker por dentro dele (Retry(CircuitBreaker(...))). Com o circuito aberto, cada uma das até 3 tentativas do Retry é interceptada pelo CircuitBreaker, que devolve CallNotPermittedException instantaneamente, sem nunca chegar a chamar clienteHttp.consultarFreteExterno(cep). O fallback é chamado ao final das tentativas esgotadas, mas o serviço externo real nunca é tocado enquanto o circuito estiver aberto.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 55.2 — Por que o Retry não \"furou\" o circuito aberto</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p><code>ServicoFrete.calcularFrete</code> está anotado com <code>@Retry(name = \"servico-frete\", maxAttempts = 3)</code> e <code>@CircuitBreaker(name = \"servico-frete\")</code>. O circuito já está aberto (falhas recentes ultrapassaram o threshold). Um novo pedido chama <code>calcularFrete</code>. Quantas vezes o serviço HTTP real de frete é efetivamente chamado, e por quê?</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>Zero vezes.</strong> A ordem fixa de decoradores do Resilience4j coloca <code>Retry</code> por fora e <code>CircuitBreaker</code> por dentro dele (<code>Retry(CircuitBreaker(...))</code>). Com o circuito aberto, cada uma das até 3 tentativas do <code>Retry</code> é interceptada pelo <code>CircuitBreaker</code>, que devolve <code>CallNotPermittedException</code> instantaneamente, sem nunca chegar a chamar <code>clienteHttp.consultarFreteExterno(cep)</code>. O fallback é chamado ao final das tentativas esgotadas, mas o serviço externo real nunca é tocado enquanto o circuito estiver aberto.</p>\n        </div>\n      </div>"},{"id":"resilience-quiz","type":"quiz","authorship":"authored","conceptId":"circuit-breaker-state-machine","prompt":"O que um circuit breaker aberto faz?","options":[{"id":"res-a","label":"Evita novas chamadas reais por um período/política e pode devolver fallback ou erro controlado.","correct":true,"explanation":"Ele limita pressão na dependência e no chamador."},{"id":"res-b","label":"Repara automaticamente o serviço remoto.","correct":false,"explanation":"Ele não conserta a dependência; só controla comportamento do chamador."},{"id":"res-c","label":"Garante que nenhuma requisição falhe para o usuário.","correct":false,"explanation":"Fallback pode falhar ou ser semanticamente inadequado."}]}],"resources":[{"id":"resilience4j-circuitbreaker","type":"reference","title":"Resilience4j CircuitBreaker guide","url":"https://resilience4j.readme.io/docs/circuitbreaker","reinforces":"Estados, métricas e configuração de circuit breaker.","language":"en","publisher":"Resilience4j","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"aws-retries-backoff","type":"reference","title":"AWS Builders Library: Timeouts, retries, and backoff with jitter","url":"https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/","reinforces":"Risco de retry, backoff, jitter e timeout em sistemas reais.","language":"en","publisher":"AWS","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the resilience flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for resilience. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for resilience with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"@Service","instruction":"Design the resilience flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for resilience with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"webclient","moduleId":"synchronous-integration","order":0,"title":"WebClient, RestClient & RestTemplate — consumindo APIs externas","summary":"Sua API não vive isolada — frequentemente ela mesma precisa consumir outra API (serviço de frete, gateway de pagamento, API de terceiros). O capítulo 62 mostrou o front-end fazendo isso com fetch/axios; este capítulo mostra o back-end Spring fazendo o equivalente.","objectives":["Escolher WebClient ou RestTemplate pelo contexto","Configurar timeout e tratamento de erro explícitos","Mapear DTO externo sem contaminar domínio interno","Preparar cliente para políticas de resiliência ensinadas depois"],"whyItExists":"Depois de HTTP puro, JSON, contratos e API Spring, o aluno pode consumir APIs com ferramentas do ecossistema Spring. O capítulo não começa por circuit breaker: primeiro vem chamada, contrato, erro e limite de tempo.","prerequisiteChapterIds":["http","java-httpclient-json","unreliable-api-client","spring-mvc"],"conceptIds":["webclient-cliente-nao-bloqueante-e-reativo","resttemplate-o-cliente-classico-ainda-comum-em-codigo-legado","restclient-a-opcao-moderna-para-mvc-sincrono-na-pratica","request-factories-e-connection-pooling-quem-realmente-abre-a-conexao-tcp","interceptors-injetando-comportamento-transversal-em-toda-chamada","status-handling-decidir-o-que-4xx-5xx-significam-para-o-seu-dominio","testando-sem-depender-do-servico-externo-real"],"introducedConceptIds":["spring-webclient-reactive-client","resttemplate-legacy-client"],"usedConceptIds":["httpclient-reuso-timeout","timeout-deadline-cancelamento-http","json-formato-contrato","dto-mapping-fronteira"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"webclient-intuition","type":"intuition","authorship":"authored","title":"Cliente HTTP é fronteira, não atalho para chamar método remoto","body":"Ao consumir outra API, você atravessa rede, contrato, latência e falha parcial. WebClient e RestTemplate só organizam a chamada; eles não removem timeout, parsing, status, autenticação nem decisão de fallback.","analogyLimit":"Parece chamada de método, mas o outro lado pode atrasar, mudar, responder erro ou nem responder."},{"id":"webclient-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; REST</a>, <a class=\"prereq-tag\" href=\"#resilience\">55 · Resilience4j</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~2h30 de estudo + prática Pré-requisitos: 26 · HTTP & REST, 55 · Resilience4j"},{"id":"webclient-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Sua API não vive isolada — frequentemente ela mesma precisa consumir <strong>outra</strong> API (serviço de frete, gateway de pagamento, API de terceiros). O capítulo 62 mostrou o front-end fazendo isso com <code>fetch</code>/axios; este capítulo mostra o back-end Spring fazendo o equivalente.</p>","fidelityText":"Sua API não vive isolada — frequentemente ela mesma precisa consumir outra API (serviço de frete, gateway de pagamento, API de terceiros). O capítulo 62 mostrou o front-end fazendo isso com fetch/axios; este capítulo mostra o back-end Spring fazendo o equivalente."},{"id":"webclient-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>WebClient — cliente não bloqueante e reativo</h2>","fidelityText":"WebClient — cliente não bloqueante e reativo"},{"id":"webclient-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class CepService {\n    private final WebClient webClient;\n\n    public CepService(WebClient.Builder builder) {\n        this.webClient = builder.baseUrl(\"https://viacep.com.br\").build();\n    }\n\n    @CircuitBreaker(name = \"cep-service\", fallbackMethod = \"addressFallback\") // capítulo 55!\n    public AddressDTO findAddress(String cep) {\n        return webClient.get()\n            .uri(\"/ws/{cep}/json\", cep)\n            .retrieve()\n            .bodyToMono(AddressDTO.class)\n            .block(); // versão síncrona simples -- .block() espera o resultado\n    }\n\n    public AddressDTO addressFallback(String cep, Throwable t) {\n        return new AddressDTO(\"Address unavailable\");\n    }\n}","fidelityText":"@Service public class CepServico { private final WebClient webClient; public CepServico(WebClient.Builder builder) { this.webClient = builder.baseUrl(\"https://viacep.com.br\").build(); } @CircuitBreaker(name = \"cep-servico\", fallbackMethod = \"enderecoFallback\") // capítulo 55! public EnderecoDTO buscarEndereco(String cep) { return webClient.get() .uri(\"/ws/{cep}/json\", cep) .retrieve() .bodyToMono(EnderecoDTO.class) .block(); // versão síncrona simples -- .block() espera o resultado } public EnderecoDTO enderecoFallback(String cep, Throwable t) { return new EnderecoDTO(\"Endereço indisponível\"); } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">CepService</span> {\n    <span class=\"kw\">private final</span> WebClient webClient;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">CepService</span>(WebClient.Builder builder) {\n        <span class=\"kw\">this</span>.webClient = builder.baseUrl(<span class=\"str\">\"https://viacep.com.br\"</span>).build();\n    }\n\n    <span class=\"annotation\">@CircuitBreaker</span>(name = <span class=\"str\">\"cep-service\"</span>, fallbackMethod = <span class=\"str\">\"addressFallback\"</span>) <span class=\"com\">// capítulo 55!</span>\n    <span class=\"kw\">public</span> <span class=\"cls\">AddressDTO</span> <span class=\"fn\">findAddress</span>(<span class=\"kw\">String</span> cep) {\n        <span class=\"kw\">return</span> webClient.get()\n            .uri(<span class=\"str\">\"/ws/{cep}/json\"</span>, cep)\n            .retrieve()\n            .bodyToMono(<span class=\"cls\">AddressDTO</span>.<span class=\"kw\">class</span>)\n            .block(); <span class=\"com\">// versão síncrona simples -- .block() espera o resultado</span>\n    }\n\n    <span class=\"kw\">public</span> <span class=\"cls\">AddressDTO</span> <span class=\"fn\">addressFallback</span>(<span class=\"kw\">String</span> cep, <span class=\"cls\">Throwable</span> t) {\n        <span class=\"kw\">return new</span> <span class=\"cls\">AddressDTO</span>(<span class=\"str\">\"Address unavailable\"</span>);\n    }\n}","caption":"Exemplo executável de webclient.","explanation":["O WebClient monta uma requisição HTTP tipada e permite configurar tratamento de status e corpo.","Em código de produção, timeout e mapeamento de erro devem estar visíveis no contrato do cliente."],"commonMistakes":["Usar retrieve sem tratar status relevante","Bloquear indiscriminadamente sem entender executor/contexto"]},{"id":"webclient-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>RestTemplate — o cliente clássico, ainda comum em código legado</h2>","fidelityText":"RestTemplate — o cliente clássico, ainda comum em código legado"},{"id":"webclient-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"@Bean\npublic RestTemplate restTemplate() { return new RestTemplate(); }\n\n// uso:\nAddressDTO address = restTemplate.getForObject(\n    \"https://viacep.com.br/ws/{cep}/json\", AddressDTO.class, cep);","fidelityText":"@Bean public RestTemplate restTemplate() { return new RestTemplate(); } // uso: EnderecoDTO endereco = restTemplate.getForObject( \"https://viacep.com.br/ws/{cep}/json\", EnderecoDTO.class, cep);","highlightedHtml":"<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> RestTemplate <span class=\"fn\">restTemplate</span>() { <span class=\"kw\">return new</span> RestTemplate(); }\n\n<span class=\"com\">// uso:</span>\n<span class=\"cls\">AddressDTO</span> address = restTemplate.getForObject(\n    <span class=\"str\">\"https://viacep.com.br/ws/{cep}/json\"</span>, <span class=\"cls\">AddressDTO</span>.<span class=\"kw\">class</span>, cep);","caption":"Exemplo executável de webclient.","explanation":["RestTemplate aparece para manutenção de sistemas existentes e clientes bloqueantes simples.","Ser legado não significa errado, mas novas integrações Spring tendem a preferir WebClient ou clientes declarativos atuais."],"commonMistakes":["Migrar só por moda","Achar que bloqueante dispensa timeout"]},{"id":"webclient-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Escolha pelo modelo da aplicação e pela versão:</b> em Spring Framework 7, <code>RestTemplate</code> está deprecated em favor do <code>RestClient</code> síncrono. Use <code>WebClient</code> quando o fluxo for não bloqueante, reativo ou streaming. Em Spring Framework 6, <code>RestTemplate</code> permanece comum em legado, mas <code>RestClient</code> oferece a API síncrona moderna.</div>","fidelityText":"Escolha pelo modelo da aplicação e pela versão: em Spring Framework 7, RestTemplate está deprecated em favor do RestClient síncrono. Use WebClient quando o fluxo for não bloqueante, reativo ou streaming. Em Spring Framework 6, RestTemplate permanece comum em legado, mas RestClient oferece a API síncrona moderna."},{"id":"webclient-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Se o fluxo síncrono tradicional (<code>@RestController</code> chamando <code>@Service</code> chamando o banco, capítulo 44) é uma conversa cara a cara, uma chamada via <code>WebClient</code> para uma API externa é uma ligação telefônica para um fornecedor terceiro — mais lenta, sujeita a falhar por motivos fora do seu controle, e por isso <strong>sempre</strong> deveria estar protegida por circuit breaker (capítulo 55), timeout e, quando fizer sentido, cache (capítulo 71).</div>","fidelityText":"Se o fluxo síncrono tradicional (@RestController chamando @Service chamando o banco, capítulo 44) é uma conversa cara a cara, uma chamada via WebClient para uma API externa é uma ligação telefônica para um fornecedor terceiro — mais lenta, sujeita a falhar por motivos fora do seu controle, e por isso sempre deveria estar protegida por circuit breaker (capítulo 55), timeout e, quando fizer sentido, cache (capítulo 71)."},{"id":"webclient-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>WebClient</code> retorna <code>Mono&lt;T&gt;</code> ou <code>Flux&lt;T&gt;</code> e preserva I/O não bloqueante enquanto a cadeia inteira permanece reativa. Chamar <code>.block()</code> transforma a fronteira em síncrona e não deve ocorrer em uma thread de event loop. Para uma aplicação MVC síncrona, <code>RestClient</code> costuma comunicar melhor a intenção; para WebFlux, componha o <code>Mono</code> sem bloquear.</div>","fidelityText":"WebClient retorna Mono<T> ou Flux<T> e preserva I/O não bloqueante enquanto a cadeia inteira permanece reativa. Chamar .block() transforma a fronteira em síncrona e não deve ocorrer em uma thread de event loop. Para uma aplicação MVC síncrona, RestClient costuma comunicar melhor a intenção; para WebFlux, componha o Mono sem bloquear."},{"id":"webclient-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>RestClient — a opção moderna para MVC síncrono, na prática</h2>","fidelityText":"RestClient — a opção moderna para MVC síncrono, na prática"},{"id":"webclient-content-11","type":"html","authorship":"legacy-preserved","html":"<p>As duas seções acima já disseram que <code>RestClient</code> costuma comunicar melhor a intenção numa aplicação MVC síncrona — mas dizer isso sem mostrar código deixa a recomendação abstrata. Aqui está o mesmo <code>CepServico</code> com <code>RestClient</code>:</p>","fidelityText":"As duas seções acima já disseram que RestClient costuma comunicar melhor a intenção numa aplicação MVC síncrona — mas dizer isso sem mostrar código deixa a recomendação abstrata. Aqui está o mesmo CepServico com RestClient:"},{"id":"webclient-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class CepService {\n    private final RestClient restClient;\n\n    public CepService(RestClient.Builder builder) {\n        this.restClient = builder.baseUrl(\"https://viacep.com.br\").build();\n    }\n\n    @CircuitBreaker(name = \"cep-service\", fallbackMethod = \"addressFallback\") // capítulo 55!\n    public AddressDTO findAddress(String cep) {\n        return restClient.get()\n            .uri(\"/ws/{cep}/json\", cep)\n            .retrieve()\n            .body(AddressDTO.class); // já é síncrono -- sem .block(), sem Mono\n    }\n\n    public AddressDTO addressFallback(String cep, Throwable t) {\n        return new AddressDTO(\"Address unavailable\");\n    }\n}","fidelityText":"@Service public class CepServico { private final RestClient restClient; public CepServico(RestClient.Builder builder) { this.restClient = builder.baseUrl(\"https://viacep.com.br\").build(); } @CircuitBreaker(name = \"cep-servico\", fallbackMethod = \"enderecoFallback\") // capítulo 55! public EnderecoDTO buscarEndereco(String cep) { return restClient.get() .uri(\"/ws/{cep}/json\", cep) .retrieve() .body(EnderecoDTO.class); // já é síncrono -- sem .block(), sem Mono } public EnderecoDTO enderecoFallback(String cep, Throwable t) { return new EnderecoDTO(\"Endereço indisponível\"); } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">CepService</span> {\n    <span class=\"kw\">private final</span> RestClient restClient;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">CepService</span>(RestClient.Builder builder) {\n        <span class=\"kw\">this</span>.restClient = builder.baseUrl(<span class=\"str\">\"https://viacep.com.br\"</span>).build();\n    }\n\n    <span class=\"annotation\">@CircuitBreaker</span>(name = <span class=\"str\">\"cep-service\"</span>, fallbackMethod = <span class=\"str\">\"addressFallback\"</span>) <span class=\"com\">// capítulo 55!</span>\n    <span class=\"kw\">public</span> <span class=\"cls\">AddressDTO</span> <span class=\"fn\">findAddress</span>(<span class=\"kw\">String</span> cep) {\n        <span class=\"kw\">return</span> restClient.get()\n            .uri(<span class=\"str\">\"/ws/{cep}/json\"</span>, cep)\n            .retrieve()\n            .body(<span class=\"cls\">AddressDTO</span>.<span class=\"kw\">class</span>); <span class=\"com\">// já é síncrono -- sem .block(), sem Mono</span>\n    }\n\n    <span class=\"kw\">public</span> <span class=\"cls\">AddressDTO</span> <span class=\"fn\">addressFallback</span>(<span class=\"kw\">String</span> cep, <span class=\"cls\">Throwable</span> t) {\n        <span class=\"kw\">return new</span> <span class=\"cls\">AddressDTO</span>(<span class=\"str\">\"Address unavailable\"</span>);\n    }\n}","caption":"Exemplo executável de webclient.","explanation":["RestClient usa o mesmo formato fluente do WebClient (.get().uri(...).retrieve()), mas .body(Class) já devolve o objeto direto -- não existe Mono nem .block() porque a API nunca foi reativa.","Para um @RestController tradicional (não WebFlux), RestClient expressa a intenção real do código sem carregar a bagagem reativa que a aplicação não usa."],"commonMistakes":["Usar WebClient + .block() em uma aplicação inteiramente síncrona só por hábito, quando RestClient comunica melhor a intenção"]},{"id":"webclient-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Mesmo formato fluente do <code>WebClient</code> (<code>.get().uri(...).retrieve()</code>), mas <code>.body(Class)</code> já devolve o objeto direto — não existe <code>Mono</code> nem <code>.block()</code> nessa API porque ela nunca foi reativa: é síncrona desde a primeira chamada, disponível desde o Spring Framework 6.1. Para um <code>@RestController</code> tradicional (não WebFlux), essa é a opção que expressa a intenção real do código sem carregar a bagagem reativa que a aplicação não usa.</p>","fidelityText":"Mesmo formato fluente do WebClient (.get().uri(...).retrieve()), mas .body(Class) já devolve o objeto direto — não existe Mono nem .block() nessa API porque ela nunca foi reativa: é síncrona desde a primeira chamada, disponível desde o Spring Framework 6.1. Para um @RestController tradicional (não WebFlux), essa é a opção que expressa a intenção real do código sem carregar a bagagem reativa que a aplicação não usa."},{"id":"webclient-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Request factories e connection pooling: quem realmente abre a conexão TCP</h2>","fidelityText":"Request factories e connection pooling: quem realmente abre a conexão TCP"},{"id":"webclient-content-15","type":"html","authorship":"legacy-preserved","html":"<p><code>WebClient</code>/<code>RestClient</code> não abrem uma conexão nova para cada chamada por padrão — por trás da API fluente existe uma <code>ClientHttpRequestFactory</code> (para <code>RestClient</code>/<code>RestTemplate</code>) ou um <code>ClientHttpConnector</code> (para <code>WebClient</code>) que reaproveita conexões TCP/TLS de um pool. Sem pooling, cada chamada pagaria o custo de handshake TCP+TLS do zero — caro o suficiente para dominar a latência de chamadas pequenas e frequentes.</p>","fidelityText":"WebClient/RestClient não abrem uma conexão nova para cada chamada por padrão — por trás da API fluente existe uma ClientHttpRequestFactory (para RestClient/RestTemplate) ou um ClientHttpConnector (para WebClient) que reaproveita conexões TCP/TLS de um pool. Sem pooling, cada chamada pagaria o custo de handshake TCP+TLS do zero — caro o suficiente para dominar a latência de chamadas pequenas e frequentes."},{"id":"webclient-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"@Bean\npublic RestClient.Builder restClientBuilder() {\n    var factory = new JdkClientHttpRequestFactory(); // usa java.net.http.HttpClient por baixo\n    factory.setReadTimeout(Duration.ofSeconds(3));\n    return RestClient.builder().requestFactory(factory);\n}","fidelityText":"@Bean public RestClient.Builder restClientBuilder() { var factory = new JdkClientHttpRequestFactory(); // usa java.net.http.HttpClient por baixo factory.setReadTimeout(Duration.ofSeconds(3)); return RestClient.builder().requestFactory(factory); }","highlightedHtml":"<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> RestClient.Builder <span class=\"fn\">restClientBuilder</span>() {\n    <span class=\"kw\">var</span> factory = <span class=\"kw\">new</span> JdkClientHttpRequestFactory(); <span class=\"com\">// usa java.net.http.HttpClient por baixo</span>\n    factory.setReadTimeout(Duration.ofSeconds(<span class=\"num\">3</span>));\n    <span class=\"kw\">return</span> RestClient.builder().requestFactory(factory);\n}","caption":"Exemplo executável de webclient.","explanation":["ClientHttpRequestFactory é a peça que de fato abre e reaproveita a conexão TCP/TLS por trás da API fluente do RestClient -- sem pooling, cada chamada pagaria handshake do zero.","Trocar a factory (ex.: para HttpComponentsClientHttpRequestFactory) dá controle explícito sobre timeout de conexão vs. leitura e tamanho do pool."],"commonMistakes":["Nunca configurar a request factory e não saber quais timeouts/pool estão realmente em vigor"]},{"id":"webclient-content-17","type":"html","authorship":"legacy-preserved","html":"<p>Trocar a <code>ClientHttpRequestFactory</code> (por exemplo, para <code>HttpComponentsClientHttpRequestFactory</code>, baseada no Apache HttpClient5) dá controle explícito sobre tamanho do pool de conexões, timeout de conexão vs. timeout de leitura, e keep-alive — decisões que, deixadas no padrão, a aplicação nunca escolheu conscientemente.</p>","fidelityText":"Trocar a ClientHttpRequestFactory (por exemplo, para HttpComponentsClientHttpRequestFactory, baseada no Apache HttpClient5) dá controle explícito sobre tamanho do pool de conexões, timeout de conexão vs. timeout de leitura, e keep-alive — decisões que, deixadas no padrão, a aplicação nunca escolheu conscientemente."},{"id":"webclient-content-18","type":"html","authorship":"legacy-preserved","html":"<h2>Interceptors: injetando comportamento transversal em toda chamada</h2>","fidelityText":"Interceptors: injetando comportamento transversal em toda chamada"},{"id":"webclient-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Adicionar um cabeçalho de autenticação, logar toda requisição/resposta ou medir latência não deveria ser responsabilidade de cada método que usa o client — interceptors resolvem isso uma vez, para todas as chamadas daquele client:</p>","fidelityText":"Adicionar um cabeçalho de autenticação, logar toda requisição/resposta ou medir latência não deveria ser responsabilidade de cada método que usa o client — interceptors resolvem isso uma vez, para todas as chamadas daquele client:"},{"id":"webclient-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"RestClient restClient = RestClient.builder()\n    .baseUrl(\"https://viacep.com.br\")\n    .requestInterceptor((request, body, execution) -> {\n        request.getHeaders().add(\"Authorization\", \"Bearer \" + tokenService.tokenCurrent()); // capítulo 52!\n        long start = System.currentTimeMillis();\n        try {\n            return execution.execute(request, body);\n        } finally {\n            log.info(\"{} {} took {}ms\", request.getMethod(), request.getURI(), System.currentTimeMillis() - start);\n        }\n    })\n    .build();","fidelityText":"RestClient restClient = RestClient.builder() .baseUrl(\"https://viacep.com.br\") .requestInterceptor((request, body, execution) -> { request.getHeaders().add(\"Authorization\", \"Bearer \" + tokenServico.tokenAtual()); // capítulo 52! long inicio = System.currentTimeMillis(); try { return execution.execute(request, body); } finally { log.info(\"{} {} levou {}ms\", request.getMethod(), request.getURI(), System.currentTimeMillis() - inicio); } }) .build();","highlightedHtml":"<span class=\"cls\">RestClient</span> restClient = RestClient.builder()\n    .baseUrl(<span class=\"str\">\"https://viacep.com.br\"</span>)\n    .requestInterceptor((request, body, execution) -&gt; {\n        request.getHeaders().add(<span class=\"str\">\"Authorization\"</span>, <span class=\"str\">\"Bearer \"</span> + tokenService.tokenCurrent()); <span class=\"com\">// capítulo 52!</span>\n        <span class=\"kw\">long</span> start = System.currentTimeMillis();\n        <span class=\"kw\">try</span> {\n            <span class=\"kw\">return</span> execution.execute(request, body);\n        } <span class=\"kw\">finally</span> {\n            log.info(<span class=\"str\">\"{} {} took {}ms\"</span>, request.getMethod(), request.getURI(), System.currentTimeMillis() - start);\n        }\n    })\n    .build();","caption":"Exemplo executável de webclient.","explanation":["requestInterceptor roda antes/depois de toda chamada feita por este client -- adicionar cabeçalho de autenticação ou logar latência uma vez aqui evita repetir isso em cada método que usa o client.","O equivalente reativo em WebClient é ExchangeFilterFunction -- mesma ideia, API adaptada a Mono/Flux."],"commonMistakes":["Repetir a lógica de cabeçalho/log em cada método que chama o client, em vez de centralizar num interceptor"]},{"id":"webclient-content-21","type":"html","authorship":"legacy-preserved","html":"<p>O equivalente reativo em <code>WebClient</code> é <code>ExchangeFilterFunction</code> — mesma ideia (interceptar antes/depois da chamada real), API adaptada ao mundo <code>Mono</code>/<code>Flux</code>.</p>","fidelityText":"O equivalente reativo em WebClient é ExchangeFilterFunction — mesma ideia (interceptar antes/depois da chamada real), API adaptada ao mundo Mono/Flux."},{"id":"webclient-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>Status handling: decidir o que 4xx/5xx significam para o seu domínio</h2>","fidelityText":"Status handling: decidir o que 4xx/5xx significam para o seu domínio"},{"id":"webclient-content-23","type":"html","authorship":"legacy-preserved","html":"<p>Por padrão, um <code>4xx</code>/<code>5xx</code> vira uma exceção genérica (<code>RestClientResponseException</code>/<code>WebClientResponseException</code>) sem significado de domínio. <code>.onStatus(...)</code> deixa você decidir explicitamente:</p>","fidelityText":"Por padrão, um 4xx/5xx vira uma exceção genérica (RestClientResponseException/WebClientResponseException) sem significado de domínio. .onStatus(...) deixa você decidir explicitamente:"},{"id":"webclient-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"return restClient.get()\n    .uri(\"/ws/{cep}/json\", cep)\n    .retrieve()\n    .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {\n        throw new CepInvalidException(cep); // erro de domínio -- CEP não existe, não falha do serviço\n    })\n    .onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {\n        throw new ServiceExternalUnavailableException(); // falha real para o CircuitBreaker\n    })\n    .body(AddressDTO.class);","fidelityText":"return restClient.get() .uri(\"/ws/{cep}/json\", cep) .retrieve() .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { throw new CepInvalidoException(cep); // erro de domínio -- CEP não existe, não falha do serviço }) .onStatus(HttpStatusCode::is5xxServerError, (request, response) -> { throw new ServicoExternoIndisponivelException(); // falha real para o CircuitBreaker }) .body(EnderecoDTO.class);","highlightedHtml":"<span class=\"kw\">return</span> restClient.get()\n    .uri(<span class=\"str\">\"/ws/{cep}/json\"</span>, cep)\n    .retrieve()\n    .onStatus(HttpStatusCode::is4xxClientError, (request, response) -&gt; {\n        <span class=\"kw\">throw new</span> <span class=\"cls\">CepInvalidException</span>(cep); <span class=\"com\">// erro de domínio -- CEP não existe, não falha do serviço</span>\n    })\n    .onStatus(HttpStatusCode::is5xxServerError, (request, response) -&gt; {\n        <span class=\"kw\">throw new</span> <span class=\"cls\">ServiceExternalUnavailableException</span>(); <span class=\"com\">// falha real para o CircuitBreaker</span>\n    })\n    .body(<span class=\"cls\">AddressDTO</span>.<span class=\"kw\">class</span>);","caption":"Exemplo executável de webclient.","explanation":["onStatus(...) substitui a exceção genérica padrão (RestClientResponseException) por uma decisão explícita de domínio, diferenciando 4xx (erro do chamador) de 5xx (erro do serviço).","Essa distinção é o que faz o @CircuitBreaker contar corretamente: só a falha real do serviço (5xx) deveria abrir o circuito."],"commonMistakes":["Deixar qualquer 4xx/5xx virar a mesma exceção genérica, fazendo erro de validação do cliente abrir o circuito para todo mundo"]},{"id":"webclient-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\">Diferenciar 4xx de 5xx aqui não é só estilo — é o que faz o <code>@CircuitBreaker</code> funcionar corretamente. Um CEP inválido (4xx, erro do <em>chamador</em>) não deveria contar como falha do serviço externo e abrir o circuito para todo mundo; um 5xx real (erro do <em>serviço</em>) deveria. Sem essa distinção, alguém digitando CEPs errados repetidamente derruba a disponibilidade da funcionalidade para todos os outros usuários.</div>","fidelityText":"Diferenciar 4xx de 5xx aqui não é só estilo — é o que faz o @CircuitBreaker funcionar corretamente. Um CEP inválido (4xx, erro do chamador) não deveria contar como falha do serviço externo e abrir o circuito para todo mundo; um 5xx real (erro do serviço) deveria. Sem essa distinção, alguém digitando CEPs errados repetidamente derruba a disponibilidade da funcionalidade para todos os outros usuários."},{"id":"webclient-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>Testando sem depender do serviço externo real</h2>","fidelityText":"Testando sem depender do serviço externo real"},{"id":"webclient-code-27","type":"code","authorship":"legacy-preserved","language":"java","source":"MockRestServiceServer serverMock;\nCepService cepService;\n\n@BeforeEach\nvoid configure() {\n    RestClient.Builder builder = RestClient.builder().baseUrl(\"https://viacep.com.br\");\n    serverMock = MockRestServiceServer.bindTo(builder).build();\n    cepService = new CepService(builder);\n}\n\n@Test\nvoid shouldReturnResponseWithSuccess() {\n    serverMock.expect(requestTo(\"/ws/01001000/json\"))\n        .andRespond(withSuccess(\"{\\\"cep\\\":\\\"01001-000\\\",\\\"street address\\\":\\\"Rua Augusta\\\"}\", MediaType.APPLICATION_JSON));\n\n    AddressDTO address = cepService.findAddress(\"01001000\");\n\n    assertThat(address.street address()).isEqualTo(\"Rua Augusta\");\n}\n\n@Test\nvoid shouldThrowExceptionWhenCepNotExists() {\n    serverMock.expect(requestTo(\"/ws/00000000/json\"))\n        .andRespond(withStatus(HttpStatus.NOT_FOUND));\n\n    assertThatThrownBy(() -> cepService.findAddress(\"00000000\"))\n        .isInstanceOf(CepInvalidException.class);\n}","fidelityText":"MockRestServiceServer servidorMock; CepServico cepServico; @BeforeEach void configurar() { RestClient.Builder builder = RestClient.builder().baseUrl(\"https://viacep.com.br\"); servidorMock = MockRestServiceServer.bindTo(builder).build(); cepServico = new CepServico(builder); } @Test void deveRetornarRespostaComSucesso() { servidorMock.expect(requestTo(\"/ws/01001000/json\")) .andRespond(withSuccess(\"{\\\"cep\\\":\\\"01001-000\\\",\\\"logradouro\\\":\\\"Rua Augusta\\\"}\", MediaType.APPLICATION_JSON)); EnderecoDTO endereco = cepServico.buscarEndereco(\"01001000\"); assertThat(endereco.logradouro()).isEqualTo(\"Rua Augusta\"); } @Test void deveLancarExcecaoQuandoCepNaoExiste() { servidorMock.expect(requestTo(\"/ws/00000000/json\")) .andRespond(withStatus(HttpStatus.NOT_FOUND)); assertThatThrownBy(() -> cepServico.buscarEndereco(\"00000000\")) .isInstanceOf(CepInvalidoException.class); }","highlightedHtml":"<span class=\"cls\">MockRestServiceServer</span> serverMock;\n<span class=\"cls\">CepService</span> cepService;\n\n<span class=\"annotation\">@BeforeEach</span>\n<span class=\"kw\">void</span> <span class=\"fn\">configure</span>() {\n    RestClient.Builder builder = RestClient.builder().baseUrl(<span class=\"str\">\"https://viacep.com.br\"</span>);\n    serverMock = MockRestServiceServer.bindTo(builder).build();\n    cepService = <span class=\"kw\">new</span> <span class=\"cls\">CepService</span>(builder);\n}\n\n<span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldReturnResponseWithSuccess</span>() {\n    serverMock.expect(requestTo(<span class=\"str\">\"/ws/01001000/json\"</span>))\n        .andRespond(withSuccess(<span class=\"str\">\"{\\\"cep\\\":\\\"01001-000\\\",\\\"street address\\\":\\\"Rua Augusta\\\"}\"</span>, MediaType.APPLICATION_JSON));\n\n    <span class=\"cls\">AddressDTO</span> address = cepService.findAddress(<span class=\"str\">\"01001000\"</span>);\n\n    assertThat(address.street address()).isEqualTo(<span class=\"str\">\"Rua Augusta\"</span>);\n}\n\n<span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldThrowExceptionWhenCepNotExists</span>() {\n    serverMock.expect(requestTo(<span class=\"str\">\"/ws/00000000/json\"</span>))\n        .andRespond(withStatus(HttpStatus.NOT_FOUND));\n\n    assertThatThrownBy(() -&gt; cepService.findAddress(<span class=\"str\">\"00000000\"</span>))\n        .isInstanceOf(<span class=\"cls\">CepInvalidException</span>.<span class=\"kw\">class</span>);\n}","caption":"Exemplo executável de webclient.","explanation":["MockRestServiceServer intercepta a chamada HTTP antes que ela saia da JVM -- o teste roda sem rede, sem depender do serviço externo estar no ar, e ainda exercita o RestClient real.","Os dois testes provam contratos diferentes: resposta válida mapeada corretamente, e 404 virando a exceção de domínio certa (não uma exceção genérica)."],"commonMistakes":["Mockar o próprio CepService em vez de simular a resposta HTTP -- isso deixaria de testar o mapeamento real"]},{"id":"webclient-content-28","type":"html","authorship":"legacy-preserved","html":"<p><code>MockRestServiceServer</code> (do próprio Spring, sem biblioteca externa) intercepta a chamada HTTP real antes que ela saia da JVM — o teste roda sem rede, sem depender do ViaCEP estar no ar, e ainda assim exercita o <code>RestClient</code>/<code>ClientHttpRequestFactory</code> reais, não um mock do seu próprio código.</p>","fidelityText":"MockRestServiceServer (do próprio Spring, sem biblioteca externa) intercepta a chamada HTTP real antes que ela saia da JVM — o teste roda sem rede, sem depender do ViaCEP estar no ar, e ainda assim exercita o RestClient/ClientHttpRequestFactory reais, não um mock do seu próprio código."},{"id":"webclient-exercise-29","type":"exercise","authorship":"legacy-preserved","title":"Exercício 72.1 — Consumindo uma API externa protegida","prompt":"Escreva um CepServico usando WebClient para consultar a API pública ViaCEP, protegido por @CircuitBreaker com um fallback retornando um endereço genérico \"indisponível\" em caso de falha.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 72.1 — Consumindo uma API externa protegidamédio Escreva um CepServico usando WebClient para consultar a API pública ViaCEP, protegido por @CircuitBreaker com um fallback retornando um endereço genérico \"indisponível\" em caso de falha. Ver solução Inclua timeout de conexão e resposta, mapeamento de 4xx/5xx, circuit breaker e teste com servidor HTTP simulado. O fallback não deve transformar CEP inválido em sucesso; falha funcional e indisponibilidade transitória precisam de resultados diferentes. Em MVC síncrono, apresente também a versão com RestClient.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 72.1 — Consumindo uma API externa protegida</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva um <code>CepServico</code> usando <code>WebClient</code> para consultar a API pública ViaCEP, protegido por <code>@CircuitBreaker</code> com um fallback retornando um endereço genérico \"indisponível\" em caso de falha.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Inclua timeout de conexão e resposta, mapeamento de 4xx/5xx, circuit breaker e teste com servidor HTTP simulado. O fallback não deve transformar CEP inválido em sucesso; falha funcional e indisponibilidade transitória precisam de resultados diferentes. Em MVC síncrono, apresente também a versão com <code>RestClient</code>.</p>\n        </div>\n      </div>"},{"id":"webclient-exercise-30","type":"exercise","authorship":"legacy-preserved","title":"Exercício 72.2 — Por que 4xx não deveria abrir o circuito","prompt":"No CepServico com @CircuitBreaker, um usuário mal-intencionado envia 50 CEPs inválidos seguidos, cada um recebendo 404 do ViaCEP. Sem .onStatus(...) diferenciando 4xx de 5xx, o que acontece com o circuito, e por que isso prejudica outros usuários que nem enviaram CEP nenhum inválido? Escreva a correção com MockRestServiceServer provando que um 404 não conta como falha do circuito, mas um 500 conta.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 72.2 — Por que 4xx não deveria abrir o circuitodifícil No CepServico com @CircuitBreaker, um usuário mal-intencionado envia 50 CEPs inválidos seguidos, cada um recebendo 404 do ViaCEP. Sem .onStatus(...) diferenciando 4xx de 5xx, o que acontece com o circuito, e por que isso prejudica outros usuários que nem enviaram CEP nenhum inválido? Escreva a correção com MockRestServiceServer provando que um 404 não conta como falha do circuito, mas um 500 conta. Ver solução Sem diferenciar status, qualquer exceção lançada pelo client (inclusive a genérica de um 404) é contada como falha pelo @CircuitBreaker — 50 CEPs inválidos seguidos podem sozinhos ultrapassar o failure-rate-threshold e abrir o circuito, fazendo o fallback \"Endereço indisponível\" ser devolvido também para usuários com CEP válido, que não têm nada a ver com o problema. A correção usa .onStatus(is4xxClientError, ...) lançando CepInvalidoException (registrada em ignore-exceptions na configuração do circuit breaker) e .onStatus(is5xxServerError, ...) lançando uma exceção que conta como falha de verdade. Dois testes com MockRestServiceServer — um respondendo 404, outro 500 — comprovam que só o segundo incrementa a contagem de falha do circuito.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 72.2 — Por que 4xx não deveria abrir o circuito</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>No <code>CepServico</code> com <code>@CircuitBreaker</code>, um usuário mal-intencionado envia 50 CEPs inválidos seguidos, cada um recebendo 404 do ViaCEP. Sem <code>.onStatus(...)</code> diferenciando 4xx de 5xx, o que acontece com o circuito, e por que isso prejudica outros usuários que nem enviaram CEP nenhum inválido? Escreva a correção com <code>MockRestServiceServer</code> provando que um 404 não conta como falha do circuito, mas um 500 conta.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Sem diferenciar status, qualquer exceção lançada pelo client (inclusive a genérica de um 404) é contada como falha pelo <code>@CircuitBreaker</code> — 50 CEPs inválidos seguidos podem sozinhos ultrapassar o <code>failure-rate-threshold</code> e abrir o circuito, fazendo o fallback \"Endereço indisponível\" ser devolvido também para usuários com CEP válido, que não têm nada a ver com o problema. A correção usa <code>.onStatus(is4xxClientError, ...)</code> lançando <code>CepInvalidoException</code> (registrada em <code>ignore-exceptions</code> na configuração do circuit breaker) e <code>.onStatus(is5xxServerError, ...)</code> lançando uma exceção que conta como falha de verdade. Dois testes com <code>MockRestServiceServer</code> — um respondendo 404, outro 500 — comprovam que só o segundo incrementa a contagem de falha do circuito.</p>\n        </div>\n      </div>"},{"id":"webclient-quiz","type":"quiz","authorship":"authored","conceptId":"spring-webclient-reactive-client","prompt":"Antes de adicionar retry ou circuit breaker, qual contrato o cliente HTTP precisa ter?","options":[{"id":"wc-a","label":"Timeout, mapeamento de status/erro e DTO de fronteira explícitos.","correct":true,"explanation":"Sem isso, políticas de resiliência apenas repetem ambiguidade."},{"id":"wc-b","label":"Um while infinito até a API responder.","correct":false,"explanation":"Retry sem limite amplifica falha e prende recurso."},{"id":"wc-c","label":"Entity JPA compartilhada entre os dois serviços.","correct":false,"explanation":"Isso acopla persistência interna ao contrato externo."}]}],"resources":[{"id":"spring-webclient-reference","type":"official-docs","title":"Spring WebClient reference","url":"https://docs.spring.io/spring-framework/reference/web/webflux-webclient.html","reinforces":"Cliente HTTP moderno do Spring, request/response, body e status.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-rest-clients-reference","type":"official-docs","title":"Spring REST clients","url":"https://docs.spring.io/spring-framework/reference/integration/rest-clients.html","reinforces":"RestClient, WebClient, RestTemplate e HTTP interface clients.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The webclient component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to webclient. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible webclient failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"@Service","instruction":"The webclient component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible webclient failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"spring-aop","moduleId":"resilience-observability","order":1,"title":"Spring AOP — formalizando o dynamic proxy","summary":"Você já viu o efeito de AOP quatro vezes no curso — @Transactional, @PreAuthorize, @CircuitBreaker, @Cacheable (próximo capítulo) — sempre com a mesma explicação de \"dynamic proxy intercepta a chamada\". Este capítulo formaliza esse padrão para você escrever o seu próprio aspecto.","objectives":["Entender proxy, join point e advice","Reconhecer limites de self-invocation e métodos não interceptados","Aplicar AOP somente a preocupações transversais","Evitar esconder regra de negócio em aspecto"],"whyItExists":"Spring Security, transações, cache e observabilidade usam interceptação. AOP entra para explicar o mecanismo por trás dessas mágicas aparentes e impedir que o aluno transforme aspecto em domínio escondido.","prerequisiteChapterIds":["anotacoes","spring-security","resilience","jpa-transacoes"],"conceptIds":["os-tipos-de-advice","uma-anotacao-customizada-como-o-seu-logexecucao-do-capitulo-20-agora-fun","jdk-dynamic-proxy-vs-cglib-como-o-spring-decide-o-mecanismo","weaving-por-que-o-proxy-so-intercepta-chamadas-vindas-de-fora"],"introducedConceptIds":["aop-proxy-advice-joinpoint","cross-cutting-concern-boundary"],"usedConceptIds":["annotation-metadata-contract","transaction-proxy-boundary","log-parametrizado-causa"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"spring-aop-intuition","type":"intuition","authorship":"authored","title":"AOP coloca comportamento na fronteira da chamada","body":"Um proxy fica entre chamador e objeto real. Ele consegue executar código antes/depois/em volta da chamada, mas só quando a chamada passa por ele. Por isso AOP é poderoso e fácil de usar errado.","analogyLimit":"Porteiro ajuda, mas chamadas internas no próprio objeto podem não passar pela portaria."},{"id":"spring-aop-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#anotacoes\">20 · Anotações &amp; Reflection</a> (dynamic proxy), <a class=\"prereq-tag\" href=\"#spring-security\">52 · Spring Security</a>, <a class=\"prereq-tag\" href=\"#resilience\">55 · Resilience4j</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Avançado ⏱ ~2h de estudo Pré-requisitos: 20 · Anotações & Reflection (dynamic proxy), 52 · Spring Security, 55 · Resilience4j"},{"id":"spring-aop-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Você já viu o efeito de AOP quatro vezes no curso — <code>@Transactional</code>, <code>@PreAuthorize</code>, <code>@CircuitBreaker</code>, <code>@Cacheable</code> (próximo capítulo) — sempre com a mesma explicação de \"dynamic proxy intercepta a chamada\". Este capítulo formaliza esse padrão para você escrever o <strong>seu próprio</strong> aspecto.</p>","fidelityText":"Você já viu o efeito de AOP quatro vezes no curso — @Transactional, @PreAuthorize, @CircuitBreaker, @Cacheable (próximo capítulo) — sempre com a mesma explicação de \"dynamic proxy intercepta a chamada\". Este capítulo formaliza esse padrão para você escrever o seu próprio aspecto."},{"id":"spring-aop-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\"><strong>AOP</strong> (Aspect-Oriented Programming) existe para resolver \"preocupações transversais\" — código que se repetiria de forma idêntica em dezenas de métodos não relacionados entre si (logar tempo de execução, verificar permissão, abrir transação). Pense nisso como uma câmera de segurança instalada no teto de um prédio, em vez de pedir para cada funcionário carregar sua própria câmera pessoal — uma única \"camada\" observa tudo, sem precisar duplicar o código de vigilância em cada sala.</div>","fidelityText":"AOP (Aspect-Oriented Programming) existe para resolver \"preocupações transversais\" — código que se repetiria de forma idêntica em dezenas de métodos não relacionados entre si (logar tempo de execução, verificar permissão, abrir transação). Pense nisso como uma câmera de segurança instalada no teto de um prédio, em vez de pedir para cada funcionário carregar sua própria câmera pessoal — uma única \"camada\" observa tudo, sem precisar duplicar o código de vigilância em cada sala."},{"id":"spring-aop-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"@Aspect\n@Component\npublic class ExecutionLogAspect {\n\n    private static final Logger log = LoggerFactory.getLogger(ExecutionLogAspect.class);\n\n    // \"pointcut\": QUAIS métodos interceptar -- todo método em qualquer classe do pacote servico\n    @Around(\"execution(* with.felipy.library.service..*(..))\")\n    public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {\n        long start = System.nanoTime();\n        Object result = joinPoint.proceed(); // chama o método REAL -- exatamente como metodo.invoke() do capítulo 20\n        long durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);\n        log.info(\"{} executed in {}ms\", joinPoint.getSignature(), durationMs);\n        return result;\n    }\n}","fidelityText":"@Aspect @Component public class LogExecucaoAspect { private static final Logger log = LoggerFactory.getLogger(LogExecucaoAspect.class); // \"pointcut\": QUAIS métodos interceptar -- todo método em qualquer classe do pacote servico @Around(\"execution(* com.felipy.biblioteca.servico..*(..))\") public Object medirTempo(ProceedingJoinPoint joinPoint) throws Throwable { long inicio = System.nanoTime(); Object resultado = joinPoint.proceed(); // chama o método REAL -- exatamente como metodo.invoke() do capítulo 20 long duracaoMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - inicio); log.info(\"{} executou em {}ms\", joinPoint.getSignature(), duracaoMs); return resultado; } }","highlightedHtml":"<span class=\"annotation\">@Aspect</span>\n<span class=\"annotation\">@Component</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">ExecutionLogAspect</span> {\n\n    <span class=\"kw\">private static final</span> Logger log = LoggerFactory.getLogger(<span class=\"cls\">ExecutionLogAspect</span>.<span class=\"kw\">class</span>);\n\n    <span class=\"com\">// \"pointcut\": QUAIS métodos interceptar -- todo método em qualquer classe do pacote servico</span>\n    <span class=\"annotation\">@Around</span>(<span class=\"str\">\"execution(* with.felipy.library.service..*(..))\"</span>)\n    <span class=\"kw\">public</span> <span class=\"kw\">Object</span> <span class=\"fn\">measureTime</span>(ProceedingJoinPoint joinPoint) <span class=\"kw\">throws</span> <span class=\"cls\">Throwable</span> {\n        <span class=\"kw\">long</span> start = System.nanoTime();\n        <span class=\"kw\">Object</span> result = joinPoint.proceed(); <span class=\"com\">// chama o método REAL -- exatamente como metodo.invoke() do capítulo 20</span>\n        <span class=\"kw\">long</span> durationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);\n        log.info(<span class=\"str\">\"{} executed in {}ms\"</span>, joinPoint.getSignature(), durationMs);\n        <span class=\"kw\">return</span> result;\n    }\n}","caption":"Exemplo executável de spring-aop.","explanation":["O advice define quando o comportamento transversal executa em relação ao método interceptado.","Pointcut amplo demais captura chamadas inesperadas e cria acoplamento invisível."],"commonMistakes":["Interceptar pacote inteiro sem critério","Colocar regra de negócio no aspecto"]},{"id":"spring-aop-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Os tipos de advice</h2>","fidelityText":"Os tipos de advice"},{"id":"spring-aop-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Anotação</th><th>Quando executa</th></tr>\n        <tr><td><code>@Before</code></td><td>Antes do método real</td></tr>\n        <tr><td><code>@After</code></td><td>Depois do método real (sucesso ou exceção)</td></tr>\n        <tr><td><code>@AfterReturning</code></td><td>Só se o método retornar normalmente</td></tr>\n        <tr><td><code>@AfterThrowing</code></td><td>Só se o método lançar exceção</td></tr>\n        <tr><td><code>@Around</code></td><td>Envolve a chamada inteira — o mais poderoso, permite decidir se o método real roda ou não</td></tr>\n      </tbody></table>","fidelityText":"AnotaçãoQuando executa @BeforeAntes do método real @AfterDepois do método real (sucesso ou exceção) @AfterReturningSó se o método retornar normalmente @AfterThrowingSó se o método lançar exceção @AroundEnvolve a chamada inteira — o mais poderoso, permite decidir se o método real roda ou não"},{"id":"spring-aop-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Uma anotação customizada, como o seu @LogExecucao do capítulo 20 — agora funcionando de verdade</h2>","fidelityText":"Uma anotação customizada, como o seu @LogExecucao do capítulo 20 — agora funcionando de verdade"},{"id":"spring-aop-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.METHOD)\npublic @interface MeasureTime {}\n\n@Aspect @Component\npublic class MeasureTimeAspect {\n    @Around(\"@annotation(MeasureTime)\") // intercepta qualquer método marcado com @MedirTempo\n    public Object measure(ProceedingJoinPoint jp) throws Throwable { /* ... */ }\n}\n\n// uso, em qualquer service:\n@MeasureTime\npublic void processOrder(Order p) { ... }","fidelityText":"@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MedirTempo {} @Aspect @Component public class MedirTempoAspect { @Around(\"@annotation(MedirTempo)\") // intercepta qualquer método marcado com @MedirTempo public Object medir(ProceedingJoinPoint jp) throws Throwable { /* ... */ } } // uso, em qualquer service: @MedirTempo public void processarPedido(Pedido p) { ... }","highlightedHtml":"<span class=\"annotation\">@Retention</span>(RetentionPolicy.RUNTIME)\n<span class=\"annotation\">@Target</span>(ElementType.METHOD)\n<span class=\"kw\">public @interface</span> <span class=\"cls\">MeasureTime</span> {}\n\n<span class=\"annotation\">@Aspect</span> <span class=\"annotation\">@Component</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">MeasureTimeAspect</span> {\n    <span class=\"annotation\">@Around</span>(<span class=\"str\">\"@annotation(MeasureTime)\"</span>) <span class=\"com\">// intercepta qualquer método marcado com @MedirTempo</span>\n    <span class=\"kw\">public</span> <span class=\"kw\">Object</span> <span class=\"fn\">measure</span>(ProceedingJoinPoint jp) <span class=\"kw\">throws</span> <span class=\"cls\">Throwable</span> { <span class=\"com\">/* ... */</span> }\n}\n\n<span class=\"com\">// uso, em qualquer service:</span>\n<span class=\"annotation\">@MeasureTime</span>\n<span class=\"kw\">public void</span> <span class=\"fn\">processOrder</span>(<span class=\"cls\">Order</span> p) { ... }","caption":"Exemplo executável de spring-aop.","explanation":["Anotação customizada marca intenção e o aspecto interpreta o metadado.","Isso é útil para observabilidade/política transversal, não para esconder fluxo essencial."],"commonMistakes":["Criar anotação mágica sem teste","Assumir que método privado será interceptado"]},{"id":"spring-aop-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Este é o fechamento do ciclo que começou no capítulo 20: seu <code>@LogExecucao</code> lá era só metadado lido manualmente. Aqui, o <code>@Aspect</code> registra automaticamente um dynamic proxy (capítulo 20) para toda classe gerenciada pelo Spring cujo método bata com o <em>pointcut</em> — exatamente o mesmo mecanismo por trás de <code>@Transactional</code>, <code>@PreAuthorize</code> e <code>@CircuitBreaker</code>, só que agora você escreve a <strong>regra de interceptação</strong> em vez de só consumir uma pronta.</div>","fidelityText":"Este é o fechamento do ciclo que começou no capítulo 20: seu @LogExecucao lá era só metadado lido manualmente. Aqui, o @Aspect registra automaticamente um dynamic proxy (capítulo 20) para toda classe gerenciada pelo Spring cujo método bata com o pointcut — exatamente o mesmo mecanismo por trás de @Transactional, @PreAuthorize e @CircuitBreaker, só que agora você escreve a regra de interceptação em vez de só consumir uma pronta."},{"id":"spring-aop-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>JDK dynamic proxy vs. CGLIB: como o Spring decide o mecanismo</h2>","fidelityText":"JDK dynamic proxy vs. CGLIB: como o Spring decide o mecanismo"},{"id":"spring-aop-content-11","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 20 mostrou <code>Proxy.newProxyInstance</code> — dynamic proxy do próprio JDK, que só sabe proxiar <strong>interfaces</strong>. Mas nem toda classe gerenciada pelo Spring implementa uma interface, e ainda assim <code>@Transactional</code>/<code>@Aspect</code> funcionam nela. O Spring resolve isso escolhendo entre dois mecanismos:</p>","fidelityText":"O capítulo 20 mostrou Proxy.newProxyInstance — dynamic proxy do próprio JDK, que só sabe proxiar interfaces. Mas nem toda classe gerenciada pelo Spring implementa uma interface, e ainda assim @Transactional/@Aspect funcionam nela. O Spring resolve isso escolhendo entre dois mecanismos:"},{"id":"spring-aop-content-12","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Mecanismo</th><th>Quando o Spring usa</th><th>Limitação</th></tr>\n        <tr><td><strong>JDK dynamic proxy</strong></td><td>A classe implementa pelo menos uma interface</td><td>Só métodos declarados na interface são interceptados</td></tr>\n        <tr><td><strong>CGLIB</strong></td><td>A classe não implementa interface nenhuma (ou <code>proxyTargetClass=true</code> força isso mesmo com interface)</td><td>Gera uma <em>subclasse</em> em runtime — não funciona em classe/método <code>final</code></td></tr>\n      </tbody></table>","fidelityText":"MecanismoQuando o Spring usaLimitação JDK dynamic proxyA classe implementa pelo menos uma interfaceSó métodos declarados na interface são interceptados CGLIBA classe não implementa interface nenhuma (ou proxyTargetClass=true força isso mesmo com interface)Gera uma subclasse em runtime — não funciona em classe/método final"},{"id":"spring-aop-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\">CGLIB funciona <strong>subclasseando</strong> sua classe em runtime para interceptar chamadas — e por isso não consegue proxiar uma classe <code>final</code> nem sobrescrever um método <code>final</code>. Marcar um <code>@Service</code> ou um método anotado com <code>@Transactional</code>/<code>@Cacheable</code>/<code>@Aspect</code> customizado como <code>final</code> não gera erro nenhum — o aspecto simplesmente <strong>nunca dispara</strong>, silenciosamente. É um dos bugs mais frustrantes de diagnosticar em projetos Spring exatamente por não lançar exceção nenhuma. Desde o Spring Boot 2, o <em>default</em> já é CGLIB (<code>proxyTargetClass=true</code>) mesmo quando a classe implementa interface — diferente do Spring Framework puro, que prefere JDK dynamic proxy quando há interface disponível.</div>","fidelityText":"CGLIB funciona subclasseando sua classe em runtime para interceptar chamadas — e por isso não consegue proxiar uma classe final nem sobrescrever um método final. Marcar um @Service ou um método anotado com @Transactional/@Cacheable/@Aspect customizado como final não gera erro nenhum — o aspecto simplesmente nunca dispara, silenciosamente. É um dos bugs mais frustrantes de diagnosticar em projetos Spring exatamente por não lançar exceção nenhuma. Desde o Spring Boot 2, o default já é CGLIB (proxyTargetClass=true) mesmo quando a classe implementa interface — diferente do Spring Framework puro, que prefere JDK dynamic proxy quando há interface disponível."},{"id":"spring-aop-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Weaving: por que o proxy só intercepta chamadas vindas de fora</h2>","fidelityText":"Weaving: por que o proxy só intercepta chamadas vindas de fora"},{"id":"spring-aop-content-15","type":"html","authorship":"legacy-preserved","html":"<p><strong>Weaving</strong> é o nome do processo que \"tece\" o código do aspecto junto ao código do método interceptado — e Spring AOP e AspectJ completo fazem isso de formas fundamentalmente diferentes:</p>","fidelityText":"Weaving é o nome do processo que \"tece\" o código do aspecto junto ao código do método interceptado — e Spring AOP e AspectJ completo fazem isso de formas fundamentalmente diferentes:"},{"id":"spring-aop-content-16","type":"html","authorship":"legacy-preserved","html":"<ul style=\"color:var(--ink-dim)\">\n        <li><strong>Spring AOP</strong> faz <em>weaving via proxy</em>, em runtime: o objeto real continua existindo intocado; o Spring cria um objeto proxy por cima dele, e só as chamadas que <strong>chegam através do proxy</strong> (de fora da classe, via injeção de dependência) passam pelo aspecto.</li>\n        <li><strong>AspectJ completo</strong> (uma ferramenta separada, com compilador próprio) faz <em>weaving no bytecode</em> — em tempo de compilação ou de carregamento da classe (<em>load-time weaving</em>), o código do aspecto é inserido diretamente dentro do <code>.class</code> do método interceptado. Não existe proxy nem objeto por fora; a interceptação acontece não importa de onde a chamada venha.</li>\n      </ul>","fidelityText":"Spring AOP faz weaving via proxy, em runtime: o objeto real continua existindo intocado; o Spring cria um objeto proxy por cima dele, e só as chamadas que chegam através do proxy (de fora da classe, via injeção de dependência) passam pelo aspecto. AspectJ completo (uma ferramenta separada, com compilador próprio) faz weaving no bytecode — em tempo de compilação ou de carregamento da classe (load-time weaving), o código do aspecto é inserido diretamente dentro do .class do método interceptado. Não existe proxy nem objeto por fora; a interceptação acontece não importa de onde a chamada venha."},{"id":"spring-aop-content-17","type":"html","authorship":"legacy-preserved","html":"<p>É exatamente essa diferença de mecanismo que explica a limitação de self-invocation da seção seguinte: como o Spring AOP intercepta <em>apenas o que passa pelo proxy</em>, uma chamada de dentro da própria classe nunca passa por ele. AspectJ com weaving de bytecode não teria essa limitação — mas custa a complexidade extra de um compilador/agente próprio, por isso o Spring AOP (mais simples, baseado em proxy) é o padrão adotado na grande maioria dos projetos.</p>","fidelityText":"É exatamente essa diferença de mecanismo que explica a limitação de self-invocation da seção seguinte: como o Spring AOP intercepta apenas o que passa pelo proxy, uma chamada de dentro da própria classe nunca passa por ele. AspectJ com weaving de bytecode não teria essa limitação — mas custa a complexidade extra de um compilador/agente próprio, por isso o Spring AOP (mais simples, baseado em proxy) é o padrão adotado na grande maioria dos projetos."},{"id":"spring-aop-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>A mesma limitação do proxy sempre se aplica:</b> chamar um método anotado a partir de <strong>outro método da mesma classe</strong> (<code>this.metodoAnotado()</code>) nunca passa pelo proxy, então o aspecto nunca dispara — a chamada interna não passa pela \"câmera de segurança\" que só observa chamadas vindas de fora da classe. Isso já apareceu no capítulo 52 com <code>@PreAuthorize</code>, e é exatamente o mesmo motivo pelo qual um <code>@Transactional</code> chamado via <code>this.metodoTransacional()</code> de dentro do próprio service <strong>não abre transação nenhuma</strong> — provavelmente o exemplo mais citado dessa armadilha em qualquer comunidade Spring, porque o código compila normalmente e só falha em runtime, sem erro nenhum na maioria dos casos.</div>","fidelityText":"A mesma limitação do proxy sempre se aplica: chamar um método anotado a partir de outro método da mesma classe (this.metodoAnotado()) nunca passa pelo proxy, então o aspecto nunca dispara — a chamada interna não passa pela \"câmera de segurança\" que só observa chamadas vindas de fora da classe. Isso já apareceu no capítulo 52 com @PreAuthorize, e é exatamente o mesmo motivo pelo qual um @Transactional chamado via this.metodoTransacional() de dentro do próprio service não abre transação nenhuma — provavelmente o exemplo mais citado dessa armadilha em qualquer comunidade Spring, porque o código compila normalmente e só falha em runtime, sem erro nenhum na maioria dos casos."},{"id":"spring-aop-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não crie aspectos customizados como primeira solução — a maioria dos casos de uso reais já é coberta pelos aspectos prontos do Spring (<code>@Transactional</code>, <code>@Cacheable</code>, <code>@PreAuthorize</code>). Escreva seu próprio <code>@Aspect</code> quando identificar uma preocupação transversal genuinamente específica do seu domínio, repetida em muitos lugares — não para logar uma única chamada, onde um log direto já resolveria mais simplesmente.</div>","fidelityText":"Não crie aspectos customizados como primeira solução — a maioria dos casos de uso reais já é coberta pelos aspectos prontos do Spring (@Transactional, @Cacheable, @PreAuthorize). Escreva seu próprio @Aspect quando identificar uma preocupação transversal genuinamente específica do seu domínio, repetida em muitos lugares — não para logar uma única chamada, onde um log direto já resolveria mais simplesmente."},{"id":"spring-aop-exercise-20","type":"exercise","authorship":"legacy-preserved","title":"Exercício 70.1 — Aspecto de auditoria","prompt":"Crie uma anotação @Auditavel e um @Aspect correspondente que, usando @Around, registra em log o nome do método, os argumentos recebidos e se a execução terminou com sucesso ou exceção. Aplique em um método de Biblioteca (ex: emprestar).","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 70.1 — Aspecto de auditoriadifícil Crie uma anotação @Auditavel e um @Aspect correspondente que, usando @Around, registra em log o nome do método, os argumentos recebidos e se a execução terminou com sucesso ou exceção. Aplique em um método de Biblioteca (ex: emprestar). Ver solução @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditavel {} @Aspect @Component public class AuditoriaAspect { private static final Logger log = LoggerFactory.getLogger(AuditoriaAspect.class); @Around(\"@annotation(Auditavel)\") public Object auditar(ProceedingJoinPoint jp) throws Throwable { log.info(\"Chamando {}\", jp.getSignature().getName()); // não registre args: podem conter senha, token ou PII try { Object resultado = jp.proceed(); log.info(\"{} concluído com sucesso\", jp.getSignature().getName()); return resultado; } catch (Throwable e) { log.warn(\"{} lançou exceção: {}\", jp.getSignature().getName(), e.getMessage()); throw e; // SEMPRE relance -- um aspecto nunca deve engolir a exceção original } } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 70.1 — Aspecto de auditoria</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie uma anotação <code>@Auditavel</code> e um <code>@Aspect</code> correspondente que, usando <code>@Around</code>, registra em log o nome do método, os argumentos recebidos e se a execução terminou com sucesso ou exceção. Aplique em um método de <code>Biblioteca</code> (ex: <code>emprestar</code>).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Retention</span>(RetentionPolicy.RUNTIME)\n<span class=\"annotation\">@Target</span>(ElementType.METHOD)\n<span class=\"kw\">public @interface</span> <span class=\"cls\">Auditavel</span> {}\n\n<span class=\"annotation\">@Aspect</span> <span class=\"annotation\">@Component</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">AuditAspect</span> {\n    <span class=\"kw\">private static final</span> Logger log = LoggerFactory.getLogger(<span class=\"cls\">AuditAspect</span>.<span class=\"kw\">class</span>);\n\n    <span class=\"annotation\">@Around</span>(<span class=\"str\">\"@annotation(Auditavel)\"</span>)\n    <span class=\"kw\">public</span> <span class=\"kw\">Object</span> <span class=\"fn\">auditar</span>(ProceedingJoinPoint jp) <span class=\"kw\">throws</span> <span class=\"cls\">Throwable</span> {\n        log.info(<span class=\"str\">\"Calling {}\"</span>, jp.getSignature().getName()); <span class=\"com\">// não registre args: podem conter senha, token ou PII</span>\n        <span class=\"kw\">try</span> {\n            <span class=\"kw\">Object</span> result = jp.proceed();\n            log.info(<span class=\"str\">\"{} concluido with success\"</span>, jp.getSignature().getName());\n            <span class=\"kw\">return</span> result;\n        } <span class=\"kw\">catch</span> (<span class=\"cls\">Throwable</span> e) {\n            log.warn(<span class=\"str\">\"{} lancou exception: {}\"</span>, jp.getSignature().getName(), e.getMessage());\n            <span class=\"kw\">throw</span> e; <span class=\"com\">// SEMPRE relance -- um aspecto nunca deve engolir a exceção original</span>\n        }\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"spring-aop-quiz","type":"quiz","authorship":"authored","conceptId":"aop-proxy-advice-joinpoint","prompt":"Qual limite clássico de AOP por proxy no Spring?","options":[{"id":"aop-a","label":"Uma chamada interna do próprio objeto pode não passar pelo proxy.","correct":true,"explanation":"Self-invocation evita interceptação por proxy comum."},{"id":"aop-b","label":"AOP só funciona com classes sem métodos.","correct":false,"explanation":"O ponto é a passagem pelo proxy/join point, não ausência de métodos."},{"id":"aop-c","label":"Advice deve conter toda regra de negócio central.","correct":false,"explanation":"Aspecto deve cuidar de preocupação transversal."}]}],"resources":[{"id":"spring-aop-reference","type":"official-docs","title":"Spring AOP APIs and terminology","url":"https://docs.spring.io/spring-framework/reference/core/aop.html","reinforces":"Aspect, advice, pointcut, join point e proxy-based AOP.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-proxying-mechanisms","type":"official-docs","title":"Spring AOP proxying mechanisms","url":"https://docs.spring.io/spring-framework/reference/core/aop/proxying.html","reinforces":"JDK proxy, CGLIB, self-invocation e limites de proxy.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the spring aop flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for spring aop. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for spring aop with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"@Component","instruction":"Design the spring aop flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for spring aop with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"spring-cache","moduleId":"resilience-observability","order":2,"title":"Spring Cache Abstraction","summary":"Você implementou cache-aside na mão no exercício 38.1 (checar Redis, se não achar buscar do banco, gravar de volta). O Spring formaliza esse padrão inteiro em uma única anotação.","objectives":["Entender cache como contrato de leitura, chave e invalidação","Relacionar Spring Cache ao proxy/AOP","Definir TTL e chave sem cardinalidade explosiva","Avaliar risco de dado stale"],"whyItExists":"Depois de Redis e AOP, Spring Cache pode ser ensinado corretamente: não é anotação mágica para deixar rápido, é contrato sobre dado reutilizado, validade, escopo e invalidação.","prerequisiteChapterIds":["redis","spring-aop","nosql-operacional"],"conceptIds":["key-generation-como-o-spring-decide-a-chave-do-cache","cache-stampede-quando-a-expiracao-vira-um-problema-de-escala","ttl-o-cache-nao-deveria-durar-para-sempre"],"introducedConceptIds":["spring-cache-key-ttl","cache-stale-invalidation-risk"],"usedConceptIds":["cache-aside-ttl-invalidation","redis-persistence-eviction","aop-proxy-advice-joinpoint"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"spring-cache-intuition","type":"intuition","authorship":"authored","title":"Cache troca frescor por custo menor","body":"Cache é uma cópia reutilizada. A pergunta importante não é “fica mais rápido?”, mas “qual chave identifica o resultado, por quanto tempo ele é aceitável e quando deve ser invalidado?”.","analogyLimit":"Anotação parece simples, mas a decisão real é de consistência, memória, cardinalidade e experiência do usuário."},{"id":"spring-cache-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#redis\">38 · Redis</a>, <a class=\"prereq-tag\" href=\"#spring-aop\">70 · Spring AOP</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~2h30 de estudo + prática Pré-requisitos: 38 · Redis, 70 · Spring AOP"},{"id":"spring-cache-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Você implementou cache-aside na mão no exercício 38.1 (checar Redis, se não achar buscar do banco, gravar de volta). O Spring formaliza esse padrão inteiro em uma única anotação.</p>","fidelityText":"Você implementou cache-aside na mão no exercício 38.1 (checar Redis, se não achar buscar do banco, gravar de volta). O Spring formaliza esse padrão inteiro em uma única anotação."},{"id":"spring-cache-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class BookService {\n\n    @Cacheable(value = \"books\", key = \"#id\") // mesma lógica do exercício 38.1, automática\n    public BookDTO findById(Long id) {\n        System.out.println(\"Fetching from the database -- only appears on CACHE MISS\");\n        return repository.findById(id).map(BookDTO::from).orElseThrow();\n    }\n\n    @CacheEvict(value = \"books\", key = \"#id\") // invalida o cache quando o dado muda -- o problema do capítulo 38!\n    public void update(Long id, BookDTO dto) {\n        // ... atualiza no banco ...\n    }\n\n    @CachePut(value = \"books\", key = \"#result.id\") // #result referencia o retorno do método\n    public BookDTO create(BookDTO dto) { ... }\n}","fidelityText":"@Service public class LivroServico { @Cacheable(value = \"livros\", key = \"#id\") // mesma lógica do exercício 38.1, automática public LivroDTO buscarPorId(Long id) { System.out.println(\"Buscando no banco -- só aparece no CACHE MISS\"); return repository.findById(id).map(LivroDTO::from).orElseThrow(); } @CacheEvict(value = \"livros\", key = \"#id\") // invalida o cache quando o dado muda -- o problema do capítulo 38! public void atualizar(Long id, LivroDTO dto) { // ... atualiza no banco ... } @CachePut(value = \"livros\", key = \"#result.id\") // #result referencia o retorno do método public LivroDTO criar(LivroDTO dto) { ... } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">BookService</span> {\n\n    <span class=\"annotation\">@Cacheable</span>(value = <span class=\"str\">\"books\"</span>, key = <span class=\"str\">\"#id\"</span>) <span class=\"com\">// mesma lógica do exercício 38.1, automática</span>\n    <span class=\"kw\">public</span> <span class=\"cls\">BookDTO</span> <span class=\"fn\">findById</span>(<span class=\"kw\">Long</span> id) {\n        System.out.println(<span class=\"str\">\"Buscando in the bank -- only appears in the CACHE MISS\"</span>);\n        <span class=\"kw\">return</span> repository.findById(id).map(BookDTO::from).orElseThrow();\n    }\n\n    <span class=\"annotation\">@CacheEvict</span>(value = <span class=\"str\">\"books\"</span>, key = <span class=\"str\">\"#id\"</span>) <span class=\"com\">// invalida o cache quando o dado muda -- o problema do capítulo 38!</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">update</span>(<span class=\"kw\">Long</span> id, <span class=\"cls\">BookDTO</span> dto) {\n        <span class=\"com\">// ... atualiza no banco ...</span>\n    }\n\n    <span class=\"annotation\">@CachePut</span>(value = <span class=\"str\">\"books\"</span>, key = <span class=\"str\">\"#result.id\"</span>) <span class=\"com\">// #result referencia o retorno do método</span>\n    <span class=\"kw\">public</span> <span class=\"cls\">BookDTO</span> <span class=\"fn\">create</span>(<span class=\"cls\">BookDTO</span> dto) { ... }\n}","caption":"Exemplo executável de spring-cache.","explanation":["@Cacheable normalmente é aplicado por proxy Spring, então compartilha limites de interceptação do AOP.","A chave padrão pode não representar corretamente o contrato do domínio."],"commonMistakes":["Esquecer self-invocation","Cachear resposta com usuário/permissão sem chave segura"]},{"id":"spring-cache-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties -- conectando ao Redis real (capítulo 38):\nspring.cache.type=redis\nspring.data.redis.host=localhost\nspring.data.redis.port=6379","fidelityText":"# application.properties -- conectando ao Redis real (capítulo 38): spring.cache.type=redis spring.data.redis.host=localhost spring.data.redis.port=6379","highlightedHtml":"<span class=\"com\"># application.properties -- conectando ao Redis real (capítulo 38):</span>\nspring.cache.type=redis\nspring.data.redis.host=localhost\nspring.data.redis.port=6379","caption":"Exemplo executável de spring-cache.","explanation":["Evict/invalidation precisa acompanhar mudanças que tornam a cópia antiga perigosa.","TTL reduz dano, mas não substitui invalidação quando a regra exige frescor."],"commonMistakes":["Evict amplo demais sem medir custo","Não definir política para dados sensíveis"]},{"id":"spring-cache-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>@Cacheable</code> é literalmente um aspecto AOP (capítulo 70) construído pelo próprio time do Spring: o método real (<code>buscarPorId</code>) só é chamado se a chave não existir no cache — o proxy intercepta a chamada, checa o Redis primeiro, e só delega para o código real em caso de cache miss. É exatamente o padrão que você implementou manualmente no exercício 38.1, formalizado em uma anotação.</div>","fidelityText":"@Cacheable é literalmente um aspecto AOP (capítulo 70) construído pelo próprio time do Spring: o método real (buscarPorId) só é chamado se a chave não existir no cache — o proxy intercepta a chamada, checa o Redis primeiro, e só delega para o código real em caso de cache miss. É exatamente o padrão que você implementou manualmente no exercício 38.1, formalizado em uma anotação."},{"id":"spring-cache-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Sendo um proxy, <code>@Cacheable</code> herda a mesma limitação de self-invocation já vista em <code>@Transactional</code>/<code>@PreAuthorize</code>/<code>@CircuitBreaker</code> (capítulo 70):</b> chamar <code>this.buscarPorId(id)</code> de dentro de outro método do <strong>mesmo</strong> <code>LivroServico</code> nunca passa pelo proxy — o cache é ignorado silenciosamente, sem erro nenhum, e o método real roda toda vez. Se um método precisa reaproveitar o cache de outro, a chamada precisa vir de <strong>fora</strong> da classe (via injeção), nunca de <code>this</code>.</div>","fidelityText":"Sendo um proxy, @Cacheable herda a mesma limitação de self-invocation já vista em @Transactional/@PreAuthorize/@CircuitBreaker (capítulo 70): chamar this.buscarPorId(id) de dentro de outro método do mesmo LivroServico nunca passa pelo proxy — o cache é ignorado silenciosamente, sem erro nenhum, e o método real roda toda vez. Se um método precisa reaproveitar o cache de outro, a chamada precisa vir de fora da classe (via injeção), nunca de this."},{"id":"spring-cache-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Key generation: como o Spring decide a chave do cache</h2>","fidelityText":"Key generation: como o Spring decide a chave do cache"},{"id":"spring-cache-content-8","type":"html","authorship":"legacy-preserved","html":"<p><code>key = \"#id\"</code> é uma expressão <strong>SpEL</strong> (Spring Expression Language) referenciando o parâmetro <code>id</code> do método. Sem <code>key</code> explícita, o <code>SimpleKeyGenerator</code> padrão entra em ação — e seu comportamento surpreende quem nunca leu a documentação:</p>","fidelityText":"key = \"#id\" é uma expressão SpEL (Spring Expression Language) referenciando o parâmetro id do método. Sem key explícita, o SimpleKeyGenerator padrão entra em ação — e seu comportamento surpreende quem nunca leu a documentação:"},{"id":"spring-cache-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"@Cacheable(\"books-by-filter\") // sem \"key\": SimpleKeyGenerator combina TODOS os parâmetros\npublic List<BookDTO> findWithFilter(String category, int page) {\n    // chave = SimpleKey[\"ficcao\", 0] -- os dois parâmetros definem a chave.\n    // Chamar buscarComFiltro(\"ficcao\", 1) gera uma chave DIFERENTE -- cache miss esperado.\n    ...\n}\n\n@Cacheable(value = \"books-by-filter\", key = \"#category + '-' + #page\") // chave explícita, legível\npublic List<BookDTO> findWithFilterExplicito(String category, int page) { ... }","fidelityText":"@Cacheable(\"livros-por-filtro\") // sem \"key\": SimpleKeyGenerator combina TODOS os parâmetros public List<LivroDTO> buscarComFiltro(String categoria, int pagina) { // chave = SimpleKey[\"ficcao\", 0] -- os dois parâmetros definem a chave. // Chamar buscarComFiltro(\"ficcao\", 1) gera uma chave DIFERENTE -- cache miss esperado. ... } @Cacheable(value = \"livros-por-filtro\", key = \"#categoria + '-' + #pagina\") // chave explícita, legível public List<LivroDTO> buscarComFiltroExplicito(String categoria, int pagina) { ... }","highlightedHtml":"<span class=\"annotation\">@Cacheable</span>(<span class=\"str\">\"books-by-filter\"</span>) <span class=\"com\">// sem \"key\": SimpleKeyGenerator combina TODOS os parâmetros</span>\n<span class=\"kw\">public</span> List&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">findWithFilter</span>(<span class=\"kw\">String</span> category, <span class=\"kw\">int</span> page) {\n    <span class=\"com\">// chave = SimpleKey[\"ficcao\", 0] -- os dois parâmetros definem a chave.\n    // Chamar buscarComFiltro(\"ficcao\", 1) gera uma chave DIFERENTE -- cache miss esperado.</span>\n    ...\n}\n\n<span class=\"annotation\">@Cacheable</span>(value = <span class=\"str\">\"books-by-filter\"</span>, key = <span class=\"str\">\"#category + '-' + #page\"</span>) <span class=\"com\">// chave explícita, legível</span>\n<span class=\"kw\">public</span> List&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">findWithFilterExplicito</span>(<span class=\"kw\">String</span> category, <span class=\"kw\">int</span> page) { ... }","caption":"Exemplo executável de spring-cache.","explanation":["Sem key explícita, SimpleKeyGenerator combina todos os parâmetros do método -- métodos com múltiplos parâmetros geram uma chave composta diferente para cada combinação.","Uma expressão SpEL explícita (key = \"#param1 + '-' + #param2\") torna a chave legível e intencional, em vez de depender do comportamento implícito do gerador padrão."],"commonMistakes":["Não perceber que parâmetros diferentes geram entradas de cache diferentes com SimpleKeyGenerator","Confiar na chave implícita para métodos com muitos parâmetros, multiplicando entradas de cache sem perceber"]},{"id":"spring-cache-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Sem parâmetro nenhum, o <code>SimpleKeyGenerator</code> usa uma chave fixa (<code>SimpleKey.EMPTY</code>) — todo mundo que chama o método compartilha a mesma entrada de cache, o que geralmente é o comportamento desejado para dados globais (configuração, lista fixa). O problema real aparece com múltiplos parâmetros: é fácil esquecer que a chave composta muda a cada combinação diferente, multiplicando entradas de cache sem perceber.</p>","fidelityText":"Sem parâmetro nenhum, o SimpleKeyGenerator usa uma chave fixa (SimpleKey.EMPTY) — todo mundo que chama o método compartilha a mesma entrada de cache, o que geralmente é o comportamento desejado para dados globais (configuração, lista fixa). O problema real aparece com múltiplos parâmetros: é fácil esquecer que a chave composta muda a cada combinação diferente, multiplicando entradas de cache sem perceber."},{"id":"spring-cache-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Cache stampede: quando a expiração vira um problema de escala</h2>","fidelityText":"Cache stampede: quando a expiração vira um problema de escala"},{"id":"spring-cache-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Uma chave popular expira. No mesmo instante, dezenas de requisições concorrentes checam o cache, todas encontram <em>miss</em>, e todas disparam a mesma consulta cara ao banco simultaneamente — o cache, que existia para <em>proteger</em> o banco, momentaneamente amplifica a carga exatamente quando ela deveria estar mais baixa. Esse efeito tem nome: <strong>cache stampede</strong> (ou <em>thundering herd</em>). Duas mitigações comuns: <strong>TTL com jitter</strong> (um pouco de variação aleatória no tempo de expiração de cada chave, para que chaves populares não expirem todas no mesmo milissegundo) e um padrão de <strong>lock/single-flight</strong> (só a primeira requisição que encontra o miss recalcula o valor; as demais esperam o resultado dela em vez de recalcular em paralelo).</p>","fidelityText":"Uma chave popular expira. No mesmo instante, dezenas de requisições concorrentes checam o cache, todas encontram miss, e todas disparam a mesma consulta cara ao banco simultaneamente — o cache, que existia para proteger o banco, momentaneamente amplifica a carga exatamente quando ela deveria estar mais baixa. Esse efeito tem nome: cache stampede (ou thundering herd). Duas mitigações comuns: TTL com jitter (um pouco de variação aleatória no tempo de expiração de cada chave, para que chaves populares não expirem todas no mesmo milissegundo) e um padrão de lock/single-flight (só a primeira requisição que encontra o miss recalcula o valor; as demais esperam o resultado dela em vez de recalcular em paralelo)."},{"id":"spring-cache-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>TTL: o cache não deveria durar para sempre</h2>","fidelityText":"TTL: o cache não deveria durar para sempre"},{"id":"spring-cache-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"spring.cache.redis.time-to-live=600000 # 10 minutos, em milissegundos -- vale para todos os caches por padrão","fidelityText":"spring.cache.redis.time-to-live=600000 # 10 minutos, em milissegundos -- vale para todos os caches por padrão","highlightedHtml":"spring.cache.redis.time-to-live=600000 <span class=\"com\"># 10 minutos, em milissegundos -- vale para todos os caches por padrão</span>","caption":"Exemplo executável de spring-cache.","explanation":["Sem TTL, uma entrada de cache pode sobreviver indefinidamente -- se o dado de origem mudar por um caminho que não passa pelo @CacheEvict, o cache fica desatualizado sem nenhuma autocorreção.","TTL funciona como rede de segurança complementar à invalidação explícita, não como substituto dela."],"commonMistakes":["Achar que TTL sozinho resolve a necessidade de @CacheEvict em operações de escrita","Configurar TTL longo demais para dados que mudam com frequência"]},{"id":"spring-cache-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Sem TTL configurado, uma entrada de cache no Redis pode viver indefinidamente — se o dado de origem mudar por um caminho que não passa pelo <code>@CacheEvict</code> (uma migration manual, um job em batch, outro serviço escrevendo direto no banco), o cache fica desatualizado para sempre, sem nenhum mecanismo de autocorreção. TTL é uma rede de segurança: mesmo que a invalidação explícita falhe ou seja esquecida, o dado errado tem prazo de validade.</p>","fidelityText":"Sem TTL configurado, uma entrada de cache no Redis pode viver indefinidamente — se o dado de origem mudar por um caminho que não passa pelo @CacheEvict (uma migration manual, um job em batch, outro serviço escrevendo direto no banco), o cache fica desatualizado para sempre, sem nenhum mecanismo de autocorreção. TTL é uma rede de segurança: mesmo que a invalidação explícita falhe ou seja esquecida, o dado errado tem prazo de validade."},{"id":"spring-cache-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Comece sempre sem cache. Adicione <code>@Cacheable</code> apenas depois de identificar, com dados reais (não suposição), que um método específico é chamado com frequência e é caro o suficiente para justificar a complexidade extra de invalidação. Cache prematuro tende a introduzir bugs de dado desatualizado sem ganho de performance mensurável.</div>","fidelityText":"Comece sempre sem cache. Adicione @Cacheable apenas depois de identificar, com dados reais (não suposição), que um método específico é chamado com frequência e é caro o suficiente para justificar a complexidade extra de invalidação. Cache prematuro tende a introduzir bugs de dado desatualizado sem ganho de performance mensurável."},{"id":"spring-cache-exercise-17","type":"exercise","authorship":"legacy-preserved","title":"Exercício 71.1 — Cache declarativo","prompt":"Adicione @Cacheable ao método buscarPorId do LivroServico, e @CacheEvict ao método de atualização correspondente. Habilite o cache no application.properties apontando para o Redis local do capítulo 30/38.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 71.1 — Cache declarativomédio Adicione @Cacheable ao método buscarPorId do LivroServico, e @CacheEvict ao método de atualização correspondente. Habilite o cache no application.properties apontando para o Redis local do capítulo 30/38. Ver solução Inclua @EnableCaching, TTL, chave estável, política para resultado ausente e teste que conte acessos ao repositório. Verifique invalidação após commit e documente o comportamento com duas instâncias. Demonstre por teste que self-invocation não atravessa o proxy de cache.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 71.1 — Cache declarativo</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Adicione <code>@Cacheable</code> ao método <code>buscarPorId</code> do <code>LivroServico</code>, e <code>@CacheEvict</code> ao método de atualização correspondente. Habilite o cache no <code>application.properties</code> apontando para o Redis local do capítulo 30/38.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Inclua <code>@EnableCaching</code>, TTL, chave estável, política para resultado ausente e teste que conte acessos ao repositório. Verifique invalidação após commit e documente o comportamento com duas instâncias. Demonstre por teste que self-invocation não atravessa o proxy de cache.</p>\n        </div>\n      </div>"},{"id":"spring-cache-quiz","type":"quiz","authorship":"authored","conceptId":"spring-cache-key-ttl","prompt":"Qual pergunta vem antes de adicionar @Cacheable?","options":[{"id":"cache-a","label":"Qual chave representa o resultado e por quanto tempo dado stale é aceitável?","correct":true,"explanation":"Sem chave/TTL/invalidação, cache vira bug intermitente."},{"id":"cache-b","label":"Como esconder toda escrita do banco?","correct":false,"explanation":"Cache de leitura não substitui persistência nem consistência."},{"id":"cache-c","label":"Como garantir que todo dado será sempre fresco?","correct":false,"explanation":"Cache aceita algum risco de stale ou precisa invalidação rigorosa."}]}],"resources":[{"id":"spring-cache-reference","type":"official-docs","title":"Spring Framework Cache Abstraction","url":"https://docs.spring.io/spring-framework/reference/integration/cache.html","reinforces":"@Cacheable, @CacheEvict, key generation e cache abstraction.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"redis-cache-aside","type":"reference","title":"Redis: Cache-aside pattern","url":"https://redis.io/docs/latest/develop/use-cases/cache-aside/","reinforces":"Padrão cache-aside, invalidação e uso de Redis como cache.","language":"en","publisher":"Redis","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the spring cache flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for spring cache. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for spring cache with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"@Service","instruction":"Design the spring cache flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for spring cache with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"actuator","moduleId":"resilience-observability","order":3,"title":"Spring Boot Actuator & health checks","summary":"Como você sabe se sua aplicação em produção está \"de pé\" e saudável, sem precisar logar manualmente no servidor? Actuator expõe endpoints prontos com informações operacionais da aplicação.","objectives":["Expor endpoints operacionais com escopo seguro","Diferenciar endpoint de produto e endpoint de operação","Criar health/readiness com sinal útil de dependência","Evitar vazamento por endpoints sensíveis"],"whyItExists":"A fase de produção introduziu health mínimo. Aqui o aluno aprende a ferramenta Spring que expõe sinais operacionais reais para observabilidade, sem transformar Actuator em painel público sem segurança.","prerequisiteChapterIds":["spring-boot-fundamentos","compose","hardening-producao"],"conceptIds":["outros-endpoints-uteis","exposicao-so-o-que-voce-decide-nunca-tudo-por-padrao","healthindicator-customizado-seu-proprio-criterio-de-saudavel","micrometer-o-mecanismo-real-por-tras-de-actuator-metrics","readiness-e-liveness-dois-sinais-diferentes-uma-confusao-comum"],"introducedConceptIds":["actuator-operational-endpoints","health-indicator-dependency-signal"],"usedConceptIds":["health-readiness-liveness","managed-database-operations","spring-security-authorization"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"actuator-intuition","type":"intuition","authorship":"authored","title":"Endpoint operacional responde se o serviço pode ser operado","body":"API de produto atende usuário; endpoint operacional atende quem precisa saber se o processo está vivo, pronto, saudável, medindo e seguro. Expor isso sem critério também vira risco.","analogyLimit":"Painel de carro ajuda, mas algumas informações não devem ficar públicas na internet."},{"id":"actuator-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">DevOps</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-core\">43 · Spring Core</a>, <a class=\"prereq-tag\" href=\"#compose\">32 · Docker Compose</a></div>\n      </div>","fidelityText":"DevOps Dificuldade: Intermediário ⏱ ~2h de estudo + prática Pré-requisitos: 43 · Spring Core, 32 · Docker Compose"},{"id":"actuator-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Como você sabe se sua aplicação em produção está \"de pé\" e saudável, sem precisar logar manualmente no servidor? <strong>Actuator</strong> expõe endpoints prontos com informações operacionais da aplicação.</p>","fidelityText":"Como você sabe se sua aplicação em produção está \"de pé\" e saudável, sem precisar logar manualmente no servidor? Actuator expõe endpoints prontos com informações operacionais da aplicação."},{"id":"actuator-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"<!-- pom.xml -->\n<dependency>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-starter-actuator</artifactId>\n</dependency>","fidelityText":"<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>","highlightedHtml":"<span class=\"com\">&lt;!-- pom.xml --&gt;</span>\n&lt;dependency&gt;\n    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;\n    &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;\n&lt;/dependency&gt;","caption":"Exemplo executável de actuator.","explanation":["A dependência habilita os endpoints de gerenciamento do Actuator, separados da API de produto.","Por padrão, quase nada fica exposto via HTTP -- cada endpoint adicional precisa de decisão explícita (ver bloco de exposure.include mais abaixo)."],"commonMistakes":["Achar que adicionar a dependência já expõe todos os endpoints","Misturar management port/path sem documentar"]},{"id":"actuator-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"GET /actuator/health\n{\"status\": \"UP\"}\n\nGET /actuator/health   (com details habilitados)\n{\n  \"status\": \"UP\",\n  \"components\": {\n    \"db\": {\"status\": \"UP\"},\n    \"diskSpace\": {\"status\": \"UP\"}\n  }\n}","fidelityText":"GET /actuator/health {\"status\": \"UP\"} GET /actuator/health (com detalhes habilitados) { \"status\": \"UP\", \"components\": { \"db\": {\"status\": \"UP\"}, \"diskSpace\": {\"status\": \"UP\"} } }","highlightedHtml":"GET /actuator/health\n<span class=\"com\">{\"status\": \"UP\"}</span>\n\nGET /actuator/health   <span class=\"com\">(com detalhes habilitados)</span>\n<span class=\"com\">{\n  \"status\": \"UP\",\n  \"components\": {\n    \"db\": {\"status\": \"UP\"},\n    \"diskSpace\": {\"status\": \"UP\"}\n  }\n}</span>","caption":"Exemplo executável de actuator.","explanation":["/actuator/health resume o estado da aplicação e suas dependências (banco, disco) num único status agregado.","Com detalhes habilitados, cada componente aparece individualmente -- útil para diagnóstico, mas informação a mais para expor sem cuidado."],"commonMistakes":["Habilitar detalhes completos de health publicamente sem autenticação"]},{"id":"actuator-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\"><code>/actuator/health</code> é um sinal operacional. Kubernetes pode usar probes para retirar uma instância do tráfego ou reiniciá-la conforme a configuração. Docker e Compose, isoladamente, apenas marcam o container como <code>unhealthy</code>; o <code>HEALTHCHECK</code> não cria uma política automática de restart.</div>","fidelityText":"/actuator/health é um sinal operacional. Kubernetes pode usar probes para retirar uma instância do tráfego ou reiniciá-la conforme a configuração. Docker e Compose, isoladamente, apenas marcam o container como unhealthy; o HEALTHCHECK não cria uma política automática de restart."},{"id":"actuator-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"# docker-compose.yml -- usando o health check para o Compose saber quando a API está pronta:\nservices:\n  api:\n    build: .\n    healthcheck:\n      test: [\"CMD-SHELL\", \"wget -q -O - http://localhost:8080/actuator/health/readiness || exit 1\"]\n      interval: 10s\n      timeout: 5s\n      retries: 3\n      start_period: 30s\n# a imagem precisa conter wget; se não contiver, use um probe disponível\n# ou faça a checagem externamente. Não adicione curl sem ajustar a imagem.","fidelityText":"# docker-compose.yml -- usando o health check para o Compose saber quando a API está pronta: services: api: build: . healthcheck: test: [\"CMD-SHELL\", \"wget -q -O - http://localhost:8080/actuator/health/readiness || exit 1\"] interval: 10s timeout: 5s retries: 3 start_period: 30s # a imagem precisa conter wget; se não contiver, use um probe disponível # ou faça a checagem externamente. Não adicione curl sem ajustar a imagem.","highlightedHtml":"<span class=\"com\"># docker-compose.yml -- usando o health check para o Compose saber quando a API está pronta:</span>\nservices:\n  api:\n    build: .\n    healthcheck:\n      test: [\"CMD-SHELL\", \"wget -q -O - http://localhost:8080/actuator/health/readiness || exit 1\"]\n      interval: 10s\n      timeout: 5s\n      retries: 3\n      start_period: 30s\n<span class=\"com\"># a imagem precisa conter wget; se não contiver, use um probe disponível\n# ou faça a checagem externamente. Não adicione curl sem ajustar a imagem.</span>","caption":"Exemplo executável de actuator.","explanation":["O healthcheck do Compose usa o próprio /actuator/health/readiness da aplicação -- a infraestrutura de orquestração reaproveita o mesmo sinal que o Kubernetes usaria.","interval/timeout/retries/start_period definem a política de quantas vezes e com que frequência o Compose tenta antes de marcar o serviço como saudável ou não."],"commonMistakes":["Copiar este healthcheck sem garantir que a imagem tem a ferramenta usada (wget/curl) instalada"]},{"id":"actuator-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Outros endpoints úteis</h2>","fidelityText":"Outros endpoints úteis"},{"id":"actuator-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Endpoint</th><th>Mostra</th></tr>\n        <tr><td><code>/actuator/health</code></td><td>Status geral e de dependências (banco, disco)</td></tr>\n        <tr><td><code>/actuator/metrics</code></td><td>Métricas de JVM, requisições, threads</td></tr>\n        <tr><td><code>/actuator/info</code></td><td>Metadados customizáveis da aplicação (versão, build)</td></tr>\n        <tr><td><code>/actuator/loggers</code></td><td>Ver e <strong>alterar</strong> nível de log em runtime, sem reiniciar</td></tr>\n      </tbody></table>","fidelityText":"EndpointMostra /actuator/healthStatus geral e de dependências (banco, disco) /actuator/metricsMétricas de JVM, requisições, threads /actuator/infoMetadados customizáveis da aplicação (versão, build) /actuator/loggersVer e alterar nível de log em runtime, sem reiniciar"},{"id":"actuator-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Exposição: só o que você decide, nunca \"tudo por padrão\"</h2>","fidelityText":"Exposição: só o que você decide, nunca \"tudo por padrão\""},{"id":"actuator-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Por padrão, o Spring Boot expõe muito pouco via HTTP (na prática, só <code>/actuator/health</code>) — cada endpoint adicional precisa ser incluído explicitamente:</p>","fidelityText":"Por padrão, o Spring Boot expõe muito pouco via HTTP (na prática, só /actuator/health) — cada endpoint adicional precisa ser incluído explicitamente:"},{"id":"actuator-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"management:\n  endpoints:\n    web:\n      exposure:\n        include: health,info,metrics,loggers # explícito -- nunca \"*\" em produção","fidelityText":"management: endpoints: web: exposure: include: health,info,metrics,loggers # explícito -- nunca \"*\" em produção","highlightedHtml":"management:\n  endpoints:\n    web:\n      exposure:\n        include: health,info,metrics,loggers <span class=\"com\"># explícito -- nunca \"*\" em produção</span>","caption":"Exemplo executável de actuator.","explanation":["Por padrão o Spring Boot expõe muito pouco via HTTP -- cada endpoint adicional precisa estar explicitamente na lista de exposure.include.","include: \"*\" expõe tudo, inclusive endpoints perigosos como heapdump e shutdown -- nunca use o coringa em produção."],"commonMistakes":["Usar include: \"*\" por conveniência durante o desenvolvimento e esquecer de restringir antes de produção"]},{"id":"actuator-content-12","type":"html","authorship":"legacy-preserved","html":"<p><code>include: \"*\"</code> expõe absolutamente tudo, inclusive endpoints perigosos como <code>/actuator/heapdump</code> (baixa um dump completo da memória do processo — que pode conter senhas, tokens ou dados de sessão que estavam em variáveis Java no momento da captura) e <code>/actuator/shutdown</code> (se habilitado, encerra a aplicação para qualquer requisição, sem autenticação nenhuma por padrão). Liste exatamente os endpoints que precisam estar acessíveis, nunca o coringa.</p>","fidelityText":"include: \"*\" expõe absolutamente tudo, inclusive endpoints perigosos como /actuator/heapdump (baixa um dump completo da memória do processo — que pode conter senhas, tokens ou dados de sessão que estavam em variáveis Java no momento da captura) e /actuator/shutdown (se habilitado, encerra a aplicação para qualquer requisição, sem autenticação nenhuma por padrão). Liste exatamente os endpoints que precisam estar acessíveis, nunca o coringa."},{"id":"actuator-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>HealthIndicator customizado: seu próprio critério de \"saudável\"</h2>","fidelityText":"HealthIndicator customizado: seu próprio critério de \"saudável\""},{"id":"actuator-content-14","type":"html","authorship":"legacy-preserved","html":"<p>O Actuator já verifica banco e disco automaticamente, mas uma dependência específica do seu domínio (o serviço de pagamento externo do capítulo 55, por exemplo) precisa de um indicador próprio:</p>","fidelityText":"O Actuator já verifica banco e disco automaticamente, mas uma dependência específica do seu domínio (o serviço de pagamento externo do capítulo 55, por exemplo) precisa de um indicador próprio:"},{"id":"actuator-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"@Component\npublic class ServicePaymentHealthIndicator implements HealthIndicator {\n    @Override\n    public Health health() {\n        try {\n            httpClient.verifyAvailability(); // chamada leve e rápida, não a operação de negócio inteira\n            return Health.up().build();\n        } catch (Exception e) {\n            return Health.down().withDetail(\"error\", e.getMessage()).build();\n        }\n    }\n}","fidelityText":"@Component public class ServicoPagamentoHealthIndicator implements HealthIndicator { @Override public Health health() { try { clienteHttp.verificarDisponibilidade(); // chamada leve e rápida, não a operação de negócio inteira return Health.up().build(); } catch (Exception e) { return Health.down().withDetail(\"erro\", e.getMessage()).build(); } } }","highlightedHtml":"<span class=\"annotation\">@Component</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">ServicePaymentHealthIndicator</span> <span class=\"kw\">implements</span> HealthIndicator {\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public</span> Health <span class=\"fn\">health</span>() {\n        <span class=\"kw\">try</span> {\n            httpClient.verifyAvailability(); <span class=\"com\">// chamada leve e rápida, não a operação de negócio inteira</span>\n            <span class=\"kw\">return</span> Health.up().build();\n        } <span class=\"kw\">catch</span> (Exception e) {\n            <span class=\"kw\">return</span> Health.down().withDetail(<span class=\"str\">\"error\"</span>, e.getMessage()).build();\n        }\n    }\n}","caption":"Exemplo executável de actuator.","explanation":["Um HealthIndicator customizado passa a compor automaticamente o status agregado de /actuator/health -- se a dependência específica do domínio cair, isso aparece no health geral.","A chamada de verificação deve ser leve e rápida, nunca a operação de negócio completa -- health check não deveria ter o mesmo custo/risco do fluxo real."],"commonMistakes":["Implementar o HealthIndicator chamando a operação de negócio inteira em vez de uma verificação leve de disponibilidade"]},{"id":"actuator-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Esse indicador passa a compor automaticamente <code>/actuator/health</code> — se o serviço de pagamento cair, o status geral da aplicação reflete isso, mesmo com banco e disco saudáveis.</p>","fidelityText":"Esse indicador passa a compor automaticamente /actuator/health — se o serviço de pagamento cair, o status geral da aplicação reflete isso, mesmo com banco e disco saudáveis."},{"id":"actuator-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Micrometer: o mecanismo real por trás de /actuator/metrics</h2>","fidelityText":"Micrometer: o mecanismo real por trás de /actuator/metrics"},{"id":"actuator-content-18","type":"html","authorship":"legacy-preserved","html":"<p><code>/actuator/metrics</code> não coleta métricas sozinho — quem faz isso é o <strong>Micrometer</strong>, a fachada de métricas que o Spring Boot já traz e conecta automaticamente a contadores de requisição, latência, uso de JVM/threads/GC. Sem nomear o Micrometer, é fácil achar que \"o Actuator mede tudo magicamente\" — na prática, o Actuator só expõe via HTTP o que o Micrometer já vinha coletando por trás.</p>","fidelityText":"/actuator/metrics não coleta métricas sozinho — quem faz isso é o Micrometer, a fachada de métricas que o Spring Boot já traz e conecta automaticamente a contadores de requisição, latência, uso de JVM/threads/GC. Sem nomear o Micrometer, é fácil achar que \"o Actuator mede tudo magicamente\" — na prática, o Actuator só expõe via HTTP o que o Micrometer já vinha coletando por trás."},{"id":"actuator-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Readiness e liveness: dois sinais diferentes, uma confusão comum</h2>","fidelityText":"Readiness e liveness: dois sinais diferentes, uma confusão comum"},{"id":"actuator-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"management:\n  endpoint:\n    health:\n      probes:\n        enabled: true # habilita /actuator/health/liveness e /actuator/health/readiness","fidelityText":"management: endpoint: health: probes: enabled: true # habilita /actuator/health/liveness e /actuator/health/readiness","highlightedHtml":"management:\n  endpoint:\n    health:\n      probes:\n        enabled: true <span class=\"com\"># habilita /actuator/health/liveness e /actuator/health/readiness</span>","caption":"Exemplo executável de actuator.","explanation":["probes.enabled=true habilita /actuator/health/liveness e /actuator/health/readiness como sub-endpoints separados do health geral.","Liveness e readiness respondem perguntas diferentes -- misturá-las transforma uma falha temporária de dependência em reinícios desnecessários do processo."],"commonMistakes":["Usar o mesmo endpoint de health para liveness e readiness, sem distinguir os dois sinais"]},{"id":"actuator-content-21","type":"html","authorship":"legacy-preserved","html":"<p><strong>Liveness</strong> responde se o processo precisa ser reiniciado — não deveria cair só porque uma dependência externa está indisponível. <strong>Readiness</strong> responde se a instância pode receber tráfego agora. Misturar as duas transforma uma falha temporária de banco em uma tempestade de reinícios: se o orquestrador (Kubernetes) reinicia o processo achando que ele está morto, mas o problema real era só o banco fora do ar, o restart não resolve nada e ainda derruba conexões saudáveis em andamento.</p>","fidelityText":"Liveness responde se o processo precisa ser reiniciado — não deveria cair só porque uma dependência externa está indisponível. Readiness responde se a instância pode receber tráfego agora. Misturar as duas transforma uma falha temporária de banco em uma tempestade de reinícios: se o orquestrador (Kubernetes) reinicia o processo achando que ele está morto, mas o problema real era só o banco fora do ar, o restart não resolve nada e ainda derruba conexões saudáveis em andamento."},{"id":"actuator-content-22","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Nunca exponha o Actuator completo publicamente sem proteção</b> — endpoints como <code>/actuator/env</code> podem vazar variáveis de ambiente (incluindo secrets, capítulo seguinte), <code>/actuator/heapdump</code> pode vazar dados sensíveis em memória, e <code>/actuator/shutdown</code> pode derrubar a aplicação sem autenticação. Em produção, restrinja quais endpoints ficam expostos e proteja-os atrás de autenticação (capítulo 52), geralmente liberando só <code>/actuator/health</code> publicamente para health checks de infraestrutura.</div>","fidelityText":"Nunca exponha o Actuator completo publicamente sem proteção — endpoints como /actuator/env podem vazar variáveis de ambiente (incluindo secrets, capítulo seguinte), /actuator/heapdump pode vazar dados sensíveis em memória, e /actuator/shutdown pode derrubar a aplicação sem autenticação. Em produção, restrinja quais endpoints ficam expostos e proteja-os atrás de autenticação (capítulo 52), geralmente liberando só /actuator/health publicamente para health checks de infraestrutura."},{"id":"actuator-content-23","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Habilite o Actuator desde o primeiro dia do projeto, mesmo antes de precisar dele — é mais fácil configurar corretamente (incluindo o que deve ou não ficar público) desde o início do que descobrir em produção, sob pressão, que um endpoint sensível estava exposto o tempo todo.</div>","fidelityText":"Habilite o Actuator desde o primeiro dia do projeto, mesmo antes de precisar dele — é mais fácil configurar corretamente (incluindo o que deve ou não ficar público) desde o início do que descobrir em produção, sob pressão, que um endpoint sensível estava exposto o tempo todo."},{"id":"actuator-exercise-24","type":"exercise","authorship":"legacy-preserved","title":"Exercício 56.1 — Health check no Compose","prompt":"Adicione o Actuator ao projeto da biblioteca. Configure um healthcheck no serviço api do docker-compose.yml (capítulo 32), apontando para /actuator/health, e ajuste o depends_on do serviço para esperar o healthcheck do banco também (dica: depends_on: db: condition: service_healthy).","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 56.1 — Health check no Composefácil Adicione o Actuator ao projeto da biblioteca. Configure um healthcheck no serviço api do docker-compose.yml (capítulo 32), apontando para /actuator/health, e ajuste o depends_on do serviço para esperar o healthcheck do banco também (dica: depends_on: db: condition: service_healthy). Ver solução services: api: build: . depends_on: db: condition: service_healthy healthcheck: test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8080/actuator/health\"] interval: 10s db: image: postgres:16 healthcheck: test: [\"CMD-SHELL\", \"pg_isready -U postgres\"] interval: 5s","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 56.1 — Health check no Compose</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Adicione o Actuator ao projeto da biblioteca. Configure um <code>healthcheck</code> no serviço <code>api</code> do <code>docker-compose.yml</code> (capítulo 32), apontando para <code>/actuator/health</code>, e ajuste o <code>depends_on</code> do serviço para esperar o healthcheck do banco também (dica: <code>depends_on: db: condition: service_healthy</code>).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">services:\n  api:\n    build: .\n    depends_on:\n      db:\n        condition: service_healthy\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8080/actuator/health\"]\n      interval: 10s\n\n  db:\n    image: postgres:16\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U postgres\"]\n      interval: 5s</pre>\n        </div>\n      </div>"},{"id":"actuator-quiz","type":"quiz","authorship":"authored","conceptId":"actuator-operational-endpoints","prompt":"Qual cuidado é essencial ao habilitar Actuator?","options":[{"id":"act-a","label":"Expor apenas endpoints necessários, com segurança e sem dados sensíveis.","correct":true,"explanation":"Sinal operacional demais pode virar vazamento ou superfície de ataque."},{"id":"act-b","label":"Expor todos endpoints publicamente para facilitar debug.","correct":false,"explanation":"Debug fácil não justifica risco operacional."},{"id":"act-c","label":"Usar health para executar regra de negócio complexa.","correct":false,"explanation":"Health deve ser limitado, rápido e operacional."}]}],"resources":[{"id":"spring-actuator-endpoints","type":"official-docs","title":"Spring Boot Actuator Endpoints","url":"https://docs.spring.io/spring-boot/reference/actuator/endpoints.html","reinforces":"Endpoints, exposição, segurança e operação.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-actuator-health-groups","type":"official-docs","title":"Spring Boot Actuator Health Groups","url":"https://docs.spring.io/spring-boot/reference/actuator/endpoints.html#actuator.endpoints.health.groups","reinforces":"Health groups, readiness/liveness e exposição de saúde.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the actuator flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for actuator. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for actuator with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"<!-- pom.xml -->","instruction":"Design the actuator flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for actuator with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"observabilidade-pratica","moduleId":"resilience-observability","order":4,"title":"Observabilidade com logs, métricas, traces e SLOs","summary":"Observabilidade é a capacidade de investigar o estado interno por sinais externos. Actuator expõe pontos de integração; ainda é necessário produzir, exportar, armazenar, correlacionar e consultar logs, métricas e traces.","objectives":["Separar logs, métricas e traces por pergunta operacional","Instrumentar um trace com OpenTelemetry e inspecioná-lo no Jaeger","Provisionar um dashboard do Grafana como arquivo versionado, não como estado da UI","Escrever uma regra de alerta de burn rate com duas janelas (rápida e sustentada)","Propagar correlation ID sem vazar secret","Controlar cardinalidade de labels"],"whyItExists":"Depois de Actuator e resiliência, observabilidade deixa de ser ferramenta bonita e vira capacidade de responder: o que quebrou, quem foi afetado, onde está lento e se o objetivo de serviço está sendo violado.","prerequisiteChapterIds":["actuator","logging","resilience","coupled-services-lab"],"conceptIds":["os-tres-sinais-antes-das-ferramentas","logs-estruturados","correlation-id-com-mdc-e-logback","traces-com-opentelemetry-e-jaeger","metricas-e-cardinalidade","expondo-metricas-para-o-prometheus","stack-local-prometheus-e-grafana-com-docker-compose","dashboard-como-codigo-provisionamento-do-grafana","sli-slo-e-alertas","um-alerta-de-burn-rate-de-verdade","readiness-e-liveness","checkpoints-antes-do-laboratorio"],"introducedConceptIds":["structured-log-correlation-id","metrics-cardinality-labels","trace-span-boundary","sli-slo-error-budget"],"usedConceptIds":["log-nivel-contexto","log-parametrizado-causa","actuator-operational-endpoints","temporal-coupling-sync","synchronous-deadline-budget"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"observabilidade-pratica-intuition","type":"intuition","authorship":"authored","title":"Observabilidade começa pela pergunta que você precisa responder","body":"Log conta evento, métrica conta tendência e trace mostra caminho. Se você coleta tudo sem pergunta, paga custo e ainda não sabe diagnosticar incidente.","analogyLimit":"Caixa-preta ajuda, mas sistemas vivos exigem cardinalidade, retenção, privacidade e objetivos mensuráveis."},{"id":"observabilidade-pratica-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-devops\">Produção</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~5h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#actuator\">Actuator</a>, <a class=\"prereq-tag\" href=\"#logging\">Logging</a></div></div>","fidelityText":"ProduçãoDificuldade: Avançado⏱ ~5h de estudo e práticaPré-requisitos: Actuator, Logging"},{"id":"observabilidade-pratica-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Observabilidade é a capacidade de investigar o estado interno por sinais externos. Actuator expõe pontos de integração; ainda é necessário produzir, exportar, armazenar, correlacionar e consultar logs, métricas e traces.</p>","fidelityText":"Observabilidade é a capacidade de investigar o estado interno por sinais externos. Actuator expõe pontos de integração; ainda é necessário produzir, exportar, armazenar, correlacionar e consultar logs, métricas e traces."},{"id":"observabilidade-pratica-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Os três sinais antes das ferramentas</h2></div>\n    <p>Comece pela pergunta operacional; escolha o sinal que melhor a responde e correlacione-os quando necessário.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Log</dt><dd>Registro discreto de um acontecimento com contexto, como uma recusa de pagamento.</dd></div><div class=\"concept-card\"><dt>Métrica</dt><dd>Série numérica agregável ao longo do tempo, boa para taxa, erro, duração e saturação.</dd></div><div class=\"concept-card\"><dt>Trace</dt><dd>Caminho de uma requisição por operações e serviços, dividido em spans com duração e relação causal.</dd></div><div class=\"concept-card\"><dt>Cardinalidade</dt><dd>Quantidade de valores distintos de um campo ou tag. IDs criam cardinalidade praticamente ilimitada e custo operacional alto.</dd></div><div class=\"concept-card\"><dt>Micrometer</dt><dd>Camada de instrumentação usada pelo ecossistema Spring para registrar métricas em diferentes backends.</dd></div><div class=\"concept-card\"><dt>OpenTelemetry</dt><dd>Padrão e conjunto de ferramentas para gerar e exportar traces, métricas e logs de forma interoperável.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoOs três sinais antes das ferramentas Comece pela pergunta operacional; escolha o sinal que melhor a responde e correlacione-os quando necessário. LogRegistro discreto de um acontecimento com contexto, como uma recusa de pagamento.MétricaSérie numérica agregável ao longo do tempo, boa para taxa, erro, duração e saturação.TraceCaminho de uma requisição por operações e serviços, dividido em spans com duração e relação causal.CardinalidadeQuantidade de valores distintos de um campo ou tag. IDs criam cardinalidade praticamente ilimitada e custo operacional alto.MicrometerCamada de instrumentação usada pelo ecossistema Spring para registrar métricas em diferentes backends.OpenTelemetryPadrão e conjunto de ferramentas para gerar e exportar traces, métricas e logs de forma interoperável."},{"id":"observabilidade-pratica-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Logs estruturados</h2>","fidelityText":"Logs estruturados"},{"id":"observabilidade-pratica-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"{\n  \"timestamp\": \"2026-08-14T12:00:00Z\",\n  \"level\": \"WARN\",\n  \"event\": \"payment_declined\",\n  \"traceId\": \"9f2...\",\n  \"orderId\": 42,\n  \"reason\": \"limite\"\n}","fidelityText":"{ \"timestamp\": \"2026-08-14T12:00:00Z\", \"level\": \"WARN\", \"event\": \"pagamento_recusado\", \"traceId\": \"9f2...\", \"pedidoId\": 42, \"motivo\": \"limite\" }","highlightedHtml":"{\n  <span class=\"str\">\"timestamp\"</span>: <span class=\"str\">\"2026-08-14T12:00:00Z\"</span>,\n  <span class=\"str\">\"level\"</span>: <span class=\"str\">\"WARN\"</span>,\n  <span class=\"str\">\"event\"</span>: <span class=\"str\">\"payment_declined\"</span>,\n  <span class=\"str\">\"traceId\"</span>: <span class=\"str\">\"9f2...\"</span>,\n  <span class=\"str\">\"orderId\"</span>: 42,\n  <span class=\"str\">\"reason\"</span>: <span class=\"str\">\"limite\"</span>\n}","caption":"Exemplo executável de observabilidade-pratica.","explanation":["Log estruturado facilita busca por campos como requestId, usuário interno não sensível e operação.","Correlation ID une eventos da mesma requisição entre camadas/serviços."],"commonMistakes":["Colocar token/secret no log","Usar mensagem livre sem campos pesquisáveis"]},{"id":"observabilidade-pratica-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Use campos estáveis e cardinalidade controlada. Não registre senha, token, cartão, corpo integral nem <strong>PII</strong> — informação pessoal identificável — desnecessária. Um correlation/trace ID permite seguir a mesma operação entre serviços.</p>","fidelityText":"Use campos estáveis e cardinalidade controlada. Não registre senha, token, cartão, corpo integral nem PII — informação pessoal identificável — desnecessária. Um correlation/trace ID permite seguir a mesma operação entre serviços."},{"id":"observabilidade-pratica-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Correlation ID com MDC e Logback</h2>","fidelityText":"Correlation ID com MDC e Logback"},{"id":"observabilidade-pratica-content-8","type":"html","authorship":"legacy-preserved","html":"<p>O JSON de log acima mostra um campo <code>traceId</code>, mas alguém precisa colocá-lo lá em toda linha, sem que cada chamada de log passe esse valor manualmente. O <strong>MDC</strong> (<em>Mapped Diagnostic Context</em>) do SLF4J resolve isso: é um mapa por thread que o Logback lê automaticamente ao formatar cada linha.</p>","fidelityText":"O JSON de log acima mostra um campo traceId, mas alguém precisa colocá-lo lá em toda linha, sem que cada chamada de log passe esse valor manualmente. O MDC (Mapped Diagnostic Context) do SLF4J resolve isso: é um mapa por thread que o Logback lê automaticamente ao formatar cada linha."},{"id":"observabilidade-pratica-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"// Filtro que roda uma vez por requisição\npublic class CorrelationIdFilter implements Filter {\n    public void ofFilter(ServletRequest req, ServletResponse res, FilterChain chain)\n            throws IOException, ServletException {\n        String correlationId = Optional.ofNullable(((HttpServletRequest) req).getHeader(\"X-Correlation-Id\"))\n                .orElse(UUID.randomUUID().toString());\n        MDC.put(\"correlationId\", correlationId);\n        try {\n            chain.ofFilter(req, res);\n        } finally {\n            MDC.clear(); // obrigatório: threads de pool são reaproveitadas\n        }\n    }\n}","fidelityText":"// Filtro que roda uma vez por requisição public class CorrelationIdFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { String correlationId = Optional.ofNullable(((HttpServletRequest) req).getHeader(\"X-Correlation-Id\")) .orElse(UUID.randomUUID().toString()); MDC.put(\"correlationId\", correlationId); try { chain.doFilter(req, res); } finally { MDC.clear(); // obrigatório: threads de pool são reaproveitadas } } }","highlightedHtml":"<span class=\"com\">// Filtro que roda uma vez por requisição</span>\n<span class=\"kw\">public class</span> CorrelationIdFilter <span class=\"kw\">implements</span> Filter {\n    <span class=\"kw\">public void</span> ofFilter(ServletRequest req, ServletResponse res, FilterChain chain)\n            <span class=\"kw\">throws</span> IOException, ServletException {\n        String correlationId = Optional.ofNullable(((HttpServletRequest) req).getHeader(<span class=\"str\">\"X-Correlation-Id\"</span>))\n                .orElse(UUID.randomUUID().toString());\n        MDC.put(<span class=\"str\">\"correlationId\"</span>, correlationId);\n        <span class=\"kw\">try</span> {\n            chain.ofFilter(req, res);\n        } <span class=\"kw\">finally</span> {\n            MDC.clear(); <span class=\"com\">// obrigatório: threads de pool são reaproveitadas</span>\n        }\n    }\n}","caption":"Exemplo executável de observabilidade-pratica.","explanation":["O filtro roda uma vez por requisição, antes de qualquer controller, garantindo que o correlation ID exista assim que o processamento começa.","Reaproveitar o cabeçalho X-Correlation-Id (quando já vem de um serviço anterior) preserva a mesma identidade através de múltiplos saltos; gerar um novo só quando ausente evita perder a cadeia de correlação entre serviços."],"commonMistakes":["Colocar o MDC.put fora de um bloco protegido por try/finally, deixando o clear() vulnerável a nunca rodar se uma exceção escapar antes dele.","Gerar sempre um novo UUID, ignorando um cabeçalho de correlação que já veio de um serviço upstream."]},{"id":"observabilidade-pratica-content-10","type":"html","authorship":"legacy-preserved","html":"<p>O padrão do Logback (<code>logback-spring.xml</code>) referencia a chave com <code>%X{correlationId}</code>, e o valor aparece em toda linha emitida durante essa requisição, sem precisar passar o ID como parâmetro para cada chamada de log:</p>","fidelityText":"O padrão do Logback (logback-spring.xml) referencia a chave com %X{correlationId}, e o valor aparece em toda linha emitida durante essa requisição, sem precisar passar o ID como parâmetro para cada chamada de log:"},{"id":"observabilidade-pratica-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"<pattern>%d{ISO8601} [%X{correlationId}] %-5level %logger{36} - %msg%n</pattern>","fidelityText":"<pattern>%d{ISO8601} [%X{correlationId}] %-5level %logger{36} - %msg%n</pattern>","highlightedHtml":"&lt;pattern&gt;%d{ISO8601} [%X{correlationId}] %-5level %logger{36} - %msg%n&lt;/pattern&gt;","caption":"Exemplo executável de observabilidade-pratica.","explanation":["%X{correlationId} é a sintaxe do Logback para ler uma chave específica do MDC -- se a chave não existir naquele momento, o padrão imprime uma string vazia em vez de falhar.","Colocar o correlation ID logo no início do padrão facilita busca por todas as linhas de uma mesma requisição, mesmo em um arquivo de log com múltiplas requisições intercaladas."],"commonMistakes":["Esquecer de adicionar %X{correlationId} ao pattern do appender de produção depois de já tê-lo no de desenvolvimento.","Assumir que o valor aparece automaticamente sem que o filtro anterior tenha preenchido o MDC antes de o log ser emitido."]},{"id":"observabilidade-pratica-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>O <code>finally</code> com <code>MDC.clear()</code> não é opcional.</b> Um servidor de aplicação reutiliza threads entre requisições; sem limpar o MDC, a próxima requisição atendida por essa mesma thread herda o correlation ID (e qualquer outro valor) da requisição anterior, misturando os logs de duas operações sem relação sob o mesmo identificador.</div>","fidelityText":"O finally com MDC.clear() não é opcional. Um servidor de aplicação reutiliza threads entre requisições; sem limpar o MDC, a próxima requisição atendida por essa mesma thread herda o correlation ID (e qualquer outro valor) da requisição anterior, misturando os logs de duas operações sem relação sob o mesmo identificador."},{"id":"observabilidade-pratica-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Traces com OpenTelemetry e Jaeger</h2>","fidelityText":"Traces com OpenTelemetry e Jaeger"},{"id":"observabilidade-pratica-content-14","type":"html","authorship":"legacy-preserved","html":"<p>O JSON de log da primeira seção já tinha um campo <code>traceId</code> — mas de onde vem esse valor, e o que ele conecta? O <strong>MDC</strong> resolvido acima propaga um ID manualmente escolhido; um <strong>trace</strong> de verdade é gerado automaticamente pela instrumentação, atravessa chamadas entre camadas e serviços sem que o código de negócio precise saber disso, e pode ser inspecionado visualmente span por span num backend como o <strong>Jaeger</strong>.</p>","fidelityText":"O JSON de log da primeira seção já tinha um campo traceId — mas de onde vem esse valor, e o que ele conecta? O MDC resolvido acima propaga um ID manualmente escolhido; um trace de verdade é gerado automaticamente pela instrumentação, atravessa chamadas entre camadas e serviços sem que o código de negócio precise saber disso, e pode ser inspecionado visualmente span por span num backend como o Jaeger."},{"id":"observabilidade-pratica-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"<!-- pom.xml: ponte de tracing do Micrometer + exportador OTLP -->\n<dependency>\n    <groupId>io.micrometer</groupId>\n    <artifactId>micrometer-tracing-bridge-otel</artifactId>\n</dependency>\n<dependency>\n    <groupId>io.opentelemetry</groupId>\n    <artifactId>opentelemetry-exporter-otlp</artifactId>\n</dependency>\n\n# application.yml\nmanagement:\n  tracing:\n    sampling:\n      probability: 1.0  # 100% em dev; em produção, uma fração (ex.: 0.1)\n  otlp:\n    tracing:\n      endpoint: http://jaeger:4318/v1/traces  # nome do serviço Compose, não localhost","fidelityText":"<!-- pom.xml: ponte de tracing do Micrometer + exportador OTLP --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-otel</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-otlp</artifactId> </dependency> # application.yml management: tracing: sampling: probability: 1.0 # 100% em dev; em produção, uma fração (ex.: 0.1) otlp: tracing: endpoint: http://jaeger:4318/v1/traces # nome do serviço Compose, não localhost","highlightedHtml":"<span class=\"com\">&lt;!-- pom.xml: ponte de tracing do Micrometer + exportador OTLP --&gt;</span>\n&lt;dependency&gt;\n    &lt;groupId&gt;io.micrometer&lt;/groupId&gt;\n    &lt;artifactId&gt;micrometer-tracing-bridge-otel&lt;/artifactId&gt;\n&lt;/dependency&gt;\n&lt;dependency&gt;\n    &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;\n    &lt;artifactId&gt;opentelemetry-exporter-otlp&lt;/artifactId&gt;\n&lt;/dependency&gt;\n\n<span class=\"com\"># application.yml</span>\nmanagement:\n  tracing:\n    sampling:\n      probability: 1.0  <span class=\"com\"># 100% em dev; em produção, uma fração (ex.: 0.1)</span>\n  otlp:\n    tracing:\n      endpoint: http://jaeger:4318/v1/traces  <span class=\"com\"># nome do serviço Compose, não localhost</span>","caption":"Exemplo executável de observabilidade-pratica.","explanation":["A dependência micrometer-tracing-bridge-otel conecta a API de Observation do Micrometer ao SDK do OpenTelemetry; sem ela, spans automáticos de request HTTP nunca são criados, mesmo com o exportador presente.","sampling.probability controla que fração das requisições vira trace exportado -- 1.0 em desenvolvimento para nunca perder um exemplo; uma fração menor em produção, porque exportar todo trace de todo request tem custo de armazenamento e rede."],"commonMistakes":["Adicionar só o exportador OTLP sem a ponte de tracing (bridge), o que resulta em nenhum span sendo gerado -- a ponte é o que liga a instrumentação do Spring ao formato que o exportador entende.","Deixar sampling.probability em 1.0 em produção sob alto tráfego sem avaliar custo, ou esquecer que o endpoint do Jaeger/coletor precisa ser o nome do serviço Compose (jaeger), não localhost, quando a aplicação roda em outro container."]},{"id":"observabilidade-pratica-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Com isso, todo request HTTP já ganha um span automaticamente. Para ver uma operação de negócio específica dentro desse span, em vez de só \"a requisição inteira\", anote o método com <code>@Observed</code>:</p>","fidelityText":"Com isso, todo request HTTP já ganha um span automaticamente. Para ver uma operação de negócio específica dentro desse span, em vez de só \"a requisição inteira\", anote o método com @Observed:"},{"id":"observabilidade-pratica-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class PaymentService {\n\n    @Observed(name = \"payment.process\", contextualName = \"process-payment-gateway\")\n    public ResultPayment process(Order order) {\n        // Vira um span filho, aninhado sob o span HTTP que o\n        // Spring MVC já inicia automaticamente para esta requisição.\n        return gateway.charge(order);\n    }\n}","fidelityText":"@Service public class PagamentoService { @Observed(name = \"pagamento.processar\", contextualName = \"processar-pagamento-gateway\") public ResultadoPagamento processar(Pedido pedido) { // Vira um span filho, aninhado sob o span HTTP que o // Spring MVC já inicia automaticamente para esta requisição. return gateway.cobrar(pedido); } }","highlightedHtml":"<span class=\"kw\">@Service</span>\n<span class=\"kw\">public class</span> PaymentService {\n\n    <span class=\"kw\">@Observed</span>(name = <span class=\"str\">\"payment.process\"</span>, contextualName = <span class=\"str\">\"process-payment-gateway\"</span>)\n    <span class=\"kw\">public</span> ResultPayment process(Order order) {\n        <span class=\"com\">// Vira um span filho, aninhado sob o span HTTP que o</span>\n        <span class=\"com\">// Spring MVC já inicia automaticamente para esta requisição.</span>\n        <span class=\"kw\">return</span> gateway.charge(order);\n    }\n}","caption":"Exemplo executável de observabilidade-pratica.","explanation":["@Observed cria um span filho (ou uma métrica, dependendo da configuração) em torno do método anotado, nomeado explicitamente -- diferente do span HTTP automático, que só mede a requisição inteira sem detalhar qual parte do processamento consumiu o tempo.","O span filho herda o contexto do span pai automaticamente quando a chamada acontece na mesma thread -- é essa relação pai-filho que aparece como hierarquia visual no Jaeger."],"commonMistakes":["@Observed depende de proxy AOP (spring-boot-starter-aop no classpath) para funcionar; sem essa dependência, a anotação é silenciosamente ignorada e nenhum span extra aparece, sem erro nem aviso.","Chamar o método anotado a partir de outro método do MESMO bean (self-invocation) -- assim como qualquer AOP baseado em proxy do Spring, a chamada interna não passa pelo proxy e a anotação não tem efeito."]},{"id":"observabilidade-pratica-content-18","type":"html","authorship":"legacy-preserved","html":"<p>Depois de subir o Jaeger (UI em <code>localhost:16686</code>), busque pelo nome do serviço ou cole o mesmo <code>traceId</code> que já aparece no log estruturado. Cada span vira uma barra horizontal com sua duração; o span <code>processar-pagamento-gateway</code>, aninhado sob o span HTTP, mostra exatamente quanto tempo essa etapa específica consumiu dentro da requisição total — a mesma pergunta que uma métrica de duração responde de forma agregada, mas aqui para <strong>uma requisição específica</strong>, o que uma métrica sozinha nunca mostra.</p>","fidelityText":"Depois de subir o Jaeger (UI em localhost:16686), busque pelo nome do serviço ou cole o mesmo traceId que já aparece no log estruturado. Cada span vira uma barra horizontal com sua duração; o span processar-pagamento-gateway, aninhado sob o span HTTP, mostra exatamente quanto tempo essa etapa específica consumiu dentro da requisição total — a mesma pergunta que uma métrica de duração responde de forma agregada, mas aqui para uma requisição específica, o que uma métrica sozinha nunca mostra."},{"id":"observabilidade-pratica-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Contexto de trace não atravessa uma thread nova sozinho.</b> Se o código abrir uma thread manualmente (<code>new Thread(...)</code>, um <code>ExecutorService</code> comum, ou <code>@Async</code> sem propagação de contexto), o span filho perde a referência ao span pai e aparece como um trace desconectado — ou nem aparece. Use o executor instrumentado que a auto-configuração do Spring Boot já registra, ou propague o contexto explicitamente.</div>","fidelityText":"Contexto de trace não atravessa uma thread nova sozinho. Se o código abrir uma thread manualmente (new Thread(...), um ExecutorService comum, ou @Async sem propagação de contexto), o span filho perde a referência ao span pai e aparece como um trace desconectado — ou nem aparece. Use o executor instrumentado que a auto-configuração do Spring Boot já registra, ou propague o contexto explicitamente."},{"id":"observabilidade-pratica-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Métricas e cardinalidade</h2>","fidelityText":"Métricas e cardinalidade"},{"id":"observabilidade-pratica-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"Counter.builder(\"orders.created\").tag(\"channel\", channel).register(registry).increment();\nTimer.Sample sample = Timer.start(registry);\ntry { process(); }\nfinally { sample.stop(registry.timer(\"orders.duration\")); }","fidelityText":"Counter.builder(\"pedidos.criados\").tag(\"canal\", canal).register(registry).increment(); Timer.Sample sample = Timer.start(registry); try { processar(); } finally { sample.stop(registry.timer(\"pedidos.duracao\")); }","highlightedHtml":"Counter.builder(<span class=\"str\">\"orders.created\"</span>).tag(<span class=\"str\">\"channel\"</span>, channel).register(registry).increment();\nTimer.Sample sample = Timer.start(registry);\n<span class=\"kw\">try</span> { process(); }\n<span class=\"kw\">finally</span> { sample.stop(registry.timer(<span class=\"str\">\"orders.duration\"</span>)); }","caption":"Exemplo executável de observabilidade-pratica.","explanation":["Métricas com labels permitem agregação, mas labels de alta cardinalidade explodem custo e ruído.","Trace/span mostra caminho e latência entre fronteiras de chamada."],"commonMistakes":["Usar userId como label","Confundir trace com auditoria de negócio"]},{"id":"observabilidade-pratica-content-22","type":"html","authorship":"legacy-preserved","html":"<p>Nunca use <code>usuarioId</code>, URL inteira ou mensagem de erro como tag: séries sem limite podem derrubar o backend de métricas. Meça taxa, erros, duração e saturação. Métrica diz que existe um padrão; trace mostra o caminho de uma requisição; log fornece detalhes discretos.</p>","fidelityText":"Nunca use usuarioId, URL inteira ou mensagem de erro como tag: séries sem limite podem derrubar o backend de métricas. Meça taxa, erros, duração e saturação. Métrica diz que existe um padrão; trace mostra o caminho de uma requisição; log fornece detalhes discretos."},{"id":"observabilidade-pratica-content-23","type":"html","authorship":"legacy-preserved","html":"<p>Essas quatro métricas seguem dois métodos consagrados da indústria, com nomes que valem conhecer: <strong>RED</strong> (<em>Rate, Errors, Duration</em>) para serviços orientados a requisição — quantas chegam, quantas falham, quanto tempo levam; e <strong>USE</strong> (<em>Utilization, Saturation, Errors</em>) para recursos como CPU, memória, disco ou pool de conexões — quão ocupado está, quanto trabalho está enfileirado esperando, e quantos erros o próprio recurso reporta. Um endpoint lento é RED; um pool de conexões esgotado é USE — o mesmo sintoma (lentidão) pode ter causa em qualquer um dos dois, e saber qual método aplicar a qual componente acelera o diagnóstico.</p>","fidelityText":"Essas quatro métricas seguem dois métodos consagrados da indústria, com nomes que valem conhecer: RED (Rate, Errors, Duration) para serviços orientados a requisição — quantas chegam, quantas falham, quanto tempo levam; e USE (Utilization, Saturation, Errors) para recursos como CPU, memória, disco ou pool de conexões — quão ocupado está, quanto trabalho está enfileirado esperando, e quantos erros o próprio recurso reporta. Um endpoint lento é RED; um pool de conexões esgotado é USE — o mesmo sintoma (lentidão) pode ter causa em qualquer um dos dois, e saber qual método aplicar a qual componente acelera o diagnóstico."},{"id":"observabilidade-pratica-content-24","type":"html","authorship":"legacy-preserved","html":"<h2>Expondo métricas para o Prometheus</h2>","fidelityText":"Expondo métricas para o Prometheus"},{"id":"observabilidade-pratica-content-25","type":"html","authorship":"legacy-preserved","html":"<p>Registrar um <code>Counter</code> ou <code>Timer</code> no Micrometer não basta: alguém precisa buscar esses valores periodicamente e guardá-los como série temporal. O <strong>Prometheus</strong> faz isso por <em>pull</em> — ele mesmo visita um endpoint HTTP da aplicação em intervalos regulares, em vez de a aplicação enviar métricas ativamente.</p>","fidelityText":"Registrar um Counter ou Timer no Micrometer não basta: alguém precisa buscar esses valores periodicamente e guardá-los como série temporal. O Prometheus faz isso por pull — ele mesmo visita um endpoint HTTP da aplicação em intervalos regulares, em vez de a aplicação enviar métricas ativamente."},{"id":"observabilidade-pratica-code-26","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.yml\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: health, prometheus\n  metrics:\n    tags:\n      application: orders-service","fidelityText":"# application.yml management: endpoints: web: exposure: include: health, prometheus metrics: tags: application: pedidos-service","highlightedHtml":"<span class=\"com\"># application.yml</span>\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: health, prometheus\n  metrics:\n    tags:\n      application: orders-service","caption":"Exemplo executável de observabilidade-pratica.","explanation":["Expor o endpoint prometheus é aditivo: por padrão o Actuator só expõe /health; é preciso listar explicitamente cada endpoint que deve ficar acessível via HTTP.","A tag application vira um label em toda métrica exportada, permitindo distinguir a origem quando várias instâncias/serviços compartilham o mesmo Prometheus."],"commonMistakes":["Usar include: \"*\" para expor todos os endpoints do Actuator de uma vez, incluindo alguns sensíveis (env, beans) sem avaliar o risco.","Esquecer a dependência micrometer-registry-prometheus no build, fazendo o endpoint /actuator/prometheus retornar 404 mesmo com a configuração correta."]},{"id":"observabilidade-pratica-content-27","type":"html","authorship":"legacy-preserved","html":"<p>Com a dependência <code>micrometer-registry-prometheus</code> no classpath, o Actuator passa a expor <code>/actuator/prometheus</code> num formato que o Prometheus entende:</p>","fidelityText":"Com a dependência micrometer-registry-prometheus no classpath, o Actuator passa a expor /actuator/prometheus num formato que o Prometheus entende:"},{"id":"observabilidade-pratica-code-28","type":"code","authorship":"legacy-preserved","language":"java","source":"curl localhost:8080/actuator/prometheus\n\n# resultado (recorte)\norders_created_total{channel=\"web\",} 128.0\norders_duration_seconds_bucket{le=\"0.5\",} 118.0\norders_duration_seconds_count 128.0\norders_duration_seconds_sum 34.7","fidelityText":"curl localhost:8080/actuator/prometheus # resultado (recorte) pedidos_criados_total{canal=\"web\",} 128.0 pedidos_duracao_seconds_bucket{le=\"0.5\",} 118.0 pedidos_duracao_seconds_count 128.0 pedidos_duracao_seconds_sum 34.7","highlightedHtml":"curl localhost:8080/actuator/prometheus\n\n<span class=\"com\"># resultado (recorte)</span>\norders_created_total{channel=<span class=\"str\">\"web\"</span>,} 128.0\norders_duration_seconds_bucket{le=<span class=\"str\">\"0.5\"</span>,} 118.0\norders_duration_seconds_count 128.0\norders_duration_seconds_sum 34.7","caption":"Exemplo executável de observabilidade-pratica.","explanation":["O formato de texto do Prometheus é linha a linha: nome da métrica, labels entre chaves e valor numérico -- é isso que o Prometheus faz o parse a cada scrape.","_bucket, _count e _sum juntos formam um histograma: permitem calcular qualquer percentil depois, sem a aplicação precisar pré-calcular p50/p95/p99 ela mesma."],"commonMistakes":["Tentar ler percentil diretamente de uma métrica _sum/_count sem entender que o cálculo do percentil acontece na consulta (PromQL), não na exportação.","Estranhar o sufixo _total ou _seconds e tentar removê-lo manualmente -- esses sufixos seguem a convenção de nomenclatura do Prometheus e são adicionados automaticamente pelo Micrometer."]},{"id":"observabilidade-pratica-content-29","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Esse endpoint não é para o navegador do usuário final.</b> Ele expõe nomes de rotas internas, contagem de erros por endpoint e outras pistas operacionais — trate-o como qualquer outro endpoint do Actuator: atrás de rede interna ou autenticação, nunca público sem controle.</div>","fidelityText":"Esse endpoint não é para o navegador do usuário final. Ele expõe nomes de rotas internas, contagem de erros por endpoint e outras pistas operacionais — trate-o como qualquer outro endpoint do Actuator: atrás de rede interna ou autenticação, nunca público sem controle."},{"id":"observabilidade-pratica-content-30","type":"html","authorship":"legacy-preserved","html":"<h2>Stack local: Prometheus e Grafana com Docker Compose</h2>","fidelityText":"Stack local: Prometheus e Grafana com Docker Compose"},{"id":"observabilidade-pratica-content-31","type":"html","authorship":"legacy-preserved","html":"<p>Para transformar esse endpoint em um painel visível, faltam dois serviços: um que faça o <em>scrape</em> periódico (Prometheus) e um que desenhe gráficos a partir dele (Grafana). O módulo de containers já ensinou Compose — aqui é só mais um serviço na mesma orquestra. O Jaeger da seção de traces entra na mesma composição.</p>","fidelityText":"Para transformar esse endpoint em um painel visível, faltam dois serviços: um que faça o scrape periódico (Prometheus) e um que desenhe gráficos a partir dele (Grafana). O módulo de containers já ensinou Compose — aqui é só mais um serviço na mesma orquestra. O Jaeger da seção de traces entra na mesma composição."},{"id":"observabilidade-pratica-code-32","type":"code","authorship":"legacy-preserved","language":"java","source":"# docker-compose.yml\nservices:\n  app:\n    build: .\n    ports:\n      - \"8080:8080\"\n\n  prometheus:\n    image: prom/prometheus:v2.53.0\n    volumes:\n      - ./prometheus.yml:/etc/prometheus/prometheus.yml\n    ports:\n      - \"9090:9090\"\n\n  grafana:\n    image: grafana/grafana:11.1.0\n    ports:\n      - \"3000:3000\"\n    depends_on:\n      - prometheus\n\n  jaeger:\n    image: jaegertracing/all-in-one:1.60\n    ports:\n      - \"16686:16686\"  # UI\n      - \"4318:4318\"    # recebedor OTLP HTTP","fidelityText":"# docker-compose.yml services: app: build: . ports: - \"8080:8080\" prometheus: image: prom/prometheus:v2.53.0 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml ports: - \"9090:9090\" grafana: image: grafana/grafana:11.1.0 ports: - \"3000:3000\" depends_on: - prometheus jaeger: image: jaegertracing/all-in-one:1.60 ports: - \"16686:16686\" # UI - \"4318:4318\" # recebedor OTLP HTTP","highlightedHtml":"<span class=\"com\"># docker-compose.yml</span>\nservices:\n  app:\n    build: .\n    ports:\n      - <span class=\"str\">\"8080:8080\"</span>\n\n  prometheus:\n    image: prom/prometheus:v2.53.0\n    volumes:\n      - ./prometheus.yml:/etc/prometheus/prometheus.yml\n    ports:\n      - <span class=\"str\">\"9090:9090\"</span>\n\n  grafana:\n    image: grafana/grafana:11.1.0\n    ports:\n      - <span class=\"str\">\"3000:3000\"</span>\n    depends_on:\n      - prometheus\n\n  jaeger:\n    image: jaegertracing/all-in-one:1.60\n    ports:\n      - <span class=\"str\">\"16686:16686\"</span>  <span class=\"com\"># UI</span>\n      - <span class=\"str\">\"4318:4318\"</span>    <span class=\"com\"># recebedor OTLP HTTP</span>","caption":"Exemplo executável de observabilidade-pratica.","explanation":["Os três serviços dividem a mesma rede padrão do Compose; o Prometheus alcança a aplicação pelo nome do serviço (app), nunca por localhost.","O volume monta o prometheus.yml do host dentro do container -- editar o arquivo local e reiniciar o serviço aplica a nova configuração de scrape sem reconstruir a imagem."],"commonMistakes":["Apontar o Grafana ou o Prometheus para localhost:8080 em vez do nome do serviço (app:8080), um erro que só aparece rodando dentro do Compose.","Esquecer depends_on entre grafana e prometheus, deixando o Grafana tentar configurar a fonte de dados antes de o Prometheus estar no ar."]},{"id":"observabilidade-pratica-code-33","type":"code","authorship":"legacy-preserved","language":"java","source":"# prometheus.yml\nscrape_configs:\n  - job_name: \"orders-service\"\n    scrape_interval: 15s\n    metrics_path: \"/actuator/prometheus\"\n    static_configs:\n      - targets: [\"app:8080\"]  # nome do serviço Compose, não localhost","fidelityText":"# prometheus.yml scrape_configs: - job_name: \"pedidos-service\" scrape_interval: 15s metrics_path: \"/actuator/prometheus\" static_configs: - targets: [\"app:8080\"] # nome do serviço Compose, não localhost","highlightedHtml":"<span class=\"com\"># prometheus.yml</span>\nscrape_configs:\n  - job_name: <span class=\"str\">\"orders-service\"</span>\n    scrape_interval: 15s\n    metrics_path: <span class=\"str\">\"/actuator/prometheus\"</span>\n    static_configs:\n      - targets: [<span class=\"str\">\"app:8080\"</span>]  <span class=\"com\"># nome do serviço Compose, não localhost</span>","caption":"Exemplo executável de observabilidade-pratica.","explanation":["scrape_interval controla a frequência de coleta; um intervalo curto demais aumenta carga sem necessariamente melhorar o diagnóstico, já que a maioria dos SLOs opera em janelas de minutos, não segundos.","targets usa o nome do serviço do Compose (app), não localhost nem o IP do container -- a resolução de nome acontece pela rede interna que o Compose cria."],"commonMistakes":["Deixar metrics_path no valor padrão (/metrics) em vez de /actuator/prometheus, que é o caminho real exposto pelo Spring Boot Actuator.","Declarar múltiplos targets sem job_name distintos, misturando métricas de serviços diferentes sob a mesma série."]},{"id":"observabilidade-pratica-content-34","type":"html","authorship":"legacy-preserved","html":"<p>Depois de <code>docker compose up</code>, o Grafana (em <code>localhost:3000</code>) recebe o Prometheus como fonte de dados e consulta métricas com PromQL, como <code>rate(pedidos_duracao_seconds_count[5m])</code> para taxa por segundo, ou o percentil calculado a partir dos buckets do histograma. Um painel mínimo mostra três coisas: taxa de requisições, taxa de erro e latência por percentil — nessa ordem, porque é essa a sequência que normalmente leva à causa de um incidente.</p>","fidelityText":"Depois de docker compose up, o Grafana (em localhost:3000) recebe o Prometheus como fonte de dados e consulta métricas com PromQL, como rate(pedidos_duracao_seconds_count[5m]) para taxa por segundo, ou o percentil calculado a partir dos buckets do histograma. Um painel mínimo mostra três coisas: taxa de requisições, taxa de erro e latência por percentil — nessa ordem, porque é essa a sequência que normalmente leva à causa de um incidente."},{"id":"observabilidade-pratica-content-35","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>Por que percentil, não média?</b> Uma média de 120 ms pode esconder que 1% das requisições levou 400 ms — a maioria rápida dilui os poucos outliers lentos no cálculo. O percentil p99 revela exatamente essa cauda, que costuma ser onde os problemas reais de produção aparecem.</div>","fidelityText":"Por que percentil, não média? Uma média de 120 ms pode esconder que 1% das requisições levou 400 ms — a maioria rápida dilui os poucos outliers lentos no cálculo. O percentil p99 revela exatamente essa cauda, que costuma ser onde os problemas reais de produção aparecem."},{"id":"observabilidade-pratica-content-36","type":"html","authorship":"legacy-preserved","html":"<h2>Dashboard como código: provisionamento do Grafana</h2>","fidelityText":"Dashboard como código: provisionamento do Grafana"},{"id":"observabilidade-pratica-content-37","type":"html","authorship":"legacy-preserved","html":"<p>O painel descrito acima — criado clicando na interface do Grafana — desaparece se o container for recriado sem um volume, e ninguém consegue revisar a mudança em um Pull Request. A prática correta trata o dashboard como qualquer outro artefato de configuração: um arquivo versionado que o Grafana carrega automaticamente ao subir, sem passo manual na UI.</p>","fidelityText":"O painel descrito acima — criado clicando na interface do Grafana — desaparece se o container for recriado sem um volume, e ninguém consegue revisar a mudança em um Pull Request. A prática correta trata o dashboard como qualquer outro artefato de configuração: um arquivo versionado que o Grafana carrega automaticamente ao subir, sem passo manual na UI."},{"id":"observabilidade-pratica-code-38","type":"code","authorship":"legacy-preserved","language":"java","source":"# docker-compose.yml (serviço grafana, com provisionamento)\n  grafana:\n    image: grafana/grafana:11.1.0\n    ports:\n      - \"3000:3000\"\n    volumes:\n      - ./grafana/provisioning:/etc/grafana/provisioning\n      - ./grafana/dashboards:/etc/grafana/dashboards\n    depends_on:\n      - prometheus\n\n# grafana/provisioning/datasources/prometheus.yml\napiVersion: 1\ndatasources:\n  - name: Prometheus\n    type: prometheus\n    url: http://prometheus:9090\n    isDefault: true\n\n# grafana/provisioning/dashboards/dashboards.yml\napiVersion: 1\nproviders:\n  - name: orders-service\n    folder: ''\n    type: file\n    options:\n      path: /etc/grafana/dashboards","fidelityText":"# docker-compose.yml (serviço grafana, com provisionamento) grafana: image: grafana/grafana:11.1.0 ports: - \"3000:3000\" volumes: - ./grafana/provisioning:/etc/grafana/provisioning - ./grafana/dashboards:/etc/grafana/dashboards depends_on: - prometheus # grafana/provisioning/datasources/prometheus.yml apiVersion: 1 datasources: - name: Prometheus type: prometheus url: http://prometheus:9090 isDefault: true # grafana/provisioning/dashboards/dashboards.yml apiVersion: 1 providers: - name: pedidos-service folder: '' type: file options: path: /etc/grafana/dashboards","highlightedHtml":"<span class=\"com\"># docker-compose.yml (serviço grafana, com provisionamento)</span>\n  grafana:\n    image: grafana/grafana:11.1.0\n    ports:\n      - <span class=\"str\">\"3000:3000\"</span>\n    volumes:\n      - ./grafana/provisioning:/etc/grafana/provisioning\n      - ./grafana/dashboards:/etc/grafana/dashboards\n    depends_on:\n      - prometheus\n\n<span class=\"com\"># grafana/provisioning/datasources/prometheus.yml</span>\napiVersion: 1\ndatasources:\n  - name: Prometheus\n    type: prometheus\n    url: http://prometheus:9090\n    isDefault: true\n\n<span class=\"com\"># grafana/provisioning/dashboards/dashboards.yml</span>\napiVersion: 1\nproviders:\n  - name: orders-service\n    folder: <span class=\"str\">''</span>\n    type: file\n    options:\n      path: /etc/grafana/dashboards","caption":"Exemplo executável de observabilidade-pratica.","explanation":["O provider de datasource e o de dashboards são dois arquivos de configuração separados, mas ambos seguem o mesmo mecanismo de provisioning do Grafana: ele os lê ao iniciar e aplica sem intervenção manual na UI.","O campo path do provider de dashboards aponta para uma PASTA, não um arquivo -- qualquer .json colocado ali (como o da próxima seção) vira painel automaticamente na próxima reinicialização, sem precisar registrar cada arquivo individualmente."],"commonMistakes":["Montar o volume de provisioning só parcialmente (ex.: só dashboards, sem datasources) e esperar que a fonte de dados \"Prometheus\" apareça sozinha -- sem o arquivo de datasource, o dashboard existe mas fica sem métrica nenhuma para consultar.","Editar o dashboard provisionado diretamente pela UI do Grafana esperando persistência -- painéis carregados via provisioning voltam ao estado do arquivo a cada reinício, então a edição precisa ser exportada de volta para o arquivo, não deixada só na UI."]},{"id":"observabilidade-pratica-content-39","type":"html","authorship":"legacy-preserved","html":"<p>O arquivo de datasource elimina o passo manual de \"conectar o Prometheus\" na UI a cada <code>docker compose up</code>. O de dashboards aponta para uma pasta onde qualquer arquivo <code>.json</code> vira um painel automaticamente — inclusive o que a próxima seção mostra.</p>","fidelityText":"O arquivo de datasource elimina o passo manual de \"conectar o Prometheus\" na UI a cada docker compose up. O de dashboards aponta para uma pasta onde qualquer arquivo .json vira um painel automaticamente — inclusive o que a próxima seção mostra."},{"id":"observabilidade-pratica-code-40","type":"code","authorship":"legacy-preserved","language":"java","source":"// grafana/dashboards/pedidos-service.json\n{\n  \"title\": \"orders-service\",\n  \"panels\": [\n    {\n      \"title\": \"Rate of requests by Second\",\n      \"type\": \"timeseries\",\n      \"targets\": [\n        { \"expr\": \"rate(orders_created_total[5m])\", \"legendFormat\": \"{{channel}}\" }\n      ]\n    },\n    {\n      \"title\": \"Latency p99\",\n      \"type\": \"timeseries\",\n      \"targets\": [\n        { \"expr\": \"histogram_quantile(0.99, rate(orders_duration_seconds_bucket[5m]))\", \"legendFormat\": \"p99\" }\n      ]\n    }\n  ]\n}","fidelityText":"// grafana/dashboards/pedidos-service.json { \"title\": \"pedidos-service\", \"panels\": [ { \"title\": \"Taxa de requisições por segundo\", \"type\": \"timeseries\", \"targets\": [ { \"expr\": \"rate(pedidos_criados_total[5m])\", \"legendFormat\": \"{{canal}}\" } ] }, { \"title\": \"Latência p99\", \"type\": \"timeseries\", \"targets\": [ { \"expr\": \"histogram_quantile(0.99, rate(pedidos_duracao_seconds_bucket[5m]))\", \"legendFormat\": \"p99\" } ] } ] }","highlightedHtml":"<span class=\"com\">// grafana/dashboards/pedidos-service.json</span>\n{\n  <span class=\"str\">\"title\"</span>: <span class=\"str\">\"orders-service\"</span>,\n  <span class=\"str\">\"panels\"</span>: [\n    {\n      <span class=\"str\">\"title\"</span>: <span class=\"str\">\"Rate of requests by Second\"</span>,\n      <span class=\"str\">\"type\"</span>: <span class=\"str\">\"timeseries\"</span>,\n      <span class=\"str\">\"targets\"</span>: [\n        { <span class=\"str\">\"expr\"</span>: <span class=\"str\">\"rate(orders_created_total[5m])\"</span>, <span class=\"str\">\"legendFormat\"</span>: <span class=\"str\">\"{{channel}}\"</span> }\n      ]\n    },\n    {\n      <span class=\"str\">\"title\"</span>: <span class=\"str\">\"Latency p99\"</span>,\n      <span class=\"str\">\"type\"</span>: <span class=\"str\">\"timeseries\"</span>,\n      <span class=\"str\">\"targets\"</span>: [\n        { <span class=\"str\">\"expr\"</span>: <span class=\"str\">\"histogram_quantile(0.99, rate(orders_duration_seconds_bucket[5m]))\"</span>, <span class=\"str\">\"legendFormat\"</span>: <span class=\"str\">\"p99\"</span> }\n      ]\n    }\n  ]\n}","caption":"Exemplo executável de observabilidade-pratica.","explanation":["O arquivo de dashboard é dado estruturado, não configuração de servidor -- cada panels[] descreve um gráfico com seu título, tipo e as consultas PromQL (targets) que o alimentam, exatamente os mesmos tipos de consulta que se digitaria manualmente na UI.","legendFormat com {{canal}} interpola o valor do label da série na legenda do gráfico -- útil quando uma métrica tem múltiplas séries (uma por canal) e o painel precisa distinguir visualmente cada uma."],"commonMistakes":["Escrever a expressão PromQL errada no JSON (ex.: sem rate() sobre um contador) e só descobrir o erro quando o painel carrega um gráfico sem sentido -- vale testar a expressão na aba Explore do Grafana antes de fixá-la no arquivo.","Esquecer de que o arquivo de dashboard também é código de verdade: sem revisão, um painel com a métrica errada entra despercebido tão facilmente quanto um bug em qualquer outro arquivo do repositório."]},{"id":"observabilidade-pratica-content-41","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Exportar o JSON pela UI (Share → Export) é um atalho aceitável — parar aí não é.</b> Montar o painel na tela e nunca commitar o arquivo exportado deixa o dashboard preso à máquina de quem o criou; sem o arquivo no repositório, a próxima pessoa que recriar o ambiente (ou um novo membro do time) não tem o painel, mesmo que ele \"sempre tenha existido\" para quem já usa o Grafana local.</div>","fidelityText":"Exportar o JSON pela UI (Share → Export) é um atalho aceitável — parar aí não é. Montar o painel na tela e nunca commitar o arquivo exportado deixa o dashboard preso à máquina de quem o criou; sem o arquivo no repositório, a próxima pessoa que recriar o ambiente (ou um novo membro do time) não tem o painel, mesmo que ele \"sempre tenha existido\" para quem já usa o Grafana local."},{"id":"observabilidade-pratica-content-42","type":"html","authorship":"legacy-preserved","html":"<h2>SLI, SLO e alertas</h2>","fidelityText":"SLI, SLO e alertas"},{"id":"observabilidade-pratica-content-43","type":"html","authorship":"legacy-preserved","html":"<p><strong>SLI</strong> (<em>service level indicator</em>) é a medida, como proporção de requisições válidas abaixo de 300 ms. <strong>SLO</strong> (<em>service level objective</em>) é a meta, como 99,9% em 30 dias. O <strong>error budget</strong> é a parcela de falha permitida pela meta. <strong>Burn rate</strong> mede a velocidade de consumo desse orçamento. Alerte por impacto sustentado e consumo acelerado, não por todo pico momentâneo de CPU.</p>","fidelityText":"SLI (service level indicator) é a medida, como proporção de requisições válidas abaixo de 300 ms. SLO (service level objective) é a meta, como 99,9% em 30 dias. O error budget é a parcela de falha permitida pela meta. Burn rate mede a velocidade de consumo desse orçamento. Alerte por impacto sustentado e consumo acelerado, não por todo pico momentâneo de CPU."},{"id":"observabilidade-pratica-content-44","type":"html","authorship":"legacy-preserved","html":"<h2>Um alerta de burn rate de verdade</h2>","fidelityText":"Um alerta de burn rate de verdade"},{"id":"observabilidade-pratica-content-45","type":"html","authorship":"legacy-preserved","html":"<p>A seção anterior define burn rate em prosa; falta o arquivo que o torna executável. O erro mais comum é um único alerta com uma janela fixa: janela curta (5 min) dispara para qualquer pico momentâneo sem impacto real; janela longa (os mesmos 30 dias do SLO) só avisa quando o orçamento já quase acabou. A prática consolidada pelo Google SRE Workbook usa <strong>duas janelas ao mesmo tempo</strong> — uma curta para pegar consumo rápido, outra mais longa para confirmar que não é ruído passageiro. A fórmula abaixo reaproveita o <code>Counter</code> <code>pedidos.criados</code> já registrado na seção de métricas, ao lado de um segundo <code>Counter</code> irmão (<code>pedidos.erros</code>) incrementado nos mesmos pontos do código onde a operação falha -- sem esse contador dedicado, não existe numerador para calcular a proporção de erro.</p>","fidelityText":"A seção anterior define burn rate em prosa; falta o arquivo que o torna executável. O erro mais comum é um único alerta com uma janela fixa: janela curta (5 min) dispara para qualquer pico momentâneo sem impacto real; janela longa (os mesmos 30 dias do SLO) só avisa quando o orçamento já quase acabou. A prática consolidada pelo Google SRE Workbook usa duas janelas ao mesmo tempo — uma curta para pegar consumo rápido, outra mais longa para confirmar que não é ruído passageiro. A fórmula abaixo reaproveita o Counter pedidos.criados já registrado na seção de métricas, ao lado de um segundo Counter irmão (pedidos.erros) incrementado nos mesmos pontos do código onde a operação falha -- sem esse contador dedicado, não existe numerador para calcular a proporção de erro."},{"id":"observabilidade-pratica-code-46","type":"code","authorship":"legacy-preserved","language":"java","source":"# alert-rules.yml (para prometheus.yml: rule_files: [\"alert-rules.yml\"])\ngroups:\n  - name: orders-service-slo\n    rules:\n      - alert: ErrorBudgetBurnRateFast\n        expr: |\n          (\n            sum(rate(orders_errors_total[5m]))\n            /\n            sum(rate(orders_created_total[5m]))\n          ) > (14.4 * 0.001)\n        for: 2m\n        labels: { severity: page }\n        annotations:\n          summary: \"Burn rate fast: orcamento of error being consumido 14x more fast that the sustentavel.\"\n\n      - alert: ErrorBudgetBurnRateSlow\n        expr: |\n          (\n            sum(rate(orders_errors_total[1h]))\n            /\n            sum(rate(orders_created_total[1h]))\n          ) > (6 * 0.001)\n        for: 15m\n        labels: { severity: ticket }\n        annotations:\n          summary: \"Burn rate sustentado: in the ritmo current, the orcamento of 30 days exhausts Before of the previsto.\"","fidelityText":"# alert-rules.yml (para prometheus.yml: rule_files: [\"alert-rules.yml\"]) groups: - name: pedidos-service-slo rules: - alert: ErrorBudgetBurnRateFast expr: | ( sum(rate(pedidos_erros_total[5m])) / sum(rate(pedidos_criados_total[5m])) ) > (14.4 * 0.001) for: 2m labels: { severity: page } annotations: summary: \"Burn rate rápido: orçamento de erro sendo consumido 14x mais rápido que o sustentável.\" - alert: ErrorBudgetBurnRateSlow expr: | ( sum(rate(pedidos_erros_total[1h])) / sum(rate(pedidos_criados_total[1h])) ) > (6 * 0.001) for: 15m labels: { severity: ticket } annotations: summary: \"Burn rate sustentado: no ritmo atual, o orçamento de 30 dias esgota antes do previsto.\"","highlightedHtml":"<span class=\"com\"># alert-rules.yml (para prometheus.yml: rule_files: [\"alert-rules.yml\"])</span>\ngroups:\n  - name: orders-service-slo\n    rules:\n      - alert: ErrorBudgetBurnRateFast\n        expr: |\n          (\n            sum(rate(orders_errors_total[5m]))\n            /\n            sum(rate(orders_created_total[5m]))\n          ) &gt; (14.4 * 0.001)\n        for: 2m\n        labels: { severity: page }\n        annotations:\n          summary: <span class=\"str\">\"Burn rate fast: orcamento of error being consumido 14x more fast that the sustentavel.\"</span>\n\n      - alert: ErrorBudgetBurnRateSlow\n        expr: |\n          (\n            sum(rate(orders_errors_total[1h]))\n            /\n            sum(rate(orders_created_total[1h]))\n          ) &gt; (6 * 0.001)\n        for: 15m\n        labels: { severity: ticket }\n        annotations:\n          summary: <span class=\"str\">\"Burn rate sustentado: in the ritmo current, the orcamento of 30 days exhausts Before of the previsto.\"</span>","caption":"Exemplo executável de observabilidade-pratica.","explanation":["Os multiplicadores (14.4 para queima rápida, 6 para sustentada) vêm da tabela padrão de burn rate do Google SRE Workbook: 14.4x o consumo normal em 1h esgotaria um orçamento mensal em pouco mais de 2 dias -- grave o suficiente para acordar alguém (severity: page); 6x numa janela mais longa é sério, mas não uma emergência imediata (severity: ticket).","O campo for exige que a condição permaneça verdadeira por esse tempo contínuo antes de disparar -- evita que um pico de poucos segundos de erro dispare um alerta que ninguém precisa atender às 3 da manhã."],"commonMistakes":["Usar só a regra de burn rate rápido (ou só a lenta) -- a rápida sozinha gera ruído a cada soluço passageiro; a lenta sozinha demora horas para avisar de uma degradação real. As duas juntas, cobrindo janelas diferentes, são o padrão -- não uma opcional.","Copiar os multiplicadores 14.4/6 sem recalcular para o SLO real do serviço -- esses valores pressupõem 99,9% em 30 dias; um SLO diferente (99% ou 99,99%) muda a fração de orçamento (o 0.001 na fórmula) e, em rigor, os multiplicadores recomendados também mudam."]},{"id":"observabilidade-pratica-content-47","type":"html","authorship":"legacy-preserved","html":"<p>O campo <code>severity</code> não dispara nada sozinho — ele existe para o Alertmanager rotear: <code>page</code> vai para quem está de plantão (chamada, PagerDuty); <code>ticket</code> vira um item de backlog para a próxima sprint. A regra do Prometheus decide <strong>quando</strong> alertar; o roteamento do Alertmanager decide <strong>para quem</strong>.</p>","fidelityText":"O campo severity não dispara nada sozinho — ele existe para o Alertmanager rotear: page vai para quem está de plantão (chamada, PagerDuty); ticket vira um item de backlog para a próxima sprint. A regra do Prometheus decide quando alertar; o roteamento do Alertmanager decide para quem."},{"id":"observabilidade-pratica-content-48","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Alerte sobre o SLI, não sobre um recurso interno.</b> Alertar diretamente em cima de uma métrica técnica (ex.: \"CPU acima de 80%\") em vez do indicador que representa o SLO (proporção de erro/latência percebida pelo usuário) mistura sintoma com impacto — CPU alta pode ser normal sob carga esperada e nunca violar o SLO real.</div>","fidelityText":"Alerte sobre o SLI, não sobre um recurso interno. Alertar diretamente em cima de uma métrica técnica (ex.: \"CPU acima de 80%\") em vez do indicador que representa o SLO (proporção de erro/latência percebida pelo usuário) mistura sintoma com impacto — CPU alta pode ser normal sob carga esperada e nunca violar o SLO real."},{"id":"observabilidade-pratica-content-49","type":"html","authorship":"legacy-preserved","html":"<h2>Readiness e liveness</h2>","fidelityText":"Readiness e liveness"},{"id":"observabilidade-pratica-content-50","type":"html","authorship":"legacy-preserved","html":"<p>Liveness responde se o processo precisa ser reiniciado; não deve cair apenas porque uma dependência externa está indisponível. Readiness responde se a instância pode receber tráfego. Misturar as duas transforma uma falha de banco em tempestade de reinícios.</p>","fidelityText":"Liveness responde se o processo precisa ser reiniciado; não deve cair apenas porque uma dependência externa está indisponível. Readiness responde se a instância pode receber tráfego. Misturar as duas transforma uma falha de banco em tempestade de reinícios."},{"id":"observabilidade-pratica-content-51","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoints antes do laboratório</h2>","fidelityText":"Checkpoints antes do laboratório"},{"id":"observabilidade-pratica:0","type":"quiz","authorship":"legacy-preserved","conceptId":"um-filtro-define-o-correlation-id-no-mdc-no-inicio-da-requisicao-mas-esq","prompt":"Um filtro define o correlation ID no MDC no início da requisição, mas esquece de limpá-lo (MDC.clear()) ao final. O que pode acontecer em um pool de threads reutilizado, como o do servlet container?","options":[{"id":"observabilidade-pratica:0:option:0","label":"Uma thread reaproveitada para atender outra requisição pode herdar o correlation ID (e qualquer outro valor) da requisição anterior, misturando logs de operações diferentes sob o mesmo ID.","correct":true,"explanation":"MDC é um mapa por thread; sem limpar, o valor sobrevive além da requisição que o criou e é lido pela próxima operação atendida por essa mesma thread reciclada do pool."},{"id":"observabilidade-pratica:0:option:1","label":"Nada, o MDC é recriado automaticamente a cada requisição HTTP pelo container.","correct":false,"explanation":"O container não gerencia o conteúdo do MDC -- ele é responsabilidade explícita da aplicação, tanto para preencher quanto para limpar."},{"id":"observabilidade-pratica:0:option:2","label":"O programa lança uma exceção de memória, pois o MDC não pode ser reescrito.","correct":false,"explanation":"MDC.put/clear são operações de mapa em memória, sem relação com alocação que geraria OutOfMemoryError por uma reescrita."}],"sourceIndex":52},{"id":"observabilidade-pratica:1","type":"quiz","authorship":"legacy-preserved","conceptId":"o-endpoint-actuator-prometheus-esta-exposto-publicamente-sem-autenticaca","prompt":"O endpoint /actuator/prometheus está exposto publicamente, sem autenticação, na mesma porta da aplicação. Qual é o risco mais direto?","options":[{"id":"observabilidade-pratica:1:option:0","label":"Vazamento de informação operacional (rotas internas, contagem de erro por endpoint, dependências) que ajuda um atacante a mapear a superfície da aplicação -- o endpoint deveria estar atrás de rede interna ou autenticação, como qualquer outro endpoint do Actuator.","correct":true,"explanation":"Rotas, contadores de erro por endpoint e nomes internos ajudam um atacante a mapear a aplicação antes de tentar outra classe de ataque -- é informação, não só números."},{"id":"observabilidade-pratica:1:option:1","label":"Nenhum risco, pois métricas nunca contêm informação sensível.","correct":false,"explanation":"Métricas revelam padrões de uso, volume por rota e taxa de erro -- informação operacional sensível, mesmo sem conter segredo ou dado pessoal diretamente."},{"id":"observabilidade-pratica:1:option:2","label":"O risco é só de desempenho, porque o Prometheus faz scrape com muita frequência.","correct":false,"explanation":"O scrape do Prometheus é leve e configurável; o risco real aqui é de exposição de informação, não de carga."}],"sourceIndex":53},{"id":"observabilidade-pratica:2","type":"quiz","authorship":"legacy-preserved","conceptId":"o-prometheus-nao-recebe-metricas-enviadas-ativamente-pela-aplicacao-em-v","prompt":"O Prometheus não recebe métricas enviadas ativamente pela aplicação; em vez disso, ele mesmo busca (scrape) periodicamente o endpoint /actuator/prometheus. O que isso exige na configuração do Compose?","options":[{"id":"observabilidade-pratica:2:option:0","label":"O serviço do Prometheus precisa conhecer o host/porta da aplicação (via scrape_configs no prometheus.yml), e a aplicação precisa estar acessível na rede do Compose -- é a aplicação que é \"visitada\", não que \"envia\" para o Prometheus.","correct":true,"explanation":"Pull significa que o Prometheus inicia a conexão; ele precisa saber onde procurar (scrape_configs), e o alvo precisa estar alcançável na mesma rede."},{"id":"observabilidade-pratica:2:option:1","label":"A aplicação precisa abrir uma conexão de saída para o Prometheus a cada métrica registrada.","correct":false,"explanation":"No modelo pull do Prometheus é o oposto: a aplicação só expõe o endpoint e espera ser visitada, sem abrir conexão de saída por métrica."},{"id":"observabilidade-pratica:2:option:2","label":"Não exige nenhuma configuração de rede, pois o Prometheus descobre serviços automaticamente em qualquer ambiente.","correct":false,"explanation":"Descoberta automática de serviços existe em alguns ambientes (ex.: Kubernetes), mas não é o padrão em um docker-compose simples -- aqui a configuração é explícita."}],"sourceIndex":54},{"id":"observabilidade-pratica:3","type":"quiz","authorship":"legacy-preserved","conceptId":"um-dashboard-mostra-a-latencia-media-de-um-endpoint-em-120-ms-dentro-da-","prompt":"Um dashboard mostra a latência média de um endpoint em 120 ms, dentro da meta. Só depois de olhar o percentil p99 (400 ms) o time percebe que 1% das requisições -- milhares por dia -- está lenta o suficiente para irritar usuários reais. Por que a média escondeu esse problema?","options":[{"id":"observabilidade-pratica:3:option:0","label":"Média é dominada pela maioria rápida e dilui poucos outliers lentos; percentis altos (p95/p99) revelam a experiência da cauda, que é frequentemente onde os problemas reais de produção aparecem.","correct":true,"explanation":"Um valor de cauda (poucas requisições muito lentas) tem peso pequeno numa média sobre milhares de amostras rápidas -- por isso o método de agregação escolhido decide o que fica visível."},{"id":"observabilidade-pratica:3:option:1","label":"A média está sempre errada e nunca deveria ser usada em observabilidade.","correct":false,"explanation":"Média é uma ferramenta válida para outras perguntas; o problema aqui é usá-la sozinha para decidir se a experiência do usuário está dentro da meta."},{"id":"observabilidade-pratica:3:option:2","label":"O problema é um bug no cálculo do Prometheus, que já deveria mostrar só p99.","correct":false,"explanation":"O comportamento é esperado de como médias funcionam matematicamente, não um defeito da ferramenta de coleta."}],"sourceIndex":55},{"id":"observabilidade-pratica:4","type":"quiz","authorship":"legacy-preserved","conceptId":"um-span-filho-chamado-processar-pagamento-gateway-aparece-no-jaeger-tota","prompt":"Um span filho chamado processar-pagamento-gateway aparece no Jaeger totalmente desconectado do span HTTP pai da mesma requisição -- como um trace novo e solto. O código desse span roda dentro de um ExecutorService criado manualmente com Executors.newFixedThreadPool(10), sem nenhuma configuração adicional. Qual é a causa mais provável?","options":[{"id":"observabilidade-pratica:4:option:0","label":"O contexto de tracing é propagado por thread e não atravessa automaticamente uma thread criada manualmente -- só um executor instrumentado (ou propagação explícita do contexto) preserva a relação pai-filho entre threads.","correct":true,"explanation":"O contexto de tracing do Micrometer é mantido em armazenamento local à thread; uma thread nova criada manualmente não herda esse contexto a menos que ele seja propagado explicitamente ou o executor seja instrumentado."},{"id":"observabilidade-pratica:4:option:1","label":"O Jaeger tem um limite de spans por trace e descartou a relação por excesso.","correct":false,"explanation":"Spans não são descartados por limite de quantidade em uso normal -- a desconexão observada é sobre relação pai-filho, não sobre volume."},{"id":"observabilidade-pratica:4:option:2","label":"@Observed não funciona dentro de métodos chamados por outro bean, então o span nasceu sem pai por causa da anotação, não da thread.","correct":false,"explanation":"Self-invocation (chamar pelo mesmo bean) quebraria o proxy do @Observed, mas o enunciado descreve o span aparecendo, só que desconectado -- o problema é de propagação de contexto entre threads, não de a anotação nunca ter funcionado."}],"sourceIndex":56},{"id":"observabilidade-pratica:5","type":"quiz","authorship":"legacy-preserved","conceptId":"o-time-recria-o-ambiente-em-uma-maquina-nova-docker-compose-up-do-zero-e","prompt":"O time recria o ambiente em uma máquina nova (docker compose up do zero) e percebe que o painel de latência p99 que \"sempre existiu\" não está lá -- precisa ser refeito manualmente na UI do Grafana. O que evita esse retrabalho?","options":[{"id":"observabilidade-pratica:5:option:0","label":"Provisionar o Grafana com arquivos de datasource e dashboard versionados no repositório, para que qualquer docker compose up recrie o mesmo estado sem depender de alguém lembrar de clicar de novo na UI.","correct":true,"explanation":"Provisioning por arquivo é o que torna o ambiente reproduzível a partir do zero -- exatamente o que falha quando o dashboard só existe como estado clicado na UI de uma instância específica do Grafana."},{"id":"observabilidade-pratica:5:option:1","label":"Aumentar a frequência de backup do volume interno do container do Grafana.","correct":false,"explanation":"Backup de volume ajuda a não perder o estado de UMA instância, mas não resolve criar o MESMO dashboard em uma máquina nova sem esse volume específico -- o arquivo versionado no repositório resolve isso para qualquer ambiente."},{"id":"observabilidade-pratica:5:option:2","label":"Configurar o Grafana para nunca reiniciar, independentemente do ambiente.","correct":false,"explanation":"Manter o container sempre ativo não endereça o problema descrito, que é justamente recriar o ambiente do zero em outra máquina."}],"sourceIndex":57},{"id":"observabilidade-pratica:6","type":"quiz","authorship":"legacy-preserved","conceptId":"um-servico-tem-slo-de-99-9-em-30-dias-a-equipe-configura-um-unico-alerta","prompt":"Um serviço tem SLO de 99,9% em 30 dias. A equipe configura um único alerta: \"taxa de erro acima do normal, calculada sobre uma janela de 30 dias\". Um incidente real consome 10% do orçamento de erro mensal em 2 horas. Esse alerta dispara a tempo de alguém agir?","options":[{"id":"observabilidade-pratica:6:option:0","label":"Não -- uma janela de 30 dias só reflete o incidente semanas depois de diluído; a técnica de burn rate usa uma janela curta (ex.: 1h) para sinalizar consumo rápido de imediato, além de uma janela mais longa para confirmar que não é ruído.","correct":true,"explanation":"Burn rate multi-janela existe exatamente para isso: uma janela curta (ex.: 1h) sinaliza consumo acelerado quase em tempo real, enquanto a janela de 30 dias do SLO só refletiria o mesmo incidente de forma diluída, semanas depois."},{"id":"observabilidade-pratica:6:option:1","label":"Sim, porque o Prometheus reavalia toda regra a cada poucos segundos, independentemente da janela declarada na expressão.","correct":false,"explanation":"A frequência de reavaliação da regra (a cada scrape_interval) não muda o significado da janela declarada na expressão -- rate(...[30d]) sempre calcula a taxa média sobre 30 dias, por mais vezes que seja recalculada."},{"id":"observabilidade-pratica:6:option:2","label":"Sim, desde que o campo for da regra seja reduzido para poucos segundos.","correct":false,"explanation":"Reduzir o for não muda a janela agregada dentro da expressão PromQL -- for só controla quanto tempo a condição precisa permanecer verdadeira antes de disparar, não o tamanho da janela de agregação."}],"sourceIndex":58},{"id":"observabilidade-pratica-exercise-59","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — diagnosticar sem abrir o código","prompt":"Suba a stack local (app + Prometheus + Grafana + Jaeger) do Compose acima. Instrumente um endpoint com Micrometer, adicione o filtro de correlation ID e provoque lentidão artificial numa chamada ao banco. Sem olhar o código-fonte de novo, use o painel do Grafana, um trace no Jaeger e o log correlacionado por correlationId para encontrar a causa. Depois, defina um SLO para esse endpoint e escreva a regra de alerta de burn rate correspondente.","difficulty":"advanced","criteria":["O painel separa latência por percentil (não só média), taxa de requisições e taxa de erro, e existe como arquivo de dashboard provisionado, não só criado na UI.","Nenhum ID de alta cardinalidade (usuário, pedido) virou tag/label de métrica.","O log correlacionado pelo correlationId reconstrói a requisição lenta sem expor senha, token ou dado sensível.","Um trace no Jaeger mostra o span da operação lenta aninhado sob o span HTTP, com duração visível.","O SLO tem número, janela e é verificável (ex.: \"99% abaixo de 300ms em 30 dias\"), e o alerta de burn rate está definido como regra do Prometheus (arquivo YAML com duas janelas), não como limiar único nem configuração manual numa UI."],"fidelityText":"Laboratório — diagnosticar sem abrir o códigodifícilSuba a stack local (app + Prometheus + Grafana + Jaeger) do Compose acima. Instrumente um endpoint com Micrometer, adicione o filtro de correlation ID e provoque lentidão artificial numa chamada ao banco. Sem olhar o código-fonte de novo, use o painel do Grafana, um trace no Jaeger e o log correlacionado por correlationId para encontrar a causa. Depois, defina um SLO para esse endpoint e escreva a regra de alerta de burn rate correspondente.Ver critériosO painel separa latência por percentil (não só média), taxa de requisições e taxa de erro, e existe como arquivo de dashboard provisionado, não só criado na UI.Nenhum ID de alta cardinalidade (usuário, pedido) virou tag/label de métrica.O log correlacionado pelo correlationId reconstrói a requisição lenta sem expor senha, token ou dado sensível.Um trace no Jaeger mostra o span da operação lenta aninhado sob o span HTTP, com duração visível.O SLO tem número, janela e é verificável (ex.: \"99% abaixo de 300ms em 30 dias\"), e o alerta de burn rate está definido como regra do Prometheus (arquivo YAML com duas janelas), não como limiar único nem configuração manual numa UI.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — diagnosticar sem abrir o código</h2><span class=\"exercise-tag d\">difícil</span></div><p>Suba a stack local (app + Prometheus + Grafana + Jaeger) do Compose acima. Instrumente um endpoint com Micrometer, adicione o filtro de correlation ID e provoque lentidão artificial numa chamada ao banco. Sem olhar o código-fonte de novo, use o painel do Grafana, um trace no Jaeger e o log correlacionado por <code>correlationId</code> para encontrar a causa. Depois, defina um SLO para esse endpoint e escreva a regra de alerta de burn rate correspondente.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><ul><li>O painel separa latência por percentil (não só média), taxa de requisições e taxa de erro, e existe como arquivo de dashboard provisionado, não só criado na UI.</li><li>Nenhum ID de alta cardinalidade (usuário, pedido) virou tag/label de métrica.</li><li>O log correlacionado pelo <code>correlationId</code> reconstrói a requisição lenta sem expor senha, token ou dado sensível.</li><li>Um trace no Jaeger mostra o span da operação lenta aninhado sob o span HTTP, com duração visível.</li><li>O SLO tem número, janela e é verificável (ex.: \"99% abaixo de 300ms em 30 dias\"), e o alerta de burn rate está definido como regra do Prometheus (arquivo YAML com duas janelas), não como limiar único nem configuração manual numa UI.</li></ul></div></div>"},{"id":"observabilidade-pratica-quiz","type":"quiz","authorship":"authored","conceptId":"sli-slo-error-budget","prompt":"Qual SLO é mais operacional?","options":[{"id":"obs-a","label":"99% das requisições de checkout abaixo de 300 ms em janelas de 30 dias.","correct":true,"explanation":"É mensurável, ligado a usuário e tem janela."},{"id":"obs-b","label":"O sistema deve ser sempre perfeito.","correct":false,"explanation":"100% genérico não orienta trade-off nem orçamento de erro."},{"id":"obs-c","label":"Alertar em todo log de INFO.","correct":false,"explanation":"Alerta deve representar impacto ou risco, não volume bruto."}]}],"resources":[{"id":"opentelemetry-observability-primer","type":"reference","title":"OpenTelemetry Observability Primer","url":"https://opentelemetry.io/docs/concepts/observability-primer/","reinforces":"Logs, metrics, traces e relação entre sinais.","language":"en","publisher":"OpenTelemetry","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"sre-book-slo","type":"reference","title":"Google SRE Book: Service Level Objectives","url":"https://sre.google/sre-book/service-level-objectives/","reinforces":"SLI, SLO, error budget e alertas por impacto.","language":"en","publisher":"Google SRE","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-boot-tracing-reference","type":"reference","title":"Spring Boot Reference: Tracing","url":"https://docs.spring.io/spring-boot/reference/actuator/tracing.html","reinforces":"Configuração de sampling, propagação e exportação OTLP com Micrometer Tracing.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-24","auditStatus":"approved"},{"id":"sre-workbook-alerting-on-slos","type":"reference","title":"Google SRE Workbook: Alerting on SLOs","url":"https://sre.google/workbook/alerting-on-slos/","reinforces":"Técnica de burn rate multi-janela com os multiplicadores usados na regra de alerta deste capítulo.","language":"en","publisher":"Google SRE","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-24","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the observability practice flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for observability practice. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for observability practice with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"\"timestamp\": \"2026-08-14T12:00:00Z\",","instruction":"Design the observability practice flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for observability practice with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["correlation-id-com-mdc-thread-safety","traces-com-opentelemetry-e-jaeger","metricas-expostas-para-prometheus","stack-local-prometheus-grafana","percentil-vs-media-para-diagnostico","sli-slo-error-budget"],"evidenceBlocks":{"correlation-id-com-mdc-thread-safety":["observabilidade-pratica-content-7","observabilidade-pratica-code-9","observabilidade-pratica-content-12","observabilidade-pratica:0"],"traces-com-opentelemetry-e-jaeger":["observabilidade-pratica-content-13","observabilidade-pratica-code-15","observabilidade-pratica-code-17","observabilidade-pratica:4"],"metricas-expostas-para-prometheus":["observabilidade-pratica-content-24","observabilidade-pratica-code-26","observabilidade-pratica-code-28","observabilidade-pratica:1"],"stack-local-prometheus-grafana":["observabilidade-pratica-content-30","observabilidade-pratica-code-32","observabilidade-pratica-code-33","observabilidade-pratica-content-36","observabilidade-pratica-code-38","observabilidade-pratica-code-40","observabilidade-pratica:2","observabilidade-pratica:5"],"percentil-vs-media-para-diagnostico":["observabilidade-pratica-content-35","observabilidade-pratica:3"],"sli-slo-error-budget":["observabilidade-pratica-content-43","observabilidade-pratica-content-44","observabilidade-pratica-code-46","observabilidade-pratica-quiz","observabilidade-pratica:6"]},"primarySources":["OpenTelemetry Observability Primer -- https://opentelemetry.io/docs/concepts/observability-primer/","Spring Boot Reference: Tracing -- https://docs.spring.io/spring-boot/reference/actuator/tracing.html","Google SRE Book: Service Level Objectives -- https://sre.google/sre-book/service-level-objectives/","Google SRE Workbook: Alerting on SLOs -- https://sre.google/workbook/alerting-on-slos/","Prometheus Docs: Configuration (scrape_config) -- https://prometheus.io/docs/prometheus/latest/configuration/configuration/"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"secrets","moduleId":"production-delivery","order":0,"title":"Variáveis de ambiente & secrets em produção","summary":"O capítulo 27 já cobriu ${DB_USER} em application.properties. Falta o lado de produção: onde essas variáveis realmente vivem quando sua aplicação não está mais rodando na sua máquina.","objectives":["Distinguir segredo, configuração e dado público","Separar build-time de runtime","Evitar vazamento por Git, imagem Docker e logs","Declarar contrato de configuração verificável na inicialização"],"whyItExists":"Produção começa quando o mesmo código precisa rodar em ambientes diferentes sem carregar senha dentro do repositório, da imagem ou do log. Secret é fronteira operacional, não conveniência de string.","prerequisiteChapterIds":["logging","git","spring-security","unreliable-api-client"],"conceptIds":["o-arquivo-env-nunca-vai-para-o-git","como-cada-provedor-gerencia-secrets-em-producao","build-time-vs-runtime-secrets-uma-distincao-que-confunde-muita-gente"],"introducedConceptIds":["runtime-secret-boundary","environment-config-contract"],"usedConceptIds":["gitignore-secrets","secrets-integracao","configuracao-externa","browser-token-storage-risk"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"secrets-intuition","type":"intuition","authorship":"authored","title":"Secret é valor de runtime, não parte do programa","body":"Código descreve comportamento; secret autoriza acesso a algo fora do programa. Se o valor entra no Git, na imagem ou no log, ele deixa de ser controlado pelo ambiente e vira vazamento reproduzível.","analogyLimit":"Chave física ajuda, mas secret também tem rotação, escopo, auditoria e propagação acidental por ferramentas."},{"id":"secrets-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">DevOps</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#logging\">27 · Logging &amp; config</a>, <a class=\"prereq-tag\" href=\"#git\">29 · Git</a>, <a class=\"prereq-tag\" href=\"#spring-security\">52 · Spring Security</a></div>\n      </div>","fidelityText":"DevOps Dificuldade: Intermediário ⏱ ~1h de estudo Pré-requisitos: 27 · Logging & config, 29 · Git, 52 · Spring Security"},{"id":"secrets-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 27 já cobriu <code>${DB_USER}</code> em <code>application.properties</code>. Falta o lado de produção: <strong>onde</strong> essas variáveis realmente vivem quando sua aplicação não está mais rodando na sua máquina.</p>","fidelityText":"O capítulo 27 já cobriu ${DB_USER} em application.properties. Falta o lado de produção: onde essas variáveis realmente vivem quando sua aplicação não está mais rodando na sua máquina."},{"id":"secrets-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense na diferença entre anotar sua senha em um post-it colado no monitor (código-fonte, visível a qualquer um que olhe) e guardá-la em um cofre com senha própria, que só é aberto no momento exato em que a aplicação precisa dela (variável de ambiente injetada pelo provedor de deploy). O cofre não fica gravado em nenhum lugar público — só existe no ambiente controlado de execução.</div>","fidelityText":"Pense na diferença entre anotar sua senha em um post-it colado no monitor (código-fonte, visível a qualquer um que olhe) e guardá-la em um cofre com senha própria, que só é aberto no momento exato em que a aplicação precisa dela (variável de ambiente injetada pelo provedor de deploy). O cofre não fica gravado em nenhum lugar público — só existe no ambiente controlado de execução."},{"id":"secrets-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>O arquivo .env — nunca vai para o Git</h2>","fidelityText":"O arquivo .env — nunca vai para o Git"},{"id":"secrets-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"# .env (arquivo LOCAL, nunca commitado)\nDB_URL=jdbc:postgresql://localhost:5432/biblioteca\nDB_USER=postgres\nDB_PASSWORD=password123\nJWT_SECRET=uma-key-well-long-and-random","fidelityText":"# .env (arquivo LOCAL, nunca commitado) DB_URL=jdbc:postgresql://localhost:5432/biblioteca DB_USER=postgres DB_PASSWORD=senha123 JWT_SECRET=uma-chave-bem-longa-e-aleatoria","highlightedHtml":"<span class=\"com\"># .env (arquivo LOCAL, nunca commitado)</span>\nDB_URL=jdbc:postgresql://localhost:5432/biblioteca\nDB_USER=postgres\nDB_PASSWORD=password123\nJWT_SECRET=uma-key-well-long-and-random","caption":"Exemplo executável de secrets.","explanation":["O exemplo lê configuração sensível a partir do ambiente em vez de embutir no código.","Em produção, ausência de valor obrigatório deve falhar cedo e de modo claro."],"commonMistakes":["Logar o valor ao diagnosticar","Usar default inseguro para secret obrigatório"]},{"id":"secrets-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"# .gitignore -- capítulo 29, regra de ouro reforçada:\n.env\n*.env.local","fidelityText":"# .gitignore -- capítulo 29, regra de ouro reforçada: .env *.env.local","highlightedHtml":"<span class=\"com\"># .gitignore -- capítulo 29, regra de ouro reforçada:</span>\n.env\n*.env.local","caption":"Exemplo executável de secrets.","explanation":["Separar nomes de variáveis documenta o contrato que o ambiente precisa fornecer.","A aplicação não deve saber se o valor veio de painel do provedor, vault ou secret manager."],"commonMistakes":["Criar nomes diferentes por ambiente sem padrão","Misturar valor público e secret no mesmo tratamento"]},{"id":"secrets-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Como cada provedor gerencia secrets em produção</h2>","fidelityText":"Como cada provedor gerencia secrets em produção"},{"id":"secrets-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Provedor</th><th>Onde configurar</th></tr>\n        <tr><td>Railway / Render</td><td>Painel web → aba \"Environment Variables\", nunca no código</td></tr>\n        <tr><td>GitHub Actions (CI/CD)</td><td>\"Repository Secrets\" — criptografados, nunca aparecem em log</td></tr>\n        <tr><td>AWS</td><td>AWS Secrets Manager / Parameter Store — rotação automática possível</td></tr>\n        <tr><td>Docker Compose (produção)</td><td>Arquivo <code>.env</code> separado, referenciado mas não versionado</td></tr>\n      </tbody></table>","fidelityText":"ProvedorOnde configurar Railway / RenderPainel web → aba \"Environment Variables\", nunca no código GitHub Actions (CI/CD)\"Repository Secrets\" — criptografados, nunca aparecem em log AWSAWS Secrets Manager / Parameter Store — rotação automática possível Docker Compose (produção)Arquivo .env separado, referenciado mas não versionado"},{"id":"secrets-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Build-time vs runtime secrets — uma distinção que confunde muita gente</h2>","fidelityText":"Build-time vs runtime secrets — uma distinção que confunde muita gente"},{"id":"secrets-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Cuidado com secrets de build-time em imagens Docker.</b> Se você faz <code>ARG</code>/<code>ENV</code> com uma senha durante o <code>docker build</code> (capítulo 31), ela pode ficar gravada permanentemente nas camadas da imagem, mesmo que você \"remova\" depois — qualquer um com acesso à imagem consegue extrair. Secrets deveriam ser injetados em <strong>runtime</strong> (no <code>docker run</code> ou no provedor de deploy), nunca embutidos durante o build.</div>","fidelityText":"Cuidado com secrets de build-time em imagens Docker. Se você faz ARG/ENV com uma senha durante o docker build (capítulo 31), ela pode ficar gravada permanentemente nas camadas da imagem, mesmo que você \"remova\" depois — qualquer um com acesso à imagem consegue extrair. Secrets deveriam ser injetados em runtime (no docker run ou no provedor de deploy), nunca embutidos durante o build."},{"id":"secrets-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Isso conecta direto com o capítulo 29: o motivo mais comum de vazamento de secret em produção não é um ataque sofisticado — é alguém commitando um <code>.env</code> ou um <code>application-prod.properties</code> com credenciais reais por engano, porque o <code>.gitignore</code> não estava configurado desde o primeiro commit. Ferramentas como <code>git-secrets</code> ou o próprio GitHub (que escaneia pushes públicos em busca de padrões de chave de API conhecidos) existem justamente porque esse erro é extremamente comum, não incomum.</div>","fidelityText":"Isso conecta direto com o capítulo 29: o motivo mais comum de vazamento de secret em produção não é um ataque sofisticado — é alguém commitando um .env ou um application-prod.properties com credenciais reais por engano, porque o .gitignore não estava configurado desde o primeiro commit. Ferramentas como git-secrets ou o próprio GitHub (que escaneia pushes públicos em busca de padrões de chave de API conhecidos) existem justamente porque esse erro é extremamente comum, não incomum."},{"id":"secrets-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li><code>.env</code> sempre no <code>.gitignore</code>, desde o primeiro commit do projeto (capítulo 29).</li>\n          <li>Nunca logue variáveis de ambiente inteiras — um <code>System.out.println(System.getenv())</code> \"de debug\" esquecido é um vazamento sério.</li>\n          <li>Secrets diferentes por ambiente — a senha de produção nunca deveria ser igual à de desenvolvimento.</li>\n          <li>Se um secret vazar (aconteceu, acontece), a resposta correta é <strong>rotacionar</strong> (trocar) imediatamente — nunca é \"só remover do commit\", porque o histórico do Git preserva versões antigas.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro .env sempre no .gitignore, desde o primeiro commit do projeto (capítulo 29). Nunca logue variáveis de ambiente inteiras — um System.out.println(System.getenv()) \"de debug\" esquecido é um vazamento sério. Secrets diferentes por ambiente — a senha de produção nunca deveria ser igual à de desenvolvimento. Se um secret vazar (aconteceu, acontece), a resposta correta é rotacionar (trocar) imediatamente — nunca é \"só remover do commit\", porque o histórico do Git preserva versões antigas."},{"id":"secrets-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Exercício 57.1 — Auditoria de secrets","prompt":"Revise (mentalmente ou em um projeto real seu) se existe algum application.properties, .env ou arquivo de configuração com credencial real já commitado no histórico do Git. Escreva os passos que você tomaria para corrigir isso: (1) adicionar ao .gitignore, (2) remover do controle de versão sem apagar localmente, (3) rotacionar a credencial.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 57.1 — Auditoria de secretsfácil Revise (mentalmente ou em um projeto real seu) se existe algum application.properties, .env ou arquivo de configuração com credencial real já commitado no histórico do Git. Escreva os passos que você tomaria para corrigir isso: (1) adicionar ao .gitignore, (2) remover do controle de versão sem apagar localmente, (3) rotacionar a credencial. Ver solução # 1. adiciona ao .gitignore para não commitar de novo echo \"application-prod.properties\" >> .gitignore # 2. remove do controle de versão SEM apagar o arquivo local git rm --cached application-prod.properties git commit -m \"Remove credenciais do controle de versão\" # 3. MAIS IMPORTANTE: troca a senha/chave real no provedor -- # o arquivo continua no histórico antigo do Git, então a credencial # antiga precisa ser considerada COMPROMETIDA e substituída","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 57.1 — Auditoria de secrets</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Revise (mentalmente ou em um projeto real seu) se existe algum <code>application.properties</code>, <code>.env</code> ou arquivo de configuração com credencial real já commitado no histórico do Git. Escreva os passos que você tomaria para corrigir isso: (1) adicionar ao <code>.gitignore</code>, (2) remover do controle de versão sem apagar localmente, (3) rotacionar a credencial.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"com\"># 1. adiciona ao .gitignore para não commitar de novo</span>\n<span class=\"kw\">echo</span> \"application-prod.properties\" &gt;&gt; .gitignore\n\n<span class=\"com\"># 2. remove do controle de versão SEM apagar o arquivo local</span>\ngit rm --cached application-prod.properties\ngit commit -m \"Remove credenciais of the tracking of version\"\n\n<span class=\"com\"># 3. MAIS IMPORTANTE: troca a senha/chave real no provedor --\n# o arquivo continua no histórico antigo do Git, então a credencial\n# antiga precisa ser considerada COMPROMETIDA e substituída</span></pre>\n        </div>\n      </div>"},{"id":"secrets-quiz","type":"quiz","authorship":"authored","conceptId":"runtime-secret-boundary","prompt":"Qual prática mantém melhor um secret fora do artefato publicado?","options":[{"id":"sec-a","label":"Ler o valor em runtime por variável/gerenciador de secrets e falhar se obrigatório estiver ausente.","correct":true,"explanation":"O artefato permanece genérico e o ambiente fornece o segredo controlado."},{"id":"sec-b","label":"Gravar o valor em application-prod.yml e confiar no repositório privado.","correct":false,"explanation":"Repositório privado ainda replica, loga, revisa e preserva histórico."},{"id":"sec-c","label":"Codificar em Base64 antes de commitar.","correct":false,"explanation":"Base64 é codificação reversível, não proteção criptográfica."}]}],"resources":[{"id":"github-actions-secrets","type":"official-docs","title":"GitHub Actions: Using secrets","url":"https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions","reinforces":"Secrets em CI, escopo, uso e limitações.","language":"en","publisher":"GitHub","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-external-config","type":"official-docs","title":"Spring Boot Externalized Configuration","url":"https://docs.spring.io/spring-boot/reference/features/external-config.html","reinforces":"Ordem e fontes de configuração externa no Spring Boot.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The secrets component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to secrets. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible secrets failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"# .env (arquivo LOCAL, nunca commitado)","instruction":"The secrets component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible secrets failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"cicd","moduleId":"production-delivery","order":1,"title":"CI/CD com GitHub Actions","summary":"Os testes automatizados do capítulo 15 só protegem seu código se alguém realmente lembrar de rodá-los. CI (Integração Contínua) automatiza isso: a cada push, um servidor roda seus testes sozinho. CD (Entrega/Deploy Contínuo) vai além, publicando automaticamente se tudo passar.","objectives":["Transformar commit em validação reproduzível","Separar CI de CD","Publicar artefato/imagem com identidade rastreável","Bloquear merge/deploy quando o gate falhar"],"whyItExists":"Depois de Git, testes, Dockerfile e Testcontainers, o próximo risco é humano: esquecer comandos, rodar em máquina diferente ou publicar artefato não testado. CI/CD torna a entrega repetível e auditável.","prerequisiteChapterIds":["git","testcontainers","dockerfile","estrategia-testes"],"conceptIds":["um-pipeline-basico","adicionando-build-e-push-da-imagem-docker","cache-de-dependencias-de-10-minutos-a-40-segundos","o-ciclo-completo-da-branch-ao-deploy"],"introducedConceptIds":["ci-pipeline-gate","artifact-image-provenance"],"usedConceptIds":["git-snapshot-index","build-lifecycle","wrapper-build-reprodutivel","deterministic-integration-test","dockerfile-build-context","multistage-runtime-image"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"cicd-intuition","type":"intuition","authorship":"authored","title":"Pipeline é a memória operacional do time","body":"Um pipeline registra quais comandos provam que aquele commit pode avançar. Ele não torna o código correto por magia; ele impede que a entrega dependa da lembrança ou da máquina de uma pessoa.","analogyLimit":"Esteira ajuda a imaginar sequência, mas CI também precisa identidade de commit, permissões, cache, secrets e artefatos."},{"id":"cicd-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">CI/CD</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#git\">29 · Git</a>, <a class=\"prereq-tag\" href=\"#testcontainers\">54 · Testcontainers</a>, <a class=\"prereq-tag\" href=\"#dockerfile\">31 · Dockerfile</a></div>\n      </div>","fidelityText":"CI/CD Dificuldade: Intermediário ⏱ ~2h de estudo + prática Pré-requisitos: 29 · Git, 54 · Testcontainers, 31 · Dockerfile"},{"id":"cicd-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Os testes automatizados do capítulo 15 só protegem seu código se alguém realmente <strong>lembrar</strong> de rodá-los. <strong>CI</strong> (Integração Contínua) automatiza isso: a cada push, um servidor roda seus testes sozinho. <strong>CD</strong> (Entrega/Deploy Contínuo) vai além, publicando automaticamente se tudo passar.</p>","fidelityText":"Os testes automatizados do capítulo 15 só protegem seu código se alguém realmente lembrar de rodá-los. CI (Integração Contínua) automatiza isso: a cada push, um servidor roda seus testes sozinho. CD (Entrega/Deploy Contínuo) vai além, publicando automaticamente se tudo passar."},{"id":"cicd-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">CI/CD é como ter um revisor incansável que lê <strong>cada</strong> capítulo novo deste curso assim que é escrito, checando erros de sintaxe e tags quebradas antes de publicar — sem depender de alguém lembrar de revisar manualmente. Se o revisor encontra um problema, ele impede a publicação até ser corrigido; se está tudo certo, ele libera automaticamente.</div>","fidelityText":"CI/CD é como ter um revisor incansável que lê cada capítulo novo deste curso assim que é escrito, checando erros de sintaxe e tags quebradas antes de publicar — sem depender de alguém lembrar de revisar manualmente. Se o revisor encontra um problema, ele impede a publicação até ser corrigido; se está tudo certo, ele libera automaticamente."},{"id":"cicd-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Um pipeline básico</h2>","fidelityText":"Um pipeline básico"},{"id":"cicd-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"# .github/workflows/ci.yml\nname: CI\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4          # baixa o código\n\n      - name: Configure Java\n        uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n\n      - name: Run tests                 # Testcontainers precisa de Docker -- já vem pronto no runner\n        run: ./mvnw test\n\n      - name: Build do jar\n        run: ./mvnw package -DskipTests\n        if: github.ref == 'refs/heads/main'  # só builda de verdade na branch principal","fidelityText":"# .github/workflows/ci.yml name: CI on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # baixa o código - name: Configurar Java uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' - name: Rodar testes # Testcontainers precisa de Docker -- já vem pronto no runner run: ./mvnw test - name: Build do jar run: ./mvnw package -DskipTests if: github.ref == 'refs/heads/main' # só builda de verdade na branch principal","highlightedHtml":"<span class=\"com\"># .github/workflows/ci.yml</span>\nname: CI\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4          <span class=\"com\"># baixa o código</span>\n\n      - name: Configure Java\n        uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n\n      - name: Run tests                 <span class=\"com\"># Testcontainers precisa de Docker -- já vem pronto no runner</span>\n        run: ./mvnw test\n\n      - name: Build do jar\n        run: ./mvnw package -DskipTests\n        if: github.ref == 'refs/heads/main'  <span class=\"com\"># só builda de verdade na branch principal</span>","caption":"Exemplo executável de cicd.","explanation":["O workflow declara gatilho, ambiente e comandos executados em runner limpo.","Usar wrapper e scripts do projeto reduz diferença entre local e CI."],"commonMistakes":["Instalar dependências globalmente sem versão","Deixar teste lento ou instável fora do pipeline"]},{"id":"cicd-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Toda vez que alguém der <code>git push</code> ou abrir um Pull Request (capítulo 29), esse workflow roda automaticamente — sem precisar de nenhuma máquina ligada, o GitHub fornece o servidor (<em>runner</em>) sob demanda.</p>","fidelityText":"Toda vez que alguém der git push ou abrir um Pull Request (capítulo 29), esse workflow roda automaticamente — sem precisar de nenhuma máquina ligada, o GitHub fornece o servidor (runner) sob demanda."},{"id":"cicd-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Adicionando build e push da imagem Docker</h2>","fidelityText":"Adicionando build e push da imagem Docker"},{"id":"cicd-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"  build-and-push:\n    needs: test  # só roda se o job \"test\" passar antes\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main'\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Login no Docker Hub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKERHUB_USER }}       # capítulo 57!\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build e push\n        uses: docker/build-push-action@v5\n        with:\n          push: true\n          tags: felipysantsss/library-api:latest","fidelityText":"build-and-push: needs: test # só roda se o job \"test\" passar antes runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Login no Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USER }} # capítulo 57! password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build e push uses: docker/build-push-action@v5 with: push: true tags: felipysantsss/biblioteca-api:latest","highlightedHtml":"  build-and-push:\n    needs: test  <span class=\"com\"># só roda se o job \"test\" passar antes</span>\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main'\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Login no Docker Hub\n        uses: docker/login-action@v3\n        with:\n          username: <span class=\"str\">${{ secrets.DOCKERHUB_USER }}</span>       <span class=\"com\"># capítulo 57!</span>\n          password: <span class=\"str\">${{ secrets.DOCKERHUB_TOKEN }}</span>\n\n      - name: Build e push\n        uses: docker/build-push-action@v5\n        with:\n          push: true\n          tags: felipysantsss/library-api:latest","caption":"Exemplo executável de cicd.","explanation":["A etapa de build de imagem deve produzir artefato associado ao commit.","Tags humanas ajudam, mas digest/commit dão rastreabilidade real."],"commonMistakes":["Publicar somente latest","Fazer push antes de testes essenciais"]},{"id":"cicd-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Repare que <code>secrets.DOCKERHUB_TOKEN</code> nunca aparece em texto no arquivo YAML — ele é configurado uma única vez em \"Settings → Secrets\" do repositório GitHub (a mesma ideia do capítulo 57, só que gerenciado pela própria plataforma de CI), e o GitHub Actions automaticamente mascara qualquer valor de secret que apareça acidentalmente nos logs, substituindo por <code>***</code>.</div>","fidelityText":"Repare que secrets.DOCKERHUB_TOKEN nunca aparece em texto no arquivo YAML — ele é configurado uma única vez em \"Settings → Secrets\" do repositório GitHub (a mesma ideia do capítulo 57, só que gerenciado pela própria plataforma de CI), e o GitHub Actions automaticamente mascara qualquer valor de secret que apareça acidentalmente nos logs, substituindo por ***."},{"id":"cicd-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Cache de dependências — de \"10 minutos\" a \"40 segundos\"</h2>","fidelityText":"Cache de dependências — de \"10 minutos\" a \"40 segundos\""},{"id":"cicd-content-11","type":"html","authorship":"legacy-preserved","html":"<p>A regra de ouro acima diz \"cacheie dependências do Maven/Gradle sempre que possível\" — mas isso precisa de código, não só boa vontade. O próprio <code>setup-java</code> já sabe fazer isso sozinho:</p>","fidelityText":"A regra de ouro acima diz \"cacheie dependências do Maven/Gradle sempre que possível\" — mas isso precisa de código, não só boa vontade. O próprio setup-java já sabe fazer isso sozinho:"},{"id":"cicd-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"# .github/workflows/ci.yml (mesmo job, com cache adicionado)\n      - name: Configure Java\n        uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n          cache: 'maven'          # cacheia ~/.m2/repository entre execuções\n\n      - name: Run tests\n        run: ./mvnw test","fidelityText":"# .github/workflows/ci.yml (mesmo job, com cache adicionado) - name: Configurar Java uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' cache: 'maven' # cacheia ~/.m2/repository entre execuções - name: Rodar testes run: ./mvnw test","highlightedHtml":"<span class=\"com\"># .github/workflows/ci.yml (mesmo job, com cache adicionado)</span>\n      - name: Configure Java\n        uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n          cache: 'maven'          <span class=\"com\"># cacheia ~/.m2/repository entre execuções</span>\n\n      - name: Run tests\n        run: ./mvnw test","caption":"Exemplo executável de cicd.","explanation":["O parâmetro `cache` do setup-java restaura o repositório Maven/Gradle entre execuções pela chave derivada do arquivo de build.","Cache de dependência costuma reduzir mais tempo de pipeline do que otimizar os próprios testes."],"commonMistakes":["Recomendar cache só em prosa sem configurar de fato o parâmetro","Cachear sem invalidar quando o arquivo de build muda"]},{"id":"cicd-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A chave do cache é derivada automaticamente do hash do <code>pom.xml</code> (ou <code>build.gradle</code>, com <code>cache: 'gradle'</code>) — se as dependências não mudaram desde a última execução, o download inteiro do Maven Central é pulado, restaurando o cache salvo do passo anterior. Isso sozinho costuma ser a maior redução de tempo de pipeline, maior até que otimizar os próprios testes.</div>","fidelityText":"A chave do cache é derivada automaticamente do hash do pom.xml (ou build.gradle, com cache: 'gradle') — se as dependências não mudaram desde a última execução, o download inteiro do Maven Central é pulado, restaurando o cache salvo do passo anterior. Isso sozinho costuma ser a maior redução de tempo de pipeline, maior até que otimizar os próprios testes."},{"id":"cicd-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>O ciclo completo: da branch ao deploy</h2>","fidelityText":"O ciclo completo: da branch ao deploy"},{"id":"cicd-content-15","type":"html","authorship":"legacy-preserved","html":"<ol style=\"color:var(--ink-dim)\">\n        <li>Você cria uma branch, commita (capítulo 29)</li>\n        <li>Ao dar push, CI roda testes automaticamente (com Testcontainers, capítulo 54)</li>\n        <li>Pull Request só pode ser mesclado se o CI passar (configurável como regra obrigatória no GitHub)</li>\n        <li>Merge na <code>main</code> dispara o build da imagem Docker (capítulo 31) e push para um registry</li>\n        <li>O provedor de deploy (capítulos futuros) detecta a nova imagem e atualiza a aplicação em produção</li>\n      </ol>","fidelityText":"Você cria uma branch, commita (capítulo 29) Ao dar push, CI roda testes automaticamente (com Testcontainers, capítulo 54) Pull Request só pode ser mesclado se o CI passar (configurável como regra obrigatória no GitHub) Merge na main dispara o build da imagem Docker (capítulo 31) e push para um registry O provedor de deploy (capítulos futuros) detecta a nova imagem e atualiza a aplicação em produção"},{"id":"cicd-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>CI deveria rodar em <strong>todo</strong> Pull Request, não só na <code>main</code> — pegar problemas antes do merge é o objetivo central.</li>\n          <li>Um pipeline lento demais (10+ minutos) desencoraja o time de confiar nele — cacheie dependências do Maven/Gradle sempre que possível.</li>\n          <li>Nunca faça deploy automático direto para produção sem testes passando antes — CD sem CI robusto é apenas \"publicar rápido o que pode estar quebrado\".</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro CI deveria rodar em todo Pull Request, não só na main — pegar problemas antes do merge é o objetivo central. Um pipeline lento demais (10+ minutos) desencoraja o time de confiar nele — cacheie dependências do Maven/Gradle sempre que possível. Nunca faça deploy automático direto para produção sem testes passando antes — CD sem CI robusto é apenas \"publicar rápido o que pode estar quebrado\"."},{"id":"cicd-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Configure seu primeiro pipeline rodando <strong>só</strong> os testes, sem build nem deploy — é o passo mais valioso e mais simples. Só depois de se acostumar a ver o \"check verde\" aparecer nos Pull Requests é que vale a pena complicar o workflow com build de imagem e deploy automático.</div>","fidelityText":"Configure seu primeiro pipeline rodando só os testes, sem build nem deploy — é o passo mais valioso e mais simples. Só depois de se acostumar a ver o \"check verde\" aparecer nos Pull Requests é que vale a pena complicar o workflow com build de imagem e deploy automático."},{"id":"cicd-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Exercício 58.1 — Pipeline de testes","prompt":"Escreva um workflow do GitHub Actions que roda a cada push e Pull Request, configura Java 21, e executa ./mvnw test para o projeto da biblioteca (incluindo os testes com Testcontainers do capítulo 54).","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 58.1 — Pipeline de testesmédio Escreva um workflow do GitHub Actions que roda a cada push e Pull Request, configura Java 21, e executa ./mvnw test para o projeto da biblioteca (incluindo os testes com Testcontainers do capítulo 54). Ver solução name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' - run: ./mvnw test","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 58.1 — Pipeline de testes</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva um workflow do GitHub Actions que roda a cada push e Pull Request, configura Java 21, e executa <code>./mvnw test</code> para o projeto da biblioteca (incluindo os testes com Testcontainers do capítulo 54).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">name: CI\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-java@v4\n        with:\n          java-version: '21'\n          distribution: 'temurin'\n      - run: ./mvnw test</pre>\n        </div>\n      </div>"},{"id":"cicd-quiz","type":"quiz","authorship":"authored","conceptId":"ci-pipeline-gate","prompt":"O que um gate de CI deve provar antes do merge?","options":[{"id":"ci-a","label":"Que o commit passou por comandos reproduzíveis definidos pelo projeto.","correct":true,"explanation":"O gate reduz variação humana e registra evidência."},{"id":"ci-b","label":"Que nenhum bug existe em produção.","correct":false,"explanation":"Pipeline reduz risco, mas não prova ausência total de bug."},{"id":"ci-c","label":"Que deploy e merge são sempre a mesma etapa.","correct":false,"explanation":"CI valida; CD publica/implanta conforme política."}]}],"resources":[{"id":"github-actions-workflows","type":"official-docs","title":"GitHub Actions: Understanding workflows","url":"https://docs.github.com/en/actions/using-workflows/about-workflows","reinforces":"Eventos, jobs, steps, runners e execução de workflow.","language":"en","publisher":"GitHub","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"docker-github-actions","type":"official-docs","title":"Docker: GitHub Actions CI/CD","url":"https://docs.docker.com/build/ci/github-actions/","reinforces":"Build e publicação de imagens Docker em pipelines.","language":"en","publisher":"Docker","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The cicd component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to cicd. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible cicd failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"# .github/workflows/ci.yml","instruction":"The cicd component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible cicd failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"praticas-producao","moduleId":"production-delivery","order":2,"title":"Práticas de produção: parity, backup, feature flags & versionamento","summary":"Quatro práticas que raramente aparecem em tutorial nenhum, mas que separam \"sei fazer deploy\" de \"sei operar um sistema em produção\".","objectives":["Definir paridade segura entre ambientes","Diferenciar backup de restore testado","Usar feature flags como controle operacional","Planejar versionamento e compatibilidade de release"],"whyItExists":"Deploy não é só subir processo. Produção exige ambiente previsível, caminho de recuperação, mudança controlada e compatibilidade com usuários que ainda estão usando versões anteriores.","prerequisiteChapterIds":["hospedagem-db","http","cicd","compose"],"conceptIds":["praticas-de-producao-parity-backup-feature-flags-versionamento"],"introducedConceptIds":["release-environment-parity","feature-flag-operational-control"],"usedConceptIds":["database-environment-boundary","managed-database-operations","compose-service-network","ci-pipeline-gate","environment-config-contract"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"praticas-producao-intuition","type":"intuition","authorship":"authored","title":"Produção é contrato de mudança controlada","body":"A pergunta deixa de ser “funciona na minha máquina?” e vira “consigo mudar, observar, voltar e recuperar com evidência?”. Paridade, backup, flags e versionamento existem para responder isso antes do susto.","analogyLimit":"Checklist ajuda, mas operação real envolve ordem, propriedade, dados e tempo de recuperação."},{"id":"praticas-producao-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">DevOps</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#hospedagem-db\">39 · Onde hospedar o banco</a>, <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; REST</a>, <a class=\"prereq-tag\" href=\"#cicd\">58 · CI/CD</a></div>\n      </div>","fidelityText":"DevOps Dificuldade: Intermediário ⏱ ~1h30 de estudo Pré-requisitos: 39 · Onde hospedar o banco, 26 · HTTP & REST, 58 · CI/CD"},{"id":"praticas-producao-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Quatro práticas que raramente aparecem em tutorial nenhum, mas que separam \"sei fazer deploy\" de \"sei operar um sistema em produção\".</p>","fidelityText":"Quatro práticas que raramente aparecem em tutorial nenhum, mas que separam \"sei fazer deploy\" de \"sei operar um sistema em produção\"."},{"id":"praticas-producao-content-3","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Environment Parity — \"funciona na minha máquina\" é um sintoma, não uma desculpa</h2>","fidelityText":"Environment Parity — \"funciona na minha máquina\" é um sintoma, não uma desculpa"},{"id":"praticas-producao-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Quanto mais diferente seu ambiente de dev é do de produção (versão do Postgres, versão do Java, variáveis diferentes), mais surpresas desagradáveis esperam no deploy. <strong>Environment parity</strong> é o princípio de manter dev, staging e produção o mais parecidos possível.</p>","fidelityText":"Quanto mais diferente seu ambiente de dev é do de produção (versão do Postgres, versão do Java, variáveis diferentes), mais surpresas desagradáveis esperam no deploy. Environment parity é o princípio de manter dev, staging e produção o mais parecidos possível."},{"id":"praticas-producao-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">É como ensaiar uma peça de teatro no palco real em vez de só na sala de ensaio — quanto mais o ensaio se parece com a apresentação de verdade (mesma iluminação, mesmo som, mesmo espaço), menos surpresa acontece na estreia. Docker (capítulos 30-32) é a ferramenta que mais ajuda aqui: a mesma imagem que roda no seu Compose local é, idealmente, exatamente a mesma que roda em produção.</div>","fidelityText":"É como ensaiar uma peça de teatro no palco real em vez de só na sala de ensaio — quanto mais o ensaio se parece com a apresentação de verdade (mesma iluminação, mesmo som, mesmo espaço), menos surpresa acontece na estreia. Docker (capítulos 30-32) é a ferramenta que mais ajuda aqui: a mesma imagem que roda no seu Compose local é, idealmente, exatamente a mesma que roda em produção."},{"id":"praticas-producao-content-6","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Backup &amp; Disaster Recovery</h2>","fidelityText":"Backup & Disaster Recovery"},{"id":"praticas-producao-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Ninguém pensa em backup até o dia em que precisa dele — e nesse dia, já é tarde para configurar. Todo provedor de banco gerenciado (capítulo 39) sério oferece <strong>backup automático</strong> e, frequentemente, <strong>point-in-time recovery</strong> (restaurar o banco para o estado exato de um momento específico, não só do último backup diário).</p>","fidelityText":"Ninguém pensa em backup até o dia em que precisa dele — e nesse dia, já é tarde para configurar. Todo provedor de banco gerenciado (capítulo 39) sério oferece backup automático e, frequentemente, point-in-time recovery (restaurar o banco para o estado exato de um momento específico, não só do último backup diário)."},{"id":"praticas-producao-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Confirme que backups automáticos estão <strong>ativados</strong> no provedor escolhido — não é sempre o padrão.</li>\n          <li>Teste a restauração pelo menos uma vez — um backup nunca testado é uma suposição, não uma garantia.</li>\n          <li>Backup não substitui as migrations do Flyway (capítulo 35) — um protege dados, o outro protege estrutura; você precisa dos dois.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Confirme que backups automáticos estão ativados no provedor escolhido — não é sempre o padrão. Teste a restauração pelo menos uma vez — um backup nunca testado é uma suposição, não uma garantia. Backup não substitui as migrations do Flyway (capítulo 35) — um protege dados, o outro protege estrutura; você precisa dos dois."},{"id":"praticas-producao-content-9","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Feature Flags — deploy de código sem ativar a funcionalidade</h2>","fidelityText":"Feature Flags — deploy de código sem ativar a funcionalidade"},{"id":"praticas-producao-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Um <strong>feature flag</strong> separa \"o código está em produção\" de \"a funcionalidade está visível para os usuários\" — permitindo fazer deploy de código incompleto ou arriscado com segurança, ativando gradualmente.</p>","fidelityText":"Um feature flag separa \"o código está em produção\" de \"a funcionalidade está visível para os usuários\" — permitindo fazer deploy de código incompleto ou arriscado com segurança, ativando gradualmente."},{"id":"praticas-producao-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"if (featureFlags.isEnabled(\"new-checkout\", user)) {\n    return newFlowCheckout(order);\n}\nreturn flowCheckoutCurrent(order); // comportamento padrão, sem risco","fidelityText":"if (featureFlags.isEnabled(\"novo-checkout\", usuario)) { return novoFluxoCheckout(pedido); } return fluxoCheckoutAtual(pedido); // comportamento padrão, sem risco","highlightedHtml":"<span class=\"kw\">if</span> (featureFlags.isEnabled(<span class=\"str\">\"new-checkout\"</span>, user)) {\n    <span class=\"kw\">return</span> newFlowCheckout(order);\n}\n<span class=\"kw\">return</span> flowCheckoutCurrent(order); <span class=\"com\">// comportamento padrão, sem risco</span>","caption":"Exemplo executável de praticas-producao.","explanation":["O exemplo mostra decisão por configuração/flag sem recompilar a aplicação.","A flag deve ter nome, padrão e efeito testáveis."],"commonMistakes":["Usar flag como permissão de usuário","Nunca remover flag antiga"]},{"id":"praticas-producao-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Isso conecta direto com o Strategy Pattern do capítulo 23 — uma feature flag é, na essência, uma decisão de qual \"estratégia\" executar em runtime, só que a decisão vem de configuração externa (às vezes um simples <code>application.properties</code>, às vezes uma ferramenta dedicada como LaunchDarkly) em vez de lógica fixa no código. Isso permite desligar uma funcionalidade problemática em produção <strong>sem</strong> precisar de um novo deploy — só mudando a flag.</div>","fidelityText":"Isso conecta direto com o Strategy Pattern do capítulo 23 — uma feature flag é, na essência, uma decisão de qual \"estratégia\" executar em runtime, só que a decisão vem de configuração externa (às vezes um simples application.properties, às vezes uma ferramenta dedicada como LaunchDarkly) em vez de lógica fixa no código. Isso permite desligar uma funcionalidade problemática em produção sem precisar de um novo deploy — só mudando a flag."},{"id":"praticas-producao-content-13","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Versionamento de API — evoluir sem quebrar quem já usa</h2>","fidelityText":"Versionamento de API — evoluir sem quebrar quem já usa"},{"id":"praticas-producao-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Sua API já tem clientes reais consumindo (o próprio front-end, capítulos futuros). Mudar um campo de resposta sem cuidado quebra tudo que depende dela silenciosamente.</p>","fidelityText":"Sua API já tem clientes reais consumindo (o próprio front-end, capítulos futuros). Mudar um campo de resposta sem cuidado quebra tudo que depende dela silenciosamente."},{"id":"praticas-producao-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"GET /api/v1/books    -- versão atual, clientes antigos continuam funcionando\nGET /api/v2/books    -- nova versão, com mudanças incompatíveis, coexistindo com v1","fidelityText":"GET /api/v1/livros -- versão atual, clientes antigos continuam funcionando GET /api/v2/livros -- nova versão, com mudanças incompatíveis, coexistindo com v1","highlightedHtml":"GET /api/v1/books    <span class=\"com\">-- versão atual, clientes antigos continuam funcionando</span>\nGET /api/v2/books    <span class=\"com\">-- nova versão, com mudanças incompatíveis, coexistindo com v1</span>","caption":"Exemplo executável de praticas-producao.","explanation":["Versionamento operacional precisa considerar cliente, API, banco e artefato implantado.","A mudança deve ter caminho compatível ou plano de migração."],"commonMistakes":["Versionar só o endpoint e esquecer dados","Fazer mudança quebradora sem janela de compatibilidade"]},{"id":"praticas-producao-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">É a mesma lógica do Semantic Versioning que bibliotecas usam (<code>MAJOR.MINOR.PATCH</code>): mudanças que quebram compatibilidade incrementam a versão maior (v1 → v2), permitindo que clientes migrem no seu próprio ritmo, em vez de serem forçados a atualizar da noite para o dia porque a API mudou embaixo deles sem aviso.</div>","fidelityText":"É a mesma lógica do Semantic Versioning que bibliotecas usam (MAJOR.MINOR.PATCH): mudanças que quebram compatibilidade incrementam a versão maior (v1 → v2), permitindo que clientes migrem no seu próprio ritmo, em vez de serem forçados a atualizar da noite para o dia porque a API mudou embaixo deles sem aviso."},{"id":"praticas-producao-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Nenhuma dessas quatro práticas precisa ser implementada com ferramentas sofisticadas desde o primeiro projeto. Comece simples: environment parity via Docker Compose (que você já tem), backup ativado no painel do provedor (um clique), uma flag booleana lida de <code>application.properties</code>, e um prefixo <code>/api/v1</code> nas suas rotas desde o início. A disciplina importa mais que a ferramenta.</div>","fidelityText":"Nenhuma dessas quatro práticas precisa ser implementada com ferramentas sofisticadas desde o primeiro projeto. Comece simples: environment parity via Docker Compose (que você já tem), backup ativado no painel do provedor (um clique), uma flag booleana lida de application.properties, e um prefixo /api/v1 nas suas rotas desde o início. A disciplina importa mais que a ferramenta."},{"id":"praticas-producao-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Exercício 59.1 — Feature flag simples","prompt":"Adicione uma propriedade feature.busca-avancada.enabled=false ao application.properties. No LivroController, injete esse valor com @Value(\"${feature.busca-avancada.enabled}\") e use-o para decidir, no endpoint de listagem, se aplica um filtro de busca avançado (pode ser um comentário simulando a lógica) ou o comportamento padrão.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 59.1 — Feature flag simplesfácil Adicione uma propriedade feature.busca-avancada.enabled=false ao application.properties. No LivroController, injete esse valor com @Value(\"${feature.busca-avancada.enabled}\") e use-o para decidir, no endpoint de listagem, se aplica um filtro de busca avançado (pode ser um comentário simulando a lógica) ou o comportamento padrão. Ver solução @RestController public class LivroController { @Value(\"${feature.busca-avancada.enabled}\") private boolean buscaAvancadaHabilitada; @GetMapping(\"/api/v1/livros\") public List<LivroDTO> listar(@RequestParam(required=false) String q) { if (buscaAvancadaHabilitada && q != null) { return servico.buscaAvancada(q); // lógica nova, só ativa com a flag } return servico.listarTodos(); // comportamento padrão, sempre seguro } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 59.1 — Feature flag simples</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Adicione uma propriedade <code>feature.busca-avancada.enabled=false</code> ao <code>application.properties</code>. No <code>LivroController</code>, injete esse valor com <code>@Value(\"${feature.busca-avancada.enabled}\")</code> e use-o para decidir, no endpoint de listagem, se aplica um filtro de busca avançado (pode ser um comentário simulando a lógica) ou o comportamento padrão.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@RestController</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">BookController</span> {\n\n    <span class=\"annotation\">@Value</span>(<span class=\"str\">\"${feature.search-avancada.enabled}\"</span>)\n    <span class=\"kw\">private boolean</span> searchAvancadaHabilitada;\n\n    <span class=\"annotation\">@GetMapping</span>(<span class=\"str\">\"/api/v1/books\"</span>)\n    <span class=\"kw\">public</span> List&lt;<span class=\"cls\">BookDTO</span>&gt; <span class=\"fn\">listar</span>(<span class=\"annotation\">@RequestParam</span>(required=<span class=\"kw\">false</span>) <span class=\"kw\">String</span> q) {\n        <span class=\"kw\">if</span> (searchAvancadaHabilitada &amp;&amp; q != <span class=\"kw\">null</span>) {\n            <span class=\"kw\">return</span> service.searchAvancada(q); <span class=\"com\">// lógica nova, só ativa com a flag</span>\n        }\n        <span class=\"kw\">return</span> service.listarAll(); <span class=\"com\">// comportamento padrão, sempre seguro</span>\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"praticas-producao-quiz","type":"quiz","authorship":"authored","conceptId":"feature-flag-operational-control","prompt":"Qual uso de feature flag é mais saudável?","options":[{"id":"pp-a","label":"Desligar ou limitar uma funcionalidade em runtime sem novo deploy, com dono e limpeza planejada.","correct":true,"explanation":"Flag é controle operacional temporário ou deliberado, com governança."},{"id":"pp-b","label":"Substituir autorização e regras de permissão do sistema.","correct":false,"explanation":"Flag controla exposição; autorização controla direito de acesso."},{"id":"pp-c","label":"Manter código morto para sempre porque agora não incomoda.","correct":false,"explanation":"Flags acumuladas viram complexidade e risco."}]}],"resources":[{"id":"twelve-factor-config","type":"reference","title":"The Twelve-Factor App: Config","url":"https://12factor.net/config","reinforces":"Configuração externa e separação por ambiente.","language":"en","publisher":"12factor.net","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"martinfowler-feature-toggles","type":"reference","title":"Martin Fowler: Feature Toggles","url":"https://martinfowler.com/articles/feature-toggles.html","reinforces":"Tipos de toggle, lifecycle e custo operacional.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The practices production component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to practices production. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible practices production failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"if (featureFlags.isEnabled(\"new-checkout\", user)) {","instruction":"The practices production component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible practices production failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"hardening-producao","moduleId":"production-delivery","order":3,"title":"Hardening, entrega segura e recuperação operacional","summary":"Produção exige uma cadeia verificável: construir uma vez, testar o mesmo artefato, promover por digest, observar, reverter e recuperar dados. Um deploy que funcionou uma vez não é uma estratégia operacional.","objectives":["Reduzir superfície de ataque do artefato","Separar readiness, liveness e health mínimo","Planejar deploy com rollback verificável","Provar recuperação por restore drill"],"whyItExists":"Antes de publicar de verdade, a aplicação precisa ser recuperável e operável. Hardening aqui é o básico comprovável: artefato menor, processo correto, health útil, rollback possível e recuperação testada.","prerequisiteChapterIds":["dockerfile","cicd","praticas-producao","logging"],"conceptIds":["da-imagem-ao-servico-recuperavel","imagem-minima-e-processo-correto","capabilities-e-filesystem-prova-em-codigo-nao-so-recomendacao","deploy-e-rollback","backup-nao-e-recuperacao"],"introducedConceptIds":["deploy-rollback-strategy","health-readiness-liveness","recovery-backup-restore-drill"],"usedConceptIds":["container-image-layer","multistage-runtime-image","ci-pipeline-gate","artifact-image-provenance","managed-database-operations","log-nivel-contexto","log-parametrizado-causa"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"hardening-producao-intuition","type":"intuition","authorship":"authored","title":"Serviço em produção precisa falhar de modo operável","body":"Falha vai acontecer. A diferença é se o serviço revela que não está pronto, reinicia quando travou, volta para uma versão conhecida e recupera dados dentro de um limite combinado.","analogyLimit":"Blindagem sugere impedir tudo; em software, hardening também é limitar dano e acelerar recuperação."},{"id":"hardening-producao-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-devops\">Produção</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~5h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#dockerfile\">Dockerfile</a>, <a class=\"prereq-tag\" href=\"#cicd\">CI/CD</a>, <a class=\"prereq-tag\" href=\"#observabilidade-pratica\">Observabilidade</a></div></div>","fidelityText":"ProduçãoDificuldade: Avançado⏱ ~5h de estudo e práticaPré-requisitos: Dockerfile, CI/CD, Observabilidade"},{"id":"hardening-producao-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Produção exige uma cadeia verificável: construir uma vez, testar o mesmo artefato, promover por digest, observar, reverter e recuperar dados. Um deploy que funcionou uma vez não é uma estratégia operacional.</p>","fidelityText":"Produção exige uma cadeia verificável: construir uma vez, testar o mesmo artefato, promover por digest, observar, reverter e recuperar dados. Um deploy que funcionou uma vez não é uma estratégia operacional."},{"id":"hardening-producao-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Da imagem ao serviço recuperável</h2></div>\n    <p>Hardening reduz possibilidades desnecessárias de ataque e operação. Entrega segura mantém identidade e rastreabilidade do artefato.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Artefato</dt><dd>Saída imutável do build que será implantada, como um JAR ou imagem de container.</dd></div><div class=\"concept-card\"><dt>Digest</dt><dd>Hash que identifica exatamente o conteúdo de uma imagem; ao contrário de uma tag, não muda para apontar a outra versão.</dd></div><div class=\"concept-card\"><dt>Hardening</dt><dd>Redução da superfície de ataque por configuração mínima, menos privilégios e remoção do que não é necessário.</dd></div><div class=\"concept-card\"><dt>SBOM</dt><dd><em>Software Bill of Materials</em>: inventário de componentes e versões presentes no artefato.</dd></div><div class=\"concept-card\"><dt>Rollback</dt><dd>Retorno controlado a uma versão anterior da aplicação. Não reverte automaticamente dados já migrados.</dd></div><div class=\"concept-card\"><dt>Runbook</dt><dd>Procedimento operacional reproduzível para diagnosticar, mitigar ou recuperar um serviço.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoDa imagem ao serviço recuperável Hardening reduz possibilidades desnecessárias de ataque e operação. Entrega segura mantém identidade e rastreabilidade do artefato. ArtefatoSaída imutável do build que será implantada, como um JAR ou imagem de container.DigestHash que identifica exatamente o conteúdo de uma imagem; ao contrário de uma tag, não muda para apontar a outra versão.HardeningRedução da superfície de ataque por configuração mínima, menos privilégios e remoção do que não é necessário.SBOMSoftware Bill of Materials: inventário de componentes e versões presentes no artefato.RollbackRetorno controlado a uma versão anterior da aplicação. Não reverte automaticamente dados já migrados.RunbookProcedimento operacional reproduzível para diagnosticar, mitigar ou recuperar um serviço."},{"id":"hardening-producao-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Imagem mínima e processo correto</h2>","fidelityText":"Imagem mínima e processo correto"},{"id":"hardening-producao-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"FROM eclipse-temurin:21-jdk AS build\nWORKDIR /workspace\nCOPY .mvn/ .mvn/\nCOPY mvnw pom.xml ./\nRUN ./mvnw -B -DskipTests dependency:go-offline\nCOPY src/ src/\nRUN ./mvnw -B package\n\nFROM eclipse-temurin:21-jre\nRUN useradd --system --uid 10001 app\nWORKDIR /app\nCOPY --from=build /workspace/target/*.jar app.jar\nUSER 10001\nENTRYPOINT [\"java\",\"-jar\",\"/app/app.jar\"]","fidelityText":"FROM eclipse-temurin:21-jdk AS build WORKDIR /workspace COPY .mvn/ .mvn/ COPY mvnw pom.xml ./ RUN ./mvnw -B -DskipTests dependency:go-offline COPY src/ src/ RUN ./mvnw -B package FROM eclipse-temurin:21-jre RUN useradd --system --uid 10001 app WORKDIR /app COPY --from=build /workspace/target/*.jar app.jar USER 10001 ENTRYPOINT [\"java\",\"-jar\",\"/app/app.jar\"]","highlightedHtml":"<span class=\"kw\">FROM</span> eclipse-temurin:21-jdk <span class=\"kw\">AS</span> build\n<span class=\"kw\">WORKDIR</span> /workspace\n<span class=\"kw\">COPY</span> .mvn/ .mvn/\n<span class=\"kw\">COPY</span> mvnw pom.xml ./\n<span class=\"kw\">RUN</span> ./mvnw -B -DskipTests dependency:go-offline\n<span class=\"kw\">COPY</span> src/ src/\n<span class=\"kw\">RUN</span> ./mvnw -B package\n\n<span class=\"kw\">FROM</span> eclipse-temurin:21-jre\n<span class=\"kw\">RUN</span> useradd --system --uid 10001 app\n<span class=\"kw\">WORKDIR</span> /app\n<span class=\"kw\">COPY</span> --from=build /workspace/target/*.jar app.jar\n<span class=\"kw\">USER</span> 10001\n<span class=\"kw\">ENTRYPOINT</span> [<span class=\"str\">\"java\"</span>,<span class=\"str\">\"-jar\"</span>,<span class=\"str\">\"/app/app.jar\"</span>]","caption":"Exemplo executável de hardening-producao.","explanation":["O exemplo operacional deve deixar claro qual processo roda, com qual porta e quais variáveis obrigatórias.","Imagem mínima e usuário/processo correto reduzem risco e ruído de operação."],"commonMistakes":["Rodar como root sem necessidade","Confundir health superficial com readiness real"]},{"id":"hardening-producao-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Fixe a imagem base por digest no pipeline real, gere SBOM, escaneie dependências e imagem, execute como usuário sem privilégios e prefira filesystem somente leitura com diretórios temporários explícitos. <strong>Capabilities</strong> são permissões específicas do kernel Linux; removê-las evita conceder poderes que o processo não usa.</p>","fidelityText":"Fixe a imagem base por digest no pipeline real, gere SBOM, escaneie dependências e imagem, execute como usuário sem privilégios e prefira filesystem somente leitura com diretórios temporários explícitos. Capabilities são permissões específicas do kernel Linux; removê-las evita conceder poderes que o processo não usa."},{"id":"hardening-producao-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Capabilities e filesystem: prova em código, não só recomendação</h2>","fidelityText":"Capabilities e filesystem: prova em código, não só recomendação"},{"id":"hardening-producao-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"# docker run equivalente ao trecho de hardening acima\ndocker run \\\n  --read-only \\\n  --tmpfs /tmp:rw,noexec,nosuid,size=64m \\\n  --cap-drop=ALL \\\n  --security-opt=in-new-privileges \\\n  --user 10001 \\\n  my-image:1.4.2@sha256:...\n\n# equivalente em Docker Compose:\nservices:\n  api:\n    image: my-image:1.4.2@sha256:...\n    read_only: true\n    tmpfs:\n      - /tmp:rw,noexec,nosuid,size=64m\n    cap_drop:\n      - ALL\n    security_opt:\n      - in-new-privileges:true\n    user: \"10001\"","fidelityText":"# docker run equivalente ao trecho de hardening acima docker run \\ --read-only \\ --tmpfs /tmp:rw,noexec,nosuid,size=64m \\ --cap-drop=ALL \\ --security-opt=no-new-privileges \\ --user 10001 \\ minha-imagem:1.4.2@sha256:... # equivalente em Docker Compose: services: api: image: minha-imagem:1.4.2@sha256:... read_only: true tmpfs: - /tmp:rw,noexec,nosuid,size=64m cap_drop: - ALL security_opt: - no-new-privileges:true user: \"10001\"","highlightedHtml":"<span class=\"com\"># docker run equivalente ao trecho de hardening acima</span>\ndocker run \\\n  --read-only \\\n  --tmpfs /tmp:rw,noexec,nosuid,size=64m \\\n  --cap-drop=ALL \\\n  --security-opt=in-new-privileges \\\n  --user 10001 \\\n  my-image:1.4.2@sha256:...\n\n<span class=\"com\"># equivalente em Docker Compose:</span>\nservices:\n  api:\n    image: my-image:1.4.2@sha256:...\n    read_only: true\n    tmpfs:\n      - /tmp:rw,noexec,nosuid,size=64m\n    cap_drop:\n      - ALL\n    security_opt:\n      - in-new-privileges:true\n    user: \"10001\"","caption":"Exemplo executável de hardening-producao.","explanation":["`--read-only`/`cap-drop=ALL`/`no-new-privileges` provam em comando real o que a prosa de hardening recomenda.","Filesystem somente leitura exige `tmpfs` explícito para qualquer escrita temporária real (ex.: logs de biblioteca teimosa)."],"commonMistakes":["Recomendar hardening só em prosa sem o comando/YAML equivalente","Ativar --read-only sem prever onde o processo precisa escrever"]},{"id":"hardening-producao-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>--read-only quebra qualquer coisa que escreva fora de /tmp.</b> Logs devem ir para stdout (Actuator/observabilidade), não para arquivo em disco; se uma biblioteca insistir em escrever em outro diretório, monte um <code>tmpfs</code> adicional para ele em vez de desistir do filesystem somente leitura.</div>","fidelityText":"--read-only quebra qualquer coisa que escreva fora de /tmp. Logs devem ir para stdout (Actuator/observabilidade), não para arquivo em disco; se uma biblioteca insistir em escrever em outro diretório, monte um tmpfs adicional para ele em vez de desistir do filesystem somente leitura."},{"id":"hardening-producao-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Deploy e rollback</h2>","fidelityText":"Deploy e rollback"},{"id":"hardening-producao-content-11","type":"html","authorship":"legacy-preserved","html":"<ul><li>Migrations devem ser compatíveis com a versão anterior durante rolling deploy: primeiro expanda, migre dados, depois contraia.</li><li>Blue-green troca todo o tráfego após validação; canary expõe gradualmente e exige métricas automáticas.</li><li>Rollback de aplicação não desfaz automaticamente migrations destrutivas.</li><li>Configure graceful shutdown, tempo máximo e tratamento de sinais.</li><li>Separe readiness de liveness e teste o comportamento durante indisponibilidade.</li></ul>","fidelityText":"Migrations devem ser compatíveis com a versão anterior durante rolling deploy: primeiro expanda, migre dados, depois contraia.Blue-green troca todo o tráfego após validação; canary expõe gradualmente e exige métricas automáticas.Rollback de aplicação não desfaz automaticamente migrations destrutivas.Configure graceful shutdown, tempo máximo e tratamento de sinais.Separe readiness de liveness e teste o comportamento durante indisponibilidade."},{"id":"hardening-producao-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Backup não é recuperação</h2>","fidelityText":"Backup não é recuperação"},{"id":"hardening-producao-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Defina <strong>RPO</strong> (<em>Recovery Point Objective</em>) — quanto dado pode ser perdido, medido como distância até o ponto recuperável — e <strong>RTO</strong> (<em>Recovery Time Objective</em>) — quanto tempo a recuperação pode levar. Faça backup criptografado, com retenção e cópia fora do mesmo domínio de falha. Restaure periodicamente em ambiente isolado e verifique integridade; somente então existe evidência de recuperação.</p>","fidelityText":"Defina RPO (Recovery Point Objective) — quanto dado pode ser perdido, medido como distância até o ponto recuperável — e RTO (Recovery Time Objective) — quanto tempo a recuperação pode levar. Faça backup criptografado, com retenção e cópia fora do mesmo domínio de falha. Restaure periodicamente em ambiente isolado e verifique integridade; somente então existe evidência de recuperação."},{"id":"hardening-producao-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Compose não vira produção apenas por estar em uma VM.</b> Não exponha a porta do banco publicamente, não grave senha no arquivo, configure limites, atualização, TLS, firewall, monitoramento e responsabilidade operacional.</div>","fidelityText":"Compose não vira produção apenas por estar em uma VM. Não exponha a porta do banco publicamente, não grave senha no arquivo, configure limites, atualização, TLS, firewall, monitoramento e responsabilidade operacional."},{"id":"hardening-producao-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Game day — falha controlada","prompt":"Derrube uma instância, interrompa o banco, entregue uma versão incompatível e restaure um backup. Registre detecção, impacto, rollback, RPO/RTO observado e ações preventivas.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Game day — falha controladadifícilDerrube uma instância, interrompa o banco, entregue uma versão incompatível e restaure um backup. Registre detecção, impacto, rollback, RPO/RTO observado e ações preventivas.Ver critériosO exercício só termina quando existe runbook executável por outra pessoa, evidência de restauração e uma ação corretiva priorizada.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Game day — falha controlada</h2><span class=\"exercise-tag d\">difícil</span></div><p>Derrube uma instância, interrompa o banco, entregue uma versão incompatível e restaure um backup. Registre detecção, impacto, rollback, RPO/RTO observado e ações preventivas.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>O exercício só termina quando existe runbook executável por outra pessoa, evidência de restauração e uma ação corretiva priorizada.</p></div></div>"},{"id":"hardening-producao-quiz","type":"quiz","authorship":"authored","conceptId":"recovery-backup-restore-drill","prompt":"Por que backup sem restore testado não basta?","options":[{"id":"hp-a","label":"Porque só o ensaio de restore prova que o dado pode voltar dentro de tempo e perda aceitáveis.","correct":true,"explanation":"Backup é meio; recuperação comprovada é o resultado."},{"id":"hp-b","label":"Porque backup deve substituir logs e métricas.","correct":false,"explanation":"Backup recupera dado; logs/métricas explicam comportamento."},{"id":"hp-c","label":"Porque restore sempre desfaz qualquer deploy automaticamente.","correct":false,"explanation":"Deploy, rollback e restore têm escopos diferentes."}]}],"resources":[{"id":"spring-actuator-health","type":"official-docs","title":"Spring Boot Actuator: Health Information","url":"https://docs.spring.io/spring-boot/reference/actuator/endpoints.html#actuator.endpoints.health","reinforces":"Health, grupos e exposição de sinais operacionais.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"docker-security-rootless","type":"official-docs","title":"Docker security: rootless mode","url":"https://docs.docker.com/engine/security/rootless/","reinforces":"Redução de privilégios e superfície operacional em containers.","language":"en","publisher":"Docker","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The hardening production component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to hardening production. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible hardening production failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"FROM eclipse-temurin:21-jdk AS build","instruction":"The hardening production component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible hardening production failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"deploy-backend","moduleId":"production-delivery","order":4,"title":"Deploy do back-end em produção","summary":"É a hora de tirar a API do localhost e colocá-la em um servidor real, acessível pela internet. Existem três níveis de controle/complexidade, e a escolha certa depende do estágio do projeto.","objectives":["Comparar PaaS e VPS por responsabilidade operacional","Configurar runtime com secrets e banco gerenciado","Publicar imagem/artefato rastreável","Verificar domínio, HTTPS, health e logs pós-deploy"],"whyItExists":"Depois do pipeline e do hardening mínimo, deploy deixa de ser tutorial de botão e vira escolha de contrato: quem cuida de rede, processo, TLS, logs, banco, rollback e variáveis?","prerequisiteChapterIds":["dockerfile","secrets","hospedagem-db","cicd","hardening-producao"],"conceptIds":["deploy-via-railway-paas-o-caminho-mais-direto","deploy-via-vps-com-docker-compose","configurando-o-dominio-e-https"],"introducedConceptIds":["paas-vps-deployment-boundary"],"usedConceptIds":["runtime-secret-boundary","environment-config-contract","artifact-image-provenance","deploy-rollback-strategy","tls-certificate-chain","managed-database-operations"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"deploy-backend-intuition","type":"intuition","authorship":"authored","title":"Deploy é assumir um contrato operacional","body":"PaaS entrega convenções prontas; VPS entrega controle e responsabilidade. A escolha boa é a que deixa claro como a aplicação inicia, recebe tráfego, guarda config, emite logs, volta versão e recupera dado.","analogyLimit":"Hospedagem parece aluguel de máquina, mas o contrato real envolve rede, segurança, observabilidade, banco e rotina de incidente."},{"id":"deploy-backend-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Deploy</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#dockerfile\">31 · Dockerfile</a>, <a class=\"prereq-tag\" href=\"#secrets\">57 · Secrets</a>, <a class=\"prereq-tag\" href=\"#hospedagem-db\">39 · Onde hospedar o banco</a>, <a class=\"prereq-tag\" href=\"#cicd\">58 · CI/CD</a></div>\n      </div>","fidelityText":"Deploy Dificuldade: Intermediário ⏱ ~2h de estudo + prática Pré-requisitos: 31 · Dockerfile, 57 · Secrets, 39 · Onde hospedar o banco, 58 · CI/CD"},{"id":"deploy-backend-content-2","type":"html","authorship":"legacy-preserved","html":"<p>É a hora de tirar a API do <code>localhost</code> e colocá-la em um servidor real, acessível pela internet. Existem três níveis de controle/complexidade, e a escolha certa depende do estágio do projeto.</p>","fidelityText":"É a hora de tirar a API do localhost e colocá-la em um servidor real, acessível pela internet. Existem três níveis de controle/complexidade, e a escolha certa depende do estágio do projeto."},{"id":"deploy-backend-content-3","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Opção</th><th>Nível de controle</th><th>Esforço de setup</th><th>Bom para</th></tr>\n        <tr><td><strong>Railway / Render</strong></td><td>Baixo (PaaS)</td><td>Mínimo — conecta o repositório e pronto</td><td>Projetos pessoais, MVPs, aprendizado</td></tr>\n        <tr><td><strong>VPS com Docker</strong> (DigitalOcean, Hetzner)</td><td>Médio</td><td>Você configura o servidor, mas usa Docker Compose</td><td>Mais controle, custo previsível</td></tr>\n        <tr><td><strong>AWS EC2 / ECS</strong></td><td>Alto</td><td>Maior curva de aprendizado, mais flexibilidade</td><td>Aplicações corporativas, escala grande</td></tr>\n      </tbody></table>","fidelityText":"OpçãoNível de controleEsforço de setupBom para Railway / RenderBaixo (PaaS)Mínimo — conecta o repositório e prontoProjetos pessoais, MVPs, aprendizado VPS com Docker (DigitalOcean, Hetzner)MédioVocê configura o servidor, mas usa Docker ComposeMais controle, custo previsível AWS EC2 / ECSAltoMaior curva de aprendizado, mais flexibilidadeAplicações corporativas, escala grande"},{"id":"deploy-backend-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense nessas três opções como alugar um apartamento mobiliado (Railway/Render — chega e usa), alugar um apartamento vazio que você mobilia do seu jeito (VPS com Docker — mais trabalho, mais liberdade), ou construir a casa do zero em um terreno (AWS — controle total, mas você é responsável por tudo, da fundação ao telhado).</div>","fidelityText":"Pense nessas três opções como alugar um apartamento mobiliado (Railway/Render — chega e usa), alugar um apartamento vazio que você mobilia do seu jeito (VPS com Docker — mais trabalho, mais liberdade), ou construir a casa do zero em um terreno (AWS — controle total, mas você é responsável por tudo, da fundação ao telhado)."},{"id":"deploy-backend-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Deploy via Railway (PaaS) — o caminho mais direto</h2>","fidelityText":"Deploy via Railway (PaaS) — o caminho mais direto"},{"id":"deploy-backend-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"# 1. conecta o repositório GitHub ao Railway (via interface web)\n# 2. Railway detecta o Dockerfile (capítulo 31) automaticamente\n# 3. configura as variáveis de ambiente no painel (capítulo 57):\nDB_URL=jdbc:postgresql://...\nJWT_SECRET=...\n\n# 4. a cada push na branch configurada, Railway builda e faz deploy sozinho --\n# o CI/CD do capítulo 58 combinado com isso fecha o ciclo completo","fidelityText":"# 1. conecta o repositório GitHub ao Railway (via interface web) # 2. Railway detecta o Dockerfile (capítulo 31) automaticamente # 3. configura as variáveis de ambiente no painel (capítulo 57): DB_URL=jdbc:postgresql://... JWT_SECRET=... # 4. a cada push na branch configurada, Railway builda e faz deploy sozinho -- # o CI/CD do capítulo 58 combinado com isso fecha o ciclo completo","highlightedHtml":"<span class=\"com\"># 1. conecta o repositório GitHub ao Railway (via interface web)\n# 2. Railway detecta o Dockerfile (capítulo 31) automaticamente\n# 3. configura as variáveis de ambiente no painel (capítulo 57):</span>\nDB_URL=jdbc:postgresql://...\nJWT_SECRET=...\n\n<span class=\"com\"># 4. a cada push na branch configurada, Railway builda e faz deploy sozinho --\n# o CI/CD do capítulo 58 combinado com isso fecha o ciclo completo</span>","caption":"Exemplo executável de deploy-backend.","explanation":["O exemplo de PaaS privilegia variáveis, build command, start command e health observável.","Mesmo gerenciado, o ambiente precisa de contrato explícito."],"commonMistakes":["Tratar painel do provedor como documentação","Configurar secret em build quando deveria ser runtime"]},{"id":"deploy-backend-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Deploy via VPS com Docker Compose</h2>","fidelityText":"Deploy via VPS com Docker Compose"},{"id":"deploy-backend-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"# na sua máquina local ou via CI/CD:\ndocker context create producao --docker \"host=ssh://user@my-server.with\"\ndocker context use producao\ndocker compose up -d --build   # builda e sobe DIRETO no servidor remoto","fidelityText":"# na sua máquina local ou via CI/CD: docker context create producao --docker \"host=ssh://usuario@meu-servidor.com\" docker context use producao docker compose up -d --build # builda e sobe DIRETO no servidor remoto","highlightedHtml":"<span class=\"com\"># na sua máquina local ou via CI/CD:</span>\ndocker context create producao --docker \"host=ssh://user@my-server.with\"\ndocker context use producao\ndocker compose up -d --build   <span class=\"com\"># builda e sobe DIRETO no servidor remoto</span>","caption":"Exemplo executável de deploy-backend.","explanation":["O exemplo de VPS/Compose torna explícitos imagem, portas, volumes, rede e restart policy.","Mais controle significa mais responsabilidade por atualização, firewall, TLS e backup."],"commonMistakes":["Usar latest em produção","Persistir dados dentro do container"]},{"id":"deploy-backend-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Repare que o comando final (<code>docker compose up -d --build</code>) é <strong>exatamente</strong> o mesmo que você já usa localmente desde o capítulo 32 — a única diferença é o <code>docker context</code> apontando para uma máquina remota via SSH em vez da sua própria. Isso é a prova prática de \"environment parity\" (capítulo 59): se funciona no seu Compose local, a chance de funcionar igual em produção é muito maior, porque literalmente é a mesma configuração rodando em outro lugar.</div>","fidelityText":"Repare que o comando final (docker compose up -d --build) é exatamente o mesmo que você já usa localmente desde o capítulo 32 — a única diferença é o docker context apontando para uma máquina remota via SSH em vez da sua própria. Isso é a prova prática de \"environment parity\" (capítulo 59): se funciona no seu Compose local, a chance de funcionar igual em produção é muito maior, porque literalmente é a mesma configuração rodando em outro lugar."},{"id":"deploy-backend-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Configurando o domínio e HTTPS</h2>","fidelityText":"Configurando o domínio e HTTPS"},{"id":"deploy-backend-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Provedores PaaS (Railway, Render) entregam um subdomínio próprio com HTTPS automático (capítulo 53) imediatamente. Para domínio próprio (<code>api.seusite.com</code>), você aponta um registro DNS <code>CNAME</code> para o endereço fornecido pelo provedor — a renovação do certificado continua automática.</p>","fidelityText":"Provedores PaaS (Railway, Render) entregam um subdomínio próprio com HTTPS automático (capítulo 53) imediatamente. Para domínio próprio (api.seusite.com), você aponta um registro DNS CNAME para o endereço fornecido pelo provedor — a renovação do certificado continua automática."},{"id":"deploy-backend-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Nunca faça o primeiro deploy de produção sem antes ter testado o mesmo Dockerfile localmente com <code>docker compose up</code>.</li>\n          <li>Configure o healthcheck do Actuator (capítulo 56) desde o primeiro deploy — sem ele, você só descobre que a aplicação caiu quando um usuário reclamar.</li>\n          <li>Sempre tenha logs acessíveis do ambiente de produção (a maioria dos PaaS já oferece isso no painel) — sem log, um erro em produção é um mistério sem pistas.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Nunca faça o primeiro deploy de produção sem antes ter testado o mesmo Dockerfile localmente com docker compose up. Configure o healthcheck do Actuator (capítulo 56) desde o primeiro deploy — sem ele, você só descobre que a aplicação caiu quando um usuário reclamar. Sempre tenha logs acessíveis do ambiente de produção (a maioria dos PaaS já oferece isso no painel) — sem log, um erro em produção é um mistério sem pistas."},{"id":"deploy-backend-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Para o primeiro deploy da sua vida, escolha Railway ou Render — a barreira de entrada baixa importa mais nesse momento do que o controle refinado de uma VPS ou AWS. Depois que você já entender o ciclo completo (build → deploy → monitorar → corrigir), migrar para mais controle fica muito mais natural, porque você já sabe o que está automatizando.</div>","fidelityText":"Para o primeiro deploy da sua vida, escolha Railway ou Render — a barreira de entrada baixa importa mais nesse momento do que o controle refinado de uma VPS ou AWS. Depois que você já entender o ciclo completo (build → deploy → monitorar → corrigir), migrar para mais controle fica muito mais natural, porque você já sabe o que está automatizando."},{"id":"deploy-backend-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 60.1 — Checklist de deploy","prompt":"Escreva um checklist de pré-deploy para o projeto da biblioteca antes de publicar em produção pela primeira vez, cobrindo pelo menos: variáveis de ambiente configuradas, migrations aplicadas, HTTPS ativo, CORS configurado para o domínio real do front-end, e healthcheck respondendo.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 60.1 — Checklist de deploymédio Escreva um checklist de pré-deploy para o projeto da biblioteca antes de publicar em produção pela primeira vez, cobrindo pelo menos: variáveis de ambiente configuradas, migrations aplicadas, HTTPS ativo, CORS configurado para o domínio real do front-end, e healthcheck respondendo. Ver solução ☐ Variáveis de ambiente (DB_URL, JWT_SECRET) configuradas no painel do provedor, não no código ☐ Flyway configurado com spring.flyway.enabled=true, ddl-auto=validate ☐ HTTPS confirmado (certificado ativo, sem aviso de \"conexão não segura\") ☐ CORS liberando apenas o domínio real do front-end de produção, não localhost ☐ /actuator/health respondendo \"UP\" publicamente, resto do Actuator protegido ☐ Backup automático do banco confirmado como ativo no provedor ☐ Logs acessíveis via painel do provedor para depuração pós-deploy","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 60.1 — Checklist de deploy</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva um checklist de pré-deploy para o projeto da biblioteca antes de publicar em produção pela primeira vez, cobrindo pelo menos: variáveis de ambiente configuradas, migrations aplicadas, HTTPS ativo, CORS configurado para o domínio real do front-end, e healthcheck respondendo.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">☐ Variables de ambiente (DB_URL, JWT_SECRET) configuradas no dashboard do provider, nao no code\n☐ Flyway configured com spring.flyway.enabled=true, ddl-auto=validate\n☐ HTTPS confirmed (certificate active, sem aviso de \"connection not segura\")\n☐ CORS liberando apenas o domain real do front-end de producao, nao localhost\n☐ /actuator/health respondendo \"UP\" publicamente, resto do Actuator protegido\n☐ Backup automatico do bank confirmed as active no provider\n☐ Logs acessiveis via dashboard do provider para depuracao pos-deploy</pre>\n        </div>\n      </div>"},{"id":"deploy-backend-quiz","type":"quiz","authorship":"authored","conceptId":"paas-vps-deployment-boundary","prompt":"Qual pergunta diferencia melhor PaaS de VPS na prática?","options":[{"id":"db-a","label":"Quem opera processo, TLS, logs, escala, restart e variáveis de runtime?","correct":true,"explanation":"A diferença é principalmente responsabilidade operacional assumida."},{"id":"db-b","label":"Qual opção permite ignorar secrets?","correct":false,"explanation":"Ambas exigem contrato seguro de secrets."},{"id":"db-c","label":"Qual opção elimina necessidade de testes?","correct":false,"explanation":"Deploy não substitui validação."}]}],"resources":[{"id":"spring-boot-container-images","type":"official-docs","title":"Spring Boot: Container Images","url":"https://docs.spring.io/spring-boot/reference/packaging/container-images/index.html","reinforces":"Build e publicação de aplicações Spring Boot em imagens de container.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"docker-compose-production","type":"official-docs","title":"Docker Compose: Use Compose in production","url":"https://docs.docker.com/compose/how-tos/production/","reinforces":"Considerações de Compose fora do ambiente local.","language":"en","publisher":"Docker","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The deploy backend component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to deploy backend. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible deploy backend failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"# 1. conecta o repositório GitHub ao Railway (via interface web)","instruction":"The deploy backend component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible deploy backend failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"spring-profiles","moduleId":"production-delivery","order":5,"title":"Spring Profiles avançado","summary":"O capítulo 27 mencionou application-dev.properties e application-prod.properties de leve. Aqui aprofundamos como o Spring decide qual arquivo usar, e como isso se conecta com beans inteiros que só deveriam existir em certos ambientes.","objectives":["Ativar configurações por ambiente sem duplicar regra","Diferenciar profile de feature flag","Evitar secrets em arquivos versionados","Testar comportamento condicionado por profile"],"whyItExists":"Profiles aparecem depois de configuração externa e deploy porque só fazem sentido quando há ambientes reais. Eles selecionam beans/configuração; não devem virar autorização, segredo versionado ou substituto de flag operacional.","prerequisiteChapterIds":["logging","praticas-producao","spring-boot-fundamentos","secrets"],"conceptIds":["beans-condicionais-por-profile","multiplos-profiles-ativos-e-grupos","testando-qual-bean-cada-profile-realmente-registra"],"introducedConceptIds":["spring-profile-config-activation"],"usedConceptIds":["boot-config-properties","environment-config-contract","feature-flag-operational-control","runtime-secret-boundary"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"spring-profiles-intuition","type":"intuition","authorship":"authored","title":"Profile escolhe configuração do ambiente, não identidade do usuário","body":"Um profile responde “em qual ambiente/modo esta aplicação iniciou?”. Ele ajuda a trocar beans e propriedades por contexto de execução, mas não substitui autorização, flag de produto nem gerenciador de secrets.","analogyLimit":"Interruptor de modo ajuda, mas profiles são resolvidos no bootstrap e afetam composição da aplicação."},{"id":"spring-profiles-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#logging\">27 · Logging &amp; config</a>, <a class=\"prereq-tag\" href=\"#praticas-producao\">59 · Práticas de produção</a> (environment parity)</div>\n      </div>","fidelityText":"Spring Dificuldade: Intermediário ⏱ ~1h de estudo Pré-requisitos: 27 · Logging & config, 59 · Práticas de produção (environment parity)"},{"id":"spring-profiles-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 27 mencionou <code>application-dev.properties</code> e <code>application-prod.properties</code> de leve. Aqui aprofundamos como o Spring decide qual arquivo usar, e como isso se conecta com beans inteiros que só deveriam existir em certos ambientes.</p>","fidelityText":"O capítulo 27 mencionou application-dev.properties e application-prod.properties de leve. Aqui aprofundamos como o Spring decide qual arquivo usar, e como isso se conecta com beans inteiros que só deveriam existir em certos ambientes."},{"id":"spring-profiles-code-3","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties (compartilhado por todos os profiles)\n# application-dev.properties (só quando o profile \"dev\" está ativo)\n# application-prod.properties (só quando \"prod\" está ativo)\n\n# ativando um profile:\njava -jar app.jar --spring.profiles.active=prod\n# ou via variável de ambiente (capítulo 57):\nSPRING_PROFILES_ACTIVE=prod","fidelityText":"# application.properties (compartilhado por todos os profiles) # application-dev.properties (só quando o profile \"dev\" está ativo) # application-prod.properties (só quando \"prod\" está ativo) # ativando um profile: java -jar app.jar --spring.profiles.active=prod # ou via variável de ambiente (capítulo 57): SPRING_PROFILES_ACTIVE=prod","highlightedHtml":"<span class=\"com\"># application.properties (compartilhado por todos os profiles)\n# application-dev.properties (só quando o profile \"dev\" está ativo)\n# application-prod.properties (só quando \"prod\" está ativo)</span>\n\n<span class=\"com\"># ativando um profile:</span>\njava -jar app.jar --spring.profiles.active=prod\n<span class=\"com\"># ou via variável de ambiente (capítulo 57):</span>\nSPRING_PROFILES_ACTIVE=prod","caption":"Exemplo executável de spring-profiles.","explanation":["@Profile condiciona a criação de bean ao profile ativo no bootstrap.","O domínio deve depender de interface/contrato, não do nome do ambiente."],"commonMistakes":["Espalhar if de profile pela regra de negócio","Criar profiles demais sem política"]},{"id":"spring-profiles-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Beans condicionais por profile</h2>","fidelityText":"Beans condicionais por profile"},{"id":"spring-profiles-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\n@Profile(\"dev\")\npublic class EmailServiceFake implements NotifierLoan {\n    @Override public void notify(String msg) {\n        System.out.println(\"[SIMULADO] \" + msg); // não envia e-mail de verdade em dev\n    }\n}\n\n@Service\n@Profile(\"prod\")\npublic class EmailServiceReal implements NotifierLoan {\n    @Override public void notify(String msg) { /* SMTP real */ }\n}","fidelityText":"@Service @Profile(\"dev\") public class EmailServicoFake implements NotificadorEmprestimo { @Override public void notificar(String msg) { System.out.println(\"[SIMULADO] \" + msg); // não envia e-mail de verdade em dev } } @Service @Profile(\"prod\") public class EmailServicoReal implements NotificadorEmprestimo { @Override public void notificar(String msg) { /* SMTP real */ } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"annotation\">@Profile</span>(<span class=\"str\">\"dev\"</span>)\n<span class=\"kw\">public class</span> <span class=\"cls\">EmailServiceFake</span> <span class=\"kw\">implements</span> <span class=\"cls\">NotifierLoan</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">notify</span>(<span class=\"kw\">String</span> msg) {\n        System.out.println(<span class=\"str\">\"[SIMULADO] \"</span> + msg); <span class=\"com\">// não envia e-mail de verdade em dev</span>\n    }\n}\n\n<span class=\"annotation\">@Service</span>\n<span class=\"annotation\">@Profile</span>(<span class=\"str\">\"prod\"</span>)\n<span class=\"kw\">public class</span> <span class=\"cls\">EmailServiceReal</span> <span class=\"kw\">implements</span> <span class=\"cls\">NotifierLoan</span> {\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">notify</span>(<span class=\"kw\">String</span> msg) { <span class=\"com\">/* SMTP real */</span> }\n}","caption":"Exemplo executável de spring-profiles.","explanation":["Configuração por arquivo/profile organiza propriedades, mas secrets continuam vindo do ambiente.","O valor ativo deve ser verificável por teste ou log seguro de metadado, não do secret."],"commonMistakes":["Logar todas as propriedades","Assumir profile padrão em produção"]},{"id":"spring-profiles-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Profiles são como trocar o cenário de um teatro sem trocar o roteiro (o código de negócio continua o mesmo) — em desenvolvimento, o \"ator\" que faz o papel de <code>NotificadorEmprestimo</code> é um dublê que só finge enviar e-mail; em produção, entra o ator principal que faz a ligação de verdade. A peça (a lógica de negócio da <code>Biblioteca</code>, capítulo 22) nunca sabe a diferença, porque só conhece a interface.</div>","fidelityText":"Profiles são como trocar o cenário de um teatro sem trocar o roteiro (o código de negócio continua o mesmo) — em desenvolvimento, o \"ator\" que faz o papel de NotificadorEmprestimo é um dublê que só finge enviar e-mail; em produção, entra o ator principal que faz a ligação de verdade. A peça (a lógica de negócio da Biblioteca, capítulo 22) nunca sabe a diferença, porque só conhece a interface."},{"id":"spring-profiles-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Repare que isso é literalmente <strong>Strategy Pattern</strong> (capítulo 23) combinado com <strong>injeção de dependência</strong> (capítulo 22) — o <code>@Profile</code> só decide, em tempo de inicialização, qual implementação concreta o Spring vai registrar como o bean daquela interface. Nenhum conceito novo, apenas uma nova forma de acionar padrões que você já domina profundamente.</div>","fidelityText":"Repare que isso é literalmente Strategy Pattern (capítulo 23) combinado com injeção de dependência (capítulo 22) — o @Profile só decide, em tempo de inicialização, qual implementação concreta o Spring vai registrar como o bean daquela interface. Nenhum conceito novo, apenas uma nova forma de acionar padrões que você já domina profundamente."},{"id":"spring-profiles-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Múltiplos profiles ativos e grupos</h2>","fidelityText":"Múltiplos profiles ativos e grupos"},{"id":"spring-profiles-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Profiles não são mutuamente exclusivos — dá para ativar mais de um ao mesmo tempo, e o Spring aplica as properties de todos (em caso de conflito, o último profile listado vence):</p>","fidelityText":"Profiles não são mutuamente exclusivos — dá para ativar mais de um ao mesmo tempo, e o Spring aplica as properties de todos (em caso de conflito, o último profile listado vence):"},{"id":"spring-profiles-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"# ativa \"prod\" e \"eu-west\" juntos:\nSPRING_PROFILES_ACTIVE=prod,eu-west\n\n# application.properties -- agrupa profiles que sempre andam juntos:\nspring.profiles.group.producao=prod,metrics,tracing\n# ativar só \"producao\" já liga os 3 profiles do grupo","fidelityText":"# ativa \"prod\" e \"eu-west\" juntos: SPRING_PROFILES_ACTIVE=prod,eu-west # application.properties -- agrupa profiles que sempre andam juntos: spring.profiles.group.producao=prod,metrics,tracing # ativar só \"producao\" já liga os 3 profiles do grupo","highlightedHtml":"<span class=\"com\"># ativa \"prod\" e \"eu-west\" juntos:</span>\nSPRING_PROFILES_ACTIVE=prod,eu-west\n\n<span class=\"com\"># application.properties -- agrupa profiles que sempre andam juntos:</span>\nspring.profiles.group.producao=prod,metrics,tracing\n<span class=\"com\"># ativar só \"producao\" já liga os 3 profiles do grupo</span>","caption":"Exemplo executável de spring-profiles.","explanation":["Profiles não são exclusivos: ativar mais de um aplica as properties de todos, com prioridade para o último da lista em conflito.","Profile groups nomeiam um conjunto que sempre anda junto, evitando repetir a lista inteira em todo deploy."],"commonMistakes":["Assumir que só um profile pode estar ativo por vez","Duplicar a lista de profiles em cada ambiente em vez de nomear um grupo"]},{"id":"spring-profiles-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Profile groups (<code>spring.profiles.group.*</code>) existem porque, na prática, \"prod\" quase nunca aparece sozinho — ele quase sempre vem acompanhado de \"metrics\" e \"tracing\" ativados juntos. Em vez de lembrar de listar os três toda vez no deploy (capítulo 60), você declara o grupo uma vez e ativa só o nome dele.</div>","fidelityText":"Profile groups (spring.profiles.group.*) existem porque, na prática, \"prod\" quase nunca aparece sozinho — ele quase sempre vem acompanhado de \"metrics\" e \"tracing\" ativados juntos. Em vez de lembrar de listar os três toda vez no deploy (capítulo 60), você declara o grupo uma vez e ativa só o nome dele."},{"id":"spring-profiles-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Testando qual bean cada profile realmente registra</h2>","fidelityText":"Testando qual bean cada profile realmente registra"},{"id":"spring-profiles-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"@SpringBootTest\n@ActiveProfiles(\"dev\")\nclass NotifierLoanDevTest {\n\n    @Autowired\n    private NotifierLoan notifier;\n\n    @Test\n    void profileDevRegistersServiceFake() {\n        assertThat(notifier).isInstanceOf(EmailServiceFake.class); // prova em teste, não só leitura de código\n    }\n}","fidelityText":"@SpringBootTest @ActiveProfiles(\"dev\") class NotificadorEmprestimoDevTest { @Autowired private NotificadorEmprestimo notificador; @Test void profileDevRegistraServicoFake() { assertThat(notificador).isInstanceOf(EmailServicoFake.class); // prova em teste, não só leitura de código } }","highlightedHtml":"<span class=\"annotation\">@SpringBootTest</span>\n<span class=\"annotation\">@ActiveProfiles</span>(<span class=\"str\">\"dev\"</span>)\n<span class=\"kw\">class</span> <span class=\"cls\">NotifierLoanDevTest</span> {\n\n    <span class=\"annotation\">@Autowired</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">NotifierLoan</span> notifier;\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">profileDevRegistersServiceFake</span>() {\n        assertThat(notifier).isInstanceOf(EmailServiceFake.class); <span class=\"com\">// prova em teste, não só leitura de código</span>\n    }\n}","caption":"Exemplo executável de spring-profiles.","explanation":["@ActiveProfiles ativa o profile só para aquela classe de teste, sem depender de variável de ambiente do CI.","O teste prova em código, antes do deploy, qual bean concreto cada profile realmente registra."],"commonMistakes":["Confiar só na leitura do código para saber qual bean está ativo","Testar o comportamento do bean sem testar qual profile o registrou"]},{"id":"spring-profiles-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Sem esse teste, a garantia de \"profile certo em produção\" é só confiança.</b> <code>@ActiveProfiles</code> ativa o profile só para aquela classe de teste, sem depender de nenhuma variável de ambiente do CI (capítulo 58) — é a forma de provar em código, antes do deploy, que \"prod\" nunca vai acidentalmente registrar o <code>EmailServicoFake</code>.</div>","fidelityText":"Sem esse teste, a garantia de \"profile certo em produção\" é só confiança. @ActiveProfiles ativa o profile só para aquela classe de teste, sem depender de nenhuma variável de ambiente do CI (capítulo 58) — é a forma de provar em código, antes do deploy, que \"prod\" nunca vai acidentalmente registrar o EmailServicoFake."},{"id":"spring-profiles-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Nunca deixe um profile de teste/dev ativo acidentalmente em produção — sempre confirme explicitamente qual profile está ativo no deploy (capítulo 60).</li>\n          <li>Profiles não substituem secrets (capítulo 57) — a diferença entre \"qual comportamento\" (profile) e \"qual credencial\" (variável de ambiente) deveria continuar separada.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Nunca deixe um profile de teste/dev ativo acidentalmente em produção — sempre confirme explicitamente qual profile está ativo no deploy (capítulo 60). Profiles não substituem secrets (capítulo 57) — a diferença entre \"qual comportamento\" (profile) e \"qual credencial\" (variável de ambiente) deveria continuar separada."},{"id":"spring-profiles-exercise-16","type":"exercise","authorship":"legacy-preserved","title":"Exercício 73.1 — Notificador por ambiente","prompt":"Crie EmailServicoFake (profile dev, só imprime no console) e EmailServicoReal (profile prod, simula envio real), ambos implementando NotificadorEmprestimo. Confirme que a Biblioteca (capítulo 22) continua funcionando sem nenhuma mudança de código, independente de qual profile está ativo.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 73.1 — Notificador por ambientemédio Crie EmailServicoFake (profile dev, só imprime no console) e EmailServicoReal (profile prod, simula envio real), ambos implementando NotificadorEmprestimo. Confirme que a Biblioteca (capítulo 22) continua funcionando sem nenhuma mudança de código, independente de qual profile está ativo. Ver solução Escreva dois testes de contexto com profiles diferentes e confirme qual bean existe em cada um. Faça o contexto falhar quando nenhum profile fornecer a dependência e explique por que credencial não deve ficar em application-prod.yml. Como alternativa, use propriedade condicional e compare com @Profile.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 73.1 — Notificador por ambiente</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Crie <code>EmailServicoFake</code> (profile <code>dev</code>, só imprime no console) e <code>EmailServicoReal</code> (profile <code>prod</code>, simula envio real), ambos implementando <code>NotificadorEmprestimo</code>. Confirme que a <code>Biblioteca</code> (capítulo 22) continua funcionando sem nenhuma mudança de código, independente de qual profile está ativo.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Escreva dois testes de contexto com profiles diferentes e confirme qual bean existe em cada um. Faça o contexto falhar quando nenhum profile fornecer a dependência e explique por que credencial não deve ficar em <code>application-prod.yml</code>. Como alternativa, use propriedade condicional e compare com <code>@Profile</code>.</p>\n        </div>\n      </div>"},{"id":"spring-profiles-quiz","type":"quiz","authorship":"authored","conceptId":"spring-profile-config-activation","prompt":"Qual é um bom uso de Spring Profile?","options":[{"id":"sp-a","label":"Selecionar uma implementação/configuração de ambiente durante o bootstrap da aplicação.","correct":true,"explanation":"Profile muda composição/configuração por ambiente ou modo de execução."},{"id":"sp-b","label":"Guardar a senha de produção em application-prod.yml no Git.","correct":false,"explanation":"Profile não torna secret versionado seguro."},{"id":"sp-c","label":"Liberar funcionalidade para um usuário específico em runtime.","correct":false,"explanation":"Isso é autorização/feature flag, não profile."}]}],"resources":[{"id":"spring-profiles-reference","type":"official-docs","title":"Spring Boot Profiles","url":"https://docs.spring.io/spring-boot/reference/features/profiles.html","reinforces":"Ativação, grupos e uso de profiles no Spring Boot.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-conditional-beans","type":"official-docs","title":"Spring Framework: Bean definition profiles","url":"https://docs.spring.io/spring-framework/reference/core/beans/environment.html#beans-definition-profiles","reinforces":"Profiles na criação de beans e Environment abstraction.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The spring profiles component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to spring profiles. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible spring profiles failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"# application.properties (compartilhado por todos os profiles)","instruction":"The spring profiles component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible spring profiles failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mini-deploy-observavel","moduleId":"production-delivery","order":6,"title":"Mini-projeto: deploy reproduzível e observável","summary":"Leve um dos projetos Spring até um ambiente remoto. O objetivo não é “ficou online”; é conseguir implantar, observar, diagnosticar e reverter.","objectives":["Entregar deploy com evidências reproduzíveis","Executar falha controlada e rollback","Provar health, logs e restore","Registrar runbook mínimo de operação"],"whyItExists":"O projeto fecha produção obrigando o aluno a provar o que costuma ficar implícito: URL, versão, commit, imagem, secrets, health, logs, rollback e restore. Sem evidência, o deploy é só esperança com domínio configurado.","prerequisiteChapterIds":["deploy-backend","spring-profiles","hardening-producao"],"conceptIds":["definition-of-done","experimento-de-falha","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["production-evidence-checklist"],"usedConceptIds":["deploy-rollback-strategy","health-readiness-liveness","recovery-backup-restore-drill","paas-vps-deployment-boundary","spring-profile-config-activation"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"mini-deploy-observavel-intuition","type":"intuition","authorship":"authored","title":"Deploy bom deixa rastros que outra pessoa consegue repetir","body":"A meta não é “subiu aqui”. A meta é qualquer pessoa do time conseguir apontar commit, artefato, ambiente, health, logs, rollback e restore com passos claros.","analogyLimit":"Entrega de pacote ajuda, mas software em produção continua mudando enquanto usuários e dependências estão vivos."},{"id":"mini-deploy-observavel-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>produção real</b></div><div class=\"time-est\">Tempo: <b>12–24 horas</b></div><div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#deploy-backend\">Deploy do back-end</a></div></div>","fidelityText":"Objetivo: produção realTempo: 12–24 horasPré-requisito: Deploy do back-end"},{"id":"mini-deploy-observavel-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Leve um dos projetos Spring até um ambiente remoto. O objetivo não é “ficou online”; é conseguir implantar, observar, diagnosticar e reverter.</p>","fidelityText":"Leve um dos projetos Spring até um ambiente remoto. O objetivo não é “ficou online”; é conseguir implantar, observar, diagnosticar e reverter."},{"id":"mini-deploy-observavel-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Definition of done</h2>","fidelityText":"Definition of done"},{"id":"mini-deploy-observavel-checklist-4","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-deploy-observavel-checklist-0","label":"Imagem multi-stage executada por usuário sem privilégios."},{"id":"mini-deploy-observavel-checklist-1","label":"Banco, aplicação e dependências configurados por ambiente."},{"id":"mini-deploy-observavel-checklist-2","label":"Health checks distinguem processo vivo de serviço pronto."},{"id":"mini-deploy-observavel-checklist-3","label":"Logs estruturados carregam correlação sem expor segredos."},{"id":"mini-deploy-observavel-checklist-4","label":"Pipeline executa testes, gera artefato imutável e impede deploy quebrado."},{"id":"mini-deploy-observavel-checklist-5","label":"Procedimento de rollback testado e cronometrado."}]},{"id":"mini-deploy-observavel-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Experimento de falha</h2>","fidelityText":"Experimento de falha"},{"id":"mini-deploy-observavel-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Interrompa o banco, provoque latência, envie entrada inválida e reinicie uma instância. Para cada evento, registre sinal observado, hipótese, diagnóstico e ação corretiva.</p>","fidelityText":"Interrompa o banco, provoque latência, envie entrada inválida e reinicie uma instância. Para cada evento, registre sinal observado, hipótese, diagnóstico e ação corretiva."},{"id":"mini-deploy-observavel-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\"><h2>Portfólio honesto</h2><ul><li>README contém arquitetura, decisões e limitações reais.</li><li>Demonstração inclui falha e recuperação, não apenas caminho feliz.</li><li>Métricas e logs respondem perguntas concretas sobre o sistema.</li></ul></div>","fidelityText":"Portfólio honestoREADME contém arquitetura, decisões e limitações reais.Demonstração inclui falha e recuperação, não apenas caminho feliz.Métricas e logs respondem perguntas concretas sobre o sistema."},{"id":"mini-deploy-observavel-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-deploy-observavel:0","type":"quiz","authorship":"legacy-preserved","conceptId":"o-que-demonstra-que-um-backup-e-confiavel","prompt":"O que demonstra que um backup é confiável?","options":[{"id":"mini-deploy-observavel:0:option:0","label":"Uma restauração periódica verificada contra RPO e RTO.","correct":true,"explanation":"Confiabilidade de backup só é demonstrada quando a restauração é executada e comparada aos limites de perda e tempo combinados."},{"id":"mini-deploy-observavel:0:option:1","label":"O job de backup terminar com exit code zero.","correct":false,"explanation":"Exit code zero prova que o job terminou, mas não prova que o arquivo é restaurável, íntegro ou suficiente."},{"id":"mini-deploy-observavel:0:option:2","label":"O arquivo existir na mesma máquina do banco.","correct":false,"explanation":"Arquivo no mesmo host do banco pode desaparecer junto com a falha que deveria ser recuperada."}],"sourceIndex":9},{"id":"mini-deploy-observavel:1","type":"quiz","authorship":"legacy-preserved","conceptId":"um-endpoint-health-sempre-retorna-200-enquanto-o-processo-java-esta-roda","prompt":"Um endpoint /health sempre retorna 200 enquanto o processo Java está rodando, mesmo que a conexão com o banco esteja indisponível. Qual problema isso causa?","options":[{"id":"mini-deploy-observavel:1:option:0","label":"O orquestrador continua roteando tráfego para uma instância que não consegue atender requisições de verdade, porque liveness (processo vivo) foi confundido com readiness (pronto para servir).","correct":true,"explanation":"Readiness precisa refletir dependências críticas; um health check que ignora o banco continua roteando tráfego para uma instância que não consegue servir."},{"id":"mini-deploy-observavel:1:option:1","label":"Nenhum problema, já que o processo está de fato em execução.","correct":false,"explanation":"O processo estar vivo (liveness) não implica que ele consiga completar uma requisição real (readiness)."},{"id":"mini-deploy-observavel:1:option:2","label":"O problema só existe se o banco cair durante o horário comercial.","correct":false,"explanation":"O problema é estrutural no health check, independente do horário em que a dependência falha."}],"sourceIndex":10},{"id":"mini-deploy-observavel:2","type":"quiz","authorship":"legacy-preserved","conceptId":"um-deploy-novo-introduz-um-bug-critico-em-producao-o-time-nao-tem-um-pro","prompt":"Um deploy novo introduz um bug crítico em produção. O time não tem um procedimento de rollback testado, só a expectativa de que \"dá pra reverter o commit\". O que falta?","options":[{"id":"mini-deploy-observavel:2:option:0","label":"Um procedimento de rollback ensaiado e cronometrado antes da emergência -- reverter um commit no Git não implica reverter automaticamente o artefato em produção nem o estado do banco.","correct":true,"explanation":"Só um rollback ensaiado e cronometrado prova que a reversão funciona sob pressão, sem depender de descobrir os passos durante o incidente."},{"id":"mini-deploy-observavel:2:option:1","label":"Nada; git revert resolve qualquer situação de produção automaticamente.","correct":false,"explanation":"git revert desfaz um commit no repositório; não reimplanta artefato, não reverte migrations aplicadas nem estado de dados."},{"id":"mini-deploy-observavel:2:option:2","label":"Só falta escrever um post-mortem depois do incidente.","correct":false,"explanation":"Post-mortem é útil depois, mas não substitui ter um caminho de rollback pronto durante o incidente."}],"sourceIndex":11},{"id":"mini-deploy-observavel:3","type":"quiz","authorship":"legacy-preserved","conceptId":"os-logs-estruturados-da-aplicacao-incluem-o-header-authorization-complet","prompt":"Os logs estruturados da aplicação incluem o header Authorization completo de cada requisição, para facilitar o debugging. O que há de errado nisso?","options":[{"id":"mini-deploy-observavel:3:option:0","label":"Vaza credenciais (tokens) para o sistema de logs, que costuma ter acesso mais amplo e retenção mais longa do que o apropriado para segredos.","correct":true,"explanation":"Um token de autenticação em texto claro no log é uma credencial vazada para qualquer sistema/pessoa com acesso aos logs."},{"id":"mini-deploy-observavel:3:option:1","label":"Nada, desde que os logs fiquem armazenados no mesmo servidor da aplicação.","correct":false,"explanation":"Local de armazenamento não muda o fato de que a credencial está exposta em texto claro fora do fluxo de autenticação."},{"id":"mini-deploy-observavel:3:option:2","label":"É um problema só quando o token pertence a um usuário administrador.","correct":false,"explanation":"Qualquer token vazado é um risco, não só o de um usuário administrador -- outros tokens ainda concedem acesso indevido."}],"sourceIndex":12},{"id":"mini-deploy-observavel-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"mini-deploy-observavel-exercise-runbook","type":"exercise","authorship":"authored","title":"Antes de implantar: runbook de falha e rollback","prompt":"Antes de fazer o primeiro deploy real, escreva um runbook curto respondendo: (1) qual comando ou URL prova que o serviço está \"ready\" (não só \"alive\"); (2) quais passos exatos revertem para a versão anterior, e quanto tempo isso leva quando ensaiado; (3) o que aparece nos logs quando uma dependência (banco, API externa) fica indisponível, e como isso é diferente de um erro de código; (4) qual RPO (perda de dados aceitável) e RTO (tempo de recuperação aceitável) você está assumindo para o restore de backup, e como isso foi verificado.","difficulty":"advanced","criteria":["A resposta 1 distingue explicitamente liveness de readiness, com um sinal verificável para cada um.","A resposta 2 descreve um rollback já ensaiado, com tempo medido, não apenas planejado em teoria.","A resposta 3 mostra um log com contexto suficiente para diagnóstico, sem vazar segredos.","A resposta 4 declara RPO/RTO como números concretos e cita a evidência do restore que os comprova."]},{"id":"mini-deploy-observavel-project","type":"project","authorship":"authored","title":"Deploy reproduzível e observável","brief":"Publique uma API pequena com banco, secrets, health, logs e runbook. Faça um experimento de falha controlada e registre evidências de recuperação.","requirements":["Artefato ou imagem ligado a commit específico","Secrets configurados fora do repositório e da imagem","Health/readiness verificáveis por URL ou comando","Rollback ou redeploy anterior documentado","Restore drill com evidência e limite de perda/tempo"],"guidance":"bounded","acceptanceCriteria":["Outra pessoa consegue reproduzir o deploy seguindo o README/runbook.","O relatório inclui commit, versão, URL, health, logs e rollback.","Backup foi restaurado em ambiente seguro ou simulado com evidência verificável."],"knowledgeMatrix":[{"requirement":"Artefato rastreável","conceptIds":["artifact-image-provenance","ci-pipeline-gate"],"chapterIds":["cicd","dockerfile"],"expectedEvidence":"Commit, workflow e imagem/tag/digest aparecem no relatório."},{"requirement":"Configuração segura","conceptIds":["runtime-secret-boundary","environment-config-contract"],"chapterIds":["secrets","spring-profiles"],"expectedEvidence":"Secrets não aparecem no Git, build, imagem ou logs."},{"requirement":"Operação recuperável","conceptIds":["health-readiness-liveness","deploy-rollback-strategy","recovery-backup-restore-drill"],"chapterIds":["hardening-producao","deploy-backend"],"expectedEvidence":"Falha controlada, health/logs e restore/rollback documentados."}],"englishSpecification":{"title":"Observable deployment evidence","brief":"Deploy a small backend service and provide reproducible operational evidence.","requirements":["Link the deployed version to a commit and artifact.","Keep secrets out of source control and build artifacts.","Expose a useful health/readiness signal.","Document rollback and restore evidence."],"acceptanceCriteria":["A reviewer can reproduce the deployment steps.","Operational evidence distinguishes hypothesis from verified behavior."]}},{"id":"mini-deploy-observavel-quiz","type":"quiz","authorship":"authored","conceptId":"production-evidence-checklist","prompt":"Qual evidência torna um deploy revisável?","options":[{"id":"mdo-a","label":"Commit, artefato, ambiente, health, logs e passos de rollback/restore reproduzíveis.","correct":true,"explanation":"Esses itens conectam mudança, execução e recuperação."},{"id":"mdo-b","label":"Um print da página inicial funcionando uma vez.","correct":false,"explanation":"Print não prova versão, config, health, rollback nem recuperação."},{"id":"mdo-c","label":"A frase “funcionou na minha máquina”.","correct":false,"explanation":"Produção exige evidência compartilhável, não memória local."}]}],"resources":[{"id":"sre-workbook-monitoring","type":"reference","title":"Google SRE Workbook: Monitoring Distributed Systems","url":"https://sre.google/workbook/monitoring/","reinforces":"Sinais, alertas e evidência operacional.","language":"en","publisher":"Google SRE","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-actuator-production-ready","type":"official-docs","title":"Spring Boot Actuator: Production-ready Features","url":"https://docs.spring.io/spring-boot/reference/actuator/index.html","reinforces":"Endpoints operacionais e sinais de produção em Spring Boot.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The observable deployment project component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to observable deployment project. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible observable deployment project failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// Observe how names reveal the observable deployment project contract.","instruction":"The observable deployment project component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible observable deployment project failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["readiness-diferente-de-liveness","rollback-ensaiado-e-cronometrado","logs-sem-vazamento-de-segredo","restore-com-rpo-e-rto-verificados","evidencia-reproduzivel-de-producao"],"evidenceBlocks":{"readiness-diferente-de-liveness":["mini-deploy-observavel:1","mini-deploy-observavel-checklist-4","mini-deploy-observavel-exercise-runbook"],"rollback-ensaiado-e-cronometrado":["mini-deploy-observavel:2","mini-deploy-observavel-checklist-4","mini-deploy-observavel-project"],"logs-sem-vazamento-de-segredo":["mini-deploy-observavel:3","mini-deploy-observavel-checklist-4"],"restore-com-rpo-e-rto-verificados":["mini-deploy-observavel:0","mini-deploy-observavel-exercise-runbook","mini-deploy-observavel-project"],"evidencia-reproduzivel-de-producao":["mini-deploy-observavel-content-13","mini-deploy-observavel-quiz","mini-deploy-observavel-project"]},"primarySources":["Google SRE Workbook: Monitoring Distributed Systems -- https://sre.google/workbook/monitoring/","Spring Boot Actuator: Production-ready Features -- https://docs.spring.io/spring-boot/reference/actuator/index.html"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"threads","moduleId":"concurrency-network-tui","order":0,"title":"Threads & concorrência","summary":"Uma thread é um fluxo independente de execução dentro do mesmo processo. Múltiplas threads compartilham a mesma memória (heap) — o que é ótimo para performance, mas é exatamente essa memória compartilhada que causa a maioria dos bugs de concorrência.","objectives":["Criar threads entendendo ciclo e custo","Identificar race condition em estado compartilhado","Aplicar synchronized com escopo consciente","Escolher AtomicInteger/utilitários concorrentes quando cabível"],"whyItExists":"Depois de streams e efeitos colaterais, o aluno pode ver o que muda quando duas execuções tocam o mesmo estado ao mesmo tempo. Threads entram como problema de interleaving, não como truque para deixar tudo rápido.","prerequisiteChapterIds":["streams"],"conceptIds":["criando-threads-duas-formas","o-problema-race-condition","synchronized-exclusao-mutua","a-alternativa-moderna-java-util-concurrent"],"introducedConceptIds":["thread-lifecycle-scheduler","race-condition-atomicity","monitor-synchronized-mutual-exclusion","atomic-concurrent-utilities"],"usedConceptIds":["stream-nao-interferencia"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"threads-intuition","type":"intuition","authorship":"authored","title":"Concorrência é sobre interleavings possíveis","body":"Quando duas threads executam, a ordem real das leituras e escritas pode mudar a cada execução. O bug nasce quando seu raciocínio assume uma ordem que o escalonador não prometeu.","analogyLimit":"Duas pessoas editando o mesmo quadro ajuda, mas CPU, cache, memória e scheduler impõem regras próprias."},{"id":"threads-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#streams\">13 · Lambdas &amp; Streams</a></div>\n      </div>","fidelityText":"Dificuldade: Avançado Pré-requisito: 13 · Lambdas & Streams"},{"id":"threads-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Uma <strong>thread</strong> é um fluxo independente de execução dentro do mesmo processo. Múltiplas threads compartilham a mesma memória (heap) — o que é ótimo para performance, mas é exatamente essa memória compartilhada que causa a maioria dos bugs de concorrência.</p>","fidelityText":"Uma thread é um fluxo independente de execução dentro do mesmo processo. Múltiplas threads compartilham a mesma memória (heap) — o que é ótimo para performance, mas é exatamente essa memória compartilhada que causa a maioria dos bugs de concorrência."},{"id":"threads-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Criando threads: duas formas</h2>","fidelityText":"Criando threads: duas formas"},{"id":"threads-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"// forma 1: estender Thread (menos flexível -- Java só permite herança simples)\nclass MyThread extends Thread {\n    @Override\n    public void run() { System.out.println(\"Rodando in: \" + Thread.currentThread().getName()); }\n}\nnew MyThread().start(); // start(), NUNCA run() diretamente!\n\n// forma 2: implementar Runnable (preferida -- permite herdar de outra classe)\nRunnable task = () -> System.out.println(\"Rodando in: \" + Thread.currentThread().getName());\nnew Thread(task).start();","fidelityText":"// forma 1: estender Thread (menos flexível -- Java só permite herança simples) class MinhaThread extends Thread { @Override public void run() { System.out.println(\"Rodando em: \" + Thread.currentThread().getName()); } } new MinhaThread().start(); // start(), NUNCA run() diretamente! // forma 2: implementar Runnable (preferida -- permite herdar de outra classe) Runnable tarefa = () -> System.out.println(\"Rodando em: \" + Thread.currentThread().getName()); new Thread(tarefa).start();","highlightedHtml":"<span class=\"com\">// forma 1: estender Thread (menos flexível -- Java só permite herança simples)</span>\n<span class=\"kw\">class</span> <span class=\"cls\">MyThread</span> <span class=\"kw\">extends</span> Thread {\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">run</span>() { System.out.println(<span class=\"str\">\"Rodando in: \"</span> + Thread.currentThread().getName()); }\n}\n<span class=\"kw\">new</span> <span class=\"cls\">MyThread</span>().start(); <span class=\"com\">// start(), NUNCA run() diretamente!</span>\n\n<span class=\"com\">// forma 2: implementar Runnable (preferida -- permite herdar de outra classe)</span>\nRunnable task = () -&gt; System.out.println(<span class=\"str\">\"Rodando in: \"</span> + Thread.currentThread().getName());\n<span class=\"kw\">new</span> Thread(task).start();","caption":"Exemplo executável de threads.","explanation":["Extender Thread e implementar Runnable representam formas de colocar trabalho em execução concorrente.","Runnable separa tarefa da política de execução e evita gastar a única herança da classe."],"commonMistakes":["Chamar run() esperando nova thread","Criar thread para tarefa trivial sem medir custo"]},{"id":"threads-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>start() vs run():</b> chamar <code>.run()</code> diretamente executa o código na <strong>thread atual</strong>, como uma chamada de método comum — nenhuma thread nova é criada! Só <code>.start()</code> de fato agenda uma nova thread na JVM. Esse é um erro clássico de quem está aprendendo.</div>","fidelityText":"start() vs run(): chamar .run() diretamente executa o código na thread atual, como uma chamada de método comum — nenhuma thread nova é criada! Só .start() de fato agenda uma nova thread na JVM. Esse é um erro clássico de quem está aprendendo."},{"id":"threads-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>O problema: race condition</h2>","fidelityText":"O problema: race condition"},{"id":"threads-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Counter {\n    private int value = 0;\n    public void increment() { value++; } // NÃO é atômico! é ler + somar + gravar\n    public int getValue() { return value; }\n}\n\n// duas threads chamando incrementar() 100_000 vezes cada, ao mesmo tempo,\n// frequentemente NÃO resulta em 200_000 -- porque \"valor++\" não é uma\n// operação única: threads podem ler o mesmo valor antes de uma delas gravar.","fidelityText":"public class Contador { private int valor = 0; public void incrementar() { valor++; } // NÃO é atômico! é ler + somar + gravar public int getValor() { return valor; } } // duas threads chamando incrementar() 100_000 vezes cada, ao mesmo tempo, // frequentemente NÃO resulta em 200_000 -- porque \"valor++\" não é uma // operação única: threads podem ler o mesmo valor antes de uma delas gravar.","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Counter</span> {\n    <span class=\"kw\">private int</span> value = 0;\n    <span class=\"kw\">public void</span> <span class=\"fn\">increment</span>() { value++; } <span class=\"com\">// NÃO é atômico! é ler + somar + gravar</span>\n    <span class=\"kw\">public int</span> <span class=\"fn\">getValue</span>() { <span class=\"kw\">return</span> value; }\n}\n\n<span class=\"com\">// duas threads chamando incrementar() 100_000 vezes cada, ao mesmo tempo,\n// frequentemente NÃO resulta em 200_000 -- porque \"valor++\" não é uma\n// operação única: threads podem ler o mesmo valor antes de uma delas gravar.</span>","caption":"Exemplo executável de threads.","explanation":["increment() sem proteção expõe race condition porque value++ não é atômico.","O getter pode observar valor perdido após interleavings concorrentes."],"commonMistakes":["Confiar em teste que passa uma vez","Proteger só leitura ou só escrita"]},{"id":"threads-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">\"valor++\" parece uma instrução única, mas o bytecode faz três passos: ler o valor atual, somar 1, gravar o resultado. Se a Thread A lê <code>valor = 5</code> e, antes de gravar <code>6</code>, a Thread B também lê <code>valor = 5</code> e grava <code>6</code>, o incremento da Thread A se perde quando ela grava por cima com o mesmo <code>6</code>. Esse é o bug de concorrência mais comum que existe, e é justamente por isso que <code>synchronized</code> e as classes <code>Atomic*</code> existem.</div>","fidelityText":"\"valor++\" parece uma instrução única, mas o bytecode faz três passos: ler o valor atual, somar 1, gravar o resultado. Se a Thread A lê valor = 5 e, antes de gravar 6, a Thread B também lê valor = 5 e grava 6, o incremento da Thread A se perde quando ela grava por cima com o mesmo 6. Esse é o bug de concorrência mais comum que existe, e é justamente por isso que synchronized e as classes Atomic* existem."},{"id":"threads-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>synchronized — exclusão mútua</h2>","fidelityText":"synchronized — exclusão mútua"},{"id":"threads-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Counter {\n    private int value = 0;\n    public synchronized void increment() { value++; } // só uma thread por vez executa isto\n    public synchronized int getValue() { return value; }\n}\n\n// ou um bloco sincronizado sobre um objeto específico -- mais granular:\nprivate final Object locks = new Object();\nvoid operationCritical() {\n    synchronized (locks) {\n        // só uma thread por vez entra aqui\n    }\n}","fidelityText":"public class Contador { private int valor = 0; public synchronized void incrementar() { valor++; } // só uma thread por vez executa isto public synchronized int getValor() { return valor; } } // ou um bloco sincronizado sobre um objeto específico -- mais granular: private final Object trava = new Object(); void operacaoCritica() { synchronized (trava) { // só uma thread por vez entra aqui } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Counter</span> {\n    <span class=\"kw\">private int</span> value = 0;\n    <span class=\"kw\">public synchronized void</span> <span class=\"fn\">increment</span>() { value++; } <span class=\"com\">// só uma thread por vez executa isto</span>\n    <span class=\"kw\">public synchronized int</span> <span class=\"fn\">getValue</span>() { <span class=\"kw\">return</span> value; }\n}\n\n<span class=\"com\">// ou um bloco sincronizado sobre um objeto específico -- mais granular:</span>\n<span class=\"kw\">private final</span> Object locks = <span class=\"kw\">new</span> Object();\n<span class=\"kw\">void</span> <span class=\"fn\">operationCritical</span>() {\n    <span class=\"kw\">synchronized</span> (locks) {\n        <span class=\"com\">// só uma thread por vez entra aqui</span>\n    }\n}","caption":"Exemplo executável de threads.","explanation":["synchronized no método usa o monitor do objeto para excluir execução simultânea naquele escopo.","Ele protege corretamente apenas se todas as rotas de acesso ao estado usam o mesmo monitor."],"commonMistakes":["Sincronizar objetos diferentes","Aumentar demais a região crítica"]},{"id":"threads-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>A alternativa moderna: java.util.concurrent</h2>","fidelityText":"A alternativa moderna: java.util.concurrent"},{"id":"threads-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"// AtomicInteger: incremento atômico sem precisar de synchronized manual\nimport java.util.concurrent.atomic.AtomicInteger;\nAtomicInteger counter = new AtomicInteger(0);\ncounter.incrementAndGet(); // thread-safe de verdade\n\n// ExecutorService: um pool de threads gerenciado, em vez de \"new Thread()\" solto\nimport java.util.concurrent.*;\nExecutorService pool = Executors.newFixedThreadPool(4);\nFuture<Integer> result = pool.submit(() -> {\n    return 2 + 2; // tarefa que retorna um valor\n});\nSystem.out.println(result.get()); // bloqueia até o resultado ficar pronto\npool.shutdown(); // sempre encerre o pool quando não precisar mais dele","fidelityText":"// AtomicInteger: incremento atômico sem precisar de synchronized manual import java.util.concurrent.atomic.AtomicInteger; AtomicInteger contador = new AtomicInteger(0); contador.incrementAndGet(); // thread-safe de verdade // ExecutorService: um pool de threads gerenciado, em vez de \"new Thread()\" solto import java.util.concurrent.*; ExecutorService pool = Executors.newFixedThreadPool(4); Future<Integer> resultado = pool.submit(() -> { return 2 + 2; // tarefa que retorna um valor }); System.out.println(resultado.get()); // bloqueia até o resultado ficar pronto pool.shutdown(); // sempre encerre o pool quando não precisar mais dele","highlightedHtml":"<span class=\"com\">// AtomicInteger: incremento atômico sem precisar de synchronized manual</span>\n<span class=\"kw\">import</span> java.util.concurrent.atomic.AtomicInteger;\nAtomicInteger counter = <span class=\"kw\">new</span> AtomicInteger(0);\ncounter.incrementAndGet(); <span class=\"com\">// thread-safe de verdade</span>\n\n<span class=\"com\">// ExecutorService: um pool de threads gerenciado, em vez de \"new Thread()\" solto</span>\n<span class=\"kw\">import</span> java.util.concurrent.*;\nExecutorService pool = Executors.newFixedThreadPool(4);\nFuture&lt;<span class=\"kw\">Integer</span>&gt; result = pool.submit(() -&gt; {\n    <span class=\"kw\">return</span> 2 + 2; <span class=\"com\">// tarefa que retorna um valor</span>\n});\nSystem.out.println(result.get()); <span class=\"com\">// bloqueia até o resultado ficar pronto</span>\npool.shutdown(); <span class=\"com\">// sempre encerre o pool quando não precisar mais dele</span>","caption":"Exemplo executável de threads.","explanation":["AtomicInteger oferece operações atômicas especializadas sem bloco synchronized manual.","É ótimo para contador simples, mas não resolve invariantes compostas entre vários campos."],"commonMistakes":["Usar atomic para regra composta sem transação/lock","Misturar atomic e campo comum para o mesmo estado"]},{"id":"threads-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Regra prática em código moderno:</b> raramente você vai instanciar <code>Thread</code> diretamente em uma aplicação real. O padrão em produção é usar <code>ExecutorService</code> (pool gerenciado) ou, em Java 21+, <em>virtual threads</em> (<code>Thread.ofVirtual()</code>), que são muito mais leves. <code>synchronized</code> continua essencial para entender <em>por que</em> essas abstrações existem.</div>","fidelityText":"Regra prática em código moderno: raramente você vai instanciar Thread diretamente em uma aplicação real. O padrão em produção é usar ExecutorService (pool gerenciado) ou, em Java 21+, virtual threads (Thread.ofVirtual()), que são muito mais leves. synchronized continua essencial para entender por que essas abstrações existem."},{"id":"threads-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Exercício 14.1 — Reproduzindo uma race condition","prompt":"Crie a classe Contador (sem synchronized) do exemplo acima. No main, crie duas threads, cada uma chamando incrementar() 100.000 vezes, use .join() em ambas para esperar terminarem, e imprima getValor(). Rode algumas vezes e observe que o resultado nem sempre é 200.000. Depois adicione synchronized nos métodos e confirme que o resultado passa a ser sempre correto.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 14.1 — Reproduzindo uma race conditiondifícil Crie a classe Contador (sem synchronized) do exemplo acima. No main, crie duas threads, cada uma chamando incrementar() 100.000 vezes, use .join() em ambas para esperar terminarem, e imprima getValor(). Rode algumas vezes e observe que o resultado nem sempre é 200.000. Depois adicione synchronized nos métodos e confirme que o resultado passa a ser sempre correto. Ver solução public class Contador { private int valor = 0; public synchronized void incrementar() { valor++; } public int getValor() { return valor; } } // main: Contador contador = new Contador(); Runnable tarefa = () -> { for (int i = 0; i < 100_000; i++) contador.incrementar(); }; Thread t1 = new Thread(tarefa); Thread t2 = new Thread(tarefa); t1.start(); t2.start(); t1.join(); t2.join(); // espera as duas terminarem antes de continuar System.out.println(contador.getValor()); // 200000, sempre, graças ao synchronized","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 14.1 — Reproduzindo uma race condition</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie a classe <code>Contador</code> (sem <code>synchronized</code>) do exemplo acima. No <code>main</code>, crie duas threads, cada uma chamando <code>incrementar()</code> 100.000 vezes, use <code>.join()</code> em ambas para esperar terminarem, e imprima <code>getValor()</code>. Rode algumas vezes e observe que o resultado nem sempre é 200.000. Depois adicione <code>synchronized</code> nos métodos e confirme que o resultado passa a ser sempre correto.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Counter</span> {\n    <span class=\"kw\">private int</span> value = 0;\n    <span class=\"kw\">public synchronized void</span> <span class=\"fn\">increment</span>() { value++; }\n    <span class=\"kw\">public int</span> <span class=\"fn\">getValue</span>() { <span class=\"kw\">return</span> value; }\n}\n\n<span class=\"com\">// main:</span>\n<span class=\"cls\">Counter</span> counter = <span class=\"kw\">new</span> <span class=\"cls\">Counter</span>();\nRunnable task = () -&gt; { <span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = 0; i &lt; 100_000; i++) counter.increment(); };\n\nThread t1 = <span class=\"kw\">new</span> Thread(task);\nThread t2 = <span class=\"kw\">new</span> Thread(task);\nt1.start(); t2.start();\nt1.join(); t2.join(); <span class=\"com\">// espera as duas terminarem antes de continuar</span>\n\nSystem.out.println(counter.getValue()); <span class=\"com\">// 200000, sempre, graças ao synchronized</span></pre>\n        </div>\n      </div>"},{"id":"threads-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 14.2 — ExecutorService","prompt":"Usando ExecutorService com um pool de 3 threads, submeta 5 tarefas que calculam o quadrado de um número (1 a 5) e retornam o resultado via Future. Colete todos os resultados em uma lista e imprima a soma total. Não esqueça de chamar shutdown() no final.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 14.2 — ExecutorServicemédio Usando ExecutorService com um pool de 3 threads, submeta 5 tarefas que calculam o quadrado de um número (1 a 5) e retornam o resultado via Future. Colete todos os resultados em uma lista e imprima a soma total. Não esqueça de chamar shutdown() no final. Ver solução ExecutorService pool = Executors.newFixedThreadPool(3); List<Future<Integer>> futuros = new ArrayList<>(); for (int i = 1; i <= 5; i++) { final int n = i; futuros.add(pool.submit(() -> n * n)); } int soma = 0; for (Future<Integer> f : futuros) { soma += f.get(); // bloqueia até essa tarefa terminar } System.out.println(\"Soma dos quadrados: \" + soma); // 55 pool.shutdown();","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 14.2 — ExecutorService</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Usando <code>ExecutorService</code> com um pool de 3 threads, submeta 5 tarefas que calculam o quadrado de um número (1 a 5) e retornam o resultado via <code>Future</code>. Colete todos os resultados em uma lista e imprima a soma total. Não esqueça de chamar <code>shutdown()</code> no final.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">ExecutorService pool = Executors.newFixedThreadPool(3);\nList&lt;Future&lt;<span class=\"kw\">Integer</span>&gt;&gt; futuros = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n\n<span class=\"kw\">for</span> (<span class=\"kw\">int</span> i = 1; i &lt;= 5; i++) {\n    <span class=\"kw\">final int</span> n = i;\n    futuros.add(pool.submit(() -&gt; n * n));\n}\n\n<span class=\"kw\">int</span> sum = 0;\n<span class=\"kw\">for</span> (Future&lt;<span class=\"kw\">Integer</span>&gt; f : futuros) {\n    sum += f.get(); <span class=\"com\">// bloqueia até essa tarefa terminar</span>\n}\nSystem.out.println(<span class=\"str\">\"Sum of the quadrados: \"</span> + sum); <span class=\"com\">// 55</span>\npool.shutdown();</pre>\n        </div>\n      </div>"},{"id":"threads-quiz","type":"quiz","authorship":"authored","conceptId":"race-condition-atomicity","prompt":"Por que `value++` pode falhar com múltiplas threads?","options":[{"id":"thr-a","label":"Porque envolve leitura, cálculo e escrita; outra thread pode intercalar entre essas etapas.","correct":true,"explanation":"A operação composta não é atomicamente protegida."},{"id":"thr-b","label":"Porque Java não permite inteiros em threads.","correct":false,"explanation":"O problema é interleaving, não tipo int."},{"id":"thr-c","label":"Porque synchronized nunca funciona em Java.","correct":false,"explanation":"synchronized funciona quando protege o mesmo estado pelo mesmo monitor."}]}],"resources":[{"id":"java-thread-api","type":"reference","title":"Thread API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html","reinforces":"Ciclo de vida, start, interrupt e contrato básico de Thread.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"java-concurrent-package","type":"reference","title":"java.util.concurrent package — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/package-summary.html","reinforces":"Executors, locks, atomics e utilitários concorrentes modernos.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The threads component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to threads. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible threads failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// forma 1: estender Thread (menos flexível -- Java só permite herança simples)","instruction":"The threads component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible threads failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mensageria","moduleId":"messaging-eda","order":0,"title":"Mensageria — conceitos","summary":"Até aqui, toda comunicação do curso foi síncrona: o cliente chama a API e espera a resposta ali mesmo (capítulo 26). Mensageria resolve o problema de comunicação assíncrona — um serviço manda uma mensagem e segue em frente, sem esperar quem vai processá-la, nem quando.","objectives":["Entender mensagem antes de broker","Diferenciar evento, comando, queue e pub/sub","Explicar at-least-once e duplicação com exemplos simples","Preparar o aluno para SNS/SQS e Kafka sem salto conceitual"],"whyItExists":"Depois de medir acoplamento síncrono, o aluno precisa de uma porta de entrada rasa: uma mensagem é um contrato transportado no tempo. Antes de Kafka, cloud ou framework, entram intenção, envelope, fila, fan-out, duplicação e idempotência.","prerequisiteChapterIds":["coupled-services-lab","http","json"],"conceptIds":["por-que-nao-simplesmente-chamar-a-outra-api-direto","fila-queue-vs-publish-subscribe-pub-sub","garantias-de-entrega-o-que-pode-dar-errado","idempotencia-a-habilidade-que-salva-sistemas-de-mensageria"],"introducedConceptIds":["message-envelope-payload-metadata","event-command-message-intent","queue-work-competing-consumers","pubsub-fanout-subscription","delivery-at-least-once-idempotency"],"usedConceptIds":["temporal-coupling-sync","partial-failure-unknown-state","json-formato-contrato"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"mensageria-intuition","type":"intuition","authorship":"authored","title":"Mensagem desacopla tempo, não elimina responsabilidade","body":"Mensageria começa com uma ideia simples: em vez de exigir que outro serviço responda agora, você registra algo que outro consumidor pode processar depois. Isso reduz acoplamento temporal, mas cria novas responsabilidades: contrato da mensagem, duplicação, ordem, reprocessamento e observabilidade.","analogyLimit":"Correio ajuda como metáfora de envio, mas software precisa de IDs, versão, retry, DLQ e efeito idempotente."},{"id":"mensageria-shallow-first","type":"mental-model","authorship":"authored","title":"Comece pelo caminho mais raso","body":"Na primeira leitura, pense em três perguntas: o que aconteceu, quem precisa saber e o que fazer se chegar duas vezes. Broker, partição e offset ficam para depois.","flow":["1. Produtor cria uma mensagem com tipo, payload e metadados.","2. Canal guarda ou distribui a mensagem conforme a topologia.","3. Consumidor processa e confirma somente depois do efeito esperado.","4. Se falhar, a mensagem pode voltar; por isso o efeito precisa tolerar duplicação."]},{"id":"mensageria-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-kafka\">Mensageria</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#threads\">14 · Threads &amp; concorrência</a>, <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; REST</a></div>\n      </div>","fidelityText":"Mensageria Dificuldade: Avançado ⏱ ~2h de estudo Pré-requisitos: 14 · Threads & concorrência, 26 · HTTP & REST"},{"id":"mensageria-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Até aqui, toda comunicação do curso foi <strong>síncrona</strong>: o cliente chama a API e espera a resposta ali mesmo (capítulo 26). <strong>Mensageria</strong> resolve o problema de comunicação <strong>assíncrona</strong> — um serviço manda uma mensagem e segue em frente, sem esperar quem vai processá-la, nem quando.</p>","fidelityText":"Até aqui, toda comunicação do curso foi síncrona: o cliente chama a API e espera a resposta ali mesmo (capítulo 26). Mensageria resolve o problema de comunicação assíncrona — um serviço manda uma mensagem e segue em frente, sem esperar quem vai processá-la, nem quando."},{"id":"mensageria-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Comunicação síncrona é uma ligação telefônica: você fala e espera a resposta na hora, os dois lados precisam estar disponíveis ao mesmo tempo. Mensageria é uma caixa de correio: você deixa a carta lá e segue sua vida; o destinatário lê quando puder, mesmo que esteja offline no momento em que você escreveu.</div>","fidelityText":"Comunicação síncrona é uma ligação telefônica: você fala e espera a resposta na hora, os dois lados precisam estar disponíveis ao mesmo tempo. Mensageria é uma caixa de correio: você deixa a carta lá e segue sua vida; o destinatário lê quando puder, mesmo que esteja offline no momento em que você escreveu."},{"id":"mensageria-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Por que não simplesmente chamar a outra API direto?</h2>","fidelityText":"Por que não simplesmente chamar a outra API direto?"},{"id":"mensageria-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Se o serviço de e-mail estiver fora do ar no momento em que um pedido é finalizado, uma chamada HTTP síncrona falharia e travaria (ou quebraria) o fluxo de finalização do pedido — mesmo que enviar e-mail seja secundário ao pedido em si. Com mensageria, o pedido publica uma mensagem \"pedido finalizado\" e segue seu fluxo; o serviço de e-mail processa essa mensagem quando estiver disponível, sem acoplar o sucesso do pedido ao sucesso do envio de e-mail.</p>","fidelityText":"Se o serviço de e-mail estiver fora do ar no momento em que um pedido é finalizado, uma chamada HTTP síncrona falharia e travaria (ou quebraria) o fluxo de finalização do pedido — mesmo que enviar e-mail seja secundário ao pedido em si. Com mensageria, o pedido publica uma mensagem \"pedido finalizado\" e segue seu fluxo; o serviço de e-mail processa essa mensagem quando estiver disponível, sem acoplar o sucesso do pedido ao sucesso do envio de e-mail."},{"id":"mensageria-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Fila (queue) vs Publish/Subscribe (pub/sub)</h2>","fidelityText":"Fila (queue) vs Publish/Subscribe (pub/sub)"},{"id":"mensageria-content-7","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Padrão</th><th>Como funciona</th><th>Exemplo</th></tr>\n        <tr><td><strong>Fila (Queue)</strong></td><td>Cada mensagem é consumida por <strong>um único</strong> consumidor</td><td>Processar pagamentos — cada pagamento processado uma vez, por um worker</td></tr>\n        <tr><td><strong>Pub/Sub</strong></td><td>Cada mensagem é entregue a <strong>todos</strong> os assinantes interessados</td><td>\"Pedido criado\" — o serviço de e-mail, o de estoque e o de analytics reagem, cada um à sua maneira, ao mesmo evento</td></tr>\n      </tbody></table>","fidelityText":"PadrãoComo funcionaExemplo Fila (Queue)Cada mensagem é consumida por um único consumidorProcessar pagamentos — cada pagamento processado uma vez, por um worker Pub/SubCada mensagem é entregue a todos os assinantes interessados\"Pedido criado\" — o serviço de e-mail, o de estoque e o de analytics reagem, cada um à sua maneira, ao mesmo evento"},{"id":"mensageria-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Garantias de entrega — o que pode dar errado</h2>","fidelityText":"Garantias de entrega — o que pode dar errado"},{"id":"mensageria-content-9","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Garantia</th><th>Significado</th><th>Risco</th></tr>\n        <tr><td><strong>At-most-once</strong></td><td>Mensagem entregue no máximo uma vez</td><td>Pode <strong>perder</strong> mensagens se algo falhar</td></tr>\n        <tr><td><strong>At-least-once</strong></td><td>Mensagem entregue uma ou mais vezes</td><td>Pode <strong>duplicar</strong> processamento — exige idempotência</td></tr>\n        <tr><td><strong>Exactly-once</strong></td><td>Mensagem processada exatamente uma vez</td><td>Mais caro/complexo de garantir de ponta a ponta; raramente 100% absoluto na prática</td></tr>\n      </tbody></table>","fidelityText":"GarantiaSignificadoRisco At-most-onceMensagem entregue no máximo uma vezPode perder mensagens se algo falhar At-least-onceMensagem entregue uma ou mais vezesPode duplicar processamento — exige idempotência Exactly-onceMensagem processada exatamente uma vezMais caro/complexo de garantir de ponta a ponta; raramente 100% absoluto na prática"},{"id":"mensageria-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Idempotência — a habilidade que salva sistemas de mensageria</h2>","fidelityText":"Idempotência — a habilidade que salva sistemas de mensageria"},{"id":"mensageria-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Na prática, a maioria dos sistemas de mensageria (incluindo Kafka, próximo capítulo) opera em <strong>at-least-once</strong>: é mais barato garantir \"entrega pelo menos uma vez, às vezes duas\" do que \"exatamente uma vez, sempre\". Isso significa que seu <strong>consumidor</strong> precisa ser <strong>idempotente</strong> — processar a mesma mensagem duas vezes não pode causar efeito duplicado.</p>","fidelityText":"Na prática, a maioria dos sistemas de mensageria (incluindo Kafka, próximo capítulo) opera em at-least-once: é mais barato garantir \"entrega pelo menos uma vez, às vezes duas\" do que \"exatamente uma vez, sempre\". Isso significa que seu consumidor precisa ser idempotente — processar a mesma mensagem duas vezes não pode causar efeito duplicado."},{"id":"mensageria-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"// ❌ NÃO idempotente -- processar a mesma mensagem 2x credita 2x:\nvoid processPayment(MessagePayment msg) {\n    account.creditar(msg.getValue());\n}\n\n// ✅ idempotente -- usa um ID único da mensagem para NUNCA processar duas vezes:\nvoid processPayment(MessagePayment msg) {\n    if (processedRepository.alreadyProcessed(msg.getId())) {\n        return; // já vimos essa mensagem antes -- ignora silenciosamente\n    }\n    account.creditar(msg.getValue());\n    processedRepository.markAsProcessed(msg.getId());\n}","fidelityText":"// ❌ NÃO idempotente -- processar a mesma mensagem 2x credita 2x: void processarPagamento(MensagemPagamento msg) { conta.creditar(msg.getValor()); } // ✅ idempotente -- usa um ID único da mensagem para NUNCA processar duas vezes: void processarPagamento(MensagemPagamento msg) { if (processadosRepositorio.jaProcessado(msg.getId())) { return; // já vimos essa mensagem antes -- ignora silenciosamente } conta.creditar(msg.getValor()); processadosRepositorio.marcarComoProcessado(msg.getId()); }","highlightedHtml":"<span class=\"com\">// ❌ NÃO idempotente -- processar a mesma mensagem 2x credita 2x:</span>\n<span class=\"kw\">void</span> <span class=\"fn\">processPayment</span>(<span class=\"cls\">MessagePayment</span> msg) {\n    account.creditar(msg.getValue());\n}\n\n<span class=\"com\">// ✅ idempotente -- usa um ID único da mensagem para NUNCA processar duas vezes:</span>\n<span class=\"kw\">void</span> <span class=\"fn\">processPayment</span>(<span class=\"cls\">MessagePayment</span> msg) {\n    <span class=\"kw\">if</span> (processedRepository.alreadyProcessed(msg.getId())) {\n        <span class=\"kw\">return</span>; <span class=\"com\">// já vimos essa mensagem antes -- ignora silenciosamente</span>\n    }\n    account.creditar(msg.getValue());\n    processedRepository.markAsProcessed(msg.getId());\n}","caption":"Exemplo executável de mensageria.","explanation":["O exemplo deve ser lido como contrato mínimo: uma mensagem tem tipo, identificação e conteúdo de negócio.","Antes de usar broker real, garanta que o consumidor sabe o que fazer em duplicação e falha."],"commonMistakes":["Tratar payload como objeto interno mutável","Processar antes de validar tipo/versão/ID"]},{"id":"mensageria-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Esse é literalmente o mesmo problema de race condition do capítulo 14, só que distribuído entre processos diferentes em vez de threads na mesma JVM. A solução também rima: assim como <code>synchronized</code>/<code>AtomicInteger</code> garantiam uma operação atômica dentro de uma aplicação, um registro de \"IDs já processados\" (geralmente com uma restrição <code>UNIQUE</code> no banco, capítulo 33) garante atomicidade de processamento entre múltiplas instâncias consumindo a mesma fila.</div>","fidelityText":"Esse é literalmente o mesmo problema de race condition do capítulo 14, só que distribuído entre processos diferentes em vez de threads na mesma JVM. A solução também rima: assim como synchronized/AtomicInteger garantiam uma operação atômica dentro de uma aplicação, um registro de \"IDs já processados\" (geralmente com uma restrição UNIQUE no banco, capítulo 33) garante atomicidade de processamento entre múltiplas instâncias consumindo a mesma fila."},{"id":"mensageria-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Antes de aprender a sintaxe de qualquer ferramenta de mensageria específica, treine a pergunta: <em>\"o que acontece se essa mensagem chegar duas vezes?\"</em> Se a resposta te assusta, o design ainda não está pronto para produção — independente de qual tecnologia (Kafka, RabbitMQ, SQS) você usar por baixo.</div>","fidelityText":"Antes de aprender a sintaxe de qualquer ferramenta de mensageria específica, treine a pergunta: \"o que acontece se essa mensagem chegar duas vezes?\" Se a resposta te assusta, o design ainda não está pronto para produção — independente de qual tecnologia (Kafka, RabbitMQ, SQS) você usar por baixo."},{"id":"mensageria-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 40.1 — Tornando um consumidor idempotente","prompt":"Escreva uma classe ProcessadorEmprestimo com um método processar(String mensagemId, String codigoLivro) que empresta um livro (reaproveitando a lógica da Biblioteca dos capítulos anteriores). Use um Set<String> em memória para rastrear IDs de mensagem já processados, garantindo que processar a mesma mensagemId duas vezes não empreste o livro duas vezes.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 40.1 — Tornando um consumidor idempotentemédio Escreva uma classe ProcessadorEmprestimo com um método processar(String mensagemId, String codigoLivro) que empresta um livro (reaproveitando a lógica da Biblioteca dos capítulos anteriores). Use um Set<String> em memória para rastrear IDs de mensagem já processados, garantindo que processar a mesma mensagemId duas vezes não empreste o livro duas vezes. Ver solução public class ProcessadorEmprestimo { private final Biblioteca biblioteca; private final Set<String> mensagensProcessadas = new HashSet<>(); public ProcessadorEmprestimo(Biblioteca biblioteca) { this.biblioteca = biblioteca; } public synchronized void processar(String mensagemId, String codigoLivro) throws ItemIndisponivelException { if (mensagensProcessadas.contains(mensagemId)) { System.out.println(\"Mensagem \" + mensagemId + \" já processada -- ignorando\"); return; } biblioteca.emprestar(codigoLivro); mensagensProcessadas.add(mensagemId); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 40.1 — Tornando um consumidor idempotente</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Escreva uma classe <code>ProcessadorEmprestimo</code> com um método <code>processar(String mensagemId, String codigoLivro)</code> que empresta um livro (reaproveitando a lógica da <code>Biblioteca</code> dos capítulos anteriores). Use um <code>Set&lt;String&gt;</code> em memória para rastrear IDs de mensagem já processados, garantindo que processar a mesma <code>mensagemId</code> duas vezes não empreste o livro duas vezes.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">ProcessadorLoan</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">Library</span> library;\n    <span class=\"kw\">private final</span> Set&lt;<span class=\"kw\">String</span>&gt; messagesProcessadas = <span class=\"kw\">new</span> HashSet&lt;&gt;();\n\n    <span class=\"kw\">public</span> <span class=\"fn\">ProcessadorLoan</span>(<span class=\"cls\">Library</span> library) { <span class=\"kw\">this</span>.library = library; }\n\n    <span class=\"kw\">public synchronized void</span> <span class=\"fn\">process</span>(<span class=\"kw\">String</span> messageId, <span class=\"kw\">String</span> codeBook) <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        <span class=\"kw\">if</span> (messagesProcessadas.contains(messageId)) {\n            System.out.println(<span class=\"str\">\"Message \"</span> + messageId + <span class=\"str\">\" already processada -- ignorando\"</span>);\n            <span class=\"kw\">return</span>;\n        }\n        library.borrow(codeBook);\n        messagesProcessadas.add(messageId);\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"mensageria-topology-table","type":"table","authorship":"authored","title":"Primeira decisão de topologia","headers":["Pergunta","Use","Evite confundir com"],"rows":[["Um trabalhador deve assumir uma tarefa?","Queue com consumidores competindo","Pub/sub"],["Vários contextos independentes devem reagir ao fato?","Pub/sub/fan-out","Vários workers no mesmo grupo"],["Preciso de resposta imediata para continuar a tela?","HTTP/síncrono ou comando com estado explícito","Evento usado como RPC escondido"]]},{"id":"mensageria-quiz","type":"quiz","authorship":"authored","conceptId":"delivery-at-least-once-idempotency","prompt":"Qual é a consequência prática de at-least-once?","options":[{"id":"mensageria-a","label":"A mensagem pode ser entregue mais de uma vez; o consumidor precisa tornar o efeito idempotente.","correct":true,"explanation":"At-least-once privilegia não perder mensagem, aceitando duplicação."},{"id":"mensageria-b","label":"O broker garante que o banco do consumidor nunca será alterado duas vezes.","correct":false,"explanation":"Broker não controla a transação local do consumidor."},{"id":"mensageria-c","label":"A mensagem vira síncrona e dispensa observabilidade.","correct":false,"explanation":"Assincronia exige ainda mais correlação e métrica."}]}],"resources":[{"id":"aws-sqs-exactly-once-idempotency","type":"official-docs","title":"Amazon SQS: exactly-once processing in FIFO queues","url":"https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues-exactly-once-processing.html","reinforces":"Deduplicação, FIFO e limites práticos de entrega.","language":"en","publisher":"AWS","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"aws-sns-sqs-fanout-phase16","type":"official-docs","title":"Fanout to Amazon SQS queues","url":"https://docs.aws.amazon.com/sns/latest/dg/sns-sqs-as-subscriber.html","reinforces":"Pub/sub com filas independentes para consumidores diferentes.","language":"en","publisher":"AWS","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the messaging flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for messaging. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for messaging with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"// ❌ NÃO idempotente -- processar a mesma mensagem 2x credita 2x:","instruction":"Design the messaging flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for messaging with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"kafka","moduleId":"messaging-eda","order":3,"title":"Kafka na prática","summary":"Kafka é a ferramenta de mensageria mais usada em sistemas de médio/grande porte — não é exatamente uma fila tradicional, e sim um log distribuído: mensagens são gravadas em ordem e mantidas por um tempo configurável, permitindo que múltiplos consumidores leiam o mesmo histórico de formas independentes.","objectives":["Entender Kafka como log particionado","Diferenciar topic, partition, record, key e offset","Executar producer/consumer com contrato de serialização","Explicar consumer group e rebalance sem mistério"],"whyItExists":"Kafka só entra depois que queue, pub/sub e entrega já foram entendidos. O capítulo aprofunda o broker como log distribuído: mensagens ficam ordenadas por partição, consumidores avançam por offset e grupos dividem trabalho.","prerequisiteChapterIds":["messaging-model","compose"],"conceptIds":["topicos-e-particoes-o-vocabulario-do-kafka","producer-e-consumer-em-java-puro","replicacao-e-durabilidade-o-que-acks-realmente-espera","configuracao-real-de-producer-o-que-acks-sozinho-nao-garante","commit-de-offset-automatico-nao-e-a-unica-opcao","partition-assignment-como-um-consumer-group-decide-quem-le-o-que","proximos-passos-schema-compactacao-e-observabilidade","consumer-groups-como-kafka-escala-consumidores"],"introducedConceptIds":["kafka-topic-partition-offset","kafka-producer-consumer-record","consumer-group-rebalance-offset"],"usedConceptIds":["broker-backlog-backpressure","delivery-at-least-once-idempotency","compose-service-network"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"kafka-intuition","type":"intuition","authorship":"authored","title":"Kafka é log particionado, não fila mágica","body":"Kafka organiza records em topics divididos por partitions. Cada consumidor guarda sua posição por offset. Isso permite replay e escala, mas exige escolher key, serialização, retenção e momento de commit com cuidado.","analogyLimit":"Um diário por partição ajuda a imaginar sequência, mas Kafka envolve replicação, retenção, grupos e rebalances."},{"id":"kafka-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-kafka\">Kafka</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#mensageria\">40 · Mensageria conceitos</a>, <a class=\"prereq-tag\" href=\"#compose\">32 · Docker Compose</a></div>\n      </div>","fidelityText":"Kafka Dificuldade: Avançado ⏱ ~3h de estudo + prática Pré-requisitos: 40 · Mensageria conceitos, 32 · Docker Compose"},{"id":"kafka-content-2","type":"html","authorship":"legacy-preserved","html":"<p><strong>Kafka</strong> é a ferramenta de mensageria mais usada em sistemas de médio/grande porte — não é exatamente uma fila tradicional, e sim um <strong>log distribuído</strong>: mensagens são gravadas em ordem e mantidas por um tempo configurável, permitindo que múltiplos consumidores leiam o mesmo histórico de formas independentes.</p>","fidelityText":"Kafka é a ferramenta de mensageria mais usada em sistemas de médio/grande porte — não é exatamente uma fila tradicional, e sim um log distribuído: mensagens são gravadas em ordem e mantidas por um tempo configurável, permitindo que múltiplos consumidores leiam o mesmo histórico de formas independentes."},{"id":"kafka-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"install-box\">\n        <h2>Subindo Kafka localmente</h2>\n        <pre class=\"code\"><span class=\"com\"># docker-compose.yml -- Kafka moderno não precisa mais de Zookeeper separado (modo KRaft):</span>\nservices:\n  kafka:\n    image: apache/kafka:3.7.0\n    ports:\n      - \"9092:9092\"\n    environment:\n      KAFKA_NODE_ID: 1\n      KAFKA_PROCESS_ROLES: broker,controller\n      KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093\n      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092\n      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093\n      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER</pre>\n        <pre class=\"code\">docker compose up -d kafka</pre>\n      </div>","fidelityText":"Subindo Kafka localmente # docker-compose.yml -- Kafka moderno não precisa mais de Zookeeper separado (modo KRaft): services: kafka: image: apache/kafka:3.7.0 ports: - \"9092:9092\" environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER docker compose up -d kafka"},{"id":"kafka-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Tópicos e partições — o vocabulário do Kafka</h2>","fidelityText":"Tópicos e partições — o vocabulário do Kafka"},{"id":"kafka-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um <strong>tópico</strong> Kafka é como uma revista publicada em edições numeradas — cada mensagem é uma edição nova, sempre adicionada ao final, nunca alterada. Uma <strong>partição</strong> é como imprimir essa revista em várias gráficas simultâneas para ir mais rápido: o conteúdo total do tópico é dividido entre partições, cada uma mantendo sua própria ordem interna, mas a ordem entre partições diferentes não é garantida.</div>","fidelityText":"Um tópico Kafka é como uma revista publicada em edições numeradas — cada mensagem é uma edição nova, sempre adicionada ao final, nunca alterada. Uma partição é como imprimir essa revista em várias gráficas simultâneas para ir mais rápido: o conteúdo total do tópico é dividido entre partições, cada uma mantendo sua própria ordem interna, mas a ordem entre partições diferentes não é garantida."},{"id":"kafka-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"# dentro do container:\nkafka-topics.sh --create --topic orders-completed --bootstrap-server localhost:9092 --partitions 3\nkafka-topics.sh --list --bootstrap-server localhost:9092","fidelityText":"# dentro do container: kafka-topics.sh --create --topic pedidos-finalizados --bootstrap-server localhost:9092 --partitions 3 kafka-topics.sh --list --bootstrap-server localhost:9092","highlightedHtml":"<span class=\"com\"># dentro do container:</span>\nkafka-topics.sh --create --topic orders-completed --bootstrap-server localhost:9092 --partitions 3\nkafka-topics.sh --list --bootstrap-server localhost:9092","caption":"Exemplo executável de kafka.","explanation":["kafka-topics.sh --create fixa o tópico com 3 partições antes de qualquer producer publicar nele.","--list confirma que o tópico existe de verdade dentro do broker."],"commonMistakes":["Publicar num tópico que não existe e confiar na auto-criação implícita em produção","Esquecer de fixar --partitions e aceitar o padrão do broker sem medir a necessidade real"]},{"id":"kafka-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Producer e Consumer em Java puro</h2>","fidelityText":"Producer e Consumer em Java puro"},{"id":"kafka-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"// --- Producer: publica uma mensagem ---\nProperties props = new Properties();\nprops.put(\"bootstrap.servers\", \"localhost:9092\");\nprops.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\nprops.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n\nKafkaProducer<String, String> producer = new KafkaProducer<>(props);\nproducer.send(new ProducerRecord<>(\"orders-completed\", \"order-42\", jsonOfEvent));\nproducer.close();\n\n// --- Consumer: lê mensagens ---\nProperties consumerProps = new Properties();\nconsumerProps.put(\"bootstrap.servers\", \"localhost:9092\");\nconsumerProps.put(\"group.id\", \"service-email\");\nconsumerProps.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\nconsumerProps.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n\nKafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps);\nconsumer.subscribe(List.of(\"orders-completed\"));\n\nwhile (true) {\n    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));\n    for (ConsumerRecord<String, String> r : records) {\n        System.out.println(\"Recebido: \" + r.value());\n    }\n}","fidelityText":"// --- Producer: publica uma mensagem --- Properties props = new Properties(); props.put(\"bootstrap.servers\", \"localhost:9092\"); props.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\"); props.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\"); KafkaProducer<String, String> producer = new KafkaProducer<>(props); producer.send(new ProducerRecord<>(\"pedidos-finalizados\", \"pedido-42\", jsonDoEvento)); producer.close(); // --- Consumer: lê mensagens --- Properties consumerProps = new Properties(); consumerProps.put(\"bootstrap.servers\", \"localhost:9092\"); consumerProps.put(\"group.id\", \"servico-email\"); consumerProps.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\"); consumerProps.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\"); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps); consumer.subscribe(List.of(\"pedidos-finalizados\")); while (true) { ConsumerRecords<String, String> registros = consumer.poll(Duration.ofMillis(500)); for (ConsumerRecord<String, String> r : registros) { System.out.println(\"Recebido: \" + r.value()); } }","highlightedHtml":"<span class=\"com\">// --- Producer: publica uma mensagem ---</span>\nProperties props = <span class=\"kw\">new</span> Properties();\nprops.put(<span class=\"str\">\"bootstrap.servers\"</span>, <span class=\"str\">\"localhost:9092\"</span>);\nprops.put(<span class=\"str\">\"key.serializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringSerializer\"</span>);\nprops.put(<span class=\"str\">\"value.serializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringSerializer\"</span>);\n\nKafkaProducer&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; producer = <span class=\"kw\">new</span> KafkaProducer&lt;&gt;(props);\nproducer.send(<span class=\"kw\">new</span> ProducerRecord&lt;&gt;(<span class=\"str\">\"orders-completed\"</span>, <span class=\"str\">\"order-42\"</span>, jsonOfEvent));\nproducer.close();\n\n<span class=\"com\">// --- Consumer: lê mensagens ---</span>\nProperties consumerProps = <span class=\"kw\">new</span> Properties();\nconsumerProps.put(<span class=\"str\">\"bootstrap.servers\"</span>, <span class=\"str\">\"localhost:9092\"</span>);\nconsumerProps.put(<span class=\"str\">\"group.id\"</span>, <span class=\"str\">\"service-email\"</span>);\nconsumerProps.put(<span class=\"str\">\"key.deserializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringDeserializer\"</span>);\nconsumerProps.put(<span class=\"str\">\"value.deserializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringDeserializer\"</span>);\n\nKafkaConsumer&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; consumer = <span class=\"kw\">new</span> KafkaConsumer&lt;&gt;(consumerProps);\nconsumer.subscribe(List.of(<span class=\"str\">\"orders-completed\"</span>));\n\n<span class=\"kw\">while</span> (<span class=\"kw\">true</span>) {\n    ConsumerRecords&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; records = consumer.poll(Duration.ofMillis(500));\n    <span class=\"kw\">for</span> (ConsumerRecord&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; r : records) {\n        System.out.println(<span class=\"str\">\"Recebido: \"</span> + r.value());\n    }\n}","caption":"Exemplo executável de kafka.","explanation":["O producer envia um record para o tópico com key/value conforme os serializadores configurados.","O consumer faz polling em loop; cada chamada de poll() devolve um lote de registros das partições atribuídas ao group.id."],"commonMistakes":["Usar key null quando precisa preservar ordem por entidade de negócio","Ignorar o retorno assíncrono do send() e assumir sucesso sem checar"]},{"id":"kafka-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Replicação e durabilidade — o que <code>acks</code> realmente espera</h2>","fidelityText":"Replicação e durabilidade — o que acks realmente espera"},{"id":"kafka-content-10","type":"html","authorship":"legacy-preserved","html":"<p>O compose acima sobe um único broker — ótimo para aprender o vocabulário, mas incompatível com produção: se aquele broker cair, os dados da partição vão junto. Em produção, cada partição tem um <strong>replication factor</strong> (normalmente 3): um broker é o <strong>líder</strong> (recebe toda escrita e leitura), e os outros são <strong>réplicas</strong> que copiam o log do líder continuamente. Nem toda réplica conta como garantia — só as que estão genuinamente em dia entram no <strong>ISR</strong> (<em>in-sync replica set</em>).</p>","fidelityText":"O compose acima sobe um único broker — ótimo para aprender o vocabulário, mas incompatível com produção: se aquele broker cair, os dados da partição vão junto. Em produção, cada partição tem um replication factor (normalmente 3): um broker é o líder (recebe toda escrita e leitura), e os outros são réplicas que copiam o log do líder continuamente. Nem toda réplica conta como garantia — só as que estão genuinamente em dia entram no ISR (in-sync replica set)."},{"id":"kafka-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"// replication factor 3 -- lider recebe toda escrita, replicas em sincronia copiam antes do ack\nkafka-topics.sh --create --topic orders-completed \\\n    --bootstrap-server localhost:9092 \\\n    --partitions 3 --replication-factor 3","fidelityText":"// replication factor 3 -- lider recebe toda escrita, replicas em sincronia copiam antes do ack kafka-topics.sh --create --topic pedidos-finalizados \\ --bootstrap-server localhost:9092 \\ --partitions 3 --replication-factor 3","highlightedHtml":"<span class=\"com\">// replication factor 3 -- lider recebe toda escrita, replicas em sincronia copiam antes do ack</span>\nkafka-topics.sh --create --topic orders-completed \\\n    --bootstrap-server localhost:9092 \\\n    --partitions <span class=\"num\">3</span> --replication-factor <span class=\"num\">3</span>","caption":"Exemplo executável de kafka.","explanation":["--replication-factor 3 cria 3 cópias de cada partição em brokers diferentes -- 1 líder e 2 réplicas.","Sem isso, a queda de um único broker apagaria os dados daquela partição."],"commonMistakes":["Usar replication-factor 1 em produção, deixando cada partição sem redundância","Confundir replication-factor com número de partições -- são eixos independentes"]},{"id":"kafka-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>acks=all</code> não significa \"espera todas as réplicas do mundo\" — significa \"espera confirmação de todas as réplicas que estão no ISR no momento\". Se <code>min.insync.replicas=2</code> e o ISR cair para 1 réplica (as outras ficaram para trás ou caíram), o broker rejeita a escrita em vez de fingir durabilidade que não existe — essa rejeição explícita é a durabilidade real do Kafka, não uma promessa vaga de \"réplicas o suficiente\".</div>","fidelityText":"acks=all não significa \"espera todas as réplicas do mundo\" — significa \"espera confirmação de todas as réplicas que estão no ISR no momento\". Se min.insync.replicas=2 e o ISR cair para 1 réplica (as outras ficaram para trás ou caíram), o broker rejeita a escrita em vez de fingir durabilidade que não existe — essa rejeição explícita é a durabilidade real do Kafka, não uma promessa vaga de \"réplicas o suficiente\"."},{"id":"kafka-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties / broker config -- durabilidade real, não confiança cega\nacks=all\nmin.insync.replicas=2  # exige 2 réplicas em sincronia -- não só o líder","fidelityText":"# application.properties / broker config -- durabilidade real, não confiança cega acks=all min.insync.replicas=2 # exige 2 réplicas em sincronia -- não só o líder","highlightedHtml":"<span class=\"com\"># application.properties / broker config -- durabilidade real, não confiança cega</span>\nacks=all\nmin.insync.replicas=2  <span class=\"com\"># exige 2 réplicas em sincronia -- não só o líder</span>","caption":"Exemplo executável de kafka.","explanation":["acks=all exige confirmação de todas as réplicas atualmente no ISR antes de considerar a escrita concluída.","min.insync.replicas=2 faz o broker rejeitar a escrita se menos de 2 réplicas estiverem em sincronia -- durabilidade explícita, não implícita."],"commonMistakes":["Configurar acks=all sem min.insync.replicas e achar que já tem a garantia completa","Achar que min.insync.replicas=2 com replication-factor=2 ainda tolera a queda de 1 broker"]},{"id":"kafka-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Configuração real de producer — o que <code>acks</code> sozinho não garante</h2>","fidelityText":"Configuração real de producer — o que acks sozinho não garante"},{"id":"kafka-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Chamar <code>producer.send(...)</code> com as <code>Properties</code> mínimas do início do capítulo funciona para aprender, mas nenhuma configuração real de produção usa só isso. As propriedades abaixo decidem entre performance, durabilidade e ordem:</p>","fidelityText":"Chamar producer.send(...) com as Properties mínimas do início do capítulo funciona para aprender, mas nenhuma configuração real de produção usa só isso. As propriedades abaixo decidem entre performance, durabilidade e ordem:"},{"id":"kafka-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"Properties props = new Properties();\nprops.put(\"bootstrap.servers\", \"localhost:9092\");\nprops.put(\"acks\", \"all\"); // espera ack de todas as replicas em sincronia\nprops.put(\"enable.idempotence\", \"true\"); // producer numera mensagens, broker descarta duplicata na mesma sessao\nprops.put(\"retries\", Integer.toString(Integer.MAX_VALUE)); // retry ilimitado -- seguro só com idempotência ligada\nprops.put(\"max.in.flight.requests.per.connection\", \"5\"); // até 5 -- acima disso, retry pode reordenar\nprops.put(\"linger.ms\", \"5\"); // espera até 5ms para juntar mensagens no mesmo lote\nprops.put(\"batch.size\", \"32768\"); // tamanho máximo do lote em bytes antes de enviar","fidelityText":"Properties props = new Properties(); props.put(\"bootstrap.servers\", \"localhost:9092\"); props.put(\"acks\", \"all\"); // espera ack de todas as replicas em sincronia props.put(\"enable.idempotence\", \"true\"); // producer numera mensagens, broker descarta duplicata na mesma sessao props.put(\"retries\", Integer.toString(Integer.MAX_VALUE)); // retry ilimitado -- seguro só com idempotência ligada props.put(\"max.in.flight.requests.per.connection\", \"5\"); // até 5 -- acima disso, retry pode reordenar props.put(\"linger.ms\", \"5\"); // espera até 5ms para juntar mensagens no mesmo lote props.put(\"batch.size\", \"32768\"); // tamanho máximo do lote em bytes antes de enviar","highlightedHtml":"Properties props = <span class=\"kw\">new</span> Properties();\nprops.put(<span class=\"str\">\"bootstrap.servers\"</span>, <span class=\"str\">\"localhost:9092\"</span>);\nprops.put(<span class=\"str\">\"acks\"</span>, <span class=\"str\">\"all\"</span>); <span class=\"com\">// espera ack de todas as replicas em sincronia</span>\nprops.put(<span class=\"str\">\"enable.idempotence\"</span>, <span class=\"str\">\"true\"</span>); <span class=\"com\">// producer numera mensagens, broker descarta duplicata na mesma sessao</span>\nprops.put(<span class=\"str\">\"retries\"</span>, Integer.<span class=\"fn\">toString</span>(Integer.MAX_VALUE)); <span class=\"com\">// retry ilimitado -- seguro só com idempotência ligada</span>\nprops.put(<span class=\"str\">\"max.in.flight.requests.per.connection\"</span>, <span class=\"str\">\"5\"</span>); <span class=\"com\">// até 5 -- acima disso, retry pode reordenar</span>\nprops.put(<span class=\"str\">\"linger.ms\"</span>, <span class=\"str\">\"5\"</span>); <span class=\"com\">// espera até 5ms para juntar mensagens no mesmo lote</span>\nprops.put(<span class=\"str\">\"batch.size\"</span>, <span class=\"str\">\"32768\"</span>); <span class=\"com\">// tamanho máximo do lote em bytes antes de enviar</span>","caption":"Exemplo executável de kafka.","explanation":["enable.idempotence=true faz o broker descartar duplicatas do mesmo producer na mesma sessão, tornando retries seguros.","max.in.flight.requests.per.connection limita quantas requisições não confirmadas podem estar em trânsito -- acima de 5, retry pode reordenar mensagens.","linger.ms e batch.size controlam o trade-off entre latência (esperar menos) e throughput (juntar mais mensagens por lote)."],"commonMistakes":["Ligar retries sem enable.idempotence e arriscar duplicação silenciosa","Configurar linger.ms=0 achando que sempre é mais rápido, sem medir o custo de lotes minúsculos"]},{"id":"kafka-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>enable.idempotence=true</code> é o que torna <code>retries</code> seguro:</b> sem ele, reenviar uma mensagem após timeout de rede pode duplicá-la no tópico (o broker recebeu a primeira tentativa, mas o producer nunca viu a confirmação). Com idempotência ligada, cada producer recebe um ID e numera suas mensagens; o broker detecta e descarta duplicatas da mesma sessão, tornando retry seguro por padrão — desde o Kafka 3.0, essa combinação (<code>acks=all</code> + idempotência + retries efetivamente ilimitados) é o padrão de fábrica do producer, não uma configuração exótica.</div>","fidelityText":"enable.idempotence=true é o que torna retries seguro: sem ele, reenviar uma mensagem após timeout de rede pode duplicá-la no tópico (o broker recebeu a primeira tentativa, mas o producer nunca viu a confirmação). Com idempotência ligada, cada producer recebe um ID e numera suas mensagens; o broker detecta e descarta duplicatas da mesma sessão, tornando retry seguro por padrão — desde o Kafka 3.0, essa combinação (acks=all + idempotência + retries efetivamente ilimitados) é o padrão de fábrica do producer, não uma configuração exótica."},{"id":"kafka-content-18","type":"html","authorship":"legacy-preserved","html":"<h2>Commit de offset — automático não é a única opção</h2>","fidelityText":"Commit de offset — automático não é a única opção"},{"id":"kafka-content-19","type":"html","authorship":"legacy-preserved","html":"<p>O loop <code>while(true) { poll(...) }</code> do início do capítulo confia no commit automático (<code>enable.auto.commit=true</code>, padrão), que comita periodicamente em background — simples, mas comita mesmo que o processamento da mensagem ainda não tenha terminado de verdade. Para controlar exatamente quando uma mensagem é considerada processada, desligue o commit automático e comite manualmente:</p>","fidelityText":"O loop while(true) { poll(...) } do início do capítulo confia no commit automático (enable.auto.commit=true, padrão), que comita periodicamente em background — simples, mas comita mesmo que o processamento da mensagem ainda não tenha terminado de verdade. Para controlar exatamente quando uma mensagem é considerada processada, desligue o commit automático e comite manualmente:"},{"id":"kafka-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"consumerProps.put(\"enable.auto.commit\", \"false\"); // commit manual do offset -- avanca so depois que o processamento termina\n\nwhile (true) {\n    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));\n    for (ConsumerRecord<String, String> r : records) {\n        process(r.value()); // só comita depois que isso realmente terminou\n    }\n    consumer.commitSync(); // bloqueia até o broker confirmar -- mais seguro, mais lento\n    // alternativa: consumer.commitAsync() -- não bloqueia, mas não garante ordem de confirmação\n}","fidelityText":"consumerProps.put(\"enable.auto.commit\", \"false\"); // commit manual do offset -- avanca so depois que o processamento termina while (true) { ConsumerRecords<String, String> registros = consumer.poll(Duration.ofMillis(500)); for (ConsumerRecord<String, String> r : registros) { processar(r.value()); // só comita depois que isso realmente terminou } consumer.commitSync(); // bloqueia até o broker confirmar -- mais seguro, mais lento // alternativa: consumer.commitAsync() -- não bloqueia, mas não garante ordem de confirmação }","highlightedHtml":"consumerProps.put(<span class=\"str\">\"enable.auto.commit\"</span>, <span class=\"str\">\"false\"</span>); <span class=\"com\">// commit manual do offset -- avanca so depois que o processamento termina</span>\n\n<span class=\"kw\">while</span> (<span class=\"kw\">true</span>) {\n    ConsumerRecords&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; records = consumer.poll(Duration.ofMillis(<span class=\"num\">500</span>));\n    <span class=\"kw\">for</span> (ConsumerRecord&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; r : records) {\n        process(r.value()); <span class=\"com\">// só comita depois que isso realmente terminou</span>\n    }\n    consumer.commitSync(); <span class=\"com\">// bloqueia até o broker confirmar -- mais seguro, mais lento</span>\n    <span class=\"com\">// alternativa: consumer.commitAsync() -- não bloqueia, mas não garante ordem de confirmação</span>\n}","caption":"Exemplo executável de kafka.","explanation":["enable.auto.commit=false transfere o controle de quando o offset avança para o próprio código.","commitSync() só é chamado depois que o processamento de todo o lote termina -- o commit reflete trabalho realmente concluído."],"commonMistakes":["Chamar commitSync() antes de processar todos os registros do lote","Trocar commitSync() por commitAsync() sem entender que este não garante ordem de confirmação"]},{"id":"kafka-content-21","type":"html","authorship":"legacy-preserved","html":"<h2>Partition assignment — como um consumer group decide quem lê o quê</h2>","fidelityText":"Partition assignment — como um consumer group decide quem lê o quê"},{"id":"kafka-content-22","type":"html","authorship":"legacy-preserved","html":"<p>Quando um consumer entra ou sai de um grupo, o Kafka precisa redistribuir partições entre os consumers restantes — esse processo chama-se <strong>rebalance</strong>, e a <strong>estratégia de atribuição</strong> decide como ele acontece:</p>","fidelityText":"Quando um consumer entra ou sai de um grupo, o Kafka precisa redistribuir partições entre os consumers restantes — esse processo chama-se rebalance, e a estratégia de atribuição decide como ele acontece:"},{"id":"kafka-content-23","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Estratégia</th><th>Como distribui</th><th>Custo do rebalance</th></tr>\n        <tr><td><code>range</code></td><td>Atribui faixas contíguas de partições por tópico</td><td>Pode concentrar partições no mesmo consumer entre tópicos diferentes</td></tr>\n        <tr><td><code>round-robin</code></td><td>Distribui partições ciclicamente entre consumers</td><td>Mais equilibrado, mas revoga <strong>todas</strong> as partições no rebalance</td></tr>\n        <tr><td><code>cooperative-sticky</code></td><td>Mantém atribuições anteriores, move só o necessário</td><td>Rebalance incremental -- consumers não afetados continuam processando</td></tr>\n      </tbody></table>","fidelityText":"EstratégiaComo distribuiCusto do rebalance rangeAtribui faixas contíguas de partições por tópicoPode concentrar partições no mesmo consumer entre tópicos diferentes round-robinDistribui partições ciclicamente entre consumersMais equilibrado, mas revoga todas as partições no rebalance cooperative-stickyMantém atribuições anteriores, move só o necessárioRebalance incremental -- consumers não afetados continuam processando"},{"id":"kafka-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"consumerProps.put(\"partition.assignment.strategy\",\n    \"org.apache.kafka.clients.consumer.CooperativeStickyAssignor\");\n// cooperative-sticky -- rebalanceamento revoga so as particoes necessarias, nunca todas","fidelityText":"consumerProps.put(\"partition.assignment.strategy\", \"org.apache.kafka.clients.consumer.CooperativeStickyAssignor\"); // cooperative-sticky -- rebalanceamento revoga so as particoes necessarias, nunca todas","highlightedHtml":"consumerProps.put(<span class=\"str\">\"partition.assignment.strategy\"</span>,\n    <span class=\"str\">\"org.apache.kafka.clients.consumer.CooperativeStickyAssignor\"</span>);\n<span class=\"com\">// cooperative-sticky -- rebalanceamento revoga so as particoes necessarias, nunca todas</span>","caption":"Exemplo executável de kafka.","explanation":["CooperativeStickyAssignor faz o rebalance mover só as partições que precisam trocar de dono.","Consumers cujas partições não mudaram continuam processando durante o rebalance -- diferente das estratégias eager (range, round-robin)."],"commonMistakes":["Manter a estratégia eager padrão em um consumer group com entradas/saídas frequentes de instâncias","Trocar a estratégia de assignment em produção sem coordenar a mudança entre todos os consumers do grupo"]},{"id":"kafka-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">As estratégias mais antigas (<code>range</code>, <code>round-robin</code>) são <strong>eager</strong>: todo rebalance revoga <strong>todas</strong> as partições de <strong>todos</strong> os consumers do grupo, mesmo os que não mudaram, e só depois redistribui — um \"stop-the-world\" completo do consumo. <code>cooperative-sticky</code> resolve isso fazendo o rebalance em duas fases incrementais, revogando só as partições que realmente precisam mudar de dono — consumers que mantêm suas partições nunca param de processar.</div>","fidelityText":"As estratégias mais antigas (range, round-robin) são eager: todo rebalance revoga todas as partições de todos os consumers do grupo, mesmo os que não mudaram, e só depois redistribui — um \"stop-the-world\" completo do consumo. cooperative-sticky resolve isso fazendo o rebalance em duas fases incrementais, revogando só as partições que realmente precisam mudar de dono — consumers que mantêm suas partições nunca param de processar."},{"id":"kafka-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>Próximos passos: schema, compactação e observabilidade</h2>","fidelityText":"Próximos passos: schema, compactação e observabilidade"},{"id":"kafka-content-27","type":"html","authorship":"legacy-preserved","html":"<p>Este capítulo cobriu o vocabulário e o mecanismo essencial. Três tópicos ficam para o próximo capítulo por serem densos o suficiente para merecer espaço próprio: <strong>Schema Registry</strong> (contrato estruturado da mensagem, em vez de <code>String</code> cru — capítulo seguinte), <strong>log compaction</strong> (um tópico pode reter só o registro mais recente por chave, em vez de tudo por tempo — aprofundado no capítulo de internals), e <strong>monitoramento de consumer lag</strong> (quão atrasado um consumer group está em relação à ponta do log):</p>","fidelityText":"Este capítulo cobriu o vocabulário e o mecanismo essencial. Três tópicos ficam para o próximo capítulo por serem densos o suficiente para merecer espaço próprio: Schema Registry (contrato estruturado da mensagem, em vez de String cru — capítulo seguinte), log compaction (um tópico pode reter só o registro mais recente por chave, em vez de tudo por tempo — aprofundado no capítulo de internals), e monitoramento de consumer lag (quão atrasado um consumer group está em relação à ponta do log):"},{"id":"kafka-code-28","type":"code","authorship":"legacy-preserved","language":"java","source":"# mede o lag de um consumer group -- diferença entre o offset mais recente e o offset commitado\nkafka-consumer-groups.sh --bootstrap-server localhost:9092 \\\n    --describe --group service-email","fidelityText":"# mede o lag de um consumer group -- diferença entre o offset mais recente e o offset commitado kafka-consumer-groups.sh --bootstrap-server localhost:9092 \\ --describe --group servico-email","highlightedHtml":"<span class=\"com\"># mede o lag de um consumer group -- diferença entre o offset mais recente e o offset commitado</span>\nkafka-consumer-groups.sh --bootstrap-server localhost:9092 \\\n    --describe --group service-email","caption":"Exemplo executável de kafka.","explanation":["--describe --group mostra, por partição, o offset mais recente do log e o offset commitado pelo grupo.","A coluna LAG é a diferença entre os dois -- quantas mensagens o grupo ainda não processou."],"commonMistakes":["Olhar só a taxa de mensagens/segundo e ignorar o lag acumulado","Achar que lag zero garante ausência de bug -- pode haver commit sem processamento real"]},{"id":"kafka-content-29","type":"html","authorship":"legacy-preserved","html":"<h2>Consumer Groups — como Kafka escala consumidores</h2>","fidelityText":"Consumer Groups — como Kafka escala consumidores"},{"id":"kafka-content-30","type":"html","authorship":"legacy-preserved","html":"<p>Vários consumidores com o <strong>mesmo</strong> <code>group.id</code> dividem as partições de um tópico entre si — cada mensagem vai para <strong>um</strong> consumidor do grupo (comportamento de fila). Consumidores com <code>group.id</code> <strong>diferentes</strong> recebem <strong>cada um</strong> uma cópia completa das mensagens (comportamento de pub/sub).</p>","fidelityText":"Vários consumidores com o mesmo group.id dividem as partições de um tópico entre si — cada mensagem vai para um consumidor do grupo (comportamento de fila). Consumidores com group.id diferentes recebem cada um uma cópia completa das mensagens (comportamento de pub/sub)."},{"id":"kafka-content-31","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em um tópico como um grupo de WhatsApp e o <code>group.id</code> como \"qual equipe está lendo\". Se o serviço de e-mail e o serviço de estoque têm <code>group.id</code> diferentes, os dois recebem <strong>toda</strong> mensagem — como duas pessoas diferentes lendo o mesmo grupo. Mas dentro do próprio serviço de e-mail, se você roda 3 instâncias com o <strong>mesmo</strong> <code>group.id</code> (para dividir a carga), cada mensagem vai para só uma das três — como revezar quem responde cada mensagem do grupo, sem duplicar trabalho.</div>","fidelityText":"Pense em um tópico como um grupo de WhatsApp e o group.id como \"qual equipe está lendo\". Se o serviço de e-mail e o serviço de estoque têm group.id diferentes, os dois recebem toda mensagem — como duas pessoas diferentes lendo o mesmo grupo. Mas dentro do próprio serviço de e-mail, se você roda 3 instâncias com o mesmo group.id (para dividir a carga), cada mensagem vai para só uma das três — como revezar quem responde cada mensagem do grupo, sem duplicar trabalho."},{"id":"kafka-content-32","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Kafka</th><th>RabbitMQ</th></tr>\n        <tr><td>Modelo</td><td>Log distribuído, mensagens retidas por tempo configurável</td><td>Fila tradicional, mensagem some ao ser confirmada (ack)</td></tr>\n        <tr><td>Replay</td><td>Consumidor pode reler mensagens antigas</td><td>Não, uma vez consumida e confirmada, some</td></tr>\n        <tr><td>Melhor para</td><td>Alto volume, múltiplos consumidores do mesmo evento, streaming de eventos</td><td>Roteamento de mensagens complexo, filas de prioridade, menor volume</td></tr>\n      </tbody></table>","fidelityText":"KafkaRabbitMQ ModeloLog distribuído, mensagens retidas por tempo configurávelFila tradicional, mensagem some ao ser confirmada (ack) ReplayConsumidor pode reler mensagens antigasNão, uma vez consumida e confirmada, some Melhor paraAlto volume, múltiplos consumidores do mesmo evento, streaming de eventosRoteamento de mensagens complexo, filas de prioridade, menor volume"},{"id":"kafka-content-33","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro</h2>\n        <ul>\n          <li>Sempre trate consumidores como <strong>at-least-once</strong> — projete idempotência desde o início (capítulo 40), não como remendo depois.</li>\n          <li>A chave (<code>key</code>) da mensagem determina a partição — mensagens com a mesma chave sempre vão para a mesma partição, preservando ordem <em>entre elas</em>.</li>\n          <li>Nunca use Kafka como banco de dados principal — ele é ótimo para eventos e streaming, não para consultas ad-hoc como um Postgres.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro Sempre trate consumidores como at-least-once — projete idempotência desde o início (capítulo 40), não como remendo depois. A chave (key) da mensagem determina a partição — mensagens com a mesma chave sempre vão para a mesma partição, preservando ordem entre elas. Nunca use Kafka como banco de dados principal — ele é ótimo para eventos e streaming, não para consultas ad-hoc como um Postgres."},{"id":"kafka-content-34","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Antes de configurar Kafka via Spring (próximo capítulo), pratique só com <code>kafka-console-producer.sh</code> e <code>kafka-console-consumer.sh</code> (ferramentas de linha de comando que vêm com o Kafka) publicando e lendo mensagens de texto simples. Ver o comportamento \"cru\" evita confundir problema de configuração do Spring com problema de entendimento do Kafka em si.</div>","fidelityText":"Antes de configurar Kafka via Spring (próximo capítulo), pratique só com kafka-console-producer.sh e kafka-console-consumer.sh (ferramentas de linha de comando que vêm com o Kafka) publicando e lendo mensagens de texto simples. Ver o comportamento \"cru\" evita confundir problema de configuração do Spring com problema de entendimento do Kafka em si."},{"id":"kafka-exercise-35","type":"exercise","authorship":"legacy-preserved","title":"Exercício 41.1 — Producer e Consumer simples","prompt":"Suba Kafka via Docker Compose. Crie o tópico pedidos-finalizados com 3 partições. Escreva uma classe PedidoProducer que publica uma mensagem JSON simples (id do pedido) sempre que chamada. Escreva uma classe NotificacaoConsumer que consome desse tópico e imprime cada mensagem recebida. Rode os dois e confirme que mensagens publicadas aparecem no consumidor.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 41.1 — Producer e Consumer simplesdifícil Suba Kafka via Docker Compose. Crie o tópico pedidos-finalizados com 3 partições. Escreva uma classe PedidoProducer que publica uma mensagem JSON simples (id do pedido) sempre que chamada. Escreva uma classe NotificacaoConsumer que consome desse tópico e imprime cada mensagem recebida. Rode os dois e confirme que mensagens publicadas aparecem no consumidor. Ver solução kafka-topics.sh --create --topic pedidos-finalizados --bootstrap-server localhost:9092 --partitions 3 public class PedidoProducer { private final KafkaProducer<String, String> producer; public PedidoProducer() { Properties props = new Properties(); props.put(\"bootstrap.servers\", \"localhost:9092\"); props.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\"); props.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\"); this.producer = new KafkaProducer<>(props); } public void publicar(String pedidoId) { producer.send(new ProducerRecord<>(\"pedidos-finalizados\", pedidoId, \"{\\\"id\\\":\\\"\" + pedidoId + \"\\\"}\")); } } public class NotificacaoConsumer { public void iniciar() { Properties props = new Properties(); props.put(\"bootstrap.servers\", \"localhost:9092\"); props.put(\"group.id\", \"servico-notificacao\"); props.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\"); props.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\"); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props); consumer.subscribe(List.of(\"pedidos-finalizados\")); while (true) { var registros = consumer.poll(Duration.ofMillis(500)); for (var r : registros) System.out.println(\"Notificando sobre: \" + r.value()); } } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 41.1 — Producer e Consumer simples</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Suba Kafka via Docker Compose. Crie o tópico <code>pedidos-finalizados</code> com 3 partições. Escreva uma classe <code>PedidoProducer</code> que publica uma mensagem JSON simples (id do pedido) sempre que chamada. Escreva uma classe <code>NotificacaoConsumer</code> que consome desse tópico e imprime cada mensagem recebida. Rode os dois e confirme que mensagens publicadas aparecem no consumidor.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">kafka-topics.sh --create --topic orders-completed --bootstrap-server localhost:9092 --partitions 3</pre>\n<pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">OrderProducer</span> {\n    <span class=\"kw\">private final</span> KafkaProducer&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; producer;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">OrderProducer</span>() {\n        Properties props = <span class=\"kw\">new</span> Properties();\n        props.put(<span class=\"str\">\"bootstrap.servers\"</span>, <span class=\"str\">\"localhost:9092\"</span>);\n        props.put(<span class=\"str\">\"key.serializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringSerializer\"</span>);\n        props.put(<span class=\"str\">\"value.serializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringSerializer\"</span>);\n        <span class=\"kw\">this</span>.producer = <span class=\"kw\">new</span> KafkaProducer&lt;&gt;(props);\n    }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">publish</span>(<span class=\"kw\">String</span> orderId) {\n        producer.send(<span class=\"kw\">new</span> ProducerRecord&lt;&gt;(<span class=\"str\">\"orders-completed\"</span>, orderId, <span class=\"str\">\"{\\\"id\\\":\\\"\"</span> + orderId + <span class=\"str\">\"\\\"}\"</span>));\n    }\n}\n\n<span class=\"kw\">public class</span> <span class=\"cls\">NotificationConsumer</span> {\n    <span class=\"kw\">public void</span> <span class=\"fn\">start</span>() {\n        Properties props = <span class=\"kw\">new</span> Properties();\n        props.put(<span class=\"str\">\"bootstrap.servers\"</span>, <span class=\"str\">\"localhost:9092\"</span>);\n        props.put(<span class=\"str\">\"group.id\"</span>, <span class=\"str\">\"service-notification\"</span>);\n        props.put(<span class=\"str\">\"key.deserializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringDeserializer\"</span>);\n        props.put(<span class=\"str\">\"value.deserializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringDeserializer\"</span>);\n\n        KafkaConsumer&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; consumer = <span class=\"kw\">new</span> KafkaConsumer&lt;&gt;(props);\n        consumer.subscribe(List.of(<span class=\"str\">\"orders-completed\"</span>));\n\n        <span class=\"kw\">while</span> (<span class=\"kw\">true</span>) {\n            <span class=\"kw\">var</span> records = consumer.poll(Duration.ofMillis(500));\n            <span class=\"kw\">for</span> (<span class=\"kw\">var</span> r : records) System.out.println(<span class=\"str\">\"Notificando about: \"</span> + r.value());\n        }\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"kafka-quiz","type":"quiz","authorship":"authored","conceptId":"kafka-topic-partition-offset","prompt":"Por que a key importa em Kafka?","options":[{"id":"kafka-a","label":"Ela influencia a partição e, portanto, a ordem observável daquele fluxo.","correct":true,"explanation":"Ordenação é garantida por partição, não globalmente por todo topic."},{"id":"kafka-b","label":"Ela transforma qualquer topic em transação global.","correct":false,"explanation":"Key não cria transação entre serviços."},{"id":"kafka-c","label":"Ela elimina necessidade de serializador.","correct":false,"explanation":"Key e value também são serializados por contrato."}]}],"resources":[{"id":"apache-kafka-design-phase16","type":"official-docs","title":"Apache Kafka: Design","url":"https://kafka.apache.org/40/documentation.html#design","reinforces":"Log particionado, retenção, consumers e garantias de design.","language":"en","publisher":"Apache Kafka","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"apache-kafka-intro-phase16","type":"official-docs","title":"Apache Kafka: Introduction","url":"https://kafka.apache.org/intro","reinforces":"Conceitos centrais de event streaming, topics e consumers.","language":"en","publisher":"Apache Kafka","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the kafka flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for kafka. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for kafka with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"# docker-compose.yml -- Kafka moderno não precisa mais de Zookeeper separado (modo KRaft):","instruction":"Design the kafka flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for kafka with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"spring-kafka","moduleId":"messaging-eda","order":4,"title":"Spring Kafka — integração","summary":"O que você acabou de escrever manualmente no capítulo 41 (configurar Properties, criar producer/consumer, fazer o loop de poll) é exatamente o boilerplate que o Spring Kafka elimina — mesma relação de \"entenda antes de automatizar\" que já vimos com JDBC → Spring Data JPA. Mas \"eliminar boilerplate\" não significa \"esconder o mecanismo\": este capítulo mostra o que o Spring realmente faz por baixo de KafkaTemplate e @KafkaListener, porque depurar um consumer travado exige saber disso.","objectives":["Usar KafkaTemplate e @KafkaListener sem esconder semântica Kafka","Configurar serialização e erro em Spring Boot","Relacionar listener, offset e transação local","Testar listener como fronteira de integração"],"whyItExists":"Depois de Kafka puro, Spring Kafka mostra adaptação ao ecossistema Spring. A anotação simplifica wiring, mas não muda key, partition, offset, reentrega, serialização nem idempotência.","prerequisiteChapterIds":["kafka","di","json"],"conceptIds":["produzindo-eventos-kafkatemplate-serializer-real-nao-concatenacao-de-str","consumindo-eventos-kafkalistener-o-listener-container-e-concorrencia","commit-de-offset-e-modos-de-acknowledgment","erros-no-consumer-defaulterrorhandler-retry-e-dead-letter-topic","container-factory-explicito-o-que-a-anotacao-esconde","batch-listener-processando-um-lote-inteiro-por-vez","retryabletopic-retry-sem-bloquear-a-particao","transacoes-spring-kafka-coordenando-escrita-e-commit-de-offset","testando-com-testcontainers-codigo-real-nao-mock"],"introducedConceptIds":["spring-kafka-listener-template"],"usedConceptIds":["kafka-producer-consumer-record","consumer-group-rebalance-offset","boot-bootstrap-autoconfig"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"spring-kafka-intuition","type":"intuition","authorship":"authored","title":"Spring Kafka adapta Kafka ao modelo Spring","body":"KafkaTemplate e @KafkaListener reduzem cerimônia de infraestrutura. Mesmo assim, o contrato continua sendo o do Kafka: record, topic, key, serialização, commit, reprocessamento e erro observável.","analogyLimit":"A anotação parece controller HTTP, mas o ciclo de vida assíncrono e offset do broker continuam existindo."},{"id":"spring-kafka-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#kafka\">41 · Kafka na prática</a>, <a class=\"prereq-tag\" href=\"#di\">22 · Injeção de dependência</a>, <a class=\"prereq-tag\" href=\"#json\">25 · JSON &amp; serialização</a></div>\n      </div>","fidelityText":"Spring Dificuldade: Avançado ⏱ ~3h de estudo Pré-requisitos: 41 · Kafka na prática, 22 · Injeção de dependência, 25 · JSON & serialização"},{"id":"spring-kafka-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O que você acabou de escrever manualmente no capítulo 41 (configurar <code>Properties</code>, criar producer/consumer, fazer o loop de <code>poll</code>) é exatamente o boilerplate que o <strong>Spring Kafka</strong> elimina — mesma relação de \"entenda antes de automatizar\" que já vimos com JDBC → Spring Data JPA. Mas \"eliminar boilerplate\" não significa \"esconder o mecanismo\": este capítulo mostra o que o Spring realmente faz por baixo de <code>KafkaTemplate</code> e <code>@KafkaListener</code>, porque depurar um consumer travado exige saber disso.</p>","fidelityText":"O que você acabou de escrever manualmente no capítulo 41 (configurar Properties, criar producer/consumer, fazer o loop de poll) é exatamente o boilerplate que o Spring Kafka elimina — mesma relação de \"entenda antes de automatizar\" que já vimos com JDBC → Spring Data JPA. Mas \"eliminar boilerplate\" não significa \"esconder o mecanismo\": este capítulo mostra o que o Spring realmente faz por baixo de KafkaTemplate e @KafkaListener, porque depurar um consumer travado exige saber disso."},{"id":"spring-kafka-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Produzindo eventos: KafkaTemplate + serializer real, não concatenação de string</h2>","fidelityText":"Produzindo eventos: KafkaTemplate + serializer real, não concatenação de string"},{"id":"spring-kafka-content-4","type":"html","authorship":"legacy-preserved","html":"<p>O primeiro erro comum é montar o JSON manualmente com concatenação de <code>String</code> — funciona só enquanto o payload é trivial, quebra silenciosamente assim que um campo tem aspas, acentos ou um valor <code>null</code>. O jeito correto é declarar um DTO de evento e deixar o <code>JsonSerializer</code> (baseado no Jackson, capítulo 25) fazer a serialização:</p>","fidelityText":"O primeiro erro comum é montar o JSON manualmente com concatenação de String — funciona só enquanto o payload é trivial, quebra silenciosamente assim que um campo tem aspas, acentos ou um valor null. O jeito correto é declarar um DTO de evento e deixar o JsonSerializer (baseado no Jackson, capítulo 25) fazer a serialização:"},{"id":"spring-kafka-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"// DTO do evento -- um record é suficiente e imutável (capítulo 62)\npublic record OrderCompletedEvent(String orderId, BigDecimal total, Instant occurredIn) {}\n\n@Service\npublic class OrderEventPublisher {\n    private final KafkaTemplate<String, OrderCompletedEvent> kafkaTemplate;\n\n    public OrderEventPublisher(KafkaTemplate<String, OrderCompletedEvent> kafkaTemplate) { // injeção, capítulo 22\n        this.kafkaTemplate = kafkaTemplate;\n    }\n\n    public void publishOrderCompleted(OrderCompletedEvent event) {\n        // chave = pedidoId -- garante que eventos do MESMO pedido caem sempre na MESMA partição (ordem preservada, capítulo 41)\n        CompletableFuture<SendResult<String, OrderCompletedEvent>> future =\n            kafkaTemplate.send(\"orders-completed\", event.orderId(), event);\n\n        future.whenComplete((result, error) -> {\n            if (error != null) {\n                // envio assíncrono -- erro de rede/broker só aparece aqui, não na chamada de send()\n                log.error(\"Failure to the publish event of the order {}\", event.orderId(), error);\n            } else {\n                log.info(\"Event published in the partition {} offset {}\",\n                    result.getRecordMetadata().partition(), result.getRecordMetadata().offset());\n            }\n        });\n    }\n}","fidelityText":"// DTO do evento -- um record é suficiente e imutável (capítulo 62) public record PedidoFinalizadoEvent(String pedidoId, BigDecimal total, Instant ocorridoEm) {} @Service public class PedidoEventPublisher { private final KafkaTemplate<String, PedidoFinalizadoEvent> kafkaTemplate; public PedidoEventPublisher(KafkaTemplate<String, PedidoFinalizadoEvent> kafkaTemplate) { // injeção, capítulo 22 this.kafkaTemplate = kafkaTemplate; } public void publicarPedidoFinalizado(PedidoFinalizadoEvent evento) { // chave = pedidoId -- garante que eventos do MESMO pedido caem sempre na MESMA partição (ordem preservada, capítulo 41) CompletableFuture<SendResult<String, PedidoFinalizadoEvent>> futuro = kafkaTemplate.send(\"pedidos-finalizados\", evento.pedidoId(), evento); futuro.whenComplete((resultado, erro) -> { if (erro != null) { // envio assíncrono -- erro de rede/broker só aparece aqui, não na chamada de send() log.error(\"Falha ao publicar evento do pedido {}\", evento.pedidoId(), erro); } else { log.info(\"Evento publicado na partição {} offset {}\", resultado.getRecordMetadata().partition(), resultado.getRecordMetadata().offset()); } }); } }","highlightedHtml":"<span class=\"com\">// DTO do evento -- um record é suficiente e imutável (capítulo 62)</span>\n<span class=\"kw\">public record</span> <span class=\"cls\">OrderCompletedEvent</span>(<span class=\"kw\">String</span> orderId, <span class=\"kw\">BigDecimal</span> total, Instant occurredIn) {}\n\n<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">OrderEventPublisher</span> {\n    <span class=\"kw\">private final</span> KafkaTemplate&lt;<span class=\"kw\">String</span>, OrderCompletedEvent&gt; kafkaTemplate;\n\n    <span class=\"kw\">public</span> <span class=\"fn\">OrderEventPublisher</span>(KafkaTemplate&lt;<span class=\"kw\">String</span>, OrderCompletedEvent&gt; kafkaTemplate) { <span class=\"com\">// injeção, capítulo 22</span>\n        <span class=\"kw\">this</span>.kafkaTemplate = kafkaTemplate;\n    }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">publishOrderCompleted</span>(OrderCompletedEvent event) {\n        <span class=\"com\">// chave = pedidoId -- garante que eventos do MESMO pedido caem sempre na MESMA partição (ordem preservada, capítulo 41)</span>\n        CompletableFuture&lt;SendResult&lt;<span class=\"kw\">String</span>, OrderCompletedEvent&gt;&gt; future =\n            kafkaTemplate.send(<span class=\"str\">\"orders-completed\"</span>, event.orderId(), event);\n\n        future.whenComplete((result, error) -&gt; {\n            <span class=\"kw\">if</span> (error != <span class=\"kw\">null</span>) {\n                <span class=\"com\">// envio assíncrono -- erro de rede/broker só aparece aqui, não na chamada de send()</span>\n                log.error(<span class=\"str\">\"Failure to the publish event of the order {}\"</span>, event.orderId(), error);\n            } <span class=\"kw\">else</span> {\n                log.info(<span class=\"str\">\"Event published in the partition {} offset {}\"</span>,\n                    result.getRecordMetadata().partition(), result.getRecordMetadata().offset());\n            }\n        });\n    }\n}","caption":"Exemplo executável de spring-kafka.","explanation":["KafkaTemplate publica records usando serializers e propriedades do producer configurado pelo Spring.","O envio é assíncrono: o resultado (sucesso ou falha) só é conhecido através do CompletableFuture retornado, não no momento da chamada."],"commonMistakes":["Ignorar falha assíncrona de envio","Concatenar JSON manualmente em vez de usar um serializer real"]},{"id":"spring-kafka-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties\nspring.kafka.bootstrap-servers=localhost:9092\nspring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer\nspring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer\nspring.kafka.producer.acks=all # espera confirmação de todas as réplicas in-sync (capítulo 35 -- durabilidade)","fidelityText":"# application.properties spring.kafka.bootstrap-servers=localhost:9092 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.producer.acks=all # espera confirmação de todas as réplicas in-sync (capítulo 35 -- durabilidade)","highlightedHtml":"<span class=\"com\"># application.properties</span>\nspring.kafka.bootstrap-servers=localhost:9092\nspring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer\nspring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer\nspring.kafka.producer.acks=all <span class=\"com\"># espera confirmação de todas as réplicas in-sync (capítulo 35 -- durabilidade)</span>","caption":"Exemplo executável de spring-kafka.","explanation":["As propriedades de producer definem os serializers de chave/valor e a política de confirmação (acks) usadas pelo KafkaTemplate autoconfigurado.","acks=all prioriza durabilidade sobre latência de confirmação."],"commonMistakes":["Usar StringSerializer para um valor que não é String","Não declarar acks e assumir a política default"]},{"id":"spring-kafka-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>send()</code> é assíncrono:</b> ele retorna imediatamente um <code>CompletableFuture</code> (capítulo 14) — o método <code>publicarPedidoFinalizado</code> não espera o broker confirmar nada antes de retornar. Se você precisa saber se a publicação teve sucesso antes de continuar (por exemplo, só marcar o pedido como \"notificado\" depois do envio confirmado), trate o <code>CompletableFuture</code> explicitamente, como no exemplo acima — nunca ignore o resultado do <code>send()</code> assumindo que \"se não lançou exceção na hora, deu certo\".</div>","fidelityText":"send() é assíncrono: ele retorna imediatamente um CompletableFuture (capítulo 14) — o método publicarPedidoFinalizado não espera o broker confirmar nada antes de retornar. Se você precisa saber se a publicação teve sucesso antes de continuar (por exemplo, só marcar o pedido como \"notificado\" depois do envio confirmado), trate o CompletableFuture explicitamente, como no exemplo acima — nunca ignore o resultado do send() assumindo que \"se não lançou exceção na hora, deu certo\"."},{"id":"spring-kafka-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Consumindo eventos: @KafkaListener, o listener container e concorrência</h2>","fidelityText":"Consumindo eventos: @KafkaListener, o listener container e concorrência"},{"id":"spring-kafka-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class NotificationListener {\n    @KafkaListener(\n        topics = \"orders-completed\",\n        groupId = \"service-notification\",\n        concurrency = \"3\" // até 3 threads consumidoras neste processo, uma por partição atribuída\n    )\n    public void listen(OrderCompletedEvent event) {\n        System.out.println(\"Notificando about the order \" + event.orderId());\n    }\n}","fidelityText":"@Service public class NotificacaoListener { @KafkaListener( topics = \"pedidos-finalizados\", groupId = \"servico-notificacao\", concurrency = \"3\" // até 3 threads consumidoras neste processo, uma por partição atribuída ) public void ouvir(PedidoFinalizadoEvent evento) { System.out.println(\"Notificando sobre o pedido \" + evento.pedidoId()); } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">NotificationListener</span> {\n    <span class=\"annotation\">@KafkaListener</span>(\n        topics = <span class=\"str\">\"orders-completed\"</span>,\n        groupId = <span class=\"str\">\"service-notification\"</span>,\n        concurrency = <span class=\"str\">\"3\"</span> <span class=\"com\">// até 3 threads consumidoras neste processo, uma por partição atribuída</span>\n    )\n    <span class=\"kw\">public void</span> <span class=\"fn\">listen</span>(OrderCompletedEvent event) {\n        System.out.println(<span class=\"str\">\"Notificando about the order \"</span> + event.orderId());\n    }\n}","caption":"Exemplo executável de spring-kafka.","explanation":["@KafkaListener declara o ponto de consumo, mas o método precisa validar payload, tratar erro e preservar idempotência.","concurrency define quantas threads consumidoras o listener container sobe, nunca mais que o número de partições atribuíveis."],"commonMistakes":["Fazer efeito não idempotente antes de validar a mensagem","Assumir que listener roda exatamente uma vez"]},{"id":"spring-kafka-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>@KafkaListener</code> <strong>não</strong> cria \"uma thread dedicada\" — ele cria um <strong>listener container</strong> (<code>ConcurrentMessageListenerContainer</code>) que gerencia um ou mais <em>consumer threads</em>, cada um rodando o equivalente ao loop <code>while (true) { poll(...) }</code> que você escreveu manualmente no capítulo 41. A propriedade <code>concurrency</code> define quantas threads (e portanto quantas instâncias internas de <code>Consumer</code>) o container sobe para este listener, dentro deste processo. Uma regra que a Kafka já te ensinou no capítulo anterior continua valendo aqui: o número efetivo de consumidores ativos <strong>de um mesmo consumer group</strong> nunca ultrapassa o número de partições do tópico — se o tópico tem 3 partições e você configurar <code>concurrency = 5</code>, 2 threads ficam ociosas, sem nenhuma partição atribuída. Cada thread do container processa suas partições atribuídas sequencialmente e sozinha; não há paralelismo dentro de uma mesma partição, exatamente como no consumer \"cru\" do capítulo 41 — é isso que preserva a ordem de eventos com a mesma chave.</div>","fidelityText":"@KafkaListener não cria \"uma thread dedicada\" — ele cria um listener container (ConcurrentMessageListenerContainer) que gerencia um ou mais consumer threads, cada um rodando o equivalente ao loop while (true) { poll(...) } que você escreveu manualmente no capítulo 41. A propriedade concurrency define quantas threads (e portanto quantas instâncias internas de Consumer) o container sobe para este listener, dentro deste processo. Uma regra que a Kafka já te ensinou no capítulo anterior continua valendo aqui: o número efetivo de consumidores ativos de um mesmo consumer group nunca ultrapassa o número de partições do tópico — se o tópico tem 3 partições e você configurar concurrency = 5, 2 threads ficam ociosas, sem nenhuma partição atribuída. Cada thread do container processa suas partições atribuídas sequencialmente e sozinha; não há paralelismo dentro de uma mesma partição, exatamente como no consumer \"cru\" do capítulo 41 — é isso que preserva a ordem de eventos com a mesma chave."},{"id":"spring-kafka-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Commit de offset e modos de acknowledgment</h2>","fidelityText":"Commit de offset e modos de acknowledgment"},{"id":"spring-kafka-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Por padrão (<code>AckMode.BATCH</code>), o container comita o offset automaticamente depois que o lote de registros retornado pelo <code>poll()</code> é processado com sucesso pelo seu método <code>@KafkaListener</code> — isso te dá <strong>at-least-once</strong>: se o processo cair depois de processar mas antes de comitar, a mensagem será reentregue e processada de novo no restart. Se sua lógica não for naturalmente idempotente (capítulo 39), processar duas vezes pode duplicar efeitos (enviar duas notificações, debitar duas vezes).</p>","fidelityText":"Por padrão (AckMode.BATCH), o container comita o offset automaticamente depois que o lote de registros retornado pelo poll() é processado com sucesso pelo seu método @KafkaListener — isso te dá at-least-once: se o processo cair depois de processar mas antes de comitar, a mensagem será reentregue e processada de novo no restart. Se sua lógica não for naturalmente idempotente (capítulo 39), processar duas vezes pode duplicar efeitos (enviar duas notificações, debitar duas vezes)."},{"id":"spring-kafka-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"spring.kafka.consumer.enable-auto-commit=false # deixe o container controlar o commit, não o cliente Kafka cru\nspring.kafka.listener.ack-mode=record # comita a cada registro processado (mais seguro, mais overhead)","fidelityText":"spring.kafka.consumer.enable-auto-commit=false # deixe o container controlar o commit, não o cliente Kafka cru spring.kafka.listener.ack-mode=record # comita a cada registro processado (mais seguro, mais overhead)","highlightedHtml":"spring.kafka.consumer.enable-auto-commit=false <span class=\"com\"># deixe o container controlar o commit, não o cliente Kafka cru</span>\nspring.kafka.listener.ack-mode=record <span class=\"com\"># comita a cada registro processado (mais seguro, mais overhead)</span>","caption":"Exemplo executável de spring-kafka.","explanation":["Desabilitar o auto-commit do cliente Kafka cru entrega o controle de commit ao listener container do Spring.","ack-mode=record comita a cada registro processado, priorizando segurança sobre throughput."],"commonMistakes":["Deixar enable-auto-commit=true e ainda assim tratar erro manualmente","Achar que ack-mode=record elimina duplicidade -- ainda é at-least-once"]},{"id":"spring-kafka-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>\"Exactly once\" tem escopo limitado:</b> Kafka oferece semântica <em>exactly-once</em> apenas dentro de transações Kafka-para-Kafka (ler de um tópico, escrever em outro, dentro da mesma transação). Assim que seu listener produz um <strong>efeito colateral fora do Kafka</strong> (enviar e-mail, gravar em outro banco via HTTP), você está de volta ao mundo de <em>at-least-once</em> — a defesa real contra duplicidade é fazer o efeito colateral ser <strong>idempotente</strong> (capítulo 39), não confiar em uma garantia de entrega única.</div>","fidelityText":"\"Exactly once\" tem escopo limitado: Kafka oferece semântica exactly-once apenas dentro de transações Kafka-para-Kafka (ler de um tópico, escrever em outro, dentro da mesma transação). Assim que seu listener produz um efeito colateral fora do Kafka (enviar e-mail, gravar em outro banco via HTTP), você está de volta ao mundo de at-least-once — a defesa real contra duplicidade é fazer o efeito colateral ser idempotente (capítulo 39), não confiar em uma garantia de entrega única."},{"id":"spring-kafka-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Erros no consumer: DefaultErrorHandler, retry e Dead Letter Topic</h2>","fidelityText":"Erros no consumer: DefaultErrorHandler, retry e Dead Letter Topic"},{"id":"spring-kafka-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Se <code>ouvir(...)</code> lançar uma exceção, o comportamento padrão do Spring Kafka não é simplesmente travar o listener nem descartar a mensagem silenciosamente — é reprocessar com retry configurável e, esgotadas as tentativas, desviar a mensagem para um <strong>Dead Letter Topic</strong> (DLT) em vez de bloquear o processamento de tudo que vem depois dela:</p>","fidelityText":"Se ouvir(...) lançar uma exceção, o comportamento padrão do Spring Kafka não é simplesmente travar o listener nem descartar a mensagem silenciosamente — é reprocessar com retry configurável e, esgotadas as tentativas, desviar a mensagem para um Dead Letter Topic (DLT) em vez de bloquear o processamento de tudo que vem depois dela:"},{"id":"spring-kafka-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"@Bean\npublic DefaultErrorHandler errorHandler(KafkaTemplate<Object, Object> template) {\n    var recoverer = new DeadLetterPublishingRecoverer(template); // publica no tópico \"pedidos-finalizados.DLT\"\n    var backoff = new FixedBackOff(1000L, 3L); // 3 tentativas, 1s entre elas\n    return new DefaultErrorHandler(recoverer, backoff);\n}","fidelityText":"@Bean public DefaultErrorHandler errorHandler(KafkaTemplate<Object, Object> template) { var recoverer = new DeadLetterPublishingRecoverer(template); // publica no tópico \"pedidos-finalizados.DLT\" var backoff = new FixedBackOff(1000L, 3L); // 3 tentativas, 1s entre elas return new DefaultErrorHandler(recoverer, backoff); }","highlightedHtml":"<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> DefaultErrorHandler <span class=\"fn\">errorHandler</span>(KafkaTemplate&lt;<span class=\"kw\">Object</span>, <span class=\"kw\">Object</span>&gt; template) {\n    <span class=\"kw\">var</span> recoverer = <span class=\"kw\">new</span> DeadLetterPublishingRecoverer(template); <span class=\"com\">// publica no tópico \"pedidos-finalizados.DLT\"</span>\n    <span class=\"kw\">var</span> backoff = <span class=\"kw\">new</span> FixedBackOff(<span class=\"num\">1000L</span>, <span class=\"num\">3L</span>); <span class=\"com\">// 3 tentativas, 1s entre elas</span>\n    <span class=\"kw\">return new</span> DefaultErrorHandler(recoverer, backoff);\n}","caption":"Exemplo executável de spring-kafka.","explanation":["DefaultErrorHandler com DeadLetterPublishingRecoverer tenta novamente com backoff e, esgotadas as tentativas, desvia a mensagem para um Dead Letter Topic.","Retry não resolve mensagem envenenada (defeito de dados) -- só falhas transitórias se beneficiam de nova tentativa."],"commonMistakes":["Aplicar retry indiscriminadamente a erros de dado permanentes","Não monitorar o volume do DLT"]},{"id":"spring-kafka-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Mensagem envenenada (poison message):</b> se o erro é um defeito de dados (um JSON malformado que nunca vai deserializar, um campo obrigatório ausente), retry não resolve nada — a mesma mensagem falha as 3 tentativas e só então vai pro DLT, atrasando o processamento das mensagens seguintes daquela partição enquanto isso. Diferencie, no seu código, uma falha transitória (banco fora do ar, timeout de rede — vale retry) de uma falha permanente de dados (vale ir direto pro DLT, sem gastar tentativas).</div>","fidelityText":"Mensagem envenenada (poison message): se o erro é um defeito de dados (um JSON malformado que nunca vai deserializar, um campo obrigatório ausente), retry não resolve nada — a mesma mensagem falha as 3 tentativas e só então vai pro DLT, atrasando o processamento das mensagens seguintes daquela partição enquanto isso. Diferencie, no seu código, uma falha transitória (banco fora do ar, timeout de rede — vale retry) de uma falha permanente de dados (vale ir direto pro DLT, sem gastar tentativas)."},{"id":"spring-kafka-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Container factory explícito — o que a anotação esconde</h2>","fidelityText":"Container factory explícito — o que a anotação esconde"},{"id":"spring-kafka-content-20","type":"html","authorship":"legacy-preserved","html":"<p><code>@KafkaListener</code> sozinho usa a <code>ConcurrentKafkaListenerContainerFactory</code> padrão que o Spring Boot auto-configura. Para controlar concorrência, modo de ack e comportamento de erro para <strong>todos</strong> os listeners do jeito que sua aplicação precisa (não o padrão genérico), declare o bean explicitamente:</p>","fidelityText":"@KafkaListener sozinho usa a ConcurrentKafkaListenerContainerFactory padrão que o Spring Boot auto-configura. Para controlar concorrência, modo de ack e comportamento de erro para todos os listeners do jeito que sua aplicação precisa (não o padrão genérico), declare o bean explicitamente:"},{"id":"spring-kafka-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"@Bean\npublic ConcurrentKafkaListenerContainerFactory<String, OrderCompletedEvent> kafkaListenerContainerFactory(\n        ConsumerFactory<String, OrderCompletedEvent> consumerFactory) {\n    var factory = new ConcurrentKafkaListenerContainerFactory<String, OrderCompletedEvent>();\n    factory.setConsumerFactory(consumerFactory);\n    factory.setConcurrency(3); // mesma regra: nunca ultrapassa o número de partições\n    factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);\n    return factory;\n}","fidelityText":"@Bean public ConcurrentKafkaListenerContainerFactory<String, PedidoFinalizadoEvent> kafkaListenerContainerFactory( ConsumerFactory<String, PedidoFinalizadoEvent> consumerFactory) { var factory = new ConcurrentKafkaListenerContainerFactory<String, PedidoFinalizadoEvent>(); factory.setConsumerFactory(consumerFactory); factory.setConcurrency(3); // mesma regra: nunca ultrapassa o número de partições factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE); return factory; }","highlightedHtml":"<span class=\"annotation\">@Bean</span>\n<span class=\"kw\">public</span> ConcurrentKafkaListenerContainerFactory&lt;<span class=\"kw\">String</span>, OrderCompletedEvent&gt; <span class=\"fn\">kafkaListenerContainerFactory</span>(\n        ConsumerFactory&lt;<span class=\"kw\">String</span>, OrderCompletedEvent&gt; consumerFactory) {\n    <span class=\"kw\">var</span> factory = <span class=\"kw\">new</span> ConcurrentKafkaListenerContainerFactory&lt;<span class=\"kw\">String</span>, OrderCompletedEvent&gt;();\n    factory.setConsumerFactory(consumerFactory);\n    factory.setConcurrency(<span class=\"num\">3</span>); <span class=\"com\">// mesma regra: nunca ultrapassa o número de partições</span>\n    factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);\n    <span class=\"kw\">return</span> factory;\n}","caption":"Exemplo executável de spring-kafka.","explanation":["O bean explícito de ConcurrentKafkaListenerContainerFactory substitui o padrão auto-configurado, controlando concorrência e ack mode para todos os listeners que o usarem.","AckMode.MANUAL_IMMEDIATE exige que o próprio método chame Acknowledgment.acknowledge() -- o commit deixa de ser automático."],"commonMistakes":["Configurar MANUAL_IMMEDIATE sem nunca chamar ack.acknowledge(), travando o avanço do offset","Duplicar factories concorrentes para o mesmo caso de uso sem necessidade"]},{"id":"spring-kafka-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>Batch listener — processando um lote inteiro por vez</h2>","fidelityText":"Batch listener — processando um lote inteiro por vez"},{"id":"spring-kafka-content-23","type":"html","authorship":"legacy-preserved","html":"<p>Por padrão, <code>@KafkaListener</code> chama seu método uma vez por <strong>mensagem</strong>. Para operações que se beneficiam de processar várias mensagens juntas (um <code>INSERT</code> em lote no banco, por exemplo), ative o modo batch — o método passa a receber a <code>List</code> inteira que o <code>poll()</code> interno retornou:</p>","fidelityText":"Por padrão, @KafkaListener chama seu método uma vez por mensagem. Para operações que se beneficiam de processar várias mensagens juntas (um INSERT em lote no banco, por exemplo), ative o modo batch — o método passa a receber a List inteira que o poll() interno retornou:"},{"id":"spring-kafka-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"@KafkaListener(topics = \"orders-completed\", groupId = \"service-notification\", batch = \"true\")\npublic void listenInBatch(List<OrderCompletedEvent> events, Acknowledgment ack) {\n    for (OrderCompletedEvent event : events) {\n        System.out.println(\"Notificando about the order \" + event.orderId());\n    }\n    ack.acknowledge(); // AckMode.MANUAL_IMMEDIATE -- só comita depois que o lote inteiro processou\n}","fidelityText":"@KafkaListener(topics = \"pedidos-finalizados\", groupId = \"servico-notificacao\", batch = \"true\") public void ouvirEmLote(List<PedidoFinalizadoEvent> eventos, Acknowledgment ack) { for (PedidoFinalizadoEvent evento : eventos) { System.out.println(\"Notificando sobre o pedido \" + evento.pedidoId()); } ack.acknowledge(); // AckMode.MANUAL_IMMEDIATE -- só comita depois que o lote inteiro processou }","highlightedHtml":"<span class=\"annotation\">@KafkaListener</span>(topics = <span class=\"str\">\"orders-completed\"</span>, groupId = <span class=\"str\">\"service-notification\"</span>, batch = <span class=\"str\">\"true\"</span>)\n<span class=\"kw\">public void</span> <span class=\"fn\">listenInBatch</span>(List&lt;OrderCompletedEvent&gt; events, Acknowledgment ack) {\n    <span class=\"kw\">for</span> (OrderCompletedEvent event : events) {\n        System.out.println(<span class=\"str\">\"Notificando about the order \"</span> + event.orderId());\n    }\n    ack.acknowledge(); <span class=\"com\">// AckMode.MANUAL_IMMEDIATE -- só comita depois que o lote inteiro processou</span>\n}","caption":"Exemplo executável de spring-kafka.","explanation":["batch = \"true\" faz o método receber a lista inteira de registros que o poll() interno retornou, em vez de um registro por chamada.","ack.acknowledge() só avança o offset depois que o lote inteiro foi processado com sucesso."],"commonMistakes":["Processar parte do lote, falhar no meio e ainda assim chamar acknowledge()","Usar batch listener para lógica que precisa de garantia por mensagem individual"]},{"id":"spring-kafka-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O parâmetro <code>Acknowledgment ack</code> só existe quando <code>AckMode</code> é <code>MANUAL</code> ou <code>MANUAL_IMMEDIATE</code> (configurado no container factory acima). Com ele, <strong>seu código</strong> decide exatamente quando o offset avança — chamando <code>ack.acknowledge()</code> só depois que o efeito de negócio realmente terminou, em vez de confiar no commit automático do <code>AckMode.BATCH</code> (padrão), que comita assim que o método retorna sem lançar exceção.</div>","fidelityText":"O parâmetro Acknowledgment ack só existe quando AckMode é MANUAL ou MANUAL_IMMEDIATE (configurado no container factory acima). Com ele, seu código decide exatamente quando o offset avança — chamando ack.acknowledge() só depois que o efeito de negócio realmente terminou, em vez de confiar no commit automático do AckMode.BATCH (padrão), que comita assim que o método retorna sem lançar exceção."},{"id":"spring-kafka-content-26","type":"html","authorship":"legacy-preserved","html":"<h2><code>@RetryableTopic</code> — retry sem bloquear a partição</h2>","fidelityText":"@RetryableTopic — retry sem bloquear a partição"},{"id":"spring-kafka-content-27","type":"html","authorship":"legacy-preserved","html":"<p>O <code>DefaultErrorHandler</code> do capítulo anterior faz retry <strong>bloqueante</strong>: enquanto tenta de novo, nenhuma mensagem seguinte daquela partição é processada. <code>@RetryableTopic</code> resolve isso criando tópicos intermediários automaticamente — cada tentativa vai para um tópico próprio, liberando a partição original imediatamente:</p>","fidelityText":"O DefaultErrorHandler do capítulo anterior faz retry bloqueante: enquanto tenta de novo, nenhuma mensagem seguinte daquela partição é processada. @RetryableTopic resolve isso criando tópicos intermediários automaticamente — cada tentativa vai para um tópico próprio, liberando a partição original imediatamente:"},{"id":"spring-kafka-code-28","type":"code","authorship":"legacy-preserved","language":"java","source":"@RetryableTopic(\n    attempts = \"4\", // tentativa original + 3 retries\n    backoff = @Backoff(delay = 1000, multiplier = 2.0), // 1s, 2s, 4s\n    dltTopicSuffix = \".dlt\"\n)\n@KafkaListener(topics = \"orders-completed\", groupId = \"service-notification\")\npublic void listen(OrderCompletedEvent event) {\n    System.out.println(\"Notificando about the order \" + event.orderId());\n}","fidelityText":"@RetryableTopic( attempts = \"4\", // tentativa original + 3 retries backoff = @Backoff(delay = 1000, multiplier = 2.0), // 1s, 2s, 4s dltTopicSuffix = \".dlt\" ) @KafkaListener(topics = \"pedidos-finalizados\", groupId = \"servico-notificacao\") public void ouvir(PedidoFinalizadoEvent evento) { System.out.println(\"Notificando sobre o pedido \" + evento.pedidoId()); }","highlightedHtml":"<span class=\"annotation\">@RetryableTopic</span>(\n    attempts = <span class=\"str\">\"4\"</span>, <span class=\"com\">// tentativa original + 3 retries</span>\n    backoff = <span class=\"annotation\">@Backoff</span>(delay = <span class=\"num\">1000</span>, multiplier = <span class=\"num\">2.0</span>), <span class=\"com\">// 1s, 2s, 4s</span>\n    dltTopicSuffix = <span class=\"str\">\".dlt\"</span>\n)\n<span class=\"annotation\">@KafkaListener</span>(topics = <span class=\"str\">\"orders-completed\"</span>, groupId = <span class=\"str\">\"service-notification\"</span>)\n<span class=\"kw\">public void</span> <span class=\"fn\">listen</span>(OrderCompletedEvent event) {\n    System.out.println(<span class=\"str\">\"Notificando about the order \"</span> + event.orderId());\n}","caption":"Exemplo executável de spring-kafka.","explanation":["@RetryableTopic cria tópicos intermediários automaticamente (retry-0, retry-1...) para cada tentativa, sem bloquear a partição original.","dltTopicSuffix define o nome do tópico final quando todas as tentativas se esgotam."],"commonMistakes":["Usar @RetryableTopic quando ordem estrita é um requisito do domínio, sem avaliar o efeito colateral nos tópicos intermediários","Não monitorar os tópicos -retry-N criados automaticamente"]},{"id":"spring-kafka-content-29","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b><code>@RetryableTopic</code> cria tópicos <code>pedidos-finalizados-retry-0</code>, <code>-retry-1</code> etc. automaticamente</b> — cada tentativa republica na próxima partição-tópico da cadeia, com o backoff aplicado como delay antes da entrega naquele tópico. Isso tem um efeito colateral real: mensagens em retry saem da ordem original do tópico principal, porque cada retry vive em um tópico separado. Se sua chave de particionamento existe para garantir ordem (capítulo 41), documente que essa garantia vale só no caminho feliz, sem retry.</div>","fidelityText":"@RetryableTopic cria tópicos pedidos-finalizados-retry-0, -retry-1 etc. automaticamente — cada tentativa republica na próxima partição-tópico da cadeia, com o backoff aplicado como delay antes da entrega naquele tópico. Isso tem um efeito colateral real: mensagens em retry saem da ordem original do tópico principal, porque cada retry vive em um tópico separado. Se sua chave de particionamento existe para garantir ordem (capítulo 41), documente que essa garantia vale só no caminho feliz, sem retry."},{"id":"spring-kafka-content-30","type":"html","authorship":"legacy-preserved","html":"<h2>Transações Spring Kafka — coordenando escrita e commit de offset</h2>","fidelityText":"Transações Spring Kafka — coordenando escrita e commit de offset"},{"id":"spring-kafka-content-31","type":"html","authorship":"legacy-preserved","html":"<p>Quando um listener lê de um tópico e escreve em outro (ou faz várias escritas), transações Kafka amarram tudo — a escrita nos tópicos de saída e o commit do offset de entrada — numa única unidade atômica:</p>","fidelityText":"Quando um listener lê de um tópico e escreve em outro (ou faz várias escritas), transações Kafka amarram tudo — a escrita nos tópicos de saída e o commit do offset de entrada — numa única unidade atômica:"},{"id":"spring-kafka-code-32","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties\nspring.kafka.producer.transaction-id-prefix=order-tx-","fidelityText":"# application.properties spring.kafka.producer.transaction-id-prefix=pedido-tx-","highlightedHtml":"<span class=\"com\"># application.properties</span>\nspring.kafka.producer.transaction-id-prefix=order-tx-","caption":"Exemplo executável de spring-kafka.","explanation":["transaction-id-prefix habilita o producer transacional do Spring Kafka -- necessário para @Transactional(\"kafkaTransactionManager\") funcionar."],"commonMistakes":["Usar @Transactional(\"kafkaTransactionManager\") sem configurar transaction-id-prefix"]},{"id":"spring-kafka-code-33","type":"code","authorship":"legacy-preserved","language":"java","source":"@Transactional(\"kafkaTransactionManager\")\n@KafkaListener(topics = \"orders-completed\", groupId = \"service-consolidated\")\npublic void consolidarAndPublish(OrderCompletedEvent event) {\n    // sendOffsetsToTransaction amarra commit do offset a mesma transacao da escrita\n    kafkaTemplate.send(\"orders-consolidated\", event.orderId(), consolidar(event));\n    // se essa linha lançar exceção, o offset de entrada NÃO avança e a escrita acima é abortada\n}","fidelityText":"@Transactional(\"kafkaTransactionManager\") @KafkaListener(topics = \"pedidos-finalizados\", groupId = \"servico-consolidado\") public void consolidarEPublicar(PedidoFinalizadoEvent evento) { // sendOffsetsToTransaction amarra commit do offset a mesma transacao da escrita kafkaTemplate.send(\"pedidos-consolidados\", evento.pedidoId(), consolidar(evento)); // se essa linha lançar exceção, o offset de entrada NÃO avança e a escrita acima é abortada }","highlightedHtml":"<span class=\"annotation\">@Transactional</span>(<span class=\"str\">\"kafkaTransactionManager\"</span>)\n<span class=\"annotation\">@KafkaListener</span>(topics = <span class=\"str\">\"orders-completed\"</span>, groupId = <span class=\"str\">\"service-consolidated\"</span>)\n<span class=\"kw\">public void</span> <span class=\"fn\">consolidarAndPublish</span>(OrderCompletedEvent event) {\n    <span class=\"com\">// sendOffsetsToTransaction amarra commit do offset a mesma transacao da escrita</span>\n    kafkaTemplate.send(<span class=\"str\">\"orders-consolidated\"</span>, event.orderId(), consolidar(event));\n    <span class=\"com\">// se essa linha lançar exceção, o offset de entrada NÃO avança e a escrita acima é abortada</span>\n}","caption":"Exemplo executável de spring-kafka.","explanation":["@Transactional(\"kafkaTransactionManager\") amarra a escrita no tópico de saída e o commit do offset de entrada na mesma transação Kafka.","Uma exceção nesse método aborta a transação inteira -- nem a escrita nem o avanço do offset acontecem."],"commonMistakes":["Achar que essa transação também protege uma escrita simultânea em banco relacional fora do Kafka","Esquecer que o consumer do tópico de saída precisa de isolation.level=read_committed para não ver dados de transação abortada"]},{"id":"spring-kafka-content-34","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>\"Exactly-once\" continua limitado ao Kafka:</b> <code>@Transactional(\"kafkaTransactionManager\")</code> garante atomicidade entre ler de um tópico, escrever em outro e comitar o offset — tudo dentro do Kafka. Assim que o método também grava num banco relacional fora dessa transação, essa garantia não se estende ao banco: volte à discussão do capítulo anterior sobre efeitos colaterais externos e idempotência.</div>","fidelityText":"\"Exactly-once\" continua limitado ao Kafka: @Transactional(\"kafkaTransactionManager\") garante atomicidade entre ler de um tópico, escrever em outro e comitar o offset — tudo dentro do Kafka. Assim que o método também grava num banco relacional fora dessa transação, essa garantia não se estende ao banco: volte à discussão do capítulo anterior sobre efeitos colaterais externos e idempotência."},{"id":"spring-kafka-content-35","type":"html","authorship":"legacy-preserved","html":"<h2>Testando com Testcontainers — código real, não mock</h2>","fidelityText":"Testando com Testcontainers — código real, não mock"},{"id":"spring-kafka-content-36","type":"html","authorship":"legacy-preserved","html":"<p>Testar contra um Kafka real (via Testcontainers, capítulo 55) é mais confiável que mockar <code>KafkaTemplate</code>: um mock não valida serialização, particionamento por chave, nem o comportamento do listener container. Suba um broker Kafka efêmero por execução de teste, publique um evento real, e verifique que o listener reagiu — do jeito que você testaria qualquer integração com um sistema externo.</p>","fidelityText":"Testar contra um Kafka real (via Testcontainers, capítulo 55) é mais confiável que mockar KafkaTemplate: um mock não valida serialização, particionamento por chave, nem o comportamento do listener container. Suba um broker Kafka efêmero por execução de teste, publique um evento real, e verifique que o listener reagiu — do jeito que você testaria qualquer integração com um sistema externo."},{"id":"spring-kafka-code-37","type":"code","authorship":"legacy-preserved","language":"java","source":"@SpringBootTest\n@Testcontainers\nclass OrderEventPublisherTest {\n\n    @Container\n    @ServiceConnection // Spring Boot 3.1+ -- injeta bootstrap-servers automaticamente, sem @DynamicPropertySource\n    static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"apache/kafka:3.7.0\"));\n\n    @Autowired\n    private OrderEventPublisher publisher;\n\n    @Test\n    void shouldPublishEventQueUmConsumerConsegueRead() throws Exception {\n        try (KafkaConsumer<String, String> consumer = createConsumerOfTest()) {\n            consumer.subscribe(List.of(\"orders-completed\"));\n            publisher.publish(\"order-99\");\n\n            var records = consumer.poll(Duration.ofSeconds(5));\n            assertEquals(1, records.count());\n        }\n    }\n}","fidelityText":"@SpringBootTest @Testcontainers class PedidoEventPublisherTest { @Container @ServiceConnection // Spring Boot 3.1+ -- injeta bootstrap-servers automaticamente, sem @DynamicPropertySource static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"apache/kafka:3.7.0\")); @Autowired private PedidoEventPublisher publisher; @Test void devePublicarEventoQueUmConsumerConsegueLer() throws Exception { try (KafkaConsumer<String, String> consumer = criarConsumerDeTeste()) { consumer.subscribe(List.of(\"pedidos-finalizados\")); publisher.publicar(\"pedido-99\"); var registros = consumer.poll(Duration.ofSeconds(5)); assertEquals(1, registros.count()); } } }","highlightedHtml":"<span class=\"annotation\">@SpringBootTest</span>\n<span class=\"annotation\">@Testcontainers</span>\n<span class=\"kw\">class</span> <span class=\"cls\">OrderEventPublisherTest</span> {\n\n    <span class=\"annotation\">@Container</span>\n    <span class=\"annotation\">@ServiceConnection</span> <span class=\"com\">// Spring Boot 3.1+ -- injeta bootstrap-servers automaticamente, sem @DynamicPropertySource</span>\n    <span class=\"kw\">static</span> KafkaContainer kafka = <span class=\"kw\">new</span> KafkaContainer(DockerImageName.parse(<span class=\"str\">\"apache/kafka:3.7.0\"</span>));\n\n    <span class=\"annotation\">@Autowired</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">OrderEventPublisher</span> publisher;\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldPublishEventQueUmConsumerConsegueRead</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n        <span class=\"kw\">try</span> (KafkaConsumer&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; consumer = createConsumerOfTest()) {\n            consumer.subscribe(List.of(<span class=\"str\">\"orders-completed\"</span>));\n            publisher.publish(<span class=\"str\">\"order-99\"</span>);\n\n            <span class=\"kw\">var</span> records = consumer.poll(Duration.ofSeconds(<span class=\"num\">5</span>));\n            assertEquals(<span class=\"num\">1</span>, records.count());\n        }\n    }\n}","caption":"Exemplo executável de spring-kafka.","explanation":["@ServiceConnection injeta spring.kafka.bootstrap-servers automaticamente a partir do KafkaContainer, sem @DynamicPropertySource manual.","O teste sobe um consumer real e prova que a mensagem publicada chega -- não mocka KafkaTemplate nem particionamento."],"commonMistakes":["Mockar KafkaTemplate e achar que isso valida serialização/particionamento","Esquecer que testes com Kafka real são mais lentos -- reservar para os fluxos mais críticos"]},{"id":"spring-kafka-content-38","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><code>@ServiceConnection</code> (o mesmo padrão já usado com Testcontainers Postgres no capítulo de Testcontainers) elimina o boilerplate de <code>@DynamicPropertySource</code> — o Spring Boot reconhece o container e injeta <code>spring.kafka.bootstrap-servers</code> automaticamente. Use <code>@DynamicPropertySource</code> manual só quando o Spring Boot ainda não reconhecer o tipo de container.</div>","fidelityText":"@ServiceConnection (o mesmo padrão já usado com Testcontainers Postgres no capítulo de Testcontainers) elimina o boilerplate de @DynamicPropertySource — o Spring Boot reconhece o container e injeta spring.kafka.bootstrap-servers automaticamente. Use @DynamicPropertySource manual só quando o Spring Boot ainda não reconhecer o tipo de container."},{"id":"spring-kafka-exercise-39","type":"exercise","authorship":"legacy-preserved","title":"Exercício 42.1 — Migrando para Spring Kafka","prompt":"Reescreva o PedidoProducer e NotificacaoConsumer do exercício 41.1 usando KafkaTemplate<String, PedidoFinalizadoEvent> (não String cru) e @KafkaListener. Configure o application.properties com bootstrap-servers, os serializers/deserializers JSON e o group-id corretos. Explique por que usar o pedidoId como chave de particionamento é importante para a ordem dos eventos.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 42.1 — Migrando para Spring Kafkamédio Reescreva o PedidoProducer e NotificacaoConsumer do exercício 41.1 usando KafkaTemplate<String, PedidoFinalizadoEvent> (não String cru) e @KafkaListener. Configure o application.properties com bootstrap-servers, os serializers/deserializers JSON e o group-id corretos. Explique por que usar o pedidoId como chave de particionamento é importante para a ordem dos eventos. Ver solução @Service public class PedidoEventPublisher { private final KafkaTemplate<String, PedidoFinalizadoEvent> kafkaTemplate; public PedidoEventPublisher(KafkaTemplate<String, PedidoFinalizadoEvent> kafkaTemplate) { this.kafkaTemplate = kafkaTemplate; } public void publicar(PedidoFinalizadoEvent evento) { kafkaTemplate.send(\"pedidos-finalizados\", evento.pedidoId(), evento); // chave = pedidoId } } @Service public class NotificacaoListener { @KafkaListener(topics = \"pedidos-finalizados\", groupId = \"servico-notificacao\") public void ouvir(PedidoFinalizadoEvent evento) { System.out.println(\"Notificando: \" + evento.pedidoId()); } } Todos os eventos com a mesma chave (o mesmo pedidoId) são roteados sempre para a mesma partição, e uma partição é lida sempre por uma única thread consumidora do grupo, em ordem de offset. Se a chave fosse aleatória, dois eventos do mesmo pedido poderiam cair em partições diferentes e ser processados fora de ordem por consumidores distintos.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 42.1 — Migrando para Spring Kafka</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Reescreva o <code>PedidoProducer</code> e <code>NotificacaoConsumer</code> do exercício 41.1 usando <code>KafkaTemplate&lt;String, PedidoFinalizadoEvent&gt;</code> (não <code>String</code> cru) e <code>@KafkaListener</code>. Configure o <code>application.properties</code> com <code>bootstrap-servers</code>, os serializers/deserializers JSON e o <code>group-id</code> corretos. Explique por que usar o <code>pedidoId</code> como chave de particionamento é importante para a ordem dos eventos.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">OrderEventPublisher</span> {\n    <span class=\"kw\">private final</span> KafkaTemplate&lt;<span class=\"kw\">String</span>, OrderCompletedEvent&gt; kafkaTemplate;\n    <span class=\"kw\">public</span> <span class=\"fn\">OrderEventPublisher</span>(KafkaTemplate&lt;<span class=\"kw\">String</span>, OrderCompletedEvent&gt; kafkaTemplate) { <span class=\"kw\">this</span>.kafkaTemplate = kafkaTemplate; }\n    <span class=\"kw\">public void</span> <span class=\"fn\">publish</span>(OrderCompletedEvent event) {\n        kafkaTemplate.send(<span class=\"str\">\"orders-completed\"</span>, event.orderId(), event); <span class=\"com\">// chave = pedidoId</span>\n    }\n}\n\n<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">NotificationListener</span> {\n    <span class=\"annotation\">@KafkaListener</span>(topics = <span class=\"str\">\"orders-completed\"</span>, groupId = <span class=\"str\">\"service-notification\"</span>)\n    <span class=\"kw\">public void</span> <span class=\"fn\">listen</span>(OrderCompletedEvent event) {\n        System.out.println(<span class=\"str\">\"Notificando: \"</span> + event.orderId());\n    }\n}</pre>\n          <p style=\"margin-top:12px\">Todos os eventos com a mesma chave (o mesmo <code>pedidoId</code>) são roteados sempre para a mesma partição, e uma partição é lida sempre por uma única thread consumidora do grupo, em ordem de offset. Se a chave fosse aleatória, dois eventos do mesmo pedido poderiam cair em partições diferentes e ser processados fora de ordem por consumidores distintos.</p>\n        </div>\n      </div>"},{"id":"spring-kafka-quiz","type":"quiz","authorship":"authored","conceptId":"spring-kafka-listener-template","prompt":"O que @KafkaListener não resolve sozinho?","options":[{"id":"sk-a","label":"Idempotência, contrato de schema e política correta de commit/erro.","correct":true,"explanation":"A anotação conecta o listener; a confiabilidade continua sendo desenho explícito."},{"id":"sk-b","label":"A criação de uma classe Java.","correct":false,"explanation":"Isso é sintaxe, não semântica de mensageria."},{"id":"sk-c","label":"A necessidade de escrever métodos.","correct":false,"explanation":"Listeners ainda são métodos com contrato de entrada e efeito."}]}],"resources":[{"id":"spring-kafka-reference-phase16","type":"official-docs","title":"Spring for Apache Kafka Reference","url":"https://docs.spring.io/spring-kafka/reference/","reinforces":"Templates, listeners, containers, erro e configuração Spring Kafka.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-kafka-testing-phase16","type":"official-docs","title":"Spring Kafka: Testing Applications","url":"https://docs.spring.io/spring-kafka/reference/testing.html","reinforces":"Estratégias de teste para aplicações com Spring Kafka.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the spring kafka flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for spring kafka. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for spring kafka with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"// DTO do evento -- um record é suficiente e imutável (capítulo 62)","instruction":"Design the spring kafka flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for spring kafka with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"kafka-confiavel","moduleId":"messaging-eda","order":5,"title":"Kafka confiável: schemas, retries, DLT, outbox e exactly-once","summary":"Mensagens podem duplicar, atrasar, chegar fora da ordem esperada ou falhar permanentemente. Confiabilidade vem de contratos evolutivos, observabilidade e processamento idempotente, não da expectativa de entrega perfeita.","objectives":["Projetar consumer idempotente","Definir retry/DLT por tipo de falha","Evoluir schema sem quebrar consumidor","Conectar offset, transação local e evidência operacional"],"whyItExists":"Agora que Kafka e Spring Kafka já existem, confiabilidade deixa de ser promessa de ferramenta. O aluno aprofunda duplicação, commit, DLT, schema, reprocessamento e efeito idempotente.","prerequisiteChapterIds":["spring-kafka","jpa-transacoes"],"conceptIds":["o-caminho-minimo-de-uma-mensagem","schema-e-evolucao-codigo-real-com-schema-registry","retry-e-dead-letter-topic-retryabletopic-em-codigo","idempotent-consumer-a-distincao-com-idempotent-producer","transacoes-kafka-em-codigo-inittransactions-ao-committransaction","transactional-outbox","exactly-once-limite-da-garantia","medindo-consumer-lag-de-verdade"],"introducedConceptIds":["kafka-schema-evolution-contract","kafka-retry-dlt-policy","kafka-idempotent-consumer"],"usedConceptIds":["consumer-group-rebalance-offset","delivery-at-least-once-idempotency","aggregate-transaction-boundary"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"kafka-confiavel-intuition","type":"intuition","authorship":"authored","title":"Confiabilidade em Kafka é contrato de efeito","body":"O broker pode entregar e reentregar records. A aplicação confiável é a que sabe quando confirmar, como deduplicar, que falha deve ir para retry/DLT e como evoluir schema sem quebrar consumidores antigos.","analogyLimit":"“Não perder mensagem” é só parte do problema; o efeito no banco, no e-mail ou no estoque precisa ser correto."},{"id":"kafka-confiavel-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-kafka\">Kafka</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~7h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-kafka\">Spring Kafka</a>, <a class=\"prereq-tag\" href=\"#jpa-transacoes\">Transações</a></div></div>","fidelityText":"KafkaDificuldade: Avançado⏱ ~7h de estudo e práticaPré-requisitos: Spring Kafka, Transações"},{"id":"kafka-confiavel-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Mensagens podem duplicar, atrasar, chegar fora da ordem esperada ou falhar permanentemente. Confiabilidade vem de contratos evolutivos, observabilidade e processamento idempotente, não da expectativa de entrega perfeita.</p>","fidelityText":"Mensagens podem duplicar, atrasar, chegar fora da ordem esperada ou falhar permanentemente. Confiabilidade vem de contratos evolutivos, observabilidade e processamento idempotente, não da expectativa de entrega perfeita."},{"id":"kafka-confiavel-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>O caminho mínimo de uma mensagem</h2></div>\n    <p>Visualize primeiro produtor → tópico/partição → consumidor. As técnicas seguintes protegem pontos diferentes desse caminho.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Evento</dt><dd>Registro imutável de algo que aconteceu, nomeado no passado, como PedidoCriado.</dd></div><div class=\"concept-card\"><dt>Schema</dt><dd>Contrato estrutural e de tipos da mensagem, com regras de evolução entre versões.</dd></div><div class=\"concept-card\"><dt>Partição</dt><dd>Log ordenado dentro de um tópico; Kafka preserva ordem por partição, não globalmente.</dd></div><div class=\"concept-card\"><dt>Offset</dt><dd>Posição de uma mensagem dentro de uma partição e progresso registrado pelo consumidor.</dd></div><div class=\"concept-card\"><dt>Idempotência</dt><dd>Capacidade de processar repetição sem aplicar o efeito de negócio outra vez.</dd></div><div class=\"concept-card\"><dt>DLT</dt><dd><em>Dead-letter topic</em>: tópico separado para mensagens que não puderam ser processadas após a política definida.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoO caminho mínimo de uma mensagem Visualize primeiro produtor → tópico/partição → consumidor. As técnicas seguintes protegem pontos diferentes desse caminho. EventoRegistro imutável de algo que aconteceu, nomeado no passado, como PedidoCriado.SchemaContrato estrutural e de tipos da mensagem, com regras de evolução entre versões.PartiçãoLog ordenado dentro de um tópico; Kafka preserva ordem por partição, não globalmente.OffsetPosição de uma mensagem dentro de uma partição e progresso registrado pelo consumidor.IdempotênciaCapacidade de processar repetição sem aplicar o efeito de negócio outra vez.DLTDead-letter topic: tópico separado para mensagens que não puderam ser processadas após a política definida."},{"id":"kafka-confiavel-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Schema e evolução — código real com Schema Registry</h2>","fidelityText":"Schema e evolução — código real com Schema Registry"},{"id":"kafka-confiavel-content-5","type":"html","authorship":"legacy-preserved","html":"<p>Não produza JSON por concatenação. Um <strong>serializer</strong> transforma o objeto em bytes conforme o contrato. Avro, Protocol Buffers (Protobuf) e JSON Schema são alternativas para descrever dados e evolução. Adições opcionais com valor padrão tendem a ser compatíveis; renomear ou mudar significado exige transição. <strong>Correlation ID</strong> agrupa mensagens da mesma operação; <strong>causation ID</strong> aponta qual mensagem causou diretamente a atual.</p>","fidelityText":"Não produza JSON por concatenação. Um serializer transforma o objeto em bytes conforme o contrato. Avro, Protocol Buffers (Protobuf) e JSON Schema são alternativas para descrever dados e evolução. Adições opcionais com valor padrão tendem a ser compatíveis; renomear ou mudar significado exige transição. Correlation ID agrupa mensagens da mesma operação; causation ID aponta qual mensagem causou diretamente a atual."},{"id":"kafka-confiavel-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"// schema Avro (.avsc) -- contrato explícito, versionado, validado no registry\n{\n  \"type\": \"record\",\n  \"name\": \"OrderCreated\",\n  \"fields\": [\n    { \"name\": \"eventId\", \"type\": \"string\" },\n    { \"name\": \"orderId\", \"type\": \"string\" },\n    { \"name\": \"items\", \"type\": { \"type\": \"array\", \"items\": \"string\" } }\n  ]\n}","fidelityText":"// schema Avro (.avsc) -- contrato explícito, versionado, validado no registry { \"type\": \"record\", \"name\": \"PedidoCriado\", \"fields\": [ { \"name\": \"eventId\", \"type\": \"string\" }, { \"name\": \"pedidoId\", \"type\": \"string\" }, { \"name\": \"itens\", \"type\": { \"type\": \"array\", \"items\": \"string\" } } ] }","highlightedHtml":"<span class=\"com\">// schema Avro (.avsc) -- contrato explícito, versionado, validado no registry</span>\n{\n  <span class=\"str\">\"type\"</span>: <span class=\"str\">\"record\"</span>,\n  <span class=\"str\">\"name\"</span>: <span class=\"str\">\"OrderCreated\"</span>,\n  <span class=\"str\">\"fields\"</span>: [\n    { <span class=\"str\">\"name\"</span>: <span class=\"str\">\"eventId\"</span>, <span class=\"str\">\"type\"</span>: <span class=\"str\">\"string\"</span> },\n    { <span class=\"str\">\"name\"</span>: <span class=\"str\">\"orderId\"</span>, <span class=\"str\">\"type\"</span>: <span class=\"str\">\"string\"</span> },\n    { <span class=\"str\">\"name\"</span>: <span class=\"str\">\"items\"</span>, <span class=\"str\">\"type\"</span>: { <span class=\"str\">\"type\"</span>: <span class=\"str\">\"array\"</span>, <span class=\"str\">\"items\"</span>: <span class=\"str\">\"string\"</span> } }\n  ]\n}","caption":"Exemplo executável de kafka-confiavel.","explanation":["O schema Avro descreve os campos e tipos esperados do evento -- um contrato validado, não um JSON solto.","Campos futuros precisam de um valor padrão para manter compatibilidade com consumidores que ainda usam o schema antigo."],"commonMistakes":["Adicionar campo obrigatório sem valor padrão e quebrar consumidores antigos","Renomear um campo em vez de adicionar um novo, perdendo compatibilidade retroativa"]},{"id":"kafka-confiavel-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"# application.properties -- serializer Avro + endereço do registry\nspring.kafka.producer.value-serializer=io.confluent.kafka.serializers.KafkaAvroSerializer\nspring.kafka.properties.schema.registry.url=http://localhost:8081","fidelityText":"# application.properties -- serializer Avro + endereço do registry spring.kafka.producer.value-serializer=io.confluent.kafka.serializers.KafkaAvroSerializer spring.kafka.properties.schema.registry.url=http://localhost:8081","highlightedHtml":"<span class=\"com\"># application.properties -- serializer Avro + endereço do registry</span>\nspring.kafka.producer.value-serializer=io.confluent.kafka.serializers.KafkaAvroSerializer\nspring.kafka.properties.schema.registry.url=http://localhost:8081","caption":"Exemplo executável de kafka-confiavel.","explanation":["KafkaAvroSerializer serializa o objeto conforme o schema registrado, em vez de concatenar JSON manualmente.","schema.registry.url aponta para o serviço que valida e versiona os schemas antes de aceitar uma nova versão."],"commonMistakes":["Trocar o serializer sem configurar o endereço do Schema Registry","Assumir que qualquer mudança de schema é aceita automaticamente pelo registry"]},{"id":"kafka-confiavel-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Quando um campo novo (ex: <code>cupomDesconto</code>) precisa ser adicionado depois que consumidores antigos já estão em produção, o <strong>modo de compatibilidade</strong> registrado no Schema Registry decide se essa mudança é aceita: <code>BACKWARD</code> (o padrão mais comum) exige que o schema novo consiga ler dados escritos com o schema antigo — funciona se o campo novo tiver <code>\"default\"</code> declarado. <code>FORWARD</code> exige o inverso: consumidores antigos conseguem ler dados escritos com o schema novo. <code>FULL</code> exige os dois ao mesmo tempo. Um campo novo <strong>sem</strong> valor padrão quebra <code>BACKWARD</code> e o Schema Registry rejeita o registro antes mesmo de chegar em produção — essa rejeição em tempo de deploy é o ponto central de ter um contrato validado, em vez de descobrir a quebra em runtime.</div>","fidelityText":"Quando um campo novo (ex: cupomDesconto) precisa ser adicionado depois que consumidores antigos já estão em produção, o modo de compatibilidade registrado no Schema Registry decide se essa mudança é aceita: BACKWARD (o padrão mais comum) exige que o schema novo consiga ler dados escritos com o schema antigo — funciona se o campo novo tiver \"default\" declarado. FORWARD exige o inverso: consumidores antigos conseguem ler dados escritos com o schema novo. FULL exige os dois ao mesmo tempo. Um campo novo sem valor padrão quebra BACKWARD e o Schema Registry rejeita o registro antes mesmo de chegar em produção — essa rejeição em tempo de deploy é o ponto central de ter um contrato validado, em vez de descobrir a quebra em runtime."},{"id":"kafka-confiavel-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Retry e dead-letter topic — <code>@RetryableTopic</code> em código</h2>","fidelityText":"Retry e dead-letter topic — @RetryableTopic em código"},{"id":"kafka-confiavel-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Erros transitórios podem receber retries limitados com backoff e jitter. Erros permanentes, como schema inválido, não devem bloquear a partição indefinidamente: envie à DLT com causa e metadados, gere alerta e ofereça reprocessamento auditável.</p>","fidelityText":"Erros transitórios podem receber retries limitados com backoff e jitter. Erros permanentes, como schema inválido, não devem bloquear a partição indefinidamente: envie à DLT com causa e metadados, gere alerta e ofereça reprocessamento auditável."},{"id":"kafka-confiavel-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"@RetryableTopic(attempts = \"4\", backoff = @Backoff(delay = 1000, multiplier = 2.0, maxDelay = 10000))\n@KafkaListener(topics = \"orders-created\", groupId = \"service-inventory\")\npublic void process(OrderCreated event) {\n    // falha transitória (banco fora do ar) -- vale retry com backoff\n    // falha permanente (schema inválido) -- esgota tentativas e cai na .dlt automaticamente\n    inventoryService.reservar(event);\n}","fidelityText":"@RetryableTopic(attempts = \"4\", backoff = @Backoff(delay = 1000, multiplier = 2.0, maxDelay = 10000)) @KafkaListener(topics = \"pedidos-criados\", groupId = \"servico-estoque\") public void processar(PedidoCriado evento) { // falha transitória (banco fora do ar) -- vale retry com backoff // falha permanente (schema inválido) -- esgota tentativas e cai na .dlt automaticamente estoqueService.reservar(evento); }","highlightedHtml":"<span class=\"annotation\">@RetryableTopic</span>(attempts = <span class=\"str\">\"4\"</span>, backoff = <span class=\"annotation\">@Backoff</span>(delay = <span class=\"num\">1000</span>, multiplier = <span class=\"num\">2.0</span>, maxDelay = <span class=\"num\">10000</span>))\n<span class=\"annotation\">@KafkaListener</span>(topics = <span class=\"str\">\"orders-created\"</span>, groupId = <span class=\"str\">\"service-inventory\"</span>)\n<span class=\"kw\">public void</span> <span class=\"fn\">process</span>(OrderCreated event) {\n    <span class=\"com\">// falha transitória (banco fora do ar) -- vale retry com backoff</span>\n    <span class=\"com\">// falha permanente (schema inválido) -- esgota tentativas e cai na .dlt automaticamente</span>\n    inventoryService.reservar(event);\n}","caption":"Exemplo executável de kafka-confiavel.","explanation":["@RetryableTopic classifica implicitamente a falha pelo número de tentativas -- falha permanente (schema inválido) esgota as 4 tentativas rapidamente e cai na DLT.","Falha transitória (banco fora do ar) tem chance real de suceder numa das tentativas com backoff."],"commonMistakes":["Retry infinito em erro de schema, que nunca vai se resolver sozinho","Enviar tudo para DLT sem metadados de causa, dificultando o diagnóstico depois"]},{"id":"kafka-confiavel-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Retry topics podem alterar a ordem relativa:</b> cada tentativa republica num tópico intermediário próprio (<code>-retry-0</code>, <code>-retry-1</code>...), então uma mensagem que precisou de retry pode ser processada depois de uma mensagem publicada mais tarde no tópico principal. Declare explicitamente se ordem é um requisito do seu domínio antes de usar retry topics — se for, considere backoff sem tópicos intermediários (bloqueante) em vez de <code>@RetryableTopic</code>.</div>","fidelityText":"Retry topics podem alterar a ordem relativa: cada tentativa republica num tópico intermediário próprio (-retry-0, -retry-1...), então uma mensagem que precisou de retry pode ser processada depois de uma mensagem publicada mais tarde no tópico principal. Declare explicitamente se ordem é um requisito do seu domínio antes de usar retry topics — se for, considere backoff sem tópicos intermediários (bloqueante) em vez de @RetryableTopic."},{"id":"kafka-confiavel-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Idempotent consumer — a distinção com idempotent producer</h2>","fidelityText":"Idempotent consumer — a distinção com idempotent producer"},{"id":"kafka-confiavel-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Um erro comum é tratar \"idempotência do Kafka\" como uma coisa só. São duas garantias em camadas diferentes:</p>","fidelityText":"Um erro comum é tratar \"idempotência do Kafka\" como uma coisa só. São duas garantias em camadas diferentes:"},{"id":"kafka-confiavel-content-15","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Idempotent producer</th><th>Idempotent consumer</th></tr>\n        <tr><td>Camada</td><td>Infraestrutura (Kafka)</td><td>Aplicação (seu código)</td></tr>\n        <tr><td>O que resolve</td><td>O <strong>mesmo producer</strong> reenviando após timeout não duplica no tópico</td><td>O <strong>mesmo consumer</strong> reprocessando após restart não duplica o efeito de negócio</td></tr>\n        <tr><td>Como se ativa</td><td><code>enable.idempotence=true</code> (capítulo anterior)</td><td>Registro de <code>event_id</code> processado, na mesma transação do efeito</td></tr>\n        <tr><td>O que NÃO cobre</td><td>Duplicação vista pelo consumidor (at-least-once continua valendo)</td><td>Duplicação na escrita do producer (é problema de infraestrutura, não de aplicação)</td></tr>\n      </tbody></table>","fidelityText":"Idempotent producerIdempotent consumer CamadaInfraestrutura (Kafka)Aplicação (seu código) O que resolveO mesmo producer reenviando após timeout não duplica no tópicoO mesmo consumer reprocessando após restart não duplica o efeito de negócio Como se ativaenable.idempotence=true (capítulo anterior)Registro de event_id processado, na mesma transação do efeito O que NÃO cobreDuplicação vista pelo consumidor (at-least-once continua valendo)Duplicação na escrita do producer (é problema de infraestrutura, não de aplicação)"},{"id":"kafka-confiavel-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"BEGIN;\nINSERT INTO message_processada(consumer, event_id)\nVALUES (:consumer, :eventId)\nON CONFLICT DO NOTHING;\n-- continue somente quando uma linha foi inserida\nUPDATE inventory SET reserved = reserved + :quantity WHERE product_id = :id;\nCOMMIT;","fidelityText":"BEGIN; INSERT INTO mensagem_processada(consumer, event_id) VALUES (:consumer, :eventId) ON CONFLICT DO NOTHING; -- continue somente quando uma linha foi inserida UPDATE estoque SET reservado = reservado + :qtd WHERE produto_id = :id; COMMIT;","highlightedHtml":"<span class=\"kw\">BEGIN</span>;\n<span class=\"kw\">INSERT INTO</span> message_processada(consumer, event_id)\n<span class=\"kw\">VALUES</span> (:consumer, :eventId)\n<span class=\"kw\">ON CONFLICT DO NOTHING</span>;\n<span class=\"com\">-- continue somente quando uma linha foi inserida</span>\n<span class=\"kw\">UPDATE</span> inventory <span class=\"kw\">SET</span> reserved = reserved + :quantity <span class=\"kw\">WHERE</span> product_id = :id;\n<span class=\"kw\">COMMIT</span>;","caption":"Exemplo executável de kafka-confiavel.","explanation":["O INSERT com ON CONFLICT DO NOTHING só permite o UPDATE seguinte rodar quando o event_id ainda não tinha sido processado.","Deduplicação e efeito de negócio estão na mesma transação -- não há janela entre marcar e executar."],"commonMistakes":["Deduplicar só em memória, perdendo o registro após um restart","Separar a marca de deduplicação e o efeito em transações independentes"]},{"id":"kafka-confiavel-content-17","type":"html","authorship":"legacy-preserved","html":"<p>A deduplicação e o efeito devem estar na mesma transação local. Idempotência do <strong>produtor</strong> Kafka não torna automaticamente o <strong>consumidor</strong> idempotente — são duas camadas independentes, e um sistema confiável precisa das duas.</p>","fidelityText":"A deduplicação e o efeito devem estar na mesma transação local. Idempotência do produtor Kafka não torna automaticamente o consumidor idempotente — são duas camadas independentes, e um sistema confiável precisa das duas."},{"id":"kafka-confiavel-content-18","type":"html","authorship":"legacy-preserved","html":"<h2>Transações Kafka em código — <code>initTransactions</code> ao <code>commitTransaction</code></h2>","fidelityText":"Transações Kafka em código — initTransactions ao commitTransaction"},{"id":"kafka-confiavel-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Quando um mesmo processo lê de um tópico, escreve em outro e precisa que as duas coisas aconteçam atomicamente (ou nenhuma), uma transação Kafka nativa (não só <code>acks=all</code>) coordena tudo — inclusive o commit do offset de entrada:</p>","fidelityText":"Quando um mesmo processo lê de um tópico, escreve em outro e precisa que as duas coisas aconteçam atomicamente (ou nenhuma), uma transação Kafka nativa (não só acks=all) coordena tudo — inclusive o commit do offset de entrada:"},{"id":"kafka-confiavel-code-20","type":"code","authorship":"legacy-preserved","language":"java","source":"props.put(\"transactional.id\", \"consolidator-orders-1\"); // initTransactions habilita producer transacional com id fixo\nKafkaProducer<String, String> producer = new KafkaProducer<>(props);\nproducer.initTransactions();\n\ntry {\n    producer.beginTransaction();\n    producer.send(new ProducerRecord<>(\"orders-consolidated\", key, value));\n    // sendOffsetsToTransaction amarra commit do offset a mesma transacao da escrita\n    producer.sendOffsetsToTransaction(offsetsProcessed, consumerGroupMetadata);\n    producer.commitTransaction();\n} catch (Exception e) {\n    producer.abortTransaction(); // nem a escrita nem o offset avançam\n}","fidelityText":"props.put(\"transactional.id\", \"consolidador-pedidos-1\"); // initTransactions habilita producer transacional com id fixo KafkaProducer<String, String> producer = new KafkaProducer<>(props); producer.initTransactions(); try { producer.beginTransaction(); producer.send(new ProducerRecord<>(\"pedidos-consolidados\", chave, valor)); // sendOffsetsToTransaction amarra commit do offset a mesma transacao da escrita producer.sendOffsetsToTransaction(offsetsProcessados, consumerGroupMetadata); producer.commitTransaction(); } catch (Exception e) { producer.abortTransaction(); // nem a escrita nem o offset avançam }","highlightedHtml":"props.put(<span class=\"str\">\"transactional.id\"</span>, <span class=\"str\">\"consolidator-orders-1\"</span>); <span class=\"com\">// initTransactions habilita producer transacional com id fixo</span>\nKafkaProducer&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; producer = <span class=\"kw\">new</span> KafkaProducer&lt;&gt;(props);\nproducer.initTransactions();\n\n<span class=\"kw\">try</span> {\n    producer.beginTransaction();\n    producer.send(<span class=\"kw\">new</span> ProducerRecord&lt;&gt;(<span class=\"str\">\"orders-consolidated\"</span>, key, value));\n    <span class=\"com\">// sendOffsetsToTransaction amarra commit do offset a mesma transacao da escrita</span>\n    producer.sendOffsetsToTransaction(offsetsProcessed, consumerGroupMetadata);\n    producer.commitTransaction();\n} <span class=\"kw\">catch</span> (Exception e) {\n    producer.abortTransaction(); <span class=\"com\">// nem a escrita nem o offset avançam</span>\n}","caption":"Exemplo executável de kafka-confiavel.","explanation":["initTransactions() com um transactional.id fixo habilita o producer a coordenar escrita e commit de offset atomicamente.","sendOffsetsToTransaction amarra o commit do offset consumido à mesma transação da escrita produzida -- ou os dois efetivam juntos, ou nenhum."],"commonMistakes":["Reutilizar o mesmo transactional.id em duas instâncias do producer ao mesmo tempo","Esquecer o catch/abortTransaction e deixar uma transação pendurada em caso de erro"]},{"id":"kafka-confiavel-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"# consumer.properties\nisolation.level=read_committed # isolation.level=read_committed -- consumidor enxerga somente mensagem de transacao commitada","fidelityText":"# consumer.properties isolation.level=read_committed # isolation.level=read_committed -- consumidor enxerga somente mensagem de transacao commitada","highlightedHtml":"<span class=\"com\"># consumer.properties</span>\nisolation.level=read_committed <span class=\"com\"># isolation.level=read_committed -- consumidor enxerga somente mensagem de transacao commitada</span>","caption":"Exemplo executável de kafka-confiavel.","explanation":["isolation.level=read_committed faz o consumer não enxergar mensagens de transações ainda não confirmadas (ou abortadas).","Sem essa configuração no lado do consumer, a transação do producer não protege nada visível para quem lê."],"commonMistakes":["Configurar transação só no producer e deixar o consumer no isolamento padrão read_uncommitted","Achar que isolation.level=read_committed sozinho, sem producer transacional, já garante alguma coisa"]},{"id":"kafka-confiavel-content-22","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Sem <code>isolation.level=read_committed</code>, essa transação não protege nada:</b> por padrão (<code>read_uncommitted</code>), um consumer enxerga mensagens de transações ainda não confirmadas (e até de transações que serão abortadas). O par certo é sempre <code>producer</code> transacional + <code>consumer.isolation.level=read_committed</code> do outro lado — configurar só um dos dois lados não entrega a garantia esperada.</div>","fidelityText":"Sem isolation.level=read_committed, essa transação não protege nada: por padrão (read_uncommitted), um consumer enxerga mensagens de transações ainda não confirmadas (e até de transações que serão abortadas). O par certo é sempre producer transacional + consumer.isolation.level=read_committed do outro lado — configurar só um dos dois lados não entrega a garantia esperada."},{"id":"kafka-confiavel-content-23","type":"html","authorship":"legacy-preserved","html":"<h2>Transactional outbox</h2>","fidelityText":"Transactional outbox"},{"id":"kafka-confiavel-content-24","type":"html","authorship":"legacy-preserved","html":"<p>Grave mudança de domínio e linha de <strong>outbox</strong> — uma tabela de eventos pendentes — na mesma transação. Um <strong>relay</strong> lê e publica essa tabela; outra opção é <strong>CDC</strong> (<em>Change Data Capture</em>), que observa o log de mudanças do banco. A publicação ainda pode repetir após falha, portanto consumidores continuam idempotentes. Uma <strong>inbox</strong> registra mensagens recebidas e aplica o mesmo princípio na entrada.</p>","fidelityText":"Grave mudança de domínio e linha de outbox — uma tabela de eventos pendentes — na mesma transação. Um relay lê e publica essa tabela; outra opção é CDC (Change Data Capture), que observa o log de mudanças do banco. A publicação ainda pode repetir após falha, portanto consumidores continuam idempotentes. Uma inbox registra mensagens recebidas e aplica o mesmo princípio na entrada."},{"id":"kafka-confiavel-content-25","type":"html","authorship":"legacy-preserved","html":"<h2>Exactly-once: limite da garantia</h2>","fidelityText":"Exactly-once: limite da garantia"},{"id":"kafka-confiavel-content-26","type":"html","authorship":"legacy-preserved","html":"<p>Transações Kafka podem coordenar leitura, escrita em tópicos e offsets dentro do Kafka. Elas não incluem automaticamente um PostgreSQL ou uma API de pagamento. Descreva sempre o limite: exactly-once em qual sistema e para qual efeito?</p>","fidelityText":"Transações Kafka podem coordenar leitura, escrita em tópicos e offsets dentro do Kafka. Elas não incluem automaticamente um PostgreSQL ou uma API de pagamento. Descreva sempre o limite: exactly-once em qual sistema e para qual efeito?"},{"id":"kafka-confiavel-content-27","type":"html","authorship":"legacy-preserved","html":"<h2>Medindo consumer lag de verdade</h2>","fidelityText":"Medindo consumer lag de verdade"},{"id":"kafka-confiavel-content-28","type":"html","authorship":"legacy-preserved","html":"<p>\"Retry, DLT e schema estão configurados\" não significa \"o sistema está saudável agora\" — para isso, meça o <strong>lag</strong>: a diferença entre o offset mais recente de cada partição e o offset commitado pelo consumer group.</p>","fidelityText":"\"Retry, DLT e schema estão configurados\" não significa \"o sistema está saudável agora\" — para isso, meça o lag: a diferença entre o offset mais recente de cada partição e o offset commitado pelo consumer group."},{"id":"kafka-confiavel-code-29","type":"code","authorship":"legacy-preserved","language":"java","source":"kafka-consumer-groups.sh --bootstrap-server localhost:9092 \\\n    --describe --group service-inventory\n# coluna LAG -- quantas mensagens o grupo ainda não processou, por partição","fidelityText":"kafka-consumer-groups.sh --bootstrap-server localhost:9092 \\ --describe --group servico-estoque # coluna LAG -- quantas mensagens o grupo ainda não processou, por partição","highlightedHtml":"kafka-consumer-groups.sh --bootstrap-server localhost:9092 \\\n    --describe --group service-inventory\n<span class=\"com\"># coluna LAG -- quantas mensagens o grupo ainda não processou, por partição</span>","caption":"Exemplo executável de kafka-confiavel.","explanation":["--describe --group mostra o lag atual por partição para o consumer group informado.","Lag crescente sustentado, mesmo sem erro nenhum, indica que a taxa de consumo não acompanha a de produção."],"commonMistakes":["Medir lag uma única vez e nunca mais monitorar em produção","Confundir lag zero com garantia de que o processamento foi correto"]},{"id":"kafka-confiavel-content-30","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Lag zero não significa \"sem problema\": um consumer pode estar comitando o offset sem realmente ter tratado a mensagem (bug de lógica). Combine lag com a idade da mensagem mais antiga não processada e com a taxa de erro do handler — as três métricas juntas, não uma isolada, formam o painel real de saúde de um consumer group.</div>","fidelityText":"Lag zero não significa \"sem problema\": um consumer pode estar comitando o offset sem realmente ter tratado a mensagem (bug de lógica). Combine lag com a idade da mensagem mais antiga não processada e com a taxa de erro do handler — as três métricas juntas, não uma isolada, formam o painel real de saúde de um consumer group."},{"id":"kafka-confiavel-exercise-31","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — falhas reais","prompt":"Implemente outbox, relay, consumidor idempotente, retry e DLT. Mate o processo depois de publicar e antes de marcar a outbox; confirme que a repetição não duplica estoque.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — falhas reaisdifícilImplemente outbox, relay, consumidor idempotente, retry e DLT. Mate o processo depois de publicar e antes de marcar a outbox; confirme que a repetição não duplica estoque.Ver critériosMeça consumer lag, conte DLTs, preserve event ID e prove o comportamento após restart. O teste deve falhar se a constraint de deduplicação for removida.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — falhas reais</h2><span class=\"exercise-tag d\">difícil</span></div><p>Implemente outbox, relay, consumidor idempotente, retry e DLT. Mate o processo depois de publicar e antes de marcar a outbox; confirme que a repetição não duplica estoque.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>Meça consumer lag, conte DLTs, preserve event ID e prove o comportamento após restart. O teste deve falhar se a constraint de deduplicação for removida.</p></div></div>"},{"id":"kafka-confiavel-quiz","type":"quiz","authorship":"authored","conceptId":"kafka-idempotent-consumer","prompt":"Qual desenho protege contra duplicação de evento?","options":[{"id":"kc-a","label":"Registrar eventId e efeito na mesma transação local com restrição única.","correct":true,"explanation":"Assim a duplicação vira caso controlado, não efeito duplicado."},{"id":"kc-b","label":"Fazer commit do offset antes de processar sempre.","correct":false,"explanation":"Isso pode perder efeito em crash."},{"id":"kc-c","label":"Aumentar partitions até não duplicar.","correct":false,"explanation":"Partições não eliminam reentrega."}]}],"resources":[{"id":"spring-kafka-retry-topic-phase16","type":"official-docs","title":"Spring Kafka: Non-Blocking Retries","url":"https://docs.spring.io/spring-kafka/reference/retrytopic.html","reinforces":"Retry topics, DLT e política de falha no Spring Kafka.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"apache-kafka-guarantees-phase16","type":"official-docs","title":"Apache Kafka: Message Delivery Semantics","url":"https://kafka.apache.org/40/documentation.html#semantics","reinforces":"Garantias de entrega, producer idempotent e transações Kafka.","language":"en","publisher":"Apache Kafka","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the kafka reliable flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for kafka reliable. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for kafka reliable with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"// schema Avro (.avsc) -- contrato explícito, versionado, validado no registry","instruction":"Design the kafka reliable flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for kafka reliable with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"pilares-profundo","moduleId":"oop-modeling","order":9,"title":"Os 4 Pilares da POO — Aprofundamento Unificado","summary":"Os capítulos anteriores apresentaram os mecanismos separadamente. Agora você vai conectá-los como decisões de modelagem: qual estado proteger, quando existe uma relação real de subtipo, quando objetos apenas colaboram e como um contrato permite trocar implementações. O objetivo não é encaixar quatro palavras-chave em todo programa, mas justificar cada escolha pelos efeitos que ela produz.","objectives":["Conectar os mecanismos de POO pelo problema resolvido","Separar abstração conceitual de recursos da linguagem","Justificar custos de herança, mutabilidade e acoplamento"],"whyItExists":"Depois de aprender os mecanismos separadamente, o aluno precisa enxergar como eles cooperam numa modelagem e evitar decorar quatro slogans desconectados.","prerequisiteChapterIds":["interfaces"],"conceptIds":["pilar-1-encapsulamento-a-fronteira-entre-o-que-e-seu-e-o-que-e-do-mundo","pilar-2-heranca-reaproveitamento-por-especializacao-com-um-custo-de-acop","pilar-3-polimorfismo-a-mesma-mensagem-respostas-diferentes-decididas-em-","pilar-4-abstracao-modelando-so-o-que-importa-para-o-problema-em-questao","como-os-quatro-pilares-se-sustentam-mutuamente"],"introducedConceptIds":["quatro-pilares-cooperacao","decisao-modelagem-poo"],"usedConceptIds":["encapsulamento-invariante","heranca-composicao","despacho-dinamico","abstracao-modelagem","interface-contrato"],"estimatedMinutes":60,"englishLevel":0,"blocks":[{"id":"pilares-intuition","type":"intuition","authorship":"authored","title":"POO é responsabilidade e colaboração, não uma lista de pilares","body":"Abstração escolhe o modelo; encapsulamento protege suas regras; subtipos e interfaces estabelecem contratos; polimorfismo permite variar implementações. Herança é uma ferramenta possível, não um objetivo obrigatório.","analogyLimit":"Os quatro nomes são uma lente didática, não uma divisão normativa da especificação Java nem uma pontuação de qualidade."},{"id":"pilares-profundo-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-java\">Java Interno</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo e prática integrada</div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#interfaces\">08 · Interfaces</a></div>\n      </div>","fidelityText":"Java Interno Dificuldade: Avançado ⏱ ~3h de estudo e prática integrada Pré-requisito: 08 · Interfaces"},{"id":"pilares-profundo-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Os capítulos anteriores apresentaram os mecanismos separadamente. Agora você vai conectá-los como decisões de modelagem: qual estado proteger, quando existe uma relação real de subtipo, quando objetos apenas colaboram e como um contrato permite trocar implementações. O objetivo não é encaixar quatro palavras-chave em todo programa, mas justificar cada escolha pelos efeitos que ela produz.</p>","fidelityText":"Os capítulos anteriores apresentaram os mecanismos separadamente. Agora você vai conectá-los como decisões de modelagem: qual estado proteger, quando existe uma relação real de subtipo, quando objetos apenas colaboram e como um contrato permite trocar implementações. O objetivo não é encaixar quatro palavras-chave em todo programa, mas justificar cada escolha pelos efeitos que ela produz."},{"id":"pilares-profundo-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Pilar 1 — Encapsulamento: a fronteira entre o que é seu e o que é do mundo</h2>","fidelityText":"Pilar 1 — Encapsulamento: a fronteira entre o que é seu e o que é do mundo"},{"id":"pilares-profundo-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"┌─────────────────────────────────────────┐\n│              BankAccount                │\n│  ┌─────────────────────────────────────┐ │\n│  │  STATE PRIVATE (invisivel de outside)  │ │\n│  │  private double balance = 1000;        │ │\n│  └─────────────────────────────────────┘ │\n│                                            │\n│  INTERFACE PUBLIC (unica port de entrada)│\n│  ┌──────────────┐  ┌──────────────────┐  │\n│  │ deposit(v) │  │ withdraw(v)          │  │\n│  └──────────────┘  └──────────────────┘  │\n└─────────────────────────────────────────┘\n        ▲                    ▲\n        │                    │\n   code external       code external\n   (nao pode fazer       (so pode interagir\n    balance = -500          atraves dos methods\n    directly)           que aplicam rules)","fidelityText":"┌─────────────────────────────────────────┐ │ ContaBancaria │ │ ┌─────────────────────────────────────┐ │ │ │ ESTADO PRIVADO (invisível de fora) │ │ │ │ private double saldo = 1000; │ │ │ └─────────────────────────────────────┘ │ │ │ │ INTERFACE PÚBLICA (única porta de entrada)│ │ ┌──────────────┐ ┌──────────────────┐ │ │ │ depositar(v) │ │ sacar(v) │ │ │ └──────────────┘ └──────────────────┘ │ └─────────────────────────────────────────┘ ▲ ▲ │ │ código externo código externo (não pode fazer (só pode interagir saldo = -500 através dos métodos diretamente) que aplicam regras)","highlightedHtml":"┌─────────────────────────────────────────┐\n│              BankAccount                │\n│  ┌─────────────────────────────────────┐ │\n│  │  STATE PRIVATE (invisivel de outside)  │ │\n│  │  private double balance = 1000;        │ │\n│  └─────────────────────────────────────┘ │\n│                                            │\n│  INTERFACE PUBLIC (unica port de entrada)│\n│  ┌──────────────┐  ┌──────────────────┐  │\n│  │ deposit(v) │  │ withdraw(v)          │  │\n│  └──────────────┘  └──────────────────┘  │\n└─────────────────────────────────────────┘\n        ▲                    ▲\n        │                    │\n   code external       code external\n   (nao pode fazer       (so pode interagir\n    balance = -500          atraves dos methods\n    directly)           que aplicam rules)","caption":"Exemplo executável de pilares-profundo.","explanation":["O diagrama separa estado privado e operações públicas.","A garantia vem das invariantes preservadas, não apenas do modificador private."],"commonMistakes":["Criar setter para saldo","Tratar diagrama como layout real de memória"]},{"id":"pilares-profundo-content-5","type":"html","authorship":"legacy-preserved","html":"<p>A ideia central, revisitada com mais rigor teórico: encapsulamento não é \"esconder por esconder\" — é estabelecer um <strong>contrato</strong> entre o objeto e o resto do sistema. O objeto promete: \"eu garanto que meu estado interno sempre respeitará minhas invariantes (regras que sempre devem ser verdadeiras), desde que você só interaja comigo através da minha interface pública\". Um <em>invariante</em> de <code>ContaBancaria</code> poderia ser \"saldo nunca é negativo\" — e essa garantia só é possível porque nenhum código externo consegue atribuir diretamente a <code>saldo</code>.</p>","fidelityText":"A ideia central, revisitada com mais rigor teórico: encapsulamento não é \"esconder por esconder\" — é estabelecer um contrato entre o objeto e o resto do sistema. O objeto promete: \"eu garanto que meu estado interno sempre respeitará minhas invariantes (regras que sempre devem ser verdadeiras), desde que você só interaja comigo através da minha interface pública\". Um invariante de ContaBancaria poderia ser \"saldo nunca é negativo\" — e essa garantia só é possível porque nenhum código externo consegue atribuir diretamente a saldo."},{"id":"pilares-profundo-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em uma cápsula de remédio (a própria origem da palavra \"encapsulamento\"): o princípio ativo (o estado interno) fica protegido dentro de uma casca que controla exatamente como e quando ele é liberado no organismo (a interface pública). Você nunca manipula o princípio ativo diretamente com os dedos — interage com a cápsula como um todo, confiando que ela foi desenhada para liberar o conteúdo de forma segura e correta.</div>","fidelityText":"Pense em uma cápsula de remédio (a própria origem da palavra \"encapsulamento\"): o princípio ativo (o estado interno) fica protegido dentro de uma casca que controla exatamente como e quando ele é liberado no organismo (a interface pública). Você nunca manipula o princípio ativo diretamente com os dedos — interage com a cápsula como um todo, confiando que ela foi desenhada para liberar o conteúdo de forma segura e correta."},{"id":"pilares-profundo-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Pilar 2 — Herança: reaproveitamento por especialização, com um custo de acoplamento</h2>","fidelityText":"Pilar 2 — Herança: reaproveitamento por especialização, com um custo de acoplamento"},{"id":"pilares-profundo-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"                    ┌───────────────┐\n                    │   Employee  │  ← superclass\n                    │  - name        │\n                    │  - salaryBase │\n                    │  + calculateSalary()\n                    └───────┬───────┘\n                            │ extends\n              ┌─────────────┴─────────────┐\n              ▼                           ▼\n     ┌─────────────────┐        ┌─────────────────┐\n     │     Manager       │        │    Intern    │\n     │  - bonus           │        │  - grantAuxilio  │\n     │  + calculateSalary()│       │  + calculateSalary()\n     │    (SOBRESCRITO)   │        │    (SOBRESCRITO)  │\n     └─────────────────┘        └─────────────────┘\n\n    Manager e Intern HERDAM name e salaryBase de Employee,\n    mas cada um REDEFINE calculateSalary() com sua propria rule --\n    isso already e uma preview do NEXT pilar (polimorfismo)","fidelityText":"┌───────────────┐ │ Funcionario │ ← superclasse │ - nome │ │ - salarioBase │ │ + calcularSalario() └───────┬───────┘ │ extends ┌─────────────┴─────────────┐ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ Gerente │ │ Estagiario │ │ - bonus │ │ - bolsaAuxilio │ │ + calcularSalario()│ │ + calcularSalario() │ (SOBRESCRITO) │ │ (SOBRESCRITO) │ └─────────────────┘ └─────────────────┘ Gerente e Estagiario HERDAM nome e salarioBase de Funcionario, mas cada um REDEFINE calcularSalario() com sua própria regra -- isso já é uma prévia do PRÓXIMO pilar (polimorfismo)","highlightedHtml":"                    ┌───────────────┐\n                    │   Employee  │  ← superclass\n                    │  - name        │\n                    │  - salaryBase │\n                    │  + calculateSalary()\n                    └───────┬───────┘\n                            │ extends\n              ┌─────────────┴─────────────┐\n              ▼                           ▼\n     ┌─────────────────┐        ┌─────────────────┐\n     │     Manager       │        │    Intern    │\n     │  - bonus           │        │  - grantAuxilio  │\n     │  + calculateSalary()│       │  + calculateSalary()\n     │    (SOBRESCRITO)   │        │    (SOBRESCRITO)  │\n     └─────────────────┘        └─────────────────┘\n\n    Manager e Intern HERDAM name e salaryBase de Employee,\n    mas cada um REDEFINE calculateSalary() com sua propria rule --\n    isso already e uma preview do NEXT pilar (polimorfismo)","caption":"Exemplo executável de pilares-profundo.","explanation":["A árvore mostra uma relação de subtipo e métodos sobrescritos.","A escolha precisa preservar o contrato; composição é preferível quando não existe é-um."],"commonMistakes":["Herdar apenas para copiar campos","Criar hierarquia profunda"]},{"id":"pilares-profundo-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Herança modela relações <strong>\"é um\"</strong> (is-a): um <code>Gerente</code> é um <code>Funcionario</code>. A palavra-chave <code>extends</code> não copia código magicamente — ela estabelece que toda instância de <code>Gerente</code> <strong>também é, simultaneamente</strong>, uma instância válida de <code>Funcionario</code>, herdando sua estrutura de memória e comportamento, podendo especializar partes dele.</p>","fidelityText":"Herança modela relações \"é um\" (is-a): um Gerente é um Funcionario. A palavra-chave extends não copia código magicamente — ela estabelece que toda instância de Gerente também é, simultaneamente, uma instância válida de Funcionario, herdando sua estrutura de memória e comportamento, podendo especializar partes dele."},{"id":"pilares-profundo-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Herança cria acoplamento.</b> Uma mudança na superclasse pode afetar todas as subclasses. Use <code>extends</code> quando o subtipo realmente precisa cumprir o contrato do tipo geral. Quando um objeto apenas usa outro para realizar seu trabalho — um <code>Carro</code> tem um <code>Motor</code>, por exemplo — modele colaboração por campos e métodos. A decisão nasce da relação do domínio, não do desejo de reutilizar linhas.</div>","fidelityText":"Herança cria acoplamento. Uma mudança na superclasse pode afetar todas as subclasses. Use extends quando o subtipo realmente precisa cumprir o contrato do tipo geral. Quando um objeto apenas usa outro para realizar seu trabalho — um Carro tem um Motor, por exemplo — modele colaboração por campos e métodos. A decisão nasce da relação do domínio, não do desejo de reutilizar linhas."},{"id":"pilares-profundo-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Pilar 3 — Polimorfismo: a mesma mensagem, respostas diferentes, decididas em runtime</h2>","fidelityText":"Pilar 3 — Polimorfismo: a mesma mensagem, respostas diferentes, decididas em runtime"},{"id":"pilares-profundo-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"Employee[] team = { new Manager(...), new Intern(...) };\n\npara cada \"f\" em team:\n    f.calculateSalary()\n         │\n         ▼\n    ┌─────────────────────────────────────────┐\n    │  A JVM olha o type REAL do object no     │\n    │  HEAP (nao o type da variable \"f\")       │\n    │  e decide, EM TIME DE EXECUTION, qual    │\n    │  version de calculateSalary() call --   │\n    │  isso se chama LIGACAO DINAMICA           │\n    └─────────────────────────────────────────┘\n         │\n    ┌────┴────┐\n    ▼         ▼\n Manager   Intern\n (uma      (uma logic\n  logic)   different)","fidelityText":"Funcionario[] equipe = { new Gerente(...), new Estagiario(...) }; para cada \"f\" em equipe: f.calcularSalario() │ ▼ ┌─────────────────────────────────────────┐ │ A JVM olha o tipo REAL do objeto no │ │ HEAP (não o tipo da variável \"f\") │ │ e decide, EM TEMPO DE EXECUÇÃO, qual │ │ versão de calcularSalario() chamar -- │ │ isso se chama LIGAÇÃO DINÂMICA │ └─────────────────────────────────────────┘ │ ┌────┴────┐ ▼ ▼ Gerente Estagiario (uma (uma lógica lógica) diferente)","highlightedHtml":"Employee[] team = { new Manager(...), new Intern(...) };\n\npara cada \"f\" em team:\n    f.calculateSalary()\n         │\n         ▼\n    ┌─────────────────────────────────────────┐\n    │  A JVM olha o type REAL do object no     │\n    │  HEAP (nao o type da variable \"f\")       │\n    │  e decide, EM TIME DE EXECUTION, qual    │\n    │  version de calculateSalary() call --   │\n    │  isso se chama LIGACAO DINAMICA           │\n    └─────────────────────────────────────────┘\n         │\n    ┌────┴────┐\n    ▼         ▼\n Manager   Intern\n (uma      (uma logic\n  logic)   different)","caption":"Exemplo executável de pilares-profundo.","explanation":["A chamada pelo tipo geral permite respostas específicas dos objetos.","A especificação exige despacho dinâmico, mas não obriga uma implementação interna por vtable."],"commonMistakes":["Ensinar vtable como regra da JVM","Confundir campo com método sobrescrito"]},{"id":"pilares-profundo-content-13","type":"html","authorship":"legacy-preserved","html":"<p>A regra observável é esta: o compilador verifica se o método pode ser chamado pelo tipo da expressão; durante a execução, a classe real do objeto determina qual implementação sobrescrita responde. A especificação da linguagem exige esse comportamento, mas não obriga a JVM a usar uma estrutura interna específica. Uma implementação pode empregar tabelas, caches e otimizações diferentes sem alterar o resultado do programa. Portanto, <strong>despacho dinâmico</strong> faz parte do modelo da linguagem; “vtable obrigatória” não.</p>","fidelityText":"A regra observável é esta: o compilador verifica se o método pode ser chamado pelo tipo da expressão; durante a execução, a classe real do objeto determina qual implementação sobrescrita responde. A especificação da linguagem exige esse comportamento, mas não obriga a JVM a usar uma estrutura interna específica. Uma implementação pode empregar tabelas, caches e otimizações diferentes sem alterar o resultado do programa. Portanto, despacho dinâmico faz parte do modelo da linguagem; “vtable obrigatória” não."},{"id":"pilares-profundo-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em apertar “tocar” em um controle universal. O comando disponível é conhecido pelo controle, mas a reação pertence ao aparelho conectado. Da mesma forma, o tipo da variável define quais chamadas são permitidas e o objeto real fornece a implementação sobrescrita.</div>","fidelityText":"Pense em apertar “tocar” em um controle universal. O comando disponível é conhecido pelo controle, mas a reação pertence ao aparelho conectado. Da mesma forma, o tipo da variável define quais chamadas são permitidas e o objeto real fornece a implementação sobrescrita."},{"id":"pilares-profundo-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Pilar 4 — Abstração: modelando só o que importa para o problema em questão</h2>","fidelityText":"Pilar 4 — Abstração: modelando só o que importa para o problema em questão"},{"id":"pilares-profundo-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Este é o pilar mais frequentemente mal-entendido — muita gente confunde \"abstração\" com \"classe abstrata\", quando na verdade classe abstrata é só <strong>uma ferramenta</strong> de linguagem para expressar abstração, não o conceito em si.</p>","fidelityText":"Este é o pilar mais frequentemente mal-entendido — muita gente confunde \"abstração\" com \"classe abstrata\", quando na verdade classe abstrata é só uma ferramenta de linguagem para expressar abstração, não o conceito em si."},{"id":"pilares-profundo-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"MUNDO REAL: um car tem milhares de details --\nengine de combustion internal com centenas de parts, system electric\ncomplexo, suspension, tires com composition chemistry specifies...\n\nMODELAGEM PARA UM SYSTEM DE RENTAL DE CARROS:\n┌───────────────────────────┐\n│          Car              │  ← ABSTRACAO: so os details\n│  - placa                    │    relevant para ESTE context\n│  - model                   │    especifico (rental) enter\n│  - available: boolean       │    no model. O engine, a suspension,\n│  + rent()                  │    a composition do tire -- tudo\n│  + return()                │    IRRELEVANTE aqui, e por isso\n└───────────────────────────┘    correctly OMITIDO","fidelityText":"MUNDO REAL: um carro tem milhares de detalhes -- motor de combustão interna com centenas de peças, sistema elétrico complexo, suspensão, pneus com composição química específica... MODELAGEM PARA UM SISTEMA DE ALUGUEL DE CARROS: ┌───────────────────────────┐ │ Carro │ ← ABSTRAÇÃO: só os detalhes │ - placa │ relevantes para ESTE contexto │ - modelo │ específico (aluguel) entram │ - disponivel: boolean │ no modelo. O motor, a suspensão, │ + alugar() │ a composição do pneu -- tudo │ + devolver() │ IRRELEVANTE aqui, e por isso └───────────────────────────┘ corretamente OMITIDO","highlightedHtml":"MUNDO REAL: um car tem milhares de details --\nengine de combustion internal com centenas de parts, system electric\ncomplexo, suspension, tires com composition chemistry specifies...\n\nMODELAGEM PARA UM SYSTEM DE RENTAL DE CARROS:\n┌───────────────────────────┐\n│          Car              │  ← ABSTRACAO: so os details\n│  - placa                    │    relevant para ESTE context\n│  - model                   │    especifico (rental) enter\n│  - available: boolean       │    no model. O engine, a suspension,\n│  + rent()                  │    a composition do tire -- tudo\n│  + return()                │    IRRELEVANTE aqui, e por isso\n└───────────────────────────┘    correctly OMITIDO","caption":"Exemplo executável de pilares-profundo.","explanation":["O modelo Carro inclui somente informações pertinentes ao aluguel.","Outro contexto pode exigir outra abstração do mesmo carro real."],"commonMistakes":["Modelar o mundo inteiro","Confundir abstração com abstract"]},{"id":"pilares-profundo-content-18","type":"html","authorship":"legacy-preserved","html":"<p>Abstração é o processo de <strong>decidir o que ignorar</strong>. Um sistema de aluguel de carros não precisa modelar a pressão dos pneus; um sistema de manutenção automotiva provavelmente precisaria. A \"abstração certa\" não é universal — depende inteiramente do problema que o software está resolvendo. Uma classe <code>abstract</code> e uma <code>interface</code> são <strong>ferramentas sintáticas</strong> para expressar partes dessa decisão no código, mas escolher o que incluir ou descartar acontece antes de escrever qualquer palavra-chave.</p>","fidelityText":"Abstração é o processo de decidir o que ignorar. Um sistema de aluguel de carros não precisa modelar a pressão dos pneus; um sistema de manutenção automotiva provavelmente precisaria. A \"abstração certa\" não é universal — depende inteiramente do problema que o software está resolvendo. Uma classe abstract e uma interface são ferramentas sintáticas para expressar partes dessa decisão no código, mas escolher o que incluir ou descartar acontece antes de escrever qualquer palavra-chave."},{"id":"pilares-profundo-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um mapa de metrô é uma abstração poderosa: ele deliberadamente <strong>distorce</strong> a geografia real (distâncias e curvas exatas das linhas) para destacar só o que importa para quem vai usar o metrô — em qual estação trocar de linha, quantas paradas faltam. Um mapa \"perfeitamente preciso\" geograficamente seria, paradoxalmente, pior para esse propósito específico — cheio de detalhes irrelevantes que dificultam encontrar a informação que realmente importa. Boa abstração de software segue exatamente esse princípio: omitir informação verdadeira mas irrelevante, para destacar a informação que resolve o problema em questão.</div>","fidelityText":"Um mapa de metrô é uma abstração poderosa: ele deliberadamente distorce a geografia real (distâncias e curvas exatas das linhas) para destacar só o que importa para quem vai usar o metrô — em qual estação trocar de linha, quantas paradas faltam. Um mapa \"perfeitamente preciso\" geograficamente seria, paradoxalmente, pior para esse propósito específico — cheio de detalhes irrelevantes que dificultam encontrar a informação que realmente importa. Boa abstração de software segue exatamente esse princípio: omitir informação verdadeira mas irrelevante, para destacar a informação que resolve o problema em questão."},{"id":"pilares-profundo-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Como os quatro pilares se sustentam mutuamente</h2>","fidelityText":"Como os quatro pilares se sustentam mutuamente"},{"id":"pilares-profundo-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"           ABSTRACAO\n    (decide O QUE model)\n              │\n              ▼\n      ┌───────────────┐\n      │  ENCAPSULAMENTO │  (protects o AS foi modeled)\n      └───────┬───────┘\n              │\n              ▼\n        ┌───────────┐\n        │  HERANCA   │  (reaproveita e especializa o model)\n        └─────┬─────┘\n              │\n              ▼\n       ┌──────────────┐\n       │ POLIMORFISMO   │  (allows handle especializacoes\n       └──────────────┘   de shape uniforme)","fidelityText":"ABSTRAÇÃO (decide O QUE modelar) │ ▼ ┌───────────────┐ │ ENCAPSULAMENTO │ (protege o COMO foi modelado) └───────┬───────┘ │ ▼ ┌───────────┐ │ HERANÇA │ (reaproveita e especializa o modelo) └─────┬─────┘ │ ▼ ┌──────────────┐ │ POLIMORFISMO │ (permite tratar especializações └──────────────┘ de forma uniforme)","highlightedHtml":"           ABSTRACAO\n    (decide O QUE model)\n              │\n              ▼\n      ┌───────────────┐\n      │  ENCAPSULAMENTO │  (protects o AS foi modeled)\n      └───────┬───────┘\n              │\n              ▼\n        ┌───────────┐\n        │  HERANCA   │  (reaproveita e especializa o model)\n        └─────┬─────┘\n              │\n              ▼\n       ┌──────────────┐\n       │ POLIMORFISMO   │  (allows handle especializacoes\n       └──────────────┘   de shape uniforme)","caption":"Exemplo executável de pilares-profundo.","explanation":["O esquema relaciona os mecanismos como decisões cooperativas.","Nenhum pilar precisa aparecer artificialmente quando o problema não o exige."],"commonMistakes":["Avaliar design contando palavras-chave","Usar herança onde colaboração basta"]},{"id":"pilares-profundo-content-22","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Nenhum desses pilares funciona isoladamente na prática — eles formam um sistema coeso. Você abstrai o problema (decide que \"Funcionario\" é o conceito relevante), encapsula seu estado (protege <code>salarioBase</code> com <code>private</code>), usa herança para modelar especializações (<code>Gerente</code>, <code>Estagiario</code>) sem duplicar código, e o polimorfismo é o que permite que o resto do sistema trate qualquer <code>Funcionario</code> de forma uniforme, sem precisar saber qual especialização exata está lidando com aquele objeto em cada momento. Um projeto que usa só um ou dois desses pilares raramente aproveita todo o potencial do paradigma orientado a objetos.</div>","fidelityText":"Nenhum desses pilares funciona isoladamente na prática — eles formam um sistema coeso. Você abstrai o problema (decide que \"Funcionario\" é o conceito relevante), encapsula seu estado (protege salarioBase com private), usa herança para modelar especializações (Gerente, Estagiario) sem duplicar código, e o polimorfismo é o que permite que o resto do sistema trate qualquer Funcionario de forma uniforme, sem precisar saber qual especialização exata está lidando com aquele objeto em cada momento. Um projeto que usa só um ou dois desses pilares raramente aproveita todo o potencial do paradigma orientado a objetos."},{"id":"pilares-profundo-exercise-23","type":"exercise","authorship":"legacy-preserved","title":"Exercício 86.1 — Identificando os quatro pilares em um sistema","prompt":"Modele um sistema pequeno de notificações sem usar coleções. Crie uma interface Canal com enviar(String mensagem), duas implementações (Email e Sms) e uma classe CentralNotificacoes que recebe um Canal pelo construtor. Proteja os dados obrigatórios de cada canal. Depois explique onde há abstração, encapsulamento e polimorfismo. Só use herança de classes se conseguir demonstrar uma relação “é um” que preserve o contrato.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 86.1 — Identificando os quatro pilares em um sistemadifícil Modele um sistema pequeno de notificações sem usar coleções. Crie uma interface Canal com enviar(String mensagem), duas implementações (Email e Sms) e uma classe CentralNotificacoes que recebe um Canal pelo construtor. Proteja os dados obrigatórios de cada canal. Depois explique onde há abstração, encapsulamento e polimorfismo. Só use herança de classes se conseguir demonstrar uma relação “é um” que preserve o contrato. Ver solução Abstração: Canal mantém apenas a operação relevante para quem envia. Encapsulamento: cada implementação valida e protege seus próprios dados, como endereço ou número. Polimorfismo: CentralNotificacoes chama enviar pelo contrato e recebe comportamentos diferentes de Email e Sms. Herança: não é obrigatória; implementar a interface já expressa os subtipos necessários. Criar uma superclasse apenas para “usar os quatro pilares” adicionaria acoplamento sem resolver um problema real.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 86.1 — Identificando os quatro pilares em um sistema</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Modele um sistema pequeno de notificações sem usar coleções. Crie uma interface <code>Canal</code> com <code>enviar(String mensagem)</code>, duas implementações (<code>Email</code> e <code>Sms</code>) e uma classe <code>CentralNotificacoes</code> que recebe um <code>Canal</code> pelo construtor. Proteja os dados obrigatórios de cada canal. Depois explique onde há abstração, encapsulamento e polimorfismo. Só use herança de classes se conseguir demonstrar uma relação “é um” que preserve o contrato.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>Abstração:</strong> <code>Canal</code> mantém apenas a operação relevante para quem envia. <strong>Encapsulamento:</strong> cada implementação valida e protege seus próprios dados, como endereço ou número. <strong>Polimorfismo:</strong> <code>CentralNotificacoes</code> chama <code>enviar</code> pelo contrato e recebe comportamentos diferentes de <code>Email</code> e <code>Sms</code>. <strong>Herança:</strong> não é obrigatória; implementar a interface já expressa os subtipos necessários. Criar uma superclasse apenas para “usar os quatro pilares” adicionaria acoplamento sem resolver um problema real.</p>\n        </div>\n      </div>"},{"id":"pilares-table","type":"table","authorship":"authored","title":"Da decisão ao efeito","headers":["Decisão","Problema resolvido","Risco se mal usada"],"rows":[["Abstrair","excesso ou falta de detalhes","modelo irrelevante"],["Encapsular","estado alterado por atalhos","objeto anêmico com setters"],["Criar subtipo","variação sob contrato comum","herança frágil"],["Usar polimorfismo","if por classe concreta","contrato genérico demais"],["Compor objetos","separar responsabilidades","grafo incoerente sem operação de domínio"]],"caption":"Avalie comportamento observável e custo de mudança, não a quantidade de palavras-chave."},{"id":"pilares-quiz","type":"quiz","authorship":"authored","conceptId":"decisao-modelagem-poo","prompt":"Um modelo usa apenas composição, interfaces e objetos encapsulados, sem extends entre classes de domínio. Ele deixa de ser orientado a objetos?","options":[{"id":"pilares-q-a","label":"Não; herança de classes não é requisito para responsabilidade, colaboração e polimorfismo.","correct":true,"explanation":"Interfaces e composição podem oferecer contratos e variação sem uma hierarquia de implementação."},{"id":"pilares-q-b","label":"Sim; todo sistema POO precisa usar os quatro itens na mesma quantidade.","correct":false,"explanation":"Os mecanismos respondem a problemas; forçá-los sem necessidade piora o modelo."},{"id":"pilares-q-c","label":"Sim; composição pertence apenas à programação funcional.","correct":false,"explanation":"Composição de objetos é uma ferramenta central de modelagem orientada a objetos."}]}],"resources":[{"id":"pilares-dev-oop","type":"guide","title":"dev.java: objetos, classes, interfaces e herança","url":"https://dev.java/learn/oop/","reinforces":"Reúne a introdução oficial aos mecanismos de orientação a objetos em Java.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-21","auditStatus":"approved"},{"id":"pilares-jls-intro","type":"reference","title":"JLS 1: visão geral orientada a objetos","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-1.html","reinforces":"Contextualiza classes, instâncias, herança simples, interfaces e polimorfismo.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-21","auditStatus":"approved"}],"englishActivity":{"level":0,"label":"Context first","readPassage":"The pillars deep example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","comprehensionQuestion":"Which name tells you what the program does, and what result can you observe?","contextSupport":"Siga a execução do código. Use nomes, valores e resultado como pistas; não monte uma lista de traduções.","documentationTask":"Na referência oficial, encontre um nome de API que também aparece no capítulo e observe o exemplo ao redor dele.","productionTask":"Write one short sentence using this frame: “The method ___ when ___.”","successCriterion":"Your sentence identifies an action and its observable result from the code.","codeContext":"code external       code external","instruction":"The pillars deep example uses names that reveal state and behavior. Follow the values and the result before looking up unfamiliar words.","prompt":"Write one short sentence using this frame: “The method ___ when ___.”"},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-21","notes":["Removida a afirmação de vtable obrigatória e o enquadramento de entrevista sênior."]}},{"id":"di-ioc-profundo","moduleId":"application-design","order":6,"title":"Injeção de Dependência, IoC & Ciclo de Vida de Beans — Aprofundamento","summary":"Os capítulos 22, 28 e 43 já construíram — na mão e depois com Spring real — o mecanismo de injeção de dependência. Este capítulo separa dois conceitos que costumam ser confundidos como sinônimos, e detalha exatamente o que o container Spring faz, em ordem, do início ao fim da vida de um bean.","objectives":["Distinguir IoC de DI com lifecycle explícito","Entender objeto gerenciado como nó em grafo de dependências","Raciocinar sobre ciclo de vida, inicialização e dependência circular","Preparar leitura futura do container Spring sem depender dele para aprender o conceito"],"whyItExists":"Depois de DI manual e mini-framework, o aluno pode aprofundar o modelo de container. O capítulo usa vocabulário que Spring popularizou, mas foca no grafo, lifecycle e ordem de criação que qualquer container precisa resolver.","prerequisiteChapterIds":["di","anotacoes","projetospring"],"conceptIds":["inversao-de-controle-ioc-nao-e-a-mesma-coisa-que-injecao-de-dependencia-","o-ciclo-de-vida-completo-de-um-bean-cada-etapa-em-ordem-exata","beanfactory-vs-applicationcontext-a-fundacao-por-tras-do-container","dependencias-circulares-quando-o-grafo-nao-tem-ordem-valida"],"introducedConceptIds":["ioc-lifecycle-bean","dependencia-circular-grafo"],"usedConceptIds":["ioc-container-registro-resolucao","annotation-metadata-contract","reflection-runtime-introspection","srp-coesao-motivo-mudanca"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"ioc-intuition","type":"intuition","authorship":"authored","title":"Container é dono de criação, não dono da regra","body":"Quando um container gerencia objetos, ele decide ordem de criação, dependências, inicialização e descarte. A regra de negócio continua pertencendo ao domínio/casos de uso.","analogyLimit":"Maestro ajuda a ideia de coordenação, mas container também monta grafo, detecta ciclos, controla escopo e chama hooks de lifecycle."},{"id":"di-ioc-profundo-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Spring Interno</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#di\">22 · Injeção de dependência</a>, <a class=\"prereq-tag\" href=\"#spring-core\">43 · Spring Core</a>, <a class=\"prereq-tag\" href=\"#anotacoes\">20 · Anotações &amp; Reflection</a></div>\n      </div>","fidelityText":"Spring Interno Dificuldade: Avançado ⏱ ~2h30 de estudo Pré-requisitos: 22 · Injeção de dependência, 43 · Spring Core, 20 · Anotações & Reflection"},{"id":"di-ioc-profundo-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Os capítulos 22, 28 e 43 já construíram — na mão e depois com Spring real — o mecanismo de injeção de dependência. Este capítulo separa dois conceitos que costumam ser confundidos como sinônimos, e detalha exatamente o que o container Spring faz, em ordem, do início ao fim da vida de um bean.</p>","fidelityText":"Os capítulos 22, 28 e 43 já construíram — na mão e depois com Spring real — o mecanismo de injeção de dependência. Este capítulo separa dois conceitos que costumam ser confundidos como sinônimos, e detalha exatamente o que o container Spring faz, em ordem, do início ao fim da vida de um bean."},{"id":"di-ioc-profundo-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Inversão de Controle (IoC) não é a mesma coisa que Injeção de Dependência (DI)</h2>","fidelityText":"Inversão de Controle (IoC) não é a mesma coisa que Injeção de Dependência (DI)"},{"id":"di-ioc-profundo-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Essa distinção é sutil, mas separa quem realmente entende o padrão de quem só decorou o termo.</p>","fidelityText":"Essa distinção é sutil, mas separa quem realmente entende o padrão de quem só decorou o termo."},{"id":"di-ioc-profundo-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"SEM IoC (controle NORMAL do flow -- seu código decide tudo):\n┌─────────────────────────────────────────┐\n│  OrderService decide:                   │\n│  1. WHEN create um EmailService          │\n│  2. AS create (new EmailService())       │\n│  3. QUAL implementation use               │\n│                                            │\n│  new EmailService() ← seu code no       │\n│                        controle total     │\n└─────────────────────────────────────────┘\n\nCOM IoC (o controle e INVERTIDO -- um container externo decide):\n┌─────────────────────────────────────────┐\n│  Spring Container decide:                 │\n│  1. WHEN create (na inicializacao)       │\n│  2. AS create (via reflection)           │\n│  3. QUAL implementation use (baseado      │\n│     em qual @Component foi registrado)    │\n│                                            │\n│  OrderService so DECLARES \"eu preciso     │\n│  de um Notifier\" e RECEBE de outside --   │\n│  perdeu o controle sobre a creation        │\n└─────────────────────────────────────────┘","fidelityText":"SEM IoC (controle NORMAL do fluxo -- seu código decide tudo): ┌─────────────────────────────────────────┐ │ PedidoServico decide: │ │ 1. QUANDO criar um EmailServico │ │ 2. COMO criar (new EmailServico()) │ │ 3. QUAL implementação usar │ │ │ │ new EmailServico() ← seu código no │ │ controle total │ └─────────────────────────────────────────┘ COM IoC (o controle é INVERTIDO -- um container externo decide): ┌─────────────────────────────────────────┐ │ Spring Container decide: │ │ 1. QUANDO criar (na inicialização) │ │ 2. COMO criar (via reflection) │ │ 3. QUAL implementação usar (baseado │ │ em qual @Component foi registrado) │ │ │ │ PedidoServico só DECLARA \"eu preciso │ │ de um Notificador\" e RECEBE de fora -- │ │ perdeu o controle sobre a criação │ └─────────────────────────────────────────┘","highlightedHtml":"SEM IoC (controle NORMAL do flow -- seu código decide tudo):\n┌─────────────────────────────────────────┐\n│  OrderService decide:                   │\n│  1. WHEN create um EmailService          │\n│  2. AS create (new EmailService())       │\n│  3. QUAL implementation use               │\n│                                            │\n│  new EmailService() ← seu code no       │\n│                        controle total     │\n└─────────────────────────────────────────┘\n\nCOM IoC (o controle e INVERTIDO -- um container externo decide):\n┌─────────────────────────────────────────┐\n│  Spring Container decide:                 │\n│  1. WHEN create (na inicializacao)       │\n│  2. AS create (via reflection)           │\n│  3. QUAL implementation use (baseado      │\n│     em qual @Component foi registrado)    │\n│                                            │\n│  OrderService so DECLARES \"eu preciso     │\n│  de um Notifier\" e RECEBE de outside --   │\n│  perdeu o controle sobre a creation        │\n└─────────────────────────────────────────┘","caption":"Exemplo executável de di-ioc-profundo.","explanation":["IoC inverte quem controla criação e lifecycle.","DI é uma técnica comum dentro de IoC, não sinônimo completo."],"commonMistakes":["Chamar qualquer construtor de IoC","Achar que IoC exige Spring"]},{"id":"di-ioc-profundo-content-6","type":"html","authorship":"legacy-preserved","html":"<p><strong>Inversão de Controle</strong> é o princípio geral: em vez do seu código controlar o fluxo de criação e orquestração de objetos, essa responsabilidade é transferida (\"invertida\") para um framework/container externo. <strong>Injeção de Dependência</strong> é <em>uma técnica específica</em> de implementar IoC — a mais comum, mas não a única. Outras formas de IoC incluem o <em>Service Locator pattern</em> (onde você pede ativamente ao container por uma dependência, em vez de recebê-la automaticamente) e frameworks de eventos, onde o controle de \"quando seu código roda\" é invertido para o framework que dispara callbacks.</p>","fidelityText":"Inversão de Controle é o princípio geral: em vez do seu código controlar o fluxo de criação e orquestração de objetos, essa responsabilidade é transferida (\"invertida\") para um framework/container externo. Injeção de Dependência é uma técnica específica de implementar IoC — a mais comum, mas não a única. Outras formas de IoC incluem o Service Locator pattern (onde você pede ativamente ao container por uma dependência, em vez de recebê-la automaticamente) e frameworks de eventos, onde o controle de \"quando seu código roda\" é invertido para o framework que dispara callbacks."},{"id":"di-ioc-profundo-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em restaurantes. No modelo tradicional (sem IoC), você (o cliente, seu código) vai até a cozinha, escolhe os ingredientes, cozinha sua própria comida — controle total, mas todo o trabalho é seu. Em um restaurante de verdade (com IoC), você diz o que quer (\"preciso de um Notificador\") e o restaurante (o container Spring) decide como preparar, com quais ingredientes, e te entrega pronto — você perdeu o controle sobre o \"como\", mas ganhou simplicidade e desacoplamento da cozinha em si.</div>","fidelityText":"Pense em restaurantes. No modelo tradicional (sem IoC), você (o cliente, seu código) vai até a cozinha, escolhe os ingredientes, cozinha sua própria comida — controle total, mas todo o trabalho é seu. Em um restaurante de verdade (com IoC), você diz o que quer (\"preciso de um Notificador\") e o restaurante (o container Spring) decide como preparar, com quais ingredientes, e te entrega pronto — você perdeu o controle sobre o \"como\", mas ganhou simplicidade e desacoplamento da cozinha em si."},{"id":"di-ioc-profundo-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>O ciclo de vida completo de um Bean — cada etapa, em ordem exata</h2>","fidelityText":"O ciclo de vida completo de um Bean — cada etapa, em ordem exata"},{"id":"di-ioc-profundo-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Quando a aplicação Spring Boot inicia, para <strong>cada</strong> bean gerenciado, este é o percurso completo:</p>","fidelityText":"Quando a aplicação Spring Boot inicia, para cada bean gerenciado, este é o percurso completo:"},{"id":"di-ioc-profundo-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"┌──────────────────────────────────────────────────────────────┐\n│ 1. INSTANTIATION                                                 │\n│    O Spring encontra a type annotated (@Component/@Service/etc, │\n│    chapter 43) via @ComponentScan (reflection, chapter 20) e  │\n│    chama o constructor -- criando o objeto \"cru\", sem dependências│\n└──────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 2. INJECTION DE DEPENDENCIAS                                       │\n│    Se a injection e via CONSTRUCTOR (chapter 22, a preferida),     │\n│    isso already aconteceu no step 1 -- o Spring resolve o GRAFO       │\n│    de dependencias first (quem precisa de quem) para saber    │\n│    em que ORDER instantiate. Se e via field (@Autowired em field),│\n│    a injection acontece AQUI, depois da instantiation, via           │\n│    reflection (Field.set(), chapter 20)                          │\n└──────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 3. BeanNameAware / ApplicationContextAware (se implementados)    │\n│    Callbacks optional -- o bean \"sabe\" seu próprio nome no        │\n│    container, ou tem access ao container integer, se precisar     │\n└──────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 4. @PostConstruct                                                │\n│    Method annotated, ticket DEPOIS que ALL as dependencias already    │\n│    foram injetadas -- o lugar certo para inicialização que        │\n│    DEPENDS de other bean already be ready (algo que o constructor   │\n│    sozinho nao garantiria com security)                          │\n└──────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 5. BEAN READY PARA USO                                          │\n│    Stays assim durante toda a vida da application (se scope         │\n│    singleton, chapter 43 -- o padrão) ou até ser descartado,      │\n│    se scope prototype                                            │\n└──────────────────────────────────────────────────────────────┘\n                            │\n                    (application encerrando...)\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 6. @PreDestroy                                                   │\n│    Method annotated, ticket BEFORE do container destruir o bean --  │\n│    o place certo para release resources (close connections,          │\n│    stop threads proprias, chapter 14)                           │\n└──────────────────────────────────────────────────────────────┘","fidelityText":"┌──────────────────────────────────────────────────────────────┐ │ 1. INSTANCIAÇÃO │ │ O Spring encontra a classe anotada (@Component/@Service/etc, │ │ capítulo 43) via @ComponentScan (reflection, capítulo 20) e │ │ chama o construtor -- criando o objeto \"cru\", sem dependências│ └──────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ 2. INJEÇÃO DE DEPENDÊNCIAS │ │ Se a injeção é via CONSTRUTOR (capítulo 22, a preferida), │ │ isso já aconteceu no passo 1 -- o Spring resolve o GRAFO │ │ de dependências primeiro (quem precisa de quem) para saber │ │ em que ORDEM instanciar. Se é via campo (@Autowired em field),│ │ a injeção acontece AQUI, depois da instanciação, via │ │ reflection (Field.set(), capítulo 20) │ └──────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ 3. BeanNameAware / ApplicationContextAware (se implementados) │ │ Callbacks opcionais -- o bean \"sabe\" seu próprio nome no │ │ container, ou tem acesso ao container inteiro, se precisar │ └──────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ 4. @PostConstruct │ │ Método anotado, chamado DEPOIS que TODAS as dependências já │ │ foram injetadas -- o lugar certo para inicialização que │ │ DEPENDE de outro bean já estar pronto (algo que o construtor │ │ sozinho não garantiria com segurança) │ └──────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ 5. BEAN PRONTO PARA USO │ │ Fica assim durante toda a vida da aplicação (se escopo │ │ singleton, capítulo 43 -- o padrão) ou até ser descartado, │ │ se escopo prototype │ └──────────────────────────────────────────────────────────────┘ │ (aplicação encerrando...) ▼ ┌──────────────────────────────────────────────────────────────┐ │ 6. @PreDestroy │ │ Método anotado, chamado ANTES do container destruir o bean -- │ │ o lugar certo para liberar recursos (fechar conexões, │ │ parar threads próprias, capítulo 14) │ └──────────────────────────────────────────────────────────────┘","highlightedHtml":"┌──────────────────────────────────────────────────────────────┐\n│ 1. INSTANTIATION                                                 │\n│    O Spring encontra a type annotated (@Component/@Service/etc, │\n│    chapter 43) via @ComponentScan (reflection, chapter 20) e  │\n│    chama o constructor -- criando o objeto \"cru\", sem dependências│\n└──────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 2. INJECTION DE DEPENDENCIAS                                       │\n│    Se a injection e via CONSTRUCTOR (chapter 22, a preferida),     │\n│    isso already aconteceu no step 1 -- o Spring resolve o GRAFO       │\n│    de dependencias first (quem precisa de quem) para saber    │\n│    em que ORDER instantiate. Se e via field (@Autowired em field),│\n│    a injection acontece AQUI, depois da instantiation, via           │\n│    reflection (Field.set(), chapter 20)                          │\n└──────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 3. BeanNameAware / ApplicationContextAware (se implementados)    │\n│    Callbacks optional -- o bean \"sabe\" seu próprio nome no        │\n│    container, ou tem access ao container integer, se precisar     │\n└──────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 4. @PostConstruct                                                │\n│    Method annotated, ticket DEPOIS que ALL as dependencias already    │\n│    foram injetadas -- o lugar certo para inicialização que        │\n│    DEPENDS de other bean already be ready (algo que o constructor   │\n│    sozinho nao garantiria com security)                          │\n└──────────────────────────────────────────────────────────────┘\n                            │\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 5. BEAN READY PARA USO                                          │\n│    Stays assim durante toda a vida da application (se scope         │\n│    singleton, chapter 43 -- o padrão) ou até ser descartado,      │\n│    se scope prototype                                            │\n└──────────────────────────────────────────────────────────────┘\n                            │\n                    (application encerrando...)\n                            ▼\n┌──────────────────────────────────────────────────────────────┐\n│ 6. @PreDestroy                                                   │\n│    Method annotated, ticket BEFORE do container destruir o bean --  │\n│    o place certo para release resources (close connections,          │\n│    stop threads proprias, chapter 14)                           │\n└──────────────────────────────────────────────────────────────┘","caption":"Exemplo executável de di-ioc-profundo.","explanation":["Lifecycle possui etapas observáveis de criação, injeção, inicialização, uso e descarte.","Hooks devem preparar recurso, não esconder regra de negócio."],"commonMistakes":["Abrir recurso sem fechar","Colocar regra de domínio em callback de lifecycle"]},{"id":"di-ioc-profundo-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class ConnectionExternalService {\n    private Connection persistentConnection;\n\n    @PostConstruct\n    public void inicializar() {\n        // roda DEPOIS de todas as dependências injetadas --\n        // seguro para usar qualquer @Autowired daqui\n        persistentConnection = openConnectionWithServiceExternal();\n        System.out.println(\"Connection external established\");\n    }\n\n    @PreDestroy\n    public void close() {\n        persistentConnection.close(); // libera o recurso antes da JVM desligar\n        System.out.println(\"Connection external closed\");\n    }\n}","fidelityText":"@Service public class ConexaoExternaServico { private Connection conexaoPersistente; @PostConstruct public void inicializar() { // roda DEPOIS de todas as dependências injetadas -- // seguro para usar qualquer @Autowired daqui conexaoPersistente = abrirConexaoComServicoExterno(); System.out.println(\"Conexão externa estabelecida\"); } @PreDestroy public void encerrar() { conexaoPersistente.fechar(); // libera o recurso antes da JVM desligar System.out.println(\"Conexão externa encerrada\"); } }","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">ConnectionExternalService</span> {\n    <span class=\"kw\">private</span> Connection persistentConnection;\n\n    <span class=\"annotation\">@PostConstruct</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">inicializar</span>() {\n        <span class=\"com\">// roda DEPOIS de todas as dependências injetadas --\n        // seguro para usar qualquer @Autowired daqui</span>\n        persistentConnection = openConnectionWithServiceExternal();\n        System.out.println(<span class=\"str\">\"Connection external established\"</span>);\n    }\n\n    <span class=\"annotation\">@PreDestroy</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">close</span>() {\n        persistentConnection.close(); <span class=\"com\">// libera o recurso antes da JVM desligar</span>\n        System.out.println(<span class=\"str\">\"Connection external closed\"</span>);\n    }\n}","caption":"Exemplo executável de di-ioc-profundo.","explanation":["Context/container resolve objetos a partir de registro e grafo de dependências.","A aplicação pede casos de uso prontos; não deve espalhar resolução por todo domínio."],"commonMistakes":["Service locator global","Resolver dependência dentro de entidade"]},{"id":"di-ioc-profundo-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Por que <code>@PostConstruct</code> existe, se você já tem o construtor? Porque no momento em que o <strong>construtor</strong> roda, para injeção via campo (<code>@Autowired</code> em atributo, capítulo 22), as dependências <strong>ainda não foram injetadas</strong> — elas só são preenchidas depois, no passo 2 do ciclo acima. Chamar um método que usa uma dependência ainda não injetada dentro do próprio construtor causaria <code>NullPointerException</code>. Com injeção via <strong>construtor</strong> (a forma preferida, exatamente porque evita essa armadilha), as dependências já chegam prontas no momento da instanciação — reduzindo bastante a necessidade prática de <code>@PostConstruct</code>, mas ele continua essencial quando a inicialização envolve algo além de simplesmente receber dependências (abrir uma conexão de rede, validar configuração externa).</div>","fidelityText":"Por que @PostConstruct existe, se você já tem o construtor? Porque no momento em que o construtor roda, para injeção via campo (@Autowired em atributo, capítulo 22), as dependências ainda não foram injetadas — elas só são preenchidas depois, no passo 2 do ciclo acima. Chamar um método que usa uma dependência ainda não injetada dentro do próprio construtor causaria NullPointerException. Com injeção via construtor (a forma preferida, exatamente porque evita essa armadilha), as dependências já chegam prontas no momento da instanciação — reduzindo bastante a necessidade prática de @PostConstruct, mas ele continua essencial quando a inicialização envolve algo além de simplesmente receber dependências (abrir uma conexão de rede, validar configuração externa)."},{"id":"di-ioc-profundo-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>BeanFactory vs ApplicationContext — a fundação por trás do container</h2>","fidelityText":"BeanFactory vs ApplicationContext — a fundação por trás do container"},{"id":"di-ioc-profundo-content-14","type":"html","authorship":"legacy-preserved","html":"<p><code>ApplicationContext</code> estende capacidades de <code>BeanFactory</code> com eventos, mensagens, recursos e integração com post-processors. Por padrão, singletons não lazy são pré-instanciados durante o refresh, permitindo descobrir muitos erros cedo. Beans marcados como lazy, outros escopos e objetos criados sob demanda são exceções; “BeanFactory é sempre lazy e ApplicationContext sempre eager” é uma simplificação.</p>","fidelityText":"ApplicationContext estende capacidades de BeanFactory com eventos, mensagens, recursos e integração com post-processors. Por padrão, singletons não lazy são pré-instanciados durante o refresh, permitindo descobrir muitos erros cedo. Beans marcados como lazy, outros escopos e objetos criados sob demanda são exceções; “BeanFactory é sempre lazy e ApplicationContext sempre eager” é uma simplificação."},{"id":"di-ioc-profundo-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Dependências circulares — quando o grafo não tem ordem válida</h2>","fidelityText":"Dependências circulares — quando o grafo não tem ordem válida"},{"id":"di-ioc-profundo-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"@Service\npublic class ServiceA {\n    public ServiceA(ServiceB b) { ... } // A precisa de B\n}\n\n@Service\npublic class ServiceB {\n    public ServiceB(ServiceA a) { ... } // B precisa de A -- CICLO!\n}\n// erro na inicialização: \"The dependencies of some of the beans in the\n// application context form a cycle\" -- o Spring NÃO consegue decidir\n// qual instanciar primeiro, porque cada um exige que o outro já exista","fidelityText":"@Service public class ServicoA { public ServicoA(ServicoB b) { ... } // A precisa de B } @Service public class ServicoB { public ServicoB(ServicoA a) { ... } // B precisa de A -- CICLO! } // erro na inicialização: \"The dependencies of some of the beans in the // application context form a cycle\" -- o Spring NÃO consegue decidir // qual instanciar primeiro, porque cada um exige que o outro já exista","highlightedHtml":"<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">ServiceA</span> {\n    <span class=\"kw\">public</span> <span class=\"fn\">ServiceA</span>(<span class=\"cls\">ServiceB</span> b) { ... } <span class=\"com\">// A precisa de B</span>\n}\n\n<span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">ServiceB</span> {\n    <span class=\"kw\">public</span> <span class=\"fn\">ServiceB</span>(<span class=\"cls\">ServiceA</span> a) { ... } <span class=\"com\">// B precisa de A -- CICLO!</span>\n}\n<span class=\"com\">// erro na inicialização: \"The dependencies of some of the beans in the\n// application context form a cycle\" -- o Spring NÃO consegue decidir\n// qual instanciar primeiro, porque cada um exige que o outro já exista</span>","caption":"Exemplo executável de di-ioc-profundo.","explanation":["Ciclo A -> B -> A impede ordem simples de criação por construtor.","Quebrar ciclo geralmente exige nova responsabilidade ou direção de dependência."],"commonMistakes":["Usar field injection para esconder ciclo","Confundir sintoma do container com causa de design"]},{"id":"di-ioc-profundo-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Uma dependência circular quase sempre indica um problema de design</b>, não apenas um obstáculo técnico a contornar. Na maioria dos casos, isso sinaliza que <code>ServicoA</code> e <code>ServicoB</code> deveriam ter uma responsabilidade em comum extraída para um terceiro serviço, do qual ambos dependeriam unidirecionalmente — a mesma lição de SRP do capítulo 16. Embora exista uma forma técnica de contornar isso (injeção via campo, que o Spring resolve de forma \"preguiçosa\" através de um proxy intermediário), a solução correta na maioria absoluta dos casos reais é refatorar o design, não burlar a checagem.</div>","fidelityText":"Uma dependência circular quase sempre indica um problema de design, não apenas um obstáculo técnico a contornar. Na maioria dos casos, isso sinaliza que ServicoA e ServicoB deveriam ter uma responsabilidade em comum extraída para um terceiro serviço, do qual ambos dependeriam unidirecionalmente — a mesma lição de SRP do capítulo 16. Embora exista uma forma técnica de contornar isso (injeção via campo, que o Spring resolve de forma \"preguiçosa\" através de um proxy intermediário), a solução correta na maioria absoluta dos casos reais é refatorar o design, não burlar a checagem."},{"id":"di-ioc-profundo-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Ao debugar um problema de inicialização do Spring (\"bean não encontrado\", \"dependência circular\", \"múltiplos candidatos\"), sempre volte para o diagrama do ciclo de vida acima e pergunte: \"em que passo exato esse erro está acontecendo?\" Isso transforma uma mensagem de erro genérica e assustadora em um problema concreto e localizável — quase sempre um dos passos 1 ou 2 (grafo de dependências) ou um <code>@PostConstruct</code> tentando usar algo que ainda não existe.</div>","fidelityText":"Ao debugar um problema de inicialização do Spring (\"bean não encontrado\", \"dependência circular\", \"múltiplos candidatos\"), sempre volte para o diagrama do ciclo de vida acima e pergunte: \"em que passo exato esse erro está acontecendo?\" Isso transforma uma mensagem de erro genérica e assustadora em um problema concreto e localizável — quase sempre um dos passos 1 ou 2 (grafo de dependências) ou um @PostConstruct tentando usar algo que ainda não existe."},{"id":"di-ioc-profundo-exercise-19","type":"exercise","authorship":"legacy-preserved","title":"Exercício 87.1 — Ciclo de vida na prática","prompt":"Crie um @Service chamado PoolDeConexoesServico que, no @PostConstruct, imprime \"Pool inicializado com N conexões\" (simule N=10), e no @PreDestroy, imprime \"Pool encerrado, todas as conexões fechadas\". Injete esse serviço em outro bean via construtor e explique, em texto, em que exato momento do ciclo de vida do Spring cada uma dessas mensagens vai aparecer no log da aplicação.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 87.1 — Ciclo de vida na práticadifícil Crie um @Service chamado PoolDeConexoesServico que, no @PostConstruct, imprime \"Pool inicializado com N conexões\" (simule N=10), e no @PreDestroy, imprime \"Pool encerrado, todas as conexões fechadas\". Injete esse serviço em outro bean via construtor e explique, em texto, em que exato momento do ciclo de vida do Spring cada uma dessas mensagens vai aparecer no log da aplicação. Ver solução @Service public class PoolDeConexoesServico { private int conexoesAtivas; @PostConstruct public void inicializar() { conexoesAtivas = 10; System.out.println(\"Pool inicializado com \" + conexoesAtivas + \" conexões\"); } @PreDestroy public void encerrar() { System.out.println(\"Pool encerrado, todas as conexões fechadas\"); } } \"Pool inicializado...\" aparece no refresh para esse singleton não lazy, depois da construção e injeção. \"Pool encerrado...\" aparece no fechamento gracioso do contexto, como em SIGTERM tratado dentro do prazo. Crash abrupto, SIGKILL ou falha do host não garantem callbacks de destruição.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 87.1 — Ciclo de vida na prática</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Crie um <code>@Service</code> chamado <code>PoolDeConexoesServico</code> que, no <code>@PostConstruct</code>, imprime \"Pool inicializado com N conexões\" (simule N=10), e no <code>@PreDestroy</code>, imprime \"Pool encerrado, todas as conexões fechadas\". Injete esse serviço em outro bean via construtor e explique, em texto, em que exato momento do ciclo de vida do Spring cada uma dessas mensagens vai aparecer no log da aplicação.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">PoolOfConnectionsService</span> {\n    <span class=\"kw\">private int</span> connectionsAtivas;\n\n    <span class=\"annotation\">@PostConstruct</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">inicializar</span>() {\n        connectionsAtivas = 10;\n        System.out.println(<span class=\"str\">\"Pool inicializado with \"</span> + connectionsAtivas + <span class=\"str\">\" connections\"</span>);\n    }\n\n    <span class=\"annotation\">@PreDestroy</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">close</span>() {\n        System.out.println(<span class=\"str\">\"Pool encerrado, all the connections fechadas\"</span>);\n    }\n}</pre>\n          <p style=\"margin-top:12px\">\"Pool inicializado...\" aparece no refresh para esse singleton não lazy, depois da construção e injeção. \"Pool encerrado...\" aparece no fechamento gracioso do contexto, como em SIGTERM tratado dentro do prazo. Crash abrupto, <code>SIGKILL</code> ou falha do host não garantem callbacks de destruição.</p>\n        </div>\n      </div>"},{"id":"ioc-error","type":"error-case","authorship":"authored","title":"Dependência circular não é só erro do framework","scenario":"ServicoA precisa de ServicoB no construtor, e ServicoB precisa de ServicoA.","symptom":"O container não encontra uma ordem segura de criação ou cria objeto parcialmente inicializado.","cause":"O grafo de dependências tem ciclo e provavelmente duas responsabilidades estão misturadas.","diagnosis":["Desenhe o grafo A -> B -> A","Pergunte qual regra comum deveria virar terceiro serviço","Avalie evento, porta ou orquestrador","Evite resolver com lazy antes de entender o ciclo"],"correction":"Extraia responsabilidade compartilhada ou inverta a dependência para porta/evento unidirecional.","prevention":"Revisar direção das dependências no caso de uso antes de registrar beans/componentes."},{"id":"ioc-quiz","type":"quiz","authorship":"authored","conceptId":"dependencia-circular-grafo","prompt":"Qual é a melhor primeira reação diante de dependência circular entre dois serviços?","options":[{"id":"ioc-q-a","label":"Desenhar o grafo e procurar responsabilidade mal posicionada antes de usar escape técnico.","correct":true,"explanation":"Ciclo costuma indicar acoplamento bidirecional de design."},{"id":"ioc-q-b","label":"Adicionar lazy/proxy imediatamente e seguir sem análise.","correct":false,"explanation":"Pode mascarar problema estrutural."},{"id":"ioc-q-c","label":"Transformar todos os métodos em static.","correct":false,"explanation":"Isso evita injeção, mas destrói modelagem/testabilidade."}]}],"resources":[{"id":"spring-core-beans-reference","type":"reference","title":"Spring Framework: Core Technologies — IoC Container","url":"https://docs.spring.io/spring-framework/reference/core/beans.html","reinforces":"Referência futura para IoC/lifecycle; usada aqui como vocabulário, não como pré-requisito de uso.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"fowler-ioc-again","type":"guide","title":"Inversion of Control Containers and the Dependency Injection pattern","url":"https://martinfowler.com/articles/injection.html","reinforces":"Base conceitual de IoC, DI e service locator.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A di ioc deep operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a di ioc deep operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this di ioc deep chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"SEM IoC (controle NORMAL do flow -- seu código decide tudo):","instruction":"A di ioc deep operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this di ioc deep chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22","notes":["Capítulo reposicionado como aprofundamento conceitual de container antes de Spring Core formal."]}},{"id":"jvm-profundo","moduleId":"java-core","order":7,"title":"JVM, Bytecode e Gerenciamento de Memória — Aprofundamento","summary":"A introdução separou JDK, runtime e JVM; classes apresentaram referências e alcançabilidade. Agora você vai ligar código-fonte, bytecode, carregamento, execução, áreas de memória e coleta de lixo. O objetivo é formar um modelo útil para diagnosticar, sem transformar uma implementação específica de JVM em regra universal da linguagem.","objectives":["Separar bytecode e estratégia da JVM","Usar áreas de runtime como modelo, não endereço físico","Rastrear alcançabilidade e retenção","Comparar coletores apenas com evidência"],"whyItExists":"Depois de usar packages, generics, lambdas e objetos, o aluno pode observar o que o compilador registra e o runtime executa sem recorrer a mitos de stack/heap ou otimizações garantidas.","prerequisiteChapterIds":["functional-semantics-lab"],"conceptIds":["do-java-ao-processo-rodando-o-caminho-completo-passo-a-passo","interpretacao-vs-jit-por-que-java-esquenta-com-o-tempo","a-memoria-da-jvm-um-mapa-completo-nao-so-stack-e-heap","por-que-o-heap-e-dividido-em-geracoes","o-garbage-collector-como-a-jvm-decide-o-que-e-lixo","modelo-de-marcacao-e-recuperacao","escape-analysis-quando-uma-alocacao-pode-desaparecer"],"introducedConceptIds":["carregamento-execucao-classe","bytecode-jit-perfil","areas-runtime-modelo","alcancabilidade-gc","coletores-tradeoff","escape-analysis-otimizacao"],"usedConceptIds":["classpath-compilacao","ciclo-alcancabilidade","erasure-reificacao","chave-hash-estavel"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"jvm-intuition","type":"intuition","authorship":"authored","title":"Semântica estável, execução otimizada","body":"O class file descreve instruções e metadados portáveis. A JVM precisa preservar o resultado observável, mas pode interpretar, compilar, desotimizar e eliminar trabalho internamente.","analogyLimit":"A partitura ajuda a separar formato e execução, mas bytecode é verificado, ligado e otimizado; não é executado como uma partitura imutável passo a passo."},{"id":"jvm-profundo-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-java\">Java Interno</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo — releia com calma, é a fundação de tudo</div>\n        <div class=\"meta-item\">Pré-requisito: <a class=\"prereq-tag\" href=\"#functional-semantics-lab\">Laboratório semântico de Java Core</a></div>\n      </div>","fidelityText":"Java Interno Dificuldade: Avançado ⏱ ~3h de estudo — releia com calma, é a fundação de tudo Pré-requisito: Laboratório semântico de Java Core"},{"id":"jvm-profundo-content-2","type":"html","authorship":"legacy-preserved","html":"<p>A introdução separou JDK, runtime e JVM; classes apresentaram referências e alcançabilidade. Agora você vai ligar código-fonte, bytecode, carregamento, execução, áreas de memória e coleta de lixo. O objetivo é formar um modelo útil para diagnosticar, sem transformar uma implementação específica de JVM em regra universal da linguagem.</p>","fidelityText":"A introdução separou JDK, runtime e JVM; classes apresentaram referências e alcançabilidade. Agora você vai ligar código-fonte, bytecode, carregamento, execução, áreas de memória e coleta de lixo. O objetivo é formar um modelo útil para diagnosticar, sem transformar uma implementação específica de JVM em regra universal da linguagem."},{"id":"jvm-profundo-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Do .java ao processo rodando: o caminho completo, passo a passo</h2>","fidelityText":"Do .java ao processo rodando: o caminho completo, passo a passo"},{"id":"jvm-profundo-content-4","type":"html","authorship":"legacy-preserved","html":"<p>O caminho possui etapas observáveis, mas a JVM pode otimizar internamente sem preservar uma correspondência literal entre cada linha Java e cada instrução executada. Separe o formato especificado do arquivo <code>.class</code> das estratégias escolhidas pelo runtime.</p>","fidelityText":"O caminho possui etapas observáveis, mas a JVM pode otimizar internamente sem preservar uma correspondência literal entre cada linha Java e cada instrução executada. Separe o formato especificado do arquivo .class das estratégias escolhidas pelo runtime."},{"id":"jvm-profundo-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"STEP 1 — code-source (Book.java)\n┌─────────────────────────────────────┐\n│ public class Book {                 │\n│     private String title;           │\n│     public String getTitle() {      │\n│         return title;               │\n│     }                                │\n│ }                                    │\n└─────────────────────────────────────┘\n              │\n              │  javac Book.java\n              ▼\nSTEP 2 — Bytecode (Book.class) -- NÃO é código de máquina x86/ARM,\n          e um format intermediate, independente de system operational\n┌─────────────────────────────────────┐\n│ CAFEBABE 0000 0041 001D...           │  ← header magico + version\n│ Constant Pool: #1 = Class  #2        │  ← table de simbolos (names, types)\n│ Methods:                             │\n│   getTitle:                         │\n│     aload_0        // empilha \"this\" │\n│     getfield #3     // busca \"titulo\"│\n│     areturn         // retorna       │\n└─────────────────────────────────────┘\n              │\n              │  java Book   (a JVM CARREGA o .class)\n              ▼\nSTEP 3 — Class Loading: a JVM le o .class, verifica que o bytecode e\n          valid (Bytecode Verifier -- proteção de segurança), e registra\n          a type Book na memory da application\n              │\n              ▼\nSTEP 4 — Execution: a JVM interpreta o bytecode instrucao por instrucao\n          (slow) OU, para methods executados MUITAS vezes, o JIT\n          (Just-In-Time compiler) o compila para code de maquina NATIVE\n          real, otimizado para o processador especifico da maquina","fidelityText":"PASSO 1 — Código-fonte (Livro.java) ┌─────────────────────────────────────┐ │ public class Livro { │ │ private String titulo; │ │ public String getTitulo() { │ │ return titulo; │ │ } │ │ } │ └─────────────────────────────────────┘ │ │ javac Livro.java ▼ PASSO 2 — Bytecode (Livro.class) -- NÃO é código de máquina x86/ARM, é um formato intermediário, independente de sistema operacional ┌─────────────────────────────────────┐ │ CAFEBABE 0000 0041 001D... │ ← cabeçalho mágico + versão │ Constant Pool: #1 = Class #2 │ ← tabela de símbolos (nomes, tipos) │ Methods: │ │ getTitulo: │ │ aload_0 // empilha \"this\" │ │ getfield #3 // busca \"titulo\"│ │ areturn // retorna │ └─────────────────────────────────────┘ │ │ java Livro (a JVM CARREGA o .class) ▼ PASSO 3 — Class Loading: a JVM lê o .class, verifica que o bytecode é válido (Bytecode Verifier -- proteção de segurança), e registra a classe Livro na memória da aplicação │ ▼ PASSO 4 — Execução: a JVM interpreta o bytecode instrução por instrução (lento) OU, para métodos executados MUITAS vezes, o JIT (Just-In-Time compiler) o compila para código de máquina NATIVO real, otimizado para o processador específico da máquina","highlightedHtml":"STEP 1 — code-source (Book.java)\n┌─────────────────────────────────────┐\n│ public class Book {                 │\n│     private String title;           │\n│     public String getTitle() {      │\n│         return title;               │\n│     }                                │\n│ }                                    │\n└─────────────────────────────────────┘\n              │\n              │  javac Book.java\n              ▼\nSTEP 2 — Bytecode (Book.class) -- NÃO é código de máquina x86/ARM,\n          e um format intermediate, independente de system operational\n┌─────────────────────────────────────┐\n│ CAFEBABE 0000 0041 001D...           │  ← header magico + version\n│ Constant Pool: #1 = Class  #2        │  ← table de simbolos (names, types)\n│ Methods:                             │\n│   getTitle:                         │\n│     aload_0        // empilha \"this\" │\n│     getfield #3     // busca \"titulo\"│\n│     areturn         // retorna       │\n└─────────────────────────────────────┘\n              │\n              │  java Book   (a JVM CARREGA o .class)\n              ▼\nSTEP 3 — Class Loading: a JVM le o .class, verifica que o bytecode e\n          valid (Bytecode Verifier -- proteção de segurança), e registra\n          a type Book na memory da application\n              │\n              ▼\nSTEP 4 — Execution: a JVM interpreta o bytecode instrucao por instrucao\n          (slow) OU, para methods executados MUITAS vezes, o JIT\n          (Just-In-Time compiler) o compila para code de maquina NATIVE\n          real, otimizado para o processador especifico da maquina","caption":"Exemplo executável de jvm-profundo.","explanation":["javac produz class file com bytecode e constant pool; o runtime carrega, verifica, liga e inicializa conforme necessário.","Interpretar ou compilar são estratégias, não resultados diferentes permitidos."],"commonMistakes":["Chamar bytecode de código de máquina","Achar que import carrega classe"]},{"id":"jvm-profundo-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense no bytecode como uma partitura musical universal — qualquer orquestra do mundo (qualquer JVM, em qualquer sistema operacional) consegue lê-la e tocá-la, mesmo que os instrumentos físicos (o hardware) sejam diferentes entre uma sala de concerto e outra. O <code>javac</code> é o compositor traduzindo uma ideia musical (seu código Java) para essa notação universal; a JVM é o maestro que interpreta a partitura e faz a orquestra (o processador da sua máquina) realmente tocar o som.</div>","fidelityText":"Pense no bytecode como uma partitura musical universal — qualquer orquestra do mundo (qualquer JVM, em qualquer sistema operacional) consegue lê-la e tocá-la, mesmo que os instrumentos físicos (o hardware) sejam diferentes entre uma sala de concerto e outra. O javac é o compositor traduzindo uma ideia musical (seu código Java) para essa notação universal; a JVM é o maestro que interpreta a partitura e faz a orquestra (o processador da sua máquina) realmente tocar o som."},{"id":"jvm-profundo-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Interpretação vs JIT — por que Java \"esquenta\" com o tempo</h2>","fidelityText":"Interpretação vs JIT — por que Java \"esquenta\" com o tempo"},{"id":"jvm-profundo-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Uma JVM pode interpretar bytecode e compilar trechos considerados quentes para código nativo durante a execução. HotSpot, por exemplo, possui compilação em níveis e compiladores com objetivos diferentes, mas limiares, perfis e estratégias são detalhes configuráveis da implementação. Código compilado também pode ser desotimizado quando hipóteses deixam de valer.</p>","fidelityText":"Uma JVM pode interpretar bytecode e compilar trechos considerados quentes para código nativo durante a execução. HotSpot, por exemplo, possui compilação em níveis e compiladores com objetivos diferentes, mas limiares, perfis e estratégias são detalhes configuráveis da implementação. Código compilado também pode ser desotimizado quando hipóteses deixam de valer."},{"id":"jvm-profundo-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Perfil, carregamento de classes, inicialização e compilação dinâmica podem fazer uma execução curta se comportar de modo diferente do estado estável. Por isso, medir performance Java exige declarar versão, JVM, flags, carga, aquecimento, duração e distribuição de resultados; uma única cronometragem com <code>currentTimeMillis</code> não prova custo de uma operação.</div>","fidelityText":"Perfil, carregamento de classes, inicialização e compilação dinâmica podem fazer uma execução curta se comportar de modo diferente do estado estável. Por isso, medir performance Java exige declarar versão, JVM, flags, carga, aquecimento, duração e distribuição de resultados; uma única cronometragem com currentTimeMillis não prova custo de uma operação."},{"id":"jvm-profundo-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>A memória da JVM: um mapa completo, não só Stack e Heap</h2>","fidelityText":"A memória da JVM: um mapa completo, não só Stack e Heap"},{"id":"jvm-profundo-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"┌─────────────────────────────────────────────────────────────┐\n│                     MEMORY DA JVM                            │\n│                                                                 │\n│  ┌───────────────┐  ┌───────────────┐  ┌────────────────────┐│\n│  │  STACK         │  │  STACK         │  │  METASPACE          ││\n│  │  (Thread A)    │  │  (Thread B)    │  │  (outside do heap)     ││\n│  │                │  │                │  │  guarda METADADOS   ││\n│  │ frame: main()  │  │ frame: run()   │  │  das TYPES:        ││\n│  │  int x = 5     │  │  Book l       │  │  - bytecode dos      ││\n│  │ frame: foo()   │  │                │  │    methods            ││\n│  │  String s      │  │                │  │  - Constant Pool      ││\n│  └───────────────┘  └───────────────┘  └────────────────────┘│\n│         │ referencias apontam para                             │\n│         ▼                                                       │\n│  ┌─────────────────────────────────────────────────────────┐ │\n│  │                        HEAP                              │ │\n│  │  ┌──────────────────┐  ┌───────────────────────────────┐│ │\n│  │  │  Young Generation │  │      Old Generation            ││ │\n│  │  │  ┌─────┐┌───┐┌───┐│  │  (objects que sobreviveram     ││ │\n│  │  │  │Eden ││S0 ││S1 ││  │   varias collections de garbage na    ││ │\n│  │  │  └─────┘└───┘└───┘│  │   Young Generation)             ││ │\n│  │  │ objects new      │  │  ex: data ainda reachable    ││ │\n│  │  │ costumam start   │  │  por estruturas de long vida   ││ │\n│  │  └──────────────────┘  └───────────────────────────────┘│ │\n│  └─────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────┘","fidelityText":"┌─────────────────────────────────────────────────────────────┐ │ MEMÓRIA DA JVM │ │ │ │ ┌───────────────┐ ┌───────────────┐ ┌────────────────────┐│ │ │ STACK │ │ STACK │ │ METASPACE ││ │ │ (Thread A) │ │ (Thread B) │ │ (fora do heap) ││ │ │ │ │ │ │ guarda METADADOS ││ │ │ frame: main() │ │ frame: run() │ │ das CLASSES: ││ │ │ int x = 5 │ │ Livro l │ │ - bytecode dos ││ │ │ frame: foo() │ │ │ │ métodos ││ │ │ String s │ │ │ │ - Constant Pool ││ │ └───────────────┘ └───────────────┘ └────────────────────┘│ │ │ referências apontam para │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ HEAP │ │ │ │ ┌──────────────────┐ ┌───────────────────────────────┐│ │ │ │ │ Young Generation │ │ Old Generation ││ │ │ │ │ ┌─────┐┌───┐┌───┐│ │ (objetos que sobreviveram ││ │ │ │ │ │Eden ││S0 ││S1 ││ │ várias coletas de lixo na ││ │ │ │ │ └─────┘└───┘└───┘│ │ Young Generation) ││ │ │ │ │ objetos novos │ │ ex: dados ainda alcançáveis ││ │ │ │ │ costumam iniciar │ │ por estruturas de longa vida ││ │ │ │ └──────────────────┘ └───────────────────────────────┘│ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘","highlightedHtml":"┌─────────────────────────────────────────────────────────────┐\n│                     MEMORY DA JVM                            │\n│                                                                 │\n│  ┌───────────────┐  ┌───────────────┐  ┌────────────────────┐│\n│  │  STACK         │  │  STACK         │  │  METASPACE          ││\n│  │  (Thread A)    │  │  (Thread B)    │  │  (outside do heap)     ││\n│  │                │  │                │  │  guarda METADADOS   ││\n│  │ frame: main()  │  │ frame: run()   │  │  das TYPES:        ││\n│  │  int x = 5     │  │  Book l       │  │  - bytecode dos      ││\n│  │ frame: foo()   │  │                │  │    methods            ││\n│  │  String s      │  │                │  │  - Constant Pool      ││\n│  └───────────────┘  └───────────────┘  └────────────────────┘│\n│         │ referencias apontam para                             │\n│         ▼                                                       │\n│  ┌─────────────────────────────────────────────────────────┐ │\n│  │                        HEAP                              │ │\n│  │  ┌──────────────────┐  ┌───────────────────────────────┐│ │\n│  │  │  Young Generation │  │      Old Generation            ││ │\n│  │  │  ┌─────┐┌───┐┌───┐│  │  (objects que sobreviveram     ││ │\n│  │  │  │Eden ││S0 ││S1 ││  │   varias collections de garbage na    ││ │\n│  │  │  └─────┘└───┘└───┘│  │   Young Generation)             ││ │\n│  │  │ objects new      │  │  ex: data ainda reachable    ││ │\n│  │  │ costumam start   │  │  por estruturas de long vida   ││ │\n│  │  └──────────────────┘  └───────────────────────────────┘│ │\n│  └─────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────┘","caption":"Exemplo executável de jvm-profundo.","explanation":["O diagrama é modelo conceitual de frames, heap e metadados.","Layouts geracionais e localizações físicas variam com JVM, coletor e otimização."],"commonMistakes":["Afirmar que local primitiva sempre mora fisicamente na stack","Colocar valor do campo static no Metaspace por definição"]},{"id":"jvm-profundo-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Conceitualmente, cada thread possui frames de chamada privados, com variáveis locais e resultados intermediários. Uma variável local não é compartilhada como variável, mas pode conter referência para um objeto que também é alcançado por outras threads; o referenciado não se torna privado por isso. Objetos criados com <code>new</code> são normalmente tratados no heap gerenciado, embora o JIT possa eliminar ou decompor alocações quando o comportamento observável permitir. Sincronização e visibilidade serão estudadas no módulo de concorrência.</p>","fidelityText":"Conceitualmente, cada thread possui frames de chamada privados, com variáveis locais e resultados intermediários. Uma variável local não é compartilhada como variável, mas pode conter referência para um objeto que também é alcançado por outras threads; o referenciado não se torna privado por isso. Objetos criados com new são normalmente tratados no heap gerenciado, embora o JIT possa eliminar ou decompor alocações quando o comportamento observável permitir. Sincronização e visibilidade serão estudadas no módulo de concorrência."},{"id":"jvm-profundo-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Por que o Heap é dividido em gerações</h2>","fidelityText":"Por que o Heap é dividido em gerações"},{"id":"jvm-profundo-content-14","type":"html","authorship":"legacy-preserved","html":"<p>Muitos coletores exploram a <strong>hipótese geracional</strong>: em várias cargas, grande parte dos objetos deixa de ser alcançável cedo. Separar regiões por idade permite concentrar trabalho onde se espera recuperar mais espaço. Isso é uma estratégia de coletores, não uma exigência de que todo heap ou toda carga possua exatamente o desenho mostrado.</p>","fidelityText":"Muitos coletores exploram a hipótese geracional: em várias cargas, grande parte dos objetos deixa de ser alcançável cedo. Separar regiões por idade permite concentrar trabalho onde se espera recuperar mais espaço. Isso é uma estratégia de coletores, não uma exigência de que todo heap ou toda carga possua exatamente o desenho mostrado."},{"id":"jvm-profundo-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em uma sala de espera de hospital (Young Generation, especificamente a região <strong>Eden</strong>) onde a maioria das pessoas fica pouquíssimo tempo — atendidas e liberadas rapidamente. Só uma pequena fração precisa ficar internada por mais tempo (sobrevive a várias \"rodadas de triagem\" — coletas de lixo — e é promovida para as áreas <strong>Survivor</strong>, S0/S1). Depois de sobreviver a internações suficientes, um paciente é transferido para uma ala de tratamento de longo prazo (<strong>Old Generation</strong>). Focar o esforço de \"limpeza\" (coleta de lixo) na sala de espera, que tem alta rotatividade, é muito mais eficiente do que vasculhar a ala de longo prazo toda vez.</div>","fidelityText":"Pense em uma sala de espera de hospital (Young Generation, especificamente a região Eden) onde a maioria das pessoas fica pouquíssimo tempo — atendidas e liberadas rapidamente. Só uma pequena fração precisa ficar internada por mais tempo (sobrevive a várias \"rodadas de triagem\" — coletas de lixo — e é promovida para as áreas Survivor, S0/S1). Depois de sobreviver a internações suficientes, um paciente é transferido para uma ala de tratamento de longo prazo (Old Generation). Focar o esforço de \"limpeza\" (coleta de lixo) na sala de espera, que tem alta rotatividade, é muito mais eficiente do que vasculhar a ala de longo prazo toda vez."},{"id":"jvm-profundo-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>O Garbage Collector: como a JVM decide o que é \"lixo\"</h2>","fidelityText":"O Garbage Collector: como a JVM decide o que é \"lixo\""},{"id":"jvm-profundo-content-17","type":"html","authorship":"legacy-preserved","html":"<p>Java não oferece <code>free()</code> para liberar objetos individualmente. O GC identifica objetos <strong>inalcançáveis</strong> — nenhum caminho a partir das raízes de alcançabilidade chega mais até eles — e pode recuperar essa memória automaticamente.</p>","fidelityText":"Java não oferece free() para liberar objetos individualmente. O GC identifica objetos inalcançáveis — nenhum caminho a partir das raízes de alcançabilidade chega mais até eles — e pode recuperar essa memória automaticamente."},{"id":"jvm-profundo-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"Book l1 = new Book(); // l1 passa a referenciar o objeto A gerenciado pela JVM\nl1 = new Book();       // objeto B criado, \"l1\" agora aponta para B\n                          // objeto A ficou SEM NENHUMA referência --\n                          // agora é candidato a coleta de lixo\n\nvoid method() {\n    Book local = new Book(); // objeto C, referenciado só pela variável local\n} // o método termina e sua variável local deixa de existir --\n  // \"local\" deixa de existir -- objeto C também vira lixo","fidelityText":"Livro l1 = new Livro(); // l1 passa a referenciar o objeto A gerenciado pela JVM l1 = new Livro(); // objeto B criado, \"l1\" agora aponta para B // objeto A ficou SEM NENHUMA referência -- // agora é candidato a coleta de lixo void metodo() { Livro local = new Livro(); // objeto C, referenciado só pela variável local } // o método termina e sua variável local deixa de existir -- // \"local\" deixa de existir -- objeto C também vira lixo","highlightedHtml":"<span class=\"cls\">Book</span> l1 = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(); <span class=\"com\">// l1 passa a referenciar o objeto A gerenciado pela JVM</span>\nl1 = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>();       <span class=\"com\">// objeto B criado, \"l1\" agora aponta para B\n                          // objeto A ficou SEM NENHUMA referência --\n                          // agora é candidato a coleta de lixo</span>\n\n<span class=\"kw\">void</span> <span class=\"fn\">method</span>() {\n    <span class=\"cls\">Book</span> local = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(); <span class=\"com\">// objeto C, referenciado só pela variável local</span>\n} <span class=\"com\">// o método termina e sua variável local deixa de existir --\n  // \"local\" deixa de existir -- objeto C também vira lixo</span>","caption":"Exemplo executável de jvm-profundo.","explanation":["Reatribuição remove um caminho para A e cria outro objeto referenciado por l1.","A torna-se elegível apenas se nenhum outro caminho existir; coleta não é imediata."],"commonMistakes":["Confundir elegível com coletado","Afirmar endereço físico apesar de escape analysis"]},{"id":"jvm-profundo-content-19","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Referências retidas causam vazamento lógico mesmo com GC.</b> Se um <code>static Map</code> ou uma lista crescente mantém objetos que o domínio não usa mais, eles continuam alcançáveis e não podem ser coletados. <code>OutOfMemoryError</code> também pode resultar de heap insuficiente, carga legítima, memória nativa ou outras áreas; investigue evidências em vez de assumir uma única causa.</div>","fidelityText":"Referências retidas causam vazamento lógico mesmo com GC. Se um static Map ou uma lista crescente mantém objetos que o domínio não usa mais, eles continuam alcançáveis e não podem ser coletados. OutOfMemoryError também pode resultar de heap insuficiente, carga legítima, memória nativa ou outras áreas; investigue evidências em vez de assumir uma única causa."},{"id":"jvm-profundo-content-20","type":"html","authorship":"legacy-preserved","html":"<h2>Modelo de marcação e recuperação</h2>","fidelityText":"Modelo de marcação e recuperação"},{"id":"jvm-profundo-content-21","type":"html","authorship":"legacy-preserved","html":"<p>Para raciocinar sobre alcançabilidade, imagine duas responsabilidades conceituais: descobrir o grafo vivo a partir de raízes e tornar reutilizável o espaço que não pertence a esse grafo. Coletores reais podem executar, intercalar, compactar e implementar essas responsabilidades de formas diferentes.</p>","fidelityText":"Para raciocinar sobre alcançabilidade, imagine duas responsabilidades conceituais: descobrir o grafo vivo a partir de raízes e tornar reutilizável o espaço que não pertence a esse grafo. Coletores reais podem executar, intercalar, compactar e implementar essas responsabilidades de formas diferentes."},{"id":"jvm-profundo-code-22","type":"code","authorship":"legacy-preserved","language":"java","source":"PHASE 1 — MARK (mark)\nA JVM parte das \"raizes\" (variables em cada Stack de cada Thread,\nfields static) e percorre ALL as referencias reachable a start from\ndelas, marcando cada object found as \"alive\".\n\n     [Stack Thread A] ──┐\n                         ├──► Object X (marked: ALIVE)\n     [field static]   ───┘         │\n                                    └──► Object Y (marked: ALIVE,\n                                          referenciado por X)\n\n     Object Z (nao reached por ninguem) → permanece NAO marked\n\nPHASE 2 — RECUPERAR/REORGANIZAR\nO space dos objects nao alcancados pode be reutilizado. O coletor\npode varrer, copiar ou compactar regioes; o desenho exact varies.","fidelityText":"FASE 1 — MARK (marcar) A JVM parte das \"raízes\" (variáveis em cada Stack de cada Thread, campos static) e percorre TODAS as referências alcançáveis a partir delas, marcando cada objeto encontrado como \"vivo\". [Stack Thread A] ──┐ ├──► Objeto X (marcado: VIVO) [campo static] ───┘ │ └──► Objeto Y (marcado: VIVO, referenciado por X) Objeto Z (não alcançado por ninguém) → permanece NÃO marcado FASE 2 — RECUPERAR/REORGANIZAR O espaço dos objetos não alcançados pode ser reutilizado. O coletor pode varrer, copiar ou compactar regiões; o desenho exato varia.","highlightedHtml":"PHASE 1 — MARK (mark)\nA JVM parte das \"raizes\" (variables em cada Stack de cada Thread,\nfields static) e percorre ALL as referencias reachable a start from\ndelas, marcando cada object found as \"alive\".\n\n     [Stack Thread A] ──┐\n                         ├──► Object X (marked: ALIVE)\n     [field static]   ───┘         │\n                                    └──► Object Y (marked: ALIVE,\n                                          referenciado por X)\n\n     Object Z (nao reached por ninguem) → permanece NAO marked\n\nPHASE 2 — RECUPERAR/REORGANIZAR\nO space dos objects nao alcancados pode be reutilizado. O coletor\npode varrer, copiar ou compactar regioes; o desenho exact varies.","caption":"Exemplo executável de jvm-profundo.","explanation":["A marcação representa travessia a partir de raízes; objetos fora do grafo podem ter espaço recuperado.","Coletores reais podem copiar, compactar e executar fases concorrentes."],"commonMistakes":["Tratar o pseudodiagrama como algoritmo exato de todo GC","Prometer pausa ou varredura completa"]},{"id":"jvm-profundo-content-23","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Coletor</th><th>Estratégia</th><th>Quando usar</th></tr>\n        <tr><td><strong>Serial GC</strong></td><td>Uma única thread faz toda a coleta, pausando a aplicação inteira</td><td>Aplicações pequenas, single-core</td></tr>\n        <tr><td><strong>Parallel GC</strong></td><td>Várias threads coletam em paralelo, ainda pausando a aplicação</td><td>Maximizar throughput, pausas aceitáveis</td></tr>\n        <tr><td><strong>G1 (Garbage-First)</strong></td><td>Organiza o heap em regiões e busca equilibrar throughput e metas de pausa</td><td>É uma escolha padrão comum no HotSpot moderno; confirme JVM, versão, flags e carga</td></tr>\n        <tr><td><strong>ZGC / Shenandoah</strong></td><td>Executam grande parte do trabalho concorrentemente para reduzir pausas</td><td>Cargas sensíveis a pausas; exigem medição de throughput, latência e uso de recursos</td></tr>\n      </tbody></table>","fidelityText":"ColetorEstratégiaQuando usar Serial GCUma única thread faz toda a coleta, pausando a aplicação inteiraAplicações pequenas, single-core Parallel GCVárias threads coletam em paralelo, ainda pausando a aplicaçãoMaximizar throughput, pausas aceitáveis G1 (Garbage-First)Organiza o heap em regiões e busca equilibrar throughput e metas de pausaÉ uma escolha padrão comum no HotSpot moderno; confirme JVM, versão, flags e carga ZGC / ShenandoahExecutam grande parte do trabalho concorrentemente para reduzir pausasCargas sensíveis a pausas; exigem medição de throughput, latência e uso de recursos"},{"id":"jvm-profundo-content-24","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Durante uma fase <em>stop-the-world</em>, threads da aplicação são suspensas em pontos seguros enquanto o runtime realiza trabalho que exige uma visão consistente. Coletores diferem em quais fases pausam e quanto fazem concorrentemente. <code>-Xms</code> e <code>-Xmx</code> influenciam o espaço disponível, mas não existe tamanho universalmente correto: registre pausas, frequência, alocação, ocupação e throughput sob uma carga representativa.</div>","fidelityText":"Durante uma fase stop-the-world, threads da aplicação são suspensas em pontos seguros enquanto o runtime realiza trabalho que exige uma visão consistente. Coletores diferem em quais fases pausam e quanto fazem concorrentemente. -Xms e -Xmx influenciam o espaço disponível, mas não existe tamanho universalmente correto: registre pausas, frequência, alocação, ocupação e throughput sob uma carga representativa."},{"id":"jvm-profundo-content-25","type":"html","authorship":"legacy-preserved","html":"<h2>Escape Analysis — quando uma alocação pode desaparecer</h2>","fidelityText":"Escape Analysis — quando uma alocação pode desaparecer"},{"id":"jvm-profundo-content-26","type":"html","authorship":"legacy-preserved","html":"<p>Se o compilador prova que a identidade de um objeto não escapa para um contexto onde precisa ser observada, uma implementação pode aplicar substituição escalar, eliminar a alocação ou remover operações de sincronização. Não conclua daí que todo objeto local “vai para a stack”: o resultado é uma decisão do JIT, pode variar entre execuções e não altera a semântica Java.</p>","fidelityText":"Se o compilador prova que a identidade de um objeto não escapa para um contexto onde precisa ser observada, uma implementação pode aplicar substituição escalar, eliminar a alocação ou remover operações de sincronização. Não conclua daí que todo objeto local “vai para a stack”: o resultado é uma decisão do JIT, pode variar entre execuções e não altera a semântica Java."},{"id":"jvm-profundo-content-27","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Você não precisa (nem deveria, na maioria dos casos) escrever código \"pensando no Escape Analysis\" — é uma otimização que o JIT decide automaticamente. O valor prático de saber que ela existe é entender por que, às vezes, um código aparentemente \"cria muitos objetos temporários\" tem performance melhor do que a intuição sugeriria: o JIT frequentemente já eliminou boa parte desse custo nos bastidores, sem você precisar fazer nada.</div>","fidelityText":"Você não precisa (nem deveria, na maioria dos casos) escrever código \"pensando no Escape Analysis\" — é uma otimização que o JIT decide automaticamente. O valor prático de saber que ela existe é entender por que, às vezes, um código aparentemente \"cria muitos objetos temporários\" tem performance melhor do que a intuição sugeriria: o JIT frequentemente já eliminou boa parte desse custo nos bastidores, sem você precisar fazer nada."},{"id":"jvm-profundo-exercise-28","type":"exercise","authorship":"legacy-preserved","title":"Exercício 85.1 — Rastreando o ciclo de vida de um objeto","prompt":"Para o código abaixo, escreva um passo a passo detalhado de: (1) quais variáveis e campos mantêm caminhos de referência, (2) quando cada objeto normalmente se torna elegível para coleta e (3) quais afirmações seriam apenas detalhes possíveis de implementação, como endereço físico ou eliminação da alocação.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 85.1 — Rastreando o ciclo de vida de um objetomédio Para o código abaixo, escreva um passo a passo detalhado de: (1) quais variáveis e campos mantêm caminhos de referência, (2) quando cada objeto normalmente se torna elegível para coleta e (3) quais afirmações seriam apenas detalhes possíveis de implementação, como endereço físico ou eliminação da alocação. public class Main { static Livro livroFavorito; public static void main(String[] args) { Livro l1 = new Livro(\"1984\"); livroFavorito = l1; Livro l2 = new Livro(\"Duna\"); l1 = null; processarLivro(); } static void processarLivro() { Livro temp = new Livro(\"Temporário\"); } } Ver solução Modelo conceitual: as variáveis locais l1, l2 e temp pertencem aos frames de seus métodos; os objetos Livro criados com new são normalmente alocados no heap, embora otimizações possam eliminar alocações. livroFavorito é um campo estático associado à classe e funciona como raiz de alcançabilidade enquanto a classe estiver carregada. Não afirme que o valor do campo fica no Metaspace: detalhes físicos dependem da implementação da JVM. Passo a passo conceitual: (1) l1 referencia “1984”. (2) livroFavorito = l1 cria um segundo caminho para a mesma identidade. (3) l2 referencia “Duna”. (4) l1 = null remove um caminho, mas “1984” permanece alcançável pelo campo estático enquanto a classe e seu carregador permanecerem alcançáveis. (5) temp referencia “Temporário”. (6) Ao terminar o método, esse caminho desaparece e, se nenhuma otimização já eliminou a alocação e não há outro caminho, o objeto fica elegível. (7) Ao terminar main, o caminho por l2 desaparece. Elegível não significa coletado imediatamente.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 85.1 — Rastreando o ciclo de vida de um objeto</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Para o código abaixo, escreva um passo a passo detalhado de: (1) quais variáveis e campos mantêm caminhos de referência, (2) quando cada objeto normalmente se torna elegível para coleta e (3) quais afirmações seriam apenas detalhes possíveis de implementação, como endereço físico ou eliminação da alocação.</p>\n        <pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">Main</span> {\n    <span class=\"kw\">static</span> <span class=\"cls\">Book</span> bookFavorite;\n\n    <span class=\"kw\">public static void</span> <span class=\"fn\">main</span>(<span class=\"kw\">String</span>[] args) {\n        <span class=\"cls\">Book</span> l1 = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"1984\"</span>);\n        bookFavorite = l1;\n        <span class=\"cls\">Book</span> l2 = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Duna\"</span>);\n        l1 = <span class=\"kw\">null</span>;\n        processBook();\n    }\n\n    <span class=\"kw\">static void</span> <span class=\"fn\">processBook</span>() {\n        <span class=\"cls\">Book</span> temp = <span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Temporary\"</span>);\n    }\n}</pre>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>Modelo conceitual:</strong> as variáveis locais <code>l1</code>, <code>l2</code> e <code>temp</code> pertencem aos frames de seus métodos; os objetos <code>Livro</code> criados com <code>new</code> são normalmente alocados no heap, embora otimizações possam eliminar alocações. <code>livroFavorito</code> é um campo estático associado à classe e funciona como raiz de alcançabilidade enquanto a classe estiver carregada. Não afirme que o valor do campo fica no Metaspace: detalhes físicos dependem da implementação da JVM.</p>\n          <p><strong>Passo a passo conceitual:</strong> (1) <code>l1</code> referencia “1984”. (2) <code>livroFavorito = l1</code> cria um segundo caminho para a mesma identidade. (3) <code>l2</code> referencia “Duna”. (4) <code>l1 = null</code> remove um caminho, mas “1984” permanece alcançável pelo campo estático enquanto a classe e seu carregador permanecerem alcançáveis. (5) <code>temp</code> referencia “Temporário”. (6) Ao terminar o método, esse caminho desaparece e, se nenhuma otimização já eliminou a alocação e não há outro caminho, o objeto fica elegível. (7) Ao terminar <code>main</code>, o caminho por <code>l2</code> desaparece. Elegível não significa coletado imediatamente.</p>\n        </div>\n      </div>"},{"id":"jvm-profundo-exercise-29","type":"exercise","authorship":"legacy-preserved","title":"Exercício 85.2 — Identificando um vazamento de memória","prompt":"Analise a classe abaixo e explique por que ela causa um vazamento de memória em uma aplicação de longa duração, mesmo em uma linguagem com Garbage Collector automático. Proponha uma correção.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 85.2 — Identificando um vazamento de memóriadifícil Analise a classe abaixo e explique por que ela causa um vazamento de memória em uma aplicação de longa duração, mesmo em uma linguagem com Garbage Collector automático. Proponha uma correção. public class CacheDeUsuarios { private static final Map<Long, Usuario> cache = new HashMap<>(); public static void adicionar(Long id, Usuario usuario) { cache.put(id, usuario); // nunca remove nada! } } Ver solução Enquanto o campo estático estiver alcançável, o mapa mantém caminhos para todos os usuários inseridos. Sem remoção ou limite, o consumo cresce com novas chaves. A correção depende do contrato: remover quando o dado perde utilidade, limitar a quantidade, definir expiração ou não manter o cache. Uma estrutura externa não corrige automaticamente a ausência de política; primeiro declare tamanho, validade, descarte e comportamento quando o limite for atingido.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 85.2 — Identificando um vazamento de memória</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Analise a classe abaixo e explique por que ela causa um vazamento de memória em uma aplicação de longa duração, mesmo em uma linguagem com Garbage Collector automático. Proponha uma correção.</p>\n        <pre class=\"code\"><span class=\"kw\">public class</span> <span class=\"cls\">CacheOfUsers</span> {\n    <span class=\"kw\">private static final</span> Map&lt;<span class=\"kw\">Long</span>, <span class=\"cls\">User</span>&gt; cache = <span class=\"kw\">new</span> HashMap&lt;&gt;();\n\n    <span class=\"kw\">public static void</span> <span class=\"fn\">add</span>(<span class=\"kw\">Long</span> id, <span class=\"cls\">User</span> user) {\n        cache.put(id, user); <span class=\"com\">// nunca remove nada!</span>\n    }\n}</pre>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Enquanto o campo estático estiver alcançável, o mapa mantém caminhos para todos os usuários inseridos. Sem remoção ou limite, o consumo cresce com novas chaves. A correção depende do contrato: remover quando o dado perde utilidade, limitar a quantidade, definir expiração ou não manter o cache. Uma estrutura externa não corrige automaticamente a ausência de política; primeiro declare tamanho, validade, descarte e comportamento quando o limite for atingido.</p>\n        </div>\n      </div>"},{"id":"jvm-comparison","type":"comparison","authorship":"authored","title":"Afirmação semântica ou detalhe de implementação","criteria":["garantia","como verificar","risco"],"alternatives":[{"name":"semântica Java/JVMS","values":["resultado observável","especificação e teste","portável"],"useWhen":"explicar correção","avoidWhen":"prever layout e custo exatos"},{"name":"decisão do runtime","values":["JIT, alocação, coletor","flags, logs e profiler","muda por versão/carga"],"useWhen":"diagnosticar performance","avoidWhen":"ensinar como regra universal"}]},{"id":"jvm-quiz","type":"quiz","authorship":"authored","conceptId":"alcancabilidade-gc","prompt":"Uma lista static ainda referencia um objeto que nenhum caso de uso consulta. Ele está elegível para GC?","options":[{"id":"jvm-q-a","label":"Não, enquanto houver um caminho alcançável da raiz até ele.","correct":true,"explanation":"GC usa alcançabilidade, não utilidade de negócio."},{"id":"jvm-q-b","label":"Sim, porque o programa não chamou nenhum método nele recentemente.","correct":false,"explanation":"Frequência de uso não remove a referência mantida pela lista."},{"id":"jvm-q-c","label":"Sim, porque todo objeto static fica no Metaspace e não conta para o heap.","correct":false,"explanation":"O campo pertence à classe, mas o objeto referenciado continua no grafo gerenciado; Metaspace não torna o referenciado coletável."}]}],"resources":[{"id":"jvm-jvms2","type":"reference","title":"JVMS 2: The Structure of the JVM","url":"https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-2.html","reinforces":"Define class files, runtime data areas, frames, heap e method area de forma normativa.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"jvm-gc-guide","type":"guide","title":"Java 21 Garbage Collection Tuning Guide","url":"https://docs.oracle.com/en/java/javase/21/gctuning/","reinforces":"Documenta coletores, ergonomia, fatores de decisão e métricas do HotSpot.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A jvm deep operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a jvm deep operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this jvm deep chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"STEP 1 — code-source (Book.java)","instruction":"A jvm deep operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this jvm deep chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22","notes":["Removidas garantias falsas de stack allocation, C1/C2 universal, pausa fixa e coletor obrigatório."]}},{"id":"debugging","moduleId":"testing-engineering","order":2,"title":"Técnicas de Debugging na Prática","summary":"O capítulo 79 ensinou \"dry run\" — rastrear código manualmente no papel. Um debugger é a versão automatizada e interativa dessa mesma habilidade, integrada à sua IDE, permitindo pausar a execução e inspecionar o estado exato do programa em qualquer ponto.","objectives":["Usar debugger a partir de uma hipótese","Aplicar breakpoint comum, condicional e watchpoint","Navegar Step Over/Into/Out sem perder o modelo da stack"],"whyItExists":"Depois de recursão, JVM e projetos pequenos, o aluno consegue observar estado real em execução em vez de adivinhar por println. Debugging transforma hipótese em evidência.","prerequisiteChapterIds":["build"],"conceptIds":["breakpoints-pausando-a-execucao-exatamente-onde-voce-precisa","conditional-breakpoints-pausando-so-quando-uma-condicao-especifica-e-ver","field-watchpoints-pausando-quando-um-campo-muda-de-valor-nao-uma-linha-e","navegando-a-execucao-pausada-step-over-step-into-step-out","inspecionando-memoria-variables-watches-e-evaluate-expression"],"introducedConceptIds":["debug-hipotese-breakpoint","debug-step-watch-evaluate"],"usedConceptIds":["stack-frame-recursivo","medicao-evidencia-performance"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"debugging-intuition","type":"intuition","authorship":"authored","title":"Debugger não substitui hipótese","body":"Pausar o programa só ajuda quando você sabe qual diferença espera observar: valor errado, caminho inesperado, exceção, retorno vazio ou mutação fora de hora.","analogyLimit":"Lupa ajuda como metáfora, mas debugger pode executar expressões com efeitos e alterar o estado se usado sem cuidado."},{"id":"debugging-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Ferramenta</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de prática ativa (esse capítulo se aprende fazendo, não só lendo)</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#logica-programacao\">79 · Lógica de programação</a>, <a class=\"prereq-tag\" href=\"#jvm-profundo\">85 · JVM profundo</a> (stack frames)</div>\n      </div>","fidelityText":"Ferramenta Dificuldade: Intermediário ⏱ ~2h de prática ativa (esse capítulo se aprende fazendo, não só lendo) Pré-requisitos: 79 · Lógica de programação, 85 · JVM profundo (stack frames)"},{"id":"debugging-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 79 ensinou \"dry run\" — rastrear código manualmente no papel. Um <strong>debugger</strong> é a versão automatizada e interativa dessa mesma habilidade, integrada à sua IDE, permitindo pausar a execução e inspecionar o estado exato do programa em qualquer ponto.</p>","fidelityText":"O capítulo 79 ensinou \"dry run\" — rastrear código manualmente no papel. Um debugger é a versão automatizada e interativa dessa mesma habilidade, integrada à sua IDE, permitindo pausar a execução e inspecionar o estado exato do programa em qualquer ponto."},{"id":"debugging-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Breakpoints — pausando a execução exatamente onde você precisa</h2>","fidelityText":"Breakpoints — pausando a execução exatamente onde você precisa"},{"id":"debugging-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Um breakpoint de linha é o mais básico: clique na margem esquerda do editor (IntelliJ IDEA ou VS Code) ao lado do número da linha, e a execução pausa <strong>antes</strong> daquela linha rodar, toda vez que o fluxo do programa chegar ali.</p>","fidelityText":"Um breakpoint de linha é o mais básico: clique na margem esquerda do editor (IntelliJ IDEA ou VS Code) ao lado do número da linha, e a execução pausa antes daquela linha rodar, toda vez que o fluxo do programa chegar ali."},{"id":"debugging-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"public BookDTO findById(Long id) {\n    Optional<Book> book = repository.findById(id);  ← clique aqui na margem para\n                                                       breakpoint de line\n    return book.map(BookDTO::from)\n        .orElseThrow(() -> new BookNotFoundException(id));\n}","fidelityText":"public LivroDTO buscarPorId(Long id) { Optional<Livro> livro = repository.findById(id); ← clique aqui na margem para breakpoint de linha return livro.map(LivroDTO::from) .orElseThrow(() -> new LivroNaoEncontradoException(id)); }","highlightedHtml":"<span class=\"kw\">public</span> <span class=\"cls\">BookDTO</span> <span class=\"fn\">findById</span>(<span class=\"kw\">Long</span> id) {\n    Optional&lt;<span class=\"cls\">Book</span>&gt; book = repository.findById(id);  <span class=\"com\">← clique aqui na margem para\n                                                       breakpoint de linha</span>\n    <span class=\"kw\">return</span> book.map(BookDTO::from)\n        .orElseThrow(() -&gt; <span class=\"kw\">new</span> <span class=\"cls\">BookNotFoundException</span>(id));\n}","caption":"Exemplo executável de debugging.","explanation":["O breakpoint é colocado antes da linha que cria a evidência da hipótese.","Pausar ali permite comparar id, Optional retornado e caminho de exceção."],"commonMistakes":["Começar breakpoint longe do sintoma","Ignorar stack e olhar só uma variável","Depurar sem cenário reproduzível"]},{"id":"debugging-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Ao rodar em modo <strong>Debug</strong> (não \"Run\" normal — na IntelliJ, o ícone de inseto verde; no VS Code, F5 com a configuração de debug ativa) e a execução chegar naquela linha, tudo congela: você pode inspecionar o valor de <code>id</code>, de qualquer variável em escopo, o estado exato da Stack (capítulo 85) naquele momento.</p>","fidelityText":"Ao rodar em modo Debug (não \"Run\" normal — na IntelliJ, o ícone de inseto verde; no VS Code, F5 com a configuração de debug ativa) e a execução chegar naquela linha, tudo congela: você pode inspecionar o valor de id, de qualquer variável em escopo, o estado exato da Stack (capítulo 85) naquele momento."},{"id":"debugging-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Conditional Breakpoints — pausando só quando uma condição específica é verdadeira</h2>","fidelityText":"Conditional Breakpoints — pausando só quando uma condição específica é verdadeira"},{"id":"debugging-content-8","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um breakpoint comum pausa toda vez que a linha é alcançada — se esse código roda dentro de um loop de 10.000 iterações e o bug só acontece na iteração 7.432, você não quer apertar \"continuar\" sete mil vezes. Um <strong>breakpoint condicional</strong> é como dizer ao segurança \"só me chame se o visitante que entrar tiver exatamente este nome específico\" — ele só pausa quando a condição que você configurou é verdadeira.</div>","fidelityText":"Um breakpoint comum pausa toda vez que a linha é alcançada — se esse código roda dentro de um loop de 10.000 iterações e o bug só acontece na iteração 7.432, você não quer apertar \"continuar\" sete mil vezes. Um breakpoint condicional é como dizer ao segurança \"só me chame se o visitante que entrar tiver exatamente este nome específico\" — ele só pausa quando a condição que você configurou é verdadeira."},{"id":"debugging-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"// clique com o botão direito no breakpoint já criado, e defina uma condição:\nbook.getId() == 42L\n\n// ou, para um loop:\nfor (Book book : allOsBooks) {\n    process(book);  ← breakpoint condicional: \"book.getPages() > 900\"\n                          -- só pausa quando encontrar um livro específico com\n                          mais de 900 pages, ignorando all os others\n}","fidelityText":"// clique com o botão direito no breakpoint já criado, e defina uma condição: livro.getId() == 42L // ou, para um loop: for (Livro livro : todosOsLivros) { processar(livro); ← breakpoint condicional: \"livro.getPaginas() > 900\" -- só pausa quando encontrar um livro específico com mais de 900 páginas, ignorando todos os outros }","highlightedHtml":"<span class=\"com\">// clique com o botão direito no breakpoint já criado, e defina uma condição:</span>\nbook.getId() == 42L\n\n<span class=\"com\">// ou, para um loop:</span>\n<span class=\"kw\">for</span> (<span class=\"cls\">Book</span> book : allOsBooks) {\n    process(book);  <span class=\"com\">← breakpoint condicional: \"livro.getPaginas() &gt; 900\"\n                          -- só pausa quando encontrar um livro específico com\n                          mais de 900 páginas, ignorando todos os outros</span>\n}","caption":"Exemplo executável de debugging.","explanation":["Breakpoint condicional só pausa quando a expressão é verdadeira.","Em loops grandes, isso transforma uma hipótese específica em pausa controlada."],"commonMistakes":["Usar condição com efeito colateral","Comparar Long com == fora de contexto didático","Esquecer de remover condição depois"]},{"id":"debugging-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Field Watchpoints — pausando quando um CAMPO muda de valor, não uma linha específica</h2>","fidelityText":"Field Watchpoints — pausando quando um CAMPO muda de valor, não uma linha específica"},{"id":"debugging-content-11","type":"html","authorship":"legacy-preserved","html":"<p>Às vezes você não sabe <em>onde</em> um campo está sendo alterado incorretamente — só sabe que, em algum ponto do fluxo, ele muda para um valor errado. Um <strong>watchpoint de campo</strong> (na IntelliJ: clique com botão direito no nome do campo na declaração, \"Add Field Watchpoint\") pausa a execução <strong>toda vez que aquele campo específico é lido ou escrito</strong>, em qualquer lugar do código, sem você precisar adivinhar onde colocar um breakpoint de linha tradicional.</p>","fidelityText":"Às vezes você não sabe onde um campo está sendo alterado incorretamente — só sabe que, em algum ponto do fluxo, ele muda para um valor errado. Um watchpoint de campo (na IntelliJ: clique com botão direito no nome do campo na declaração, \"Add Field Watchpoint\") pausa a execução toda vez que aquele campo específico é lido ou escrito, em qualquer lugar do código, sem você precisar adivinhar onde colocar um breakpoint de linha tradicional."},{"id":"debugging-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Isso é especialmente valioso para depurar bugs de concorrência (capítulo 88) — se um campo compartilhado entre threads está sendo alterado inesperadamente, um field watchpoint revela <strong>qual thread exata</strong> e <strong>qual linha exata</strong> fez a alteração, algo quase impossível de descobrir só lendo o código estaticamente quando múltiplas threads têm acesso ao mesmo campo.</div>","fidelityText":"Isso é especialmente valioso para depurar bugs de concorrência (capítulo 88) — se um campo compartilhado entre threads está sendo alterado inesperadamente, um field watchpoint revela qual thread exata e qual linha exata fez a alteração, algo quase impossível de descobrir só lendo o código estaticamente quando múltiplas threads têm acesso ao mesmo campo."},{"id":"debugging-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Navegando a execução pausada: Step Over, Step Into, Step Out</h2>","fidelityText":"Navegando a execução pausada: Step Over, Step Into, Step Out"},{"id":"debugging-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"┌─────────────────────────────────────────────────────────────┐\n│ Voce esta PARADO in this line, prestes a executa-la:            │\n│                                                                   │\n│     BookDTO dto = convertForDTO(book);                     │\n│                                                                   │\n│  STEP OVER (F8 na IntelliJ)                                     │\n│    Executa a line ENTIRE de uma vez, incluindo qualquer        │\n│    chamada de method inside dela, sem \"entrar\" no method --      │\n│    voce para na NEXT line do method current                    │\n│                                                                   │\n│  STEP INTO (F7 na IntelliJ)                                     │\n│    \"Enters\" inside do method convertForDTO(), pausando na      │\n│    PRIMEIRA line INSIDE dele -- útil quando o bug pode estar    │\n│    inside dessa chamada specifies                                │\n│                                                                   │\n│  STEP OUT (Shift+F8 na IntelliJ)                                │\n│    Se voce already entrou em um method com Step Into e wants return    │\n│    para quem o chamou, Step Out executa o RESTO do method         │\n│    current de uma vez e pausa de volta no method CHAMADOR           │\n└─────────────────────────────────────────────────────────────┘","fidelityText":"┌─────────────────────────────────────────────────────────────┐ │ Você está PARADO nesta linha, prestes a executá-la: │ │ │ │ LivroDTO dto = converterParaDTO(livro); │ │ │ │ STEP OVER (F8 na IntelliJ) │ │ Executa a linha INTEIRA de uma vez, incluindo qualquer │ │ chamada de método dentro dela, sem \"entrar\" no método -- │ │ você para na PRÓXIMA linha do método atual │ │ │ │ STEP INTO (F7 na IntelliJ) │ │ \"Entra\" dentro do método converterParaDTO(), pausando na │ │ PRIMEIRA linha DENTRO dele -- útil quando o bug pode estar │ │ dentro dessa chamada específica │ │ │ │ STEP OUT (Shift+F8 na IntelliJ) │ │ Se você já entrou em um método com Step Into e quer voltar │ │ para quem o chamou, Step Out executa o RESTO do método │ │ atual de uma vez e pausa de volta no método CHAMADOR │ └─────────────────────────────────────────────────────────────┘","highlightedHtml":"┌─────────────────────────────────────────────────────────────┐\n│ Voce esta PARADO in this line, prestes a executa-la:            │\n│                                                                   │\n│     BookDTO dto = convertForDTO(book);                     │\n│                                                                   │\n│  STEP OVER (F8 na IntelliJ)                                     │\n│    Executa a line ENTIRE de uma vez, incluindo qualquer        │\n│    chamada de method inside dela, sem \"entrar\" no method --      │\n│    voce para na NEXT line do method current                    │\n│                                                                   │\n│  STEP INTO (F7 na IntelliJ)                                     │\n│    \"Enters\" inside do method convertForDTO(), pausando na      │\n│    PRIMEIRA line INSIDE dele -- útil quando o bug pode estar    │\n│    inside dessa chamada specifies                                │\n│                                                                   │\n│  STEP OUT (Shift+F8 na IntelliJ)                                │\n│    Se voce already entrou em um method com Step Into e wants return    │\n│    para quem o chamou, Step Out executa o RESTO do method         │\n│    current de uma vez e pausa de volta no method CHAMADOR           │\n└─────────────────────────────────────────────────────────────┘","caption":"Exemplo executável de debugging.","explanation":["O diagrama diferencia Step Over, Step Into e Step Out pelo escopo da próxima pausa.","A escolha depende de onde a hipótese coloca o bug: linha atual, método chamado ou retorno ao chamador."],"commonMistakes":["Entrar em toda biblioteca com Step Into","Usar Continue quando queria observar transição","Perder a noção do frame atual"]},{"id":"debugging-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense nisso como navegar capítulos de um livro com marca-páginas aninhados. Step Over é \"pular para a próxima página deste capítulo, sem ler os capítulos referenciados dentro dela\". Step Into é \"seguir a referência e começar a ler aquele capítulo referenciado agora\". Step Out é \"já entendi esse capítulo referenciado, pular direto de volta para onde eu estava antes de entrar nele\".</div>","fidelityText":"Pense nisso como navegar capítulos de um livro com marca-páginas aninhados. Step Over é \"pular para a próxima página deste capítulo, sem ler os capítulos referenciados dentro dela\". Step Into é \"seguir a referência e começar a ler aquele capítulo referenciado agora\". Step Out é \"já entendi esse capítulo referenciado, pular direto de volta para onde eu estava antes de entrar nele\"."},{"id":"debugging-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>Inspecionando memória: Variables, Watches e Evaluate Expression</h2>","fidelityText":"Inspecionando memória: Variables, Watches e Evaluate Expression"},{"id":"debugging-content-17","type":"html","authorship":"legacy-preserved","html":"<p>Com a execução pausada, a maioria das IDEs mostra um painel <strong>Variables</strong>, listando todas as variáveis em escopo naquele ponto exato — incluindo os campos de <code>this</code>, permitindo expandir objetos aninhados e navegar sua estrutura completa em memória, exatamente como o dry run manual do capítulo 79, mas sem erro humano de cálculo.</p>","fidelityText":"Com a execução pausada, a maioria das IDEs mostra um painel Variables, listando todas as variáveis em escopo naquele ponto exato — incluindo os campos de this, permitindo expandir objetos aninhados e navegar sua estrutura completa em memória, exatamente como o dry run manual do capítulo 79, mas sem erro humano de cálculo."},{"id":"debugging-code-18","type":"code","authorship":"legacy-preserved","language":"java","source":"Dashboard \"Variables\" durante a pausa:\n┌─────────────────────────────────┐\n│ this = Library@7a3f2c1        │\n│   ├─ catalog = ArrayList size=3   │\n│   │    ├─ [0] = Book@1b2c3d      │\n│   │    │    ├─ title = \"1984\"    │\n│   │    │    └─ code = \"L001\"    │\n│   │    ├─ [1] = Book@4and5f6g      │\n│   │    └─ [2] = Book@7h8i9j      │\n│   └─ notifier = EmailService   │\n│                                    │\n│ book = Book@1b2c3d (parameter   │\n│         local do method current)    │","fidelityText":"Painel \"Variables\" durante a pausa: ┌─────────────────────────────────┐ │ this = Biblioteca@7a3f2c1 │ │ ├─ acervo = ArrayList size=3 │ │ │ ├─ [0] = Livro@1b2c3d │ │ │ │ ├─ titulo = \"1984\" │ │ │ │ └─ codigo = \"L001\" │ │ │ ├─ [1] = Livro@4e5f6g │ │ │ └─ [2] = Livro@7h8i9j │ │ └─ notificador = EmailServico │ │ │ │ livro = Livro@1b2c3d (parâmetro │ │ local do método atual) │","highlightedHtml":"Dashboard \"Variables\" durante a pausa:\n┌─────────────────────────────────┐\n│ this = Library@7a3f2c1        │\n│   ├─ catalog = ArrayList size=3   │\n│   │    ├─ [0] = Book@1b2c3d      │\n│   │    │    ├─ title = \"1984\"    │\n│   │    │    └─ code = \"L001\"    │\n│   │    ├─ [1] = Book@4and5f6g      │\n│   │    └─ [2] = Book@7h8i9j      │\n│   └─ notifier = EmailService   │\n│                                    │\n│ book = Book@1b2c3d (parameter   │\n│         local do method current)    │","caption":"Exemplo executável de debugging.","explanation":["O painel de variáveis mostra referências e campos alcançáveis no frame pausado.","Expandir objetos ajuda a verificar estado real sem adicionar println."],"commonMistakes":["Confundir visualização do debugger com cópia do objeto","Avaliar expressão que altera estado sem perceber","Ignorar valores de this e parâmetros"]},{"id":"debugging-content-19","type":"html","authorship":"legacy-preserved","html":"<p>Um recurso ainda mais poderoso: <strong>Evaluate Expression</strong> (Alt+F8 na IntelliJ) permite executar código Java arbitrário no contexto exato da execução pausada — testar uma expressão, chamar um método, verificar uma condição — sem precisar modificar o código-fonte e reiniciar tudo.</p>","fidelityText":"Um recurso ainda mais poderoso: Evaluate Expression (Alt+F8 na IntelliJ) permite executar código Java arbitrário no contexto exato da execução pausada — testar uma expressão, chamar um método, verificar uma condição — sem precisar modificar o código-fonte e reiniciar tudo."},{"id":"debugging-content-20","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro do debugging eficiente</h2>\n        <ul>\n          <li>Formule uma <strong>hipótese</strong> antes de começar a debugar (\"acho que o problema é X\") — debugar sem hipótese vira navegação aleatória sem direção.</li>\n          <li>Comece o breakpoint o mais próximo possível de onde você suspeita que o problema está, não no início absoluto do fluxo — economiza dezenas de \"Step Over\" desnecessários.</li>\n          <li>Use breakpoints condicionais sempre que o bug só acontece em condições específicas dentro de loops ou chamadas repetidas.</li>\n          <li>Nunca deixe breakpoints esquecidos no código antes de commitar (capítulo 29) — eles não afetam o código em si, mas podem confundir quem for debugar depois de você.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro do debugging eficiente Formule uma hipótese antes de começar a debugar (\"acho que o problema é X\") — debugar sem hipótese vira navegação aleatória sem direção. Comece o breakpoint o mais próximo possível de onde você suspeita que o problema está, não no início absoluto do fluxo — economiza dezenas de \"Step Over\" desnecessários. Use breakpoints condicionais sempre que o bug só acontece em condições específicas dentro de loops ou chamadas repetidas. Nunca deixe breakpoints esquecidos no código antes de commitar (capítulo 29) — eles não afetam o código em si, mas podem confundir quem for debugar depois de você."},{"id":"debugging-content-21","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">A habilidade de debugar bem não vem de ler sobre debugging — vem de praticar em bugs reais, seus ou de exercícios anteriores deste curso. Pegue um exercício qualquer que você já resolveu, insira deliberadamente um bug sutil (um off-by-one, capítulo 79, ou uma condição invertida), e pratique encontrá-lo usando <strong>só</strong> o debugger, sem olhar o código com os olhos primeiro — force-se a usar Step Over/Into, inspecionar variáveis, e formular hipóteses até localizar o problema.</div>","fidelityText":"A habilidade de debugar bem não vem de ler sobre debugging — vem de praticar em bugs reais, seus ou de exercícios anteriores deste curso. Pegue um exercício qualquer que você já resolveu, insira deliberadamente um bug sutil (um off-by-one, capítulo 79, ou uma condição invertida), e pratique encontrá-lo usando só o debugger, sem olhar o código com os olhos primeiro — force-se a usar Step Over/Into, inspecionar variáveis, e formular hipóteses até localizar o problema."},{"id":"debugging-exercise-22","type":"exercise","authorship":"legacy-preserved","title":"Exercício 92.1 — Depurando com breakpoint condicional","prompt":"Usando o método segundoMaior do exercício 79.1, insira um bug deliberado (troque n > maior por n >= maior) e escreva um teste com um array de 20 números aleatórios que expõe o bug apenas em alguns casos específicos. Configure um breakpoint condicional na linha do bug, ativo apenas quando n == maior (o caso-limite exato que expõe o defeito), e descreva o que você observaria no painel de variáveis nesse ponto exato de pausa.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 92.1 — Depurando com breakpoint condicionalmédio Usando o método segundoMaior do exercício 79.1, insira um bug deliberado (troque n > maior por n >= maior) e escreva um teste com um array de 20 números aleatórios que expõe o bug apenas em alguns casos específicos. Configure um breakpoint condicional na linha do bug, ativo apenas quando n == maior (o caso-limite exato que expõe o defeito), e descreva o que você observaria no painel de variáveis nesse ponto exato de pausa. Ver solução Com o breakpoint condicional n == maior ativo, a execução só pausaria no exato momento em que o array contivesse um valor duplicado igual ao maior valor já encontrado até aquele ponto. No painel de variáveis, seria possível observar maior e segundoMaior com o mesmo valor imediatamente após essa iteração — evidenciando o bug: com n >= maior em vez de n > maior, um valor igual ao maior também dispara a atualização de segundoMaior para o mesmo valor de maior, quebrando a lógica de \"segundo maior distinto\" pretendida pelo exercício original. Esse é exatamente o tipo de bug que um breakpoint condicional revela rapidamente, mas que seria tedioso encontrar pausando em toda iteração de um array de 20 elementos.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 92.1 — Depurando com breakpoint condicional</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Usando o método <code>segundoMaior</code> do exercício 79.1, insira um bug deliberado (troque <code>n &gt; maior</code> por <code>n &gt;= maior</code>) e escreva um teste com um array de 20 números aleatórios que expõe o bug apenas em alguns casos específicos. Configure um breakpoint condicional na linha do bug, ativo apenas quando <code>n == maior</code> (o caso-limite exato que expõe o defeito), e descreva o que você observaria no painel de variáveis nesse ponto exato de pausa.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>Com o breakpoint condicional <code>n == maior</code> ativo, a execução só pausaria no exato momento em que o array contivesse um valor duplicado igual ao maior valor já encontrado até aquele ponto. No painel de variáveis, seria possível observar <code>maior</code> e <code>segundoMaior</code> com o mesmo valor imediatamente após essa iteração — evidenciando o bug: com <code>n &gt;= maior</code> em vez de <code>n &gt; maior</code>, um valor <em>igual</em> ao maior também dispara a atualização de <code>segundoMaior</code> para o mesmo valor de <code>maior</code>, quebrando a lógica de \"segundo maior distinto\" pretendida pelo exercício original. Esse é exatamente o tipo de bug que um breakpoint condicional revela rapidamente, mas que seria tedioso encontrar pausando em toda iteração de um array de 20 elementos.</p>\n        </div>\n      </div>"},{"id":"debugging-flow","type":"diagram","authorship":"authored","title":"Ciclo de debugging eficiente","description":"Depurar bem é um loop curto de hipótese, observação e ajuste.","steps":["reproduzir o bug","formular hipótese falsificável","colocar breakpoint perto da evidência","inspecionar variáveis e stack","avançar com step deliberado","corrigir ou trocar hipótese","registrar teste de regressão"]},{"id":"debugging-quiz","type":"quiz","authorship":"authored","conceptId":"debug-hipotese-breakpoint","prompt":"Um bug só aparece na iteração 7432. Qual ferramenta evita pausar milhares de vezes?","options":[{"id":"debugging-q-a","label":"Breakpoint condicional ligado à condição que caracteriza o caso suspeito.","correct":true,"explanation":"A pausa ocorre apenas quando a hipótese fica observável."},{"id":"debugging-q-b","label":"Step Into desde o início do programa até chegar lá manualmente.","correct":false,"explanation":"Isso é lento e não usa a informação já conhecida sobre o caso."},{"id":"debugging-q-c","label":"Remover todos os testes para o programa rodar mais rápido.","correct":false,"explanation":"Testes ajudam a reproduzir; removê-los elimina evidência."}]}],"resources":[{"id":"debug-vscode-java","type":"reference","title":"VS Code: Debugging Java","url":"https://code.visualstudio.com/docs/java/java-debugging","reinforces":"Documenta breakpoints, inspeção, watch e execução passo a passo em Java no VS Code.","language":"en","publisher":"Microsoft","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"debug-intellij-breakpoints","type":"reference","title":"IntelliJ IDEA: Breakpoints","url":"https://www.jetbrains.com/help/idea/using-breakpoints.html","reinforces":"Documenta breakpoints de linha, condicionais, watchpoints e políticas de suspensão.","language":"en","publisher":"JetBrains","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A debugging operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a debugging operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this debugging chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"public BookDTO findById(Long id) {","instruction":"A debugging operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this debugging chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"concorrencia-profunda","moduleId":"concurrency-network-tui","order":1,"title":"Concorrência, Threads, Race Conditions & Assíncrono — Aprofundamento","summary":"O capítulo 14 mostrou a race condition de \"valor++\" e a solução com synchronized. Este capítulo aprofunda por que isso acontece no nível de hardware/memória, formaliza os problemas clássicos de concorrência além de race conditions, e introduz programação assíncrona moderna.","objectives":["Separar atomicidade de visibilidade","Entender volatile e happens-before","Diagnosticar deadlock, livelock e starvation","Compor tarefas assíncronas com CompletableFuture sem perder erro/timeout"],"whyItExists":"O capítulo de threads mostra o primeiro bug. Este aprofundamento mostra por que corrigir concorrência exige modelo de memória, progresso e composição, não apenas colocar synchronized até o teste passar.","prerequisiteChapterIds":["threads","jvm-profundo"],"conceptIds":["por-que-valor-nao-e-atomico-visualizacao-em-nivel-de-instrucao","visibilidade-de-memoria-um-problema-diferente-de-race-condition","deadlock-quando-duas-threads-travam-esperando-uma-pela-outra-para-sempre","livelock-e-starvation-os-primos-menos-famosos-do-deadlock","virtual-threads-sob-coordenacao-real","programacao-assincrona-completablefuture"],"introducedConceptIds":["memory-visibility-happens-before","deadlock-livelock-starvation","completablefuture-composition"],"usedConceptIds":["race-condition-atomicity","monitor-synchronized-mutual-exclusion","bytecode-jit-perfil"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"concorrencia-profunda-intuition","type":"intuition","authorship":"authored","title":"Concorrência tem três perguntas: valor, visibilidade e progresso","body":"Atomicidade pergunta se a atualização é indivisível. Visibilidade pergunta se outra thread enxerga a atualização. Progresso pergunta se todos continuam andando sem esperar para sempre.","analogyLimit":"Semáforos ajudam, mas modelo de memória e happens-before precisam de regras formais."},{"id":"concorrencia-profunda-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-java\">Java Interno</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Multi-Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h30</b> de estudo — um dos tópicos mais difíceis de internalizar de verdade</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#threads\">14 · Threads &amp; concorrência</a>, <a class=\"prereq-tag\" href=\"#jvm-profundo\">85 · JVM profundo</a></div>\n      </div>","fidelityText":"Java Interno Dificuldade: Multi-Avançado ⏱ ~3h30 de estudo — um dos tópicos mais difíceis de internalizar de verdade Pré-requisitos: 14 · Threads & concorrência, 85 · JVM profundo"},{"id":"concorrencia-profunda-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 14 mostrou a race condition de \"valor++\" e a solução com <code>synchronized</code>. Este capítulo aprofunda <em>por que</em> isso acontece no nível de hardware/memória, formaliza os problemas clássicos de concorrência além de race conditions, e introduz programação assíncrona moderna.</p>","fidelityText":"O capítulo 14 mostrou a race condition de \"valor++\" e a solução com synchronized. Este capítulo aprofunda por que isso acontece no nível de hardware/memória, formaliza os problemas clássicos de concorrência além de race conditions, e introduz programação assíncrona moderna."},{"id":"concorrencia-profunda-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Por que \"valor++\" não é atômico — visualização em nível de instrução</h2>","fidelityText":"Por que \"valor++\" não é atômico — visualização em nível de instrução"},{"id":"concorrencia-profunda-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"O code Java \"value++\" na verdade vira TRES instrucoes de bytecode:\n\n    GETFIELD  value        // 1. LER o valor atual da memória principal\n    ICONST_1\n    IADD                   // 2. SOMAR 1 (em um registrador da CPU)\n    PUTFIELD  value        // 3. GRAVAR o resultado de volta na memória\n\nDuas threads executando \"value++\" SIMULTANEAMENTE, when value=5:\n\n  TIME →\n\n  Thread A: READ(5) ────────────── SUM(6) ── WRITE(6)\n  Thread B:        READ(5) ── SUM(6) ── WRITE(6)\n                     ↑\n          Thread B leu o value BEFORE de A write --\n          both read \"5\", and both calculated \"6\",\n          as duas gravaram \"6\" -- o incremento de uma\n          das duas threads foi COMPLETAMENTE PERDIDO.\n\n  Result expected: value = 7 (two incrementos)\n  Result real:     value = 6 (so um increment \"sobreviveu\")","fidelityText":"O código Java \"valor++\" na verdade vira TRÊS instruções de bytecode: GETFIELD valor // 1. LER o valor atual da memória principal ICONST_1 IADD // 2. SOMAR 1 (em um registrador da CPU) PUTFIELD valor // 3. GRAVAR o resultado de volta na memória Duas threads executando \"valor++\" SIMULTANEAMENTE, quando valor=5: TEMPO → Thread A: LER(5) ────────────── SOMAR(6) ── GRAVAR(6) Thread B: LER(5) ── SOMAR(6) ── GRAVAR(6) ↑ Thread B leu o valor ANTES de A gravar -- as duas leram \"5\", as duas calcularam \"6\", as duas gravaram \"6\" -- o incremento de uma das duas threads foi COMPLETAMENTE PERDIDO. Resultado esperado: valor = 7 (dois incrementos) Resultado real: valor = 6 (só um incremento \"sobreviveu\")","highlightedHtml":"O code Java \"value++\" na verdade vira TRES instrucoes de bytecode:\n\n    GETFIELD  value        // 1. LER o valor atual da memória principal\n    ICONST_1\n    IADD                   // 2. SOMAR 1 (em um registrador da CPU)\n    PUTFIELD  value        // 3. GRAVAR o resultado de volta na memória\n\nDuas threads executando \"value++\" SIMULTANEAMENTE, when value=5:\n\n  TIME →\n\n  Thread A: READ(5) ────────────── SUM(6) ── WRITE(6)\n  Thread B:        READ(5) ── SUM(6) ── WRITE(6)\n                     ↑\n          Thread B leu o value BEFORE de A write --\n          both read \"5\", and both calculated \"6\",\n          as duas gravaram \"6\" -- o incremento de uma\n          das duas threads foi COMPLETAMENTE PERDIDO.\n\n  Result expected: value = 7 (two incrementos)\n  Result real:     value = 6 (so um increment \"sobreviveu\")","caption":"Exemplo executável de concorrencia-profunda.","explanation":["A decomposição de value++ mostra read, add e write como etapas separáveis.","O escalonador pode alternar threads entre essas etapas."],"commonMistakes":["Chamar de bug do int","Ignorar interleaving possível"]},{"id":"concorrencia-profunda-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Visibilidade de memória — um problema diferente de race condition</h2>","fidelityText":"Visibilidade de memória — um problema diferente de race condition"},{"id":"concorrencia-profunda-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Existe um segundo problema, frequentemente confundido com race condition, mas conceitualmente distinto: <strong>visibilidade</strong>. Mesmo sem duas threads escrevendo ao mesmo tempo, uma thread pode nunca \"enxergar\" a escrita feita por outra, por causa de otimizações de cache de CPU e reordenação de instruções pelo compilador/processador.</p>","fidelityText":"Existe um segundo problema, frequentemente confundido com race condition, mas conceitualmente distinto: visibilidade. Mesmo sem duas threads escrevendo ao mesmo tempo, uma thread pode nunca \"enxergar\" a escrita feita por outra, por causa de otimizações de cache de CPU e reordenação de instruções pelo compilador/processador."},{"id":"concorrencia-profunda-code-7","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Flag {\n    private boolean ready = false; // SEM volatile\n\n    void markReady() { ready = true; }   // Thread A chama isso\n    void esperar() {\n        while (!ready) { }                // Thread B fica presa AQUI PARA SEMPRE,\n                                            // mesmo depois de A marcar pronto=true --\n                                            // porque B pode estar lendo um valor\n                                            // \"cacheado\" antigo, nunca atualizado\n                                            // da memória principal\n    }\n}","fidelityText":"public class Flag { private boolean pronto = false; // SEM volatile void marcarPronto() { pronto = true; } // Thread A chama isso void esperar() { while (!pronto) { } // Thread B fica presa AQUI PARA SEMPRE, // mesmo depois de A marcar pronto=true -- // porque B pode estar lendo um valor // \"cacheado\" antigo, nunca atualizado // da memória principal } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Flag</span> {\n    <span class=\"kw\">private boolean</span> ready = <span class=\"kw\">false</span>; <span class=\"com\">// SEM volatile</span>\n\n    <span class=\"kw\">void</span> <span class=\"fn\">markReady</span>() { ready = <span class=\"kw\">true</span>; }   <span class=\"com\">// Thread A chama isso</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">esperar</span>() {\n        <span class=\"kw\">while</span> (!ready) { }                <span class=\"com\">// Thread B fica presa AQUI PARA SEMPRE,\n                                            // mesmo depois de A marcar pronto=true --\n                                            // porque B pode estar lendo um valor\n                                            // \"cacheado\" antigo, nunca atualizado\n                                            // da memória principal</span>\n    }\n}","caption":"Exemplo executável de concorrencia-profunda.","explanation":["Sem volatile ou sincronização, uma thread pode não observar atualização feita por outra no tempo esperado.","Esse é problema de visibilidade, não necessariamente de atualização perdida."],"commonMistakes":["Resolver visibilidade com sleep","Assumir que loop sempre relê memória principal"]},{"id":"concorrencia-profunda-content-8","type":"html","authorship":"legacy-preserved","html":"<p>A palavra-chave <code>volatile</code> resolve exatamente este problema — <strong>não</strong> o de atomicidade (para isso, ainda é preciso <code>synchronized</code> ou <code>Atomic*</code>):</p>","fidelityText":"A palavra-chave volatile resolve exatamente este problema — não o de atomicidade (para isso, ainda é preciso synchronized ou Atomic*):"},{"id":"concorrencia-profunda-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"private volatile boolean ready = false;\n// uma escrita volatile acontece-before de uma leitura posterior\n// da mesma variável: isso fornece visibilidade e restrições de ordenação.\n// Não significa \"ignorar cache\" literalmente e não torna valor++ atômico.","fidelityText":"private volatile boolean pronto = false; // uma escrita volatile acontece-before de uma leitura posterior // da mesma variável: isso fornece visibilidade e restrições de ordenação. // Não significa \"ignorar cache\" literalmente e não torna valor++ atômico.","highlightedHtml":"<span class=\"kw\">private volatile boolean</span> ready = <span class=\"kw\">false</span>;\n<span class=\"com\">// uma escrita volatile acontece-before de uma leitura posterior\n// da mesma variável: isso fornece visibilidade e restrições de ordenação.\n// Não significa \"ignorar cache\" literalmente e não torna valor++ atômico.</span>","caption":"Exemplo executável de concorrencia-profunda.","explanation":["volatile cria relação happens-before entre escrita e leitura posterior do mesmo campo.","Ele publica flag simples, mas não protege invariantes compostas."],"commonMistakes":["Usar volatile para contador composto","Confundir visibilidade com exclusão mútua"]},{"id":"concorrencia-profunda-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Erro comum: achar que <code>volatile</code> substitui <code>synchronized</code>.</b> <code>volatile</code> garante que a leitura mais recente sempre será vista — <strong>não</strong> garante que uma operação composta (ler + calcular + gravar, como <code>valor++</code>) seja atômica. Para uma flag booleana simples (como no exemplo acima), <code>volatile</code> sozinho é suficiente. Para um contador que precisa incrementar corretamente sob concorrência, você ainda precisa de <code>synchronized</code> ou <code>AtomicInteger</code> (capítulo 14).</div>","fidelityText":"Erro comum: achar que volatile substitui synchronized. volatile garante que a leitura mais recente sempre será vista — não garante que uma operação composta (ler + calcular + gravar, como valor++) seja atômica. Para uma flag booleana simples (como no exemplo acima), volatile sozinho é suficiente. Para um contador que precisa incrementar corretamente sob concorrência, você ainda precisa de synchronized ou AtomicInteger (capítulo 14)."},{"id":"concorrencia-profunda-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Deadlock — quando duas threads travam esperando uma pela outra para sempre</h2>","fidelityText":"Deadlock — quando duas threads travam esperando uma pela outra para sempre"},{"id":"concorrencia-profunda-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Imagine duas pessoas em um corredor estreito, cada uma segurando um objeto que a outra precisa, ambas recusando ceder passagem até receber o objeto do outro primeiro. Nenhuma das duas nunca vai ceder — as duas ficam paradas para sempre. Isso é exatamente um deadlock.</div>","fidelityText":"Imagine duas pessoas em um corredor estreito, cada uma segurando um objeto que a outra precisa, ambas recusando ceder passagem até receber o objeto do outro primeiro. Nenhuma das duas nunca vai ceder — as duas ficam paradas para sempre. Isso é exatamente um deadlock."},{"id":"concorrencia-profunda-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"Object resourceA = new Object();\nObject resourceB = new Object();\n\n// Thread 1:\nsynchronized (resourceA) {\n    // ... faz algo ...\n    synchronized (resourceB) { // espera conseguir travar B\n        // ...\n    }\n}\n\n// Thread 2 (rodando AO MESMO TEMPO):\nsynchronized (resourceB) {\n    // ... faz algo ...\n    synchronized (resourceA) { // espera conseguir travar A\n        // ...\n    }\n}\n// se Thread 1 pegar recursoA e Thread 2 pegar recursoB QUASE ao mesmo\n// tempo, Thread 1 fica esperando recursoB (que Thread 2 tem), e\n// Thread 2 fica esperando recursoA (que Thread 1 tem) -- DEADLOCK,\n// as duas ficam bloqueadas PARA SEMPRE","fidelityText":"Object recursoA = new Object(); Object recursoB = new Object(); // Thread 1: synchronized (recursoA) { // ... faz algo ... synchronized (recursoB) { // espera conseguir travar B // ... } } // Thread 2 (rodando AO MESMO TEMPO): synchronized (recursoB) { // ... faz algo ... synchronized (recursoA) { // espera conseguir travar A // ... } } // se Thread 1 pegar recursoA e Thread 2 pegar recursoB QUASE ao mesmo // tempo, Thread 1 fica esperando recursoB (que Thread 2 tem), e // Thread 2 fica esperando recursoA (que Thread 1 tem) -- DEADLOCK, // as duas ficam bloqueadas PARA SEMPRE","highlightedHtml":"<span class=\"kw\">Object</span> resourceA = <span class=\"kw\">new</span> <span class=\"kw\">Object</span>();\n<span class=\"kw\">Object</span> resourceB = <span class=\"kw\">new</span> <span class=\"kw\">Object</span>();\n\n<span class=\"com\">// Thread 1:</span>\n<span class=\"kw\">synchronized</span> (resourceA) {\n    <span class=\"com\">// ... faz algo ...</span>\n    <span class=\"kw\">synchronized</span> (resourceB) { <span class=\"com\">// espera conseguir travar B</span>\n        <span class=\"com\">// ...</span>\n    }\n}\n\n<span class=\"com\">// Thread 2 (rodando AO MESMO TEMPO):</span>\n<span class=\"kw\">synchronized</span> (resourceB) {\n    <span class=\"com\">// ... faz algo ...</span>\n    <span class=\"kw\">synchronized</span> (resourceA) { <span class=\"com\">// espera conseguir travar A</span>\n        <span class=\"com\">// ...</span>\n    }\n}\n<span class=\"com\">// se Thread 1 pegar recursoA e Thread 2 pegar recursoB QUASE ao mesmo\n// tempo, Thread 1 fica esperando recursoB (que Thread 2 tem), e\n// Thread 2 fica esperando recursoA (que Thread 1 tem) -- DEADLOCK,\n// as duas ficam bloqueadas PARA SEMPRE</span>","caption":"Exemplo executável de concorrencia-profunda.","explanation":["Dois locks adquiridos em ordens opostas podem criar espera circular.","Deadlock é ausência de progresso, geralmente sem exceção clara."],"commonMistakes":["Adicionar sleep para mascarar deadlock","Não definir ordem global de locks"]},{"id":"concorrencia-profunda-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro para evitar deadlock</h2>\n        <ul>\n          <li>Sempre adquira locks múltiplos <strong>na mesma ordem</strong> em todo o código — se toda thread sempre trava <code>recursoA</code> antes de <code>recursoB</code>, o cenário acima é estruturalmente impossível.</li>\n          <li>Prefira travar o mínimo de recursos possível, pelo menor tempo possível.</li>\n          <li>Use timeouts em locks quando disponível (<code>tryLock(tempo, unidade)</code> de <code>java.util.concurrent.locks.Lock</code>) em vez de esperar indefinidamente.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro para evitar deadlock Sempre adquira locks múltiplos na mesma ordem em todo o código — se toda thread sempre trava recursoA antes de recursoB, o cenário acima é estruturalmente impossível. Prefira travar o mínimo de recursos possível, pelo menor tempo possível. Use timeouts em locks quando disponível (tryLock(tempo, unidade) de java.util.concurrent.locks.Lock) em vez de esperar indefinidamente."},{"id":"concorrencia-profunda-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Livelock e Starvation — os primos menos famosos do deadlock</h2>","fidelityText":"Livelock e Starvation — os primos menos famosos do deadlock"},{"id":"concorrencia-profunda-content-16","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Problema</th><th>O que acontece</th></tr>\n        <tr><td><strong>Deadlock</strong></td><td>Threads travadas, nenhuma progride, para sempre</td></tr>\n        <tr><td><strong>Livelock</strong></td><td>Threads continuam \"trabalhando\" (mudando de estado), mas nunca progridem de verdade — como duas pessoas se desviando repetidamente para o mesmo lado tentando passar uma pela outra em um corredor</td></tr>\n        <tr><td><strong>Starvation</strong></td><td>Uma thread nunca consegue acesso a um recurso porque outras threads (com prioridade mais alta, ou mais \"espertas\" na disputa) sempre chegam primeiro</td></tr>\n      </tbody></table>","fidelityText":"ProblemaO que acontece DeadlockThreads travadas, nenhuma progride, para sempre LivelockThreads continuam \"trabalhando\" (mudando de estado), mas nunca progridem de verdade — como duas pessoas se desviando repetidamente para o mesmo lado tentando passar uma pela outra em um corredor StarvationUma thread nunca consegue acesso a um recurso porque outras threads (com prioridade mais alta, ou mais \"espertas\" na disputa) sempre chegam primeiro"},{"id":"concorrencia-profunda-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>Virtual threads sob coordenação real</h2>","fidelityText":"Virtual threads sob coordenação real"},{"id":"concorrencia-profunda-content-18","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 28 (Java 21 profundo) mostrou <code>Thread.ofVirtual().start(...)</code> criando e esperando tarefas independentes, mas foi explícito: <em>\"compartilhamento de estado, sincronização, interrupção, cancelamento ou limitação de recursos... precisam do módulo de concorrência\"</em>. Aqui está essa parte pendente.</p>","fidelityText":"O capítulo 28 (Java 21 profundo) mostrou Thread.ofVirtual().start(...) criando e esperando tarefas independentes, mas foi explícito: \"compartilhamento de estado, sincronização, interrupção, cancelamento ou limitação de recursos... precisam do módulo de concorrência\". Aqui está essa parte pendente."},{"id":"concorrencia-profunda-code-19","type":"code","authorship":"legacy-preserved","language":"java","source":"try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {\n    List<Future<Book>> futuros = idsBooks.stream()\n        .map(id -> pool.submit(() -> findBookInBank(id))) // uma virtual thread NOVA por tarefa\n        .toList();\n\n    for (Future<Book> future : futuros) {\n        process(future.get());\n    }\n} // try-with-resources chama close(), que espera todas as tarefas em andamento terminarem","fidelityText":"try (var pool = Executors.newVirtualThreadPerTaskExecutor()) { List<Future<Livro>> futuros = idsLivros.stream() .map(id -> pool.submit(() -> buscarLivroNoBanco(id))) // uma virtual thread NOVA por tarefa .toList(); for (Future<Livro> futuro : futuros) { processar(futuro.get()); } } // try-with-resources chama close(), que espera todas as tarefas em andamento terminarem","highlightedHtml":"<span class=\"kw\">try</span> (<span class=\"kw\">var</span> pool = Executors.newVirtualThreadPerTaskExecutor()) {\n    List&lt;Future&lt;<span class=\"cls\">Book</span>&gt;&gt; futuros = idsBooks.stream()\n        .map(id -&gt; pool.submit(() -&gt; findBookInBank(id))) <span class=\"com\">// uma virtual thread NOVA por tarefa</span>\n        .toList();\n\n    <span class=\"kw\">for</span> (Future&lt;<span class=\"cls\">Book</span>&gt; future : futuros) {\n        process(future.get());\n    }\n} <span class=\"com\">// try-with-resources chama close(), que espera todas as tarefas em andamento terminarem</span>","caption":"Exemplo executável de concorrencia-profunda.","explanation":["newVirtualThreadPerTaskExecutor() cria uma virtual thread nova por tarefa submetida, nunca reaproveitada -- é o oposto do pool de threads de plataforma.","try-with-resources chama close() no executor, que aguarda todas as tarefas em andamento terminarem antes de liberar o bloco."],"commonMistakes":["Limitar o número de virtual threads como se fosse um pool de threads de plataforma (ex.: newFixedThreadPool)","Usar synchronized em vez de ReentrantLock em seção crítica com I/O rodando majoritariamente em virtual threads (risco de pinning)"]},{"id":"concorrencia-profunda-content-20","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Nunca \"pool\" de virtual threads como você faz com threads de plataforma.</b> <code>Executors.newFixedThreadPool(200)</code> limita quantas threads existem porque cada uma é cara (stack de ~1MB, agendada pelo SO). Virtual threads são baratas de propósito — <code>newVirtualThreadPerTaskExecutor()</code> cria uma nova a cada tarefa e descarta depois, exatamente o oposto de reaproveitar. Colocar um limite artificial de pool aqui joga fora a vantagem inteira.</div>","fidelityText":"Nunca \"pool\" de virtual threads como você faz com threads de plataforma. Executors.newFixedThreadPool(200) limita quantas threads existem porque cada uma é cara (stack de ~1MB, agendada pelo SO). Virtual threads são baratas de propósito — newVirtualThreadPerTaskExecutor() cria uma nova a cada tarefa e descarta depois, exatamente o oposto de reaproveitar. Colocar um limite artificial de pool aqui joga fora a vantagem inteira."},{"id":"concorrencia-profunda-content-21","type":"html","authorship":"legacy-preserved","html":"<p><code>synchronized</code> continua funcionando com virtual threads, mas com uma pegadinha real: no Java 21, uma virtual thread bloqueada <em>dentro</em> de um bloco <code>synchronized</code> \"prende\" (<em>pinning</em>) a thread de sistema operacional que a carrega, impedindo-a de ser liberada para outra virtual thread enquanto isso — anulando, nesse trecho específico, a vantagem de leveza que motivou usar virtual thread em primeiro lugar. Para código que roda predominantemente em virtual threads e faz I/O dentro da seção crítica, prefira <code>java.util.concurrent.locks.ReentrantLock</code> a <code>synchronized</code> — ele não sofre desse pinning.</p>","fidelityText":"synchronized continua funcionando com virtual threads, mas com uma pegadinha real: no Java 21, uma virtual thread bloqueada dentro de um bloco synchronized \"prende\" (pinning) a thread de sistema operacional que a carrega, impedindo-a de ser liberada para outra virtual thread enquanto isso — anulando, nesse trecho específico, a vantagem de leveza que motivou usar virtual thread em primeiro lugar. Para código que roda predominantemente em virtual threads e faz I/O dentro da seção crítica, prefira java.util.concurrent.locks.ReentrantLock a synchronized — ele não sofre desse pinning."},{"id":"concorrencia-profunda-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>Programação Assíncrona: CompletableFuture</h2>","fidelityText":"Programação Assíncrona: CompletableFuture"},{"id":"concorrencia-profunda-content-23","type":"html","authorship":"legacy-preserved","html":"<p>Threads (capítulo 14) e <code>ExecutorService</code> resolvem \"executar código em paralelo\". <code>CompletableFuture</code> resolve um problema diferente e complementar: <strong>compor</strong> operações assíncronas — encadear \"quando isso terminar, faça aquilo\", sem bloquear a thread principal esperando.</p>","fidelityText":"Threads (capítulo 14) e ExecutorService resolvem \"executar código em paralelo\". CompletableFuture resolve um problema diferente e complementar: compor operações assíncronas — encadear \"quando isso terminar, faça aquilo\", sem bloquear a thread principal esperando."},{"id":"concorrencia-profunda-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"CompletableFuture<Book> future = CompletableFuture\n    .supplyAsync(() -> findBookInBank(id))       // roda em outra thread, não bloqueia aqui\n    .thenApply(book -> enrichWithDataExternal(book)) // encadeia -- roda QUANDO o anterior terminar\n    .thenApply(BookDTO::from);                       // mais um passo encadeado\n\nfuture.thenAccept(dto -> System.out.println(\"Ready: \" + dto));\n// o código AQUI continua rodando IMEDIATAMENTE, sem esperar\n// o \"futuro\" terminar -- typical de programação assíncrona/reativa\n\n// combinando DUAS operações assíncronas independentes:\nCompletableFuture<Book> searchBook = CompletableFuture.supplyAsync(() -> findBook(id));\nCompletableFuture<Author> searchAuthor = CompletableFuture.supplyAsync(() -> findAuthor(authorId));\n\nCompletableFuture<String> combinado = searchBook.thenCombine(searchAuthor,\n    (book, author) -> book.getTitle() + \" by \" + author.getName());\n// as duas buscas rodam EM PARALELO, e \"combinado\" só resolve quando\n// AMBAS terminarem -- muito mais rápido que buscar uma, esperar, depois\n// buscar a outra sequencialmente","fidelityText":"CompletableFuture<Livro> futuro = CompletableFuture .supplyAsync(() -> buscarLivroNoBanco(id)) // roda em outra thread, não bloqueia aqui .thenApply(livro -> enriquecerComDadosExternos(livro)) // encadeia -- roda QUANDO o anterior terminar .thenApply(LivroDTO::from); // mais um passo encadeado futuro.thenAccept(dto -> System.out.println(\"Pronto: \" + dto)); // o código AQUI continua rodando IMEDIATAMENTE, sem esperar // o \"futuro\" terminar -- typical de programação assíncrona/reativa // combinando DUAS operações assíncronas independentes: CompletableFuture<Livro> buscaLivro = CompletableFuture.supplyAsync(() -> buscarLivro(id)); CompletableFuture<Autor> buscaAutor = CompletableFuture.supplyAsync(() -> buscarAutor(autorId)); CompletableFuture<String> combinado = buscaLivro.thenCombine(buscaAutor, (livro, autor) -> livro.getTitulo() + \" por \" + autor.getNome()); // as duas buscas rodam EM PARALELO, e \"combinado\" só resolve quando // AMBAS terminarem -- muito mais rápido que buscar uma, esperar, depois // buscar a outra sequencialmente","highlightedHtml":"CompletableFuture&lt;<span class=\"cls\">Book</span>&gt; future = CompletableFuture\n    .supplyAsync(() -&gt; findBookInBank(id))       <span class=\"com\">// roda em outra thread, não bloqueia aqui</span>\n    .thenApply(book -&gt; enrichWithDataExternal(book)) <span class=\"com\">// encadeia -- roda QUANDO o anterior terminar</span>\n    .thenApply(BookDTO::from);                       <span class=\"com\">// mais um passo encadeado</span>\n\nfuture.thenAccept(dto -&gt; System.out.println(<span class=\"str\">\"Ready: \"</span> + dto));\n<span class=\"com\">// o código AQUI continua rodando IMEDIATAMENTE, sem esperar\n// o \"futuro\" terminar -- typical de programação assíncrona/reativa</span>\n\n<span class=\"com\">// combinando DUAS operações assíncronas independentes:</span>\nCompletableFuture&lt;<span class=\"cls\">Book</span>&gt; searchBook = CompletableFuture.supplyAsync(() -&gt; findBook(id));\nCompletableFuture&lt;<span class=\"cls\">Author</span>&gt; searchAuthor = CompletableFuture.supplyAsync(() -&gt; findAuthor(authorId));\n\nCompletableFuture&lt;<span class=\"kw\">String</span>&gt; combinado = searchBook.thenCombine(searchAuthor,\n    (book, author) -&gt; book.getTitle() + <span class=\"str\">\" by \"</span> + author.getName());\n<span class=\"com\">// as duas buscas rodam EM PARALELO, e \"combinado\" só resolve quando\n// AMBAS terminarem -- muito mais rápido que buscar uma, esperar, depois\n// buscar a outra sequencialmente</span>","caption":"Exemplo executável de concorrencia-profunda.","explanation":["CompletableFuture compõe tarefas assíncronas e captura resultado/erro futuro.","Executor, timeout e tratamento de exceção devem ser explícitos em código sério."],"commonMistakes":["Usar join sem limite em caminho crítico","Executar bloqueio pesado no executor errado"]},{"id":"concorrencia-profunda-content-25","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Isso conecta diretamente com o <code>WebClient</code> reativo do capítulo 72 — <code>Mono&lt;T&gt;</code> do Project Reactor é, conceitualmente, muito próximo de <code>CompletableFuture&lt;T&gt;</code>: ambos representam \"um valor que ainda não existe, mas vai existir\". A diferença prática é que o ecossistema reativo completo (Reactor, RxJava) oferece muito mais operadores de composição, backpressure (controle de quando o produtor está gerando dados mais rápido do que o consumidor processa) e integração nativa com I/O não-bloqueante — <code>CompletableFuture</code> é a porta de entrada da própria linguagem Java para esse mesmo paradigma de pensamento.</div>","fidelityText":"Isso conecta diretamente com o WebClient reativo do capítulo 72 — Mono<T> do Project Reactor é, conceitualmente, muito próximo de CompletableFuture<T>: ambos representam \"um valor que ainda não existe, mas vai existir\". A diferença prática é que o ecossistema reativo completo (Reactor, RxJava) oferece muito mais operadores de composição, backpressure (controle de quando o produtor está gerando dados mais rápido do que o consumidor processa) e integração nativa com I/O não-bloqueante — CompletableFuture é a porta de entrada da própria linguagem Java para esse mesmo paradigma de pensamento."},{"id":"concorrencia-profunda-content-26","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Assíncrono não é sinônimo de \"mais rápido\" automaticamente.</b> Para operações rápidas e simples, o overhead de criar e coordenar tasks assíncronas pode ser maior que simplesmente executar sequencialmente. Programação assíncrona compensa quando você tem operações genuinamente independentes e potencialmente lentas (chamadas de rede, I/O de disco) que podem rodar em paralelo — a mesma lição prática de \"meça antes de otimizar\" do capítulo 34/80 se aplica aqui também.</div>","fidelityText":"Assíncrono não é sinônimo de \"mais rápido\" automaticamente. Para operações rápidas e simples, o overhead de criar e coordenar tasks assíncronas pode ser maior que simplesmente executar sequencialmente. Programação assíncrona compensa quando você tem operações genuinamente independentes e potencialmente lentas (chamadas de rede, I/O de disco) que podem rodar em paralelo — a mesma lição prática de \"meça antes de otimizar\" do capítulo 34/80 se aplica aqui também."},{"id":"concorrencia-profunda-content-27","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Antes de escrever qualquer código concorrente novo, sempre desenhe (no papel, como fizemos nos diagramas acima) a linha do tempo de duas threads executando simultaneamente, marcando exatamente onde cada uma lê e escreve dados compartilhados. Se você consegue identificar um ponto onde a ordem de execução entre as duas threads <em>importa</em> para o resultado final, esse é exatamente o ponto que precisa de sincronização — e agora você sabe exatamente qual mecanismo (synchronized, volatile, Atomic, Lock com timeout) resolve qual tipo específico de problema.</div>","fidelityText":"Antes de escrever qualquer código concorrente novo, sempre desenhe (no papel, como fizemos nos diagramas acima) a linha do tempo de duas threads executando simultaneamente, marcando exatamente onde cada uma lê e escreve dados compartilhados. Se você consegue identificar um ponto onde a ordem de execução entre as duas threads importa para o resultado final, esse é exatamente o ponto que precisa de sincronização — e agora você sabe exatamente qual mecanismo (synchronized, volatile, Atomic, Lock com timeout) resolve qual tipo específico de problema."},{"id":"concorrencia-profunda-exercise-28","type":"exercise","authorship":"legacy-preserved","title":"Exercício 88.1 — Reproduzindo e corrigindo um deadlock","prompt":"Implemente o cenário de deadlock mostrado no exemplo acima com duas threads e dois objetos de lock. Rode e confirme que a aplicação trava (pode ser necessário usar um Thread.sleep() pequeno entre pegar o primeiro lock e tentar o segundo, para aumentar a chance da condição de corrida acontecer). Depois, corrija reordenando a aquisição de locks para que ambas as threads sempre travem na mesma ordem, e confirme que o deadlock não ocorre mais.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 88.1 — Reproduzindo e corrigindo um deadlockdifícil Implemente o cenário de deadlock mostrado no exemplo acima com duas threads e dois objetos de lock. Rode e confirme que a aplicação trava (pode ser necessário usar um Thread.sleep() pequeno entre pegar o primeiro lock e tentar o segundo, para aumentar a chance da condição de corrida acontecer). Depois, corrija reordenando a aquisição de locks para que ambas as threads sempre travem na mesma ordem, e confirme que o deadlock não ocorre mais. Ver solução // versão CORRIGIDA -- ambas as threads travam recursoA ANTES de recursoB, sempre: Object recursoA = new Object(); Object recursoB = new Object(); Runnable tarefa1 = () -> { synchronized (recursoA) { synchronized (recursoB) { System.out.println(\"Tarefa 1 concluída\"); } } }; Runnable tarefa2 = () -> { synchronized (recursoA) { // mesma ORDEM que tarefa1 -- nunca inverte! synchronized (recursoB) { System.out.println(\"Tarefa 2 concluída\"); } } }; new Thread(tarefa1).start(); new Thread(tarefa2).start(); // como as DUAS threads sempre pedem recursoA primeiro, uma delas // sempre consegue pegar os dois locks em sequência antes da outra // sequer começar -- o cenário circular de espera se torna impossível","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 88.1 — Reproduzindo e corrigindo um deadlock</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Implemente o cenário de deadlock mostrado no exemplo acima com duas threads e dois objetos de lock. Rode e confirme que a aplicação trava (pode ser necessário usar um <code>Thread.sleep()</code> pequeno entre pegar o primeiro lock e tentar o segundo, para aumentar a chance da condição de corrida acontecer). Depois, corrija reordenando a aquisição de locks para que ambas as threads sempre travem na mesma ordem, e confirme que o deadlock não ocorre mais.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"com\">// versão CORRIGIDA -- ambas as threads travam recursoA ANTES de recursoB, sempre:</span>\n<span class=\"kw\">Object</span> resourceA = <span class=\"kw\">new</span> <span class=\"kw\">Object</span>();\n<span class=\"kw\">Object</span> resourceB = <span class=\"kw\">new</span> <span class=\"kw\">Object</span>();\n\nRunnable task1 = () -&gt; {\n    <span class=\"kw\">synchronized</span> (resourceA) {\n        <span class=\"kw\">synchronized</span> (resourceB) {\n            System.out.println(<span class=\"str\">\"Task 1 concluida\"</span>);\n        }\n    }\n};\n\nRunnable task2 = () -&gt; {\n    <span class=\"kw\">synchronized</span> (resourceA) { <span class=\"com\">// mesma ORDEM que tarefa1 -- nunca inverte!</span>\n        <span class=\"kw\">synchronized</span> (resourceB) {\n            System.out.println(<span class=\"str\">\"Task 2 concluida\"</span>);\n        }\n    }\n};\n\n<span class=\"kw\">new</span> Thread(task1).start();\n<span class=\"kw\">new</span> Thread(task2).start();\n<span class=\"com\">// como as DUAS threads sempre pedem recursoA primeiro, uma delas\n// sempre consegue pegar os dois locks em sequência antes da outra\n// sequer começar -- o cenário circular de espera se torna impossível</span></pre>\n        </div>\n      </div>"},{"id":"concorrencia-profunda-quiz","type":"quiz","authorship":"authored","conceptId":"memory-visibility-happens-before","prompt":"O que `volatile` resolve melhor?","options":[{"id":"cp-a","label":"Visibilidade e ordenação de leitura/escrita daquele campo conforme happens-before.","correct":true,"explanation":"volatile não torna operações compostas como value++ atômicas."},{"id":"cp-b","label":"Atomicidade de qualquer sequência de três operações.","correct":false,"explanation":"Para operação composta, use atomic apropriado, lock ou redesign."},{"id":"cp-c","label":"Deadlock entre dois locks em ordem invertida.","correct":false,"explanation":"Deadlock exige política de ordem/tempo/lock, não volatile."}]}],"resources":[{"id":"jls-memory-model","type":"reference","title":"JLS 17: Threads and Locks","url":"https://docs.oracle.com/javase/specs/jls/se21/html/jls-17.html","reinforces":"Modelo de memória, happens-before, synchronized e volatile.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"completablefuture-api","type":"reference","title":"CompletableFuture API — Java 21","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/CompletableFuture.html","reinforces":"Composição assíncrona, callbacks, erro e completions.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The concurrency deep component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to concurrency deep. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible concurrency deep failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"O code Java \"value++\" na verdade vira TRES instrucoes de bytecode:","instruction":"The concurrency deep component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible concurrency deep failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"arquitetura-software","moduleId":"application-design","order":7,"title":"Arquitetura de Software","summary":"Enquanto Clean Code organiza a árvore, arquitetura decide o desenho da floresta inteira — como os grandes blocos do sistema se relacionam, comunicam e evoluem independentemente.","objectives":["Distinguir camadas reais de pastas decorativas","Aplicar direção de dependência e ports/adapters","Escolher monólito modular antes de distribuir por moda","Registrar trade-offs arquiteturais com contexto"],"whyItExists":"Depois de DI, padrões, clean code e um mini-framework, o aluno pode elevar o desenho: onde ficam regras, portas, adapters e fronteiras. Microserviços são citados como trade-off futuro, não como requisito imediato.","prerequisiteChapterIds":["clean-code","projetospring","mini-financas-jdbc","pure-java-api-project"],"conceptIds":["monolito-vs-microsservicos-a-decisao-mais-debatida-da-engenharia-moderna","monolito-modular-organizado-por-dominio-nao-so-por-camada-tecnica","arquitetura-em-camadas-a-que-voce-ja-construiu","arquitetura-hexagonal-ports-adapters-indo-um-passo-alem"],"introducedConceptIds":["camadas-direcao-dependencia","hexagonal-port-adapter","monolito-modular-tradeoff"],"usedConceptIds":["dip-dependencia-abstracao","adapter-fronteira-externa","jdbc-transacao-rollback","httpclient-reuso-timeout"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"arquitetura-intuition","type":"intuition","authorship":"authored","title":"Arquitetura é o que torna mudança possível sem rasgar tudo","body":"Arquitetura não é diagrama bonito. É o conjunto de decisões que define direção de dependências, fronteiras, onde a regra vive e quanto custa trocar banco, API, UI ou fluxo de entrega.","analogyLimit":"Mapa ajuda a visualizar fronteiras, mas arquitetura também inclui runtime, equipe, deploy, testes, dados e falhas."},{"id":"arquitetura-software-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Engenharia</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Multi-Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#clean-code\">74 · Clean Code</a>, <a class=\"prereq-tag\" href=\"#mini-financas-jdbc\">P6 · Mini-projeto: finanças pessoais com PostgreSQL</a></div>\n      </div>","fidelityText":"Engenharia Dificuldade: Multi-Avançado ⏱ ~3h de estudo Pré-requisitos: 74 · Clean Code, P6 · Mini-projeto: finanças pessoais com PostgreSQL"},{"id":"arquitetura-software-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Enquanto Clean Code organiza a árvore, arquitetura decide o desenho da floresta inteira — como os grandes blocos do sistema se relacionam, comunicam e evoluem independentemente.</p>","fidelityText":"Enquanto Clean Code organiza a árvore, arquitetura decide o desenho da floresta inteira — como os grandes blocos do sistema se relacionam, comunicam e evoluem independentemente."},{"id":"arquitetura-software-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Monolito vs Microsserviços — a decisão mais debatida da engenharia moderna</h2>","fidelityText":"Monolito vs Microsserviços — a decisão mais debatida da engenharia moderna"},{"id":"arquitetura-software-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Monolito</th><th>Microsserviços</th></tr>\n        <tr><td>Deploy</td><td>Uma unidade, tudo junto</td><td>Cada serviço deployado independentemente</td></tr>\n        <tr><td>Comunicação interna</td><td>Chamadas de método diretas (rápido, simples)</td><td>Rede — HTTP/mensageria (capítulos 26, 40) — mais lento, mais falhas possíveis</td></tr>\n        <tr><td>Escalar</td><td>A aplicação inteira escala junto</td><td>Só o serviço sob carga escala, independente dos outros</td></tr>\n        <tr><td>Complexidade operacional</td><td>Baixa — um único artefato para monitorar</td><td>Alta — muitos serviços, cada um com seus próprios logs, deploy, falhas</td></tr>\n        <tr><td>Bom para</td><td>Times pequenos, produto em validação, a maioria dos projetos reais</td><td>Times grandes com fronteiras de domínio claras, escala genuína de produto maduro</td></tr>\n      </tbody></table>","fidelityText":"MonolitoMicrosserviços DeployUma unidade, tudo juntoCada serviço deployado independentemente Comunicação internaChamadas de método diretas (rápido, simples)Rede — HTTP/mensageria (capítulos 26, 40) — mais lento, mais falhas possíveis EscalarA aplicação inteira escala juntoSó o serviço sob carga escala, independente dos outros Complexidade operacionalBaixa — um único artefato para monitorarAlta — muitos serviços, cada um com seus próprios logs, deploy, falhas Bom paraTimes pequenos, produto em validação, a maioria dos projetos reaisTimes grandes com fronteiras de domínio claras, escala genuína de produto maduro"},{"id":"arquitetura-software-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A recomendação de arquitetos sêniores experientes, com frequência contraintuitiva para quem está começando: <strong>comece com um monolito bem organizado</strong> (com separação clara de camadas/módulos internos), não com microsserviços. Microsserviços resolvem problemas de <em>escala organizacional</em> (muitos times trabalhando sem pisar um no pé do outro) — adotá-los antes de ter esse problema real significa pagar toda a complexidade operacional (rede, mensageria, observabilidade distribuída) sem nenhum dos benefícios que a justificariam. Martin Fowler descreve isso como o \"MonolithFirst\" — e times maduros do Netflix ao Shopify já passaram por essa lição publicamente.</div>","fidelityText":"A recomendação de arquitetos sêniores experientes, com frequência contraintuitiva para quem está começando: comece com um monolito bem organizado (com separação clara de camadas/módulos internos), não com microsserviços. Microsserviços resolvem problemas de escala organizacional (muitos times trabalhando sem pisar um no pé do outro) — adotá-los antes de ter esse problema real significa pagar toda a complexidade operacional (rede, mensageria, observabilidade distribuída) sem nenhum dos benefícios que a justificariam. Martin Fowler descreve isso como o \"MonolithFirst\" — e times maduros do Netflix ao Shopify já passaram por essa lição publicamente."},{"id":"arquitetura-software-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Monólito modular — organizado por domínio, não só por camada técnica</h2>","fidelityText":"Monólito modular — organizado por domínio, não só por camada técnica"},{"id":"arquitetura-software-content-7","type":"html","authorship":"legacy-preserved","html":"<p>\"Comece com monólito bem organizado\" não significa só ter <code>controller</code>/<code>service</code>/<code>repository</code> compartilhados por tudo. Um <strong>monólito modular</strong> primeiro divide o sistema por <em>domínio de negócio</em> (pedidos, pagamentos, estoque); só dentro de cada módulo é que aparecem as camadas técnicas. Cada módulo expõe uma fachada pública; o resto é interno e nenhum outro módulo deveria importar diretamente.</p>","fidelityText":"\"Comece com monólito bem organizado\" não significa só ter controller/service/repository compartilhados por tudo. Um monólito modular primeiro divide o sistema por domínio de negócio (pedidos, pagamentos, estoque); só dentro de cada módulo é que aparecem as camadas técnicas. Cada módulo expõe uma fachada pública; o resto é interno e nenhum outro módulo deveria importar diretamente."},{"id":"arquitetura-software-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"com.loja.orders          // Orders module\n  OrderService.java          // module's public facade\n  OrderRepository.java       // internal -- no other module imports this directly\n  Order.java\n\ncom.loja.payments       // Payments module\n  PaymentService.java       // public facade\n  PaymentProcessor.java   // internal\n\n// orders can call payments.PaymentService (public facade),\n// but NEVER payments.PaymentProcessor (internal detail of another module)","fidelityText":"com.loja.pedidos // módulo Pedidos PedidoService.java // fachada pública do módulo PedidoRepository.java // interno -- nenhum outro módulo importa isso direto Pedido.java com.loja.pagamentos // módulo Pagamentos PagamentoService.java // fachada pública ProcessadorPagamento.java // interno // pedidos pode chamar pagamentos.PagamentoService (fachada pública), // mas NUNCA pagamentos.ProcessadorPagamento (detalhe interno de outro módulo)","highlightedHtml":"com.loja.orders          <span class=\"com\">// módulo Pedidos</span>\n  OrderService.java          <span class=\"com\">// fachada pública do módulo</span>\n  OrderRepository.java       <span class=\"com\">// interno -- nenhum outro módulo importa isso direto</span>\n  Order.java\n\ncom.loja.payments       <span class=\"com\">// módulo Pagamentos</span>\n  PaymentService.java       <span class=\"com\">// fachada pública</span>\n  ProcessadorPayment.java   <span class=\"com\">// interno</span>\n\n<span class=\"com\">// orders can call payments.PaymentService (public facade),\n// but NEVER payments.PaymentProcessor (internal detail of another module)</span>","caption":"Exemplo executável de arquitetura-software.","explanation":["Cada módulo expõe uma fachada pública; o resto é interno ao módulo.","A fronteira é por domínio de negócio primeiro -- camadas técnicas existem dentro de cada módulo, não substituem essa divisão."],"commonMistakes":["Importar classe interna de outro módulo em vez da fachada pública","Achar que 'monólito bem organizado' é só ter controller/service/repository compartilhado"]},{"id":"arquitetura-software-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Ferramentas como o ArchUnit transformam essa regra em teste automatizado: o build falha se uma classe de <code>pedidos</code> importar uma classe interna de <code>pagamentos</code> em vez da fachada pública dele. É essa fronteira testada — não a promessa em prosa de \"vou manter organizado\" — que separa um monólito modular de um monólito bagunçado, mesmo sem nenhum deploy separado ainda existir.</div>","fidelityText":"Ferramentas como o ArchUnit transformam essa regra em teste automatizado: o build falha se uma classe de pedidos importar uma classe interna de pagamentos em vez da fachada pública dele. É essa fronteira testada — não a promessa em prosa de \"vou manter organizado\" — que separa um monólito modular de um monólito bagunçado, mesmo sem nenhum deploy separado ainda existir."},{"id":"arquitetura-software-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Arquitetura em Camadas (a que você já construiu)</h2>","fidelityText":"Arquitetura em Camadas (a que você já construiu)"},{"id":"arquitetura-software-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"Controller  (chapter 44)  → recebe request HTTP\n    ↓\nService     (chapter 43)  → rule de business\n    ↓\nRepository  (chapter 45)  → access a data\n    ↓\nBank de data (chapter 34)","fidelityText":"Controller (capítulo 44) → recebe requisição HTTP ↓ Service (capítulo 43) → regra de negócio ↓ Repository (capítulo 45) → acesso a dados ↓ Banco de dados (capítulo 34)","highlightedHtml":"Controller  (chapter 44)  → recebe request HTTP\n    ↓\nService     (chapter 43)  → rule de business\n    ↓\nRepository  (chapter 45)  → access a data\n    ↓\nBank de data (chapter 34)","caption":"Exemplo executável de arquitetura-software.","explanation":["Camadas devem impor direção de dependência, não apenas agrupar arquivos.","Controller/API conversa com aplicação; aplicação orquestra domínio e portas."],"commonMistakes":["Repository chamado direto da UI/API","Domínio depender de framework"]},{"id":"arquitetura-software-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Essa é a arquitetura que você já pratica desde o capítulo 43 — cada camada só conhece a camada logo abaixo, nunca pula etapas (um controller nunca deveria chamar o repository diretamente, por exemplo).</p>","fidelityText":"Essa é a arquitetura que você já pratica desde o capítulo 43 — cada camada só conhece a camada logo abaixo, nunca pula etapas (um controller nunca deveria chamar o repository diretamente, por exemplo)."},{"id":"arquitetura-software-content-13","type":"html","authorship":"legacy-preserved","html":"<h2>Arquitetura Hexagonal (Ports &amp; Adapters) — indo um passo além</h2>","fidelityText":"Arquitetura Hexagonal (Ports & Adapters) — indo um passo além"},{"id":"arquitetura-software-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Na arquitetura em camadas tradicional, a lógica de negócio ainda \"sabe\" que existe um banco relacional embaixo dela. Na arquitetura <strong>hexagonal</strong>, o núcleo de regras de negócio fica completamente isolado no centro, comunicando-se com o mundo externo (banco, API REST, fila) só através de <em>interfaces</em> (\"portas\") — exatamente o padrão Repository do capítulo 23, generalizado para <strong>toda</strong> dependência externa, não só o banco. É como uma tomada elétrica universal: o aparelho (regra de negócio) não sabe nem se importa se a energia vem de uma usina hidrelétrica ou solar (Postgres ou MongoDB) — só precisa que o adaptador encaixe na porta certa.</div>","fidelityText":"Na arquitetura em camadas tradicional, a lógica de negócio ainda \"sabe\" que existe um banco relacional embaixo dela. Na arquitetura hexagonal, o núcleo de regras de negócio fica completamente isolado no centro, comunicando-se com o mundo externo (banco, API REST, fila) só através de interfaces (\"portas\") — exatamente o padrão Repository do capítulo 23, generalizado para toda dependência externa, não só o banco. É como uma tomada elétrica universal: o aparelho (regra de negócio) não sabe nem se importa se a energia vem de uma usina hidrelétrica ou solar (Postgres ou MongoDB) — só precisa que o adaptador encaixe na porta certa."},{"id":"arquitetura-software-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"// núcleo de domínio -- NÃO sabe que existe JPA, HTTP, ou Kafka:\npublic interface BookRepositoryPort { // a \"porta\"\n    void save(Book book);\n}\n\n// adaptador concreto -- É aqui que JPA aparece, isolado do núcleo:\n@Repository\npublic class BookRepositoryJpaAdapter implements BookRepositoryPort {\n    private final BookJpaRepository jpaRepository; // Spring Data, capítulo 45\n    @Override public void save(Book book) { jpaRepository.save(toEntity(book)); }\n}","fidelityText":"// núcleo de domínio -- NÃO sabe que existe JPA, HTTP, ou Kafka: public interface LivroRepositorioPort { // a \"porta\" void salvar(Livro livro); } // adaptador concreto -- É aqui que JPA aparece, isolado do núcleo: @Repository public class LivroRepositorioJpaAdapter implements LivroRepositorioPort { private final LivroJpaRepository jpaRepository; // Spring Data, capítulo 45 @Override public void salvar(Livro livro) { jpaRepository.save(toEntity(livro)); } }","highlightedHtml":"<span class=\"com\">// núcleo de domínio -- NÃO sabe que existe JPA, HTTP, ou Kafka:</span>\n<span class=\"kw\">public interface</span> <span class=\"cls\">BookRepositoryPort</span> { <span class=\"com\">// a \"porta\"</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">save</span>(<span class=\"cls\">Book</span> book);\n}\n\n<span class=\"com\">// adaptador concreto -- É aqui que JPA aparece, isolado do núcleo:</span>\n<span class=\"annotation\">@Repository</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">BookRepositoryJpaAdapter</span> <span class=\"kw\">implements</span> <span class=\"cls\">BookRepositoryPort</span> {\n    <span class=\"kw\">private final</span> <span class=\"cls\">BookJpaRepository</span> jpaRepository; <span class=\"com\">// Spring Data, capítulo 45</span>\n    <span class=\"annotation\">@Override</span> <span class=\"kw\">public void</span> <span class=\"fn\">save</span>(<span class=\"cls\">Book</span> book) { jpaRepository.save(toEntity(book)); }\n}","caption":"Exemplo executável de arquitetura-software.","explanation":["Port representa necessidade do núcleo; adapter implementa detalhe externo.","A regra pode ser testada substituindo adapter real por dublê."],"commonMistakes":["Criar porta com vocabulário da biblioteca externa","Adapter conter regra de negócio"]},{"id":"arquitetura-software-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Arquitetura hexagonal tem custo real de complexidade</b> — mais interfaces, mais mapeamento entre modelo de domínio e modelo de persistência. Vale a pena quando o núcleo de regras de negócio é genuinamente complexo e precisa sobreviver a trocas de infraestrutura (trocar banco, trocar de REST para gRPC). Para um CRUD simples, é over-engineering — a arquitetura em camadas tradicional já é suficiente e mais direta.</div>","fidelityText":"Arquitetura hexagonal tem custo real de complexidade — mais interfaces, mais mapeamento entre modelo de domínio e modelo de persistência. Vale a pena quando o núcleo de regras de negócio é genuinamente complexo e precisa sobreviver a trocas de infraestrutura (trocar banco, trocar de REST para gRPC). Para um CRUD simples, é over-engineering — a arquitetura em camadas tradicional já é suficiente e mais direta."},{"id":"arquitetura-software-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não trate \"qual arquitetura usar\" como uma pergunta com resposta certa universal — é sempre um trade-off contextual. A pergunta certa de um arquiteto sênior nunca é \"qual é a melhor arquitetura\", é \"quais problemas eu realmente tenho, e qual arquitetura resolve <em>esses</em> problemas sem introduzir complexidade desnecessária para problemas que não tenho\".</div>","fidelityText":"Não trate \"qual arquitetura usar\" como uma pergunta com resposta certa universal — é sempre um trade-off contextual. A pergunta certa de um arquiteto sênior nunca é \"qual é a melhor arquitetura\", é \"quais problemas eu realmente tenho, e qual arquitetura resolve esses problemas sem introduzir complexidade desnecessária para problemas que não tenho\"."},{"id":"arquitetura-software-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Exercício 75.1 — Monolito ou microsserviços?","prompt":"Para cada cenário, decida se monolito ou microsserviços encaixa melhor, justificando: (a) uma startup de 3 desenvolvedores validando um produto novo; (b) uma empresa com 200 desenvolvedores organizados em 15 times de produto diferentes; (c) um sistema de biblioteca de uma faculdade, uso interno, baixo tráfego.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 75.1 — Monolito ou microsserviços?difícil Para cada cenário, decida se monolito ou microsserviços encaixa melhor, justificando: (a) uma startup de 3 desenvolvedores validando um produto novo; (b) uma empresa com 200 desenvolvedores organizados em 15 times de produto diferentes; (c) um sistema de biblioteca de uma faculdade, uso interno, baixo tráfego. Ver solução (a) Monolito — 3 pessoas não têm o problema de coordenação entre times que microsserviços resolvem; velocidade de iteração importa mais que escala nesse estágio. (b) Microsserviços (ou pelo menos um monolito modular bem preparado para split) — 15 times fazendo deploy do mesmo artefato monolítico geraria gargalos de coordenação constantes. (c) Monolito, sem dúvida — baixo tráfego e escopo bem definido não justificam a complexidade operacional extra de serviços distribuídos.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 75.1 — Monolito ou microsserviços?</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Para cada cenário, decida se monolito ou microsserviços encaixa melhor, justificando: (a) uma startup de 3 desenvolvedores validando um produto novo; (b) uma empresa com 200 desenvolvedores organizados em 15 times de produto diferentes; (c) um sistema de biblioteca de uma faculdade, uso interno, baixo tráfego.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>(a) Monolito</strong> — 3 pessoas não têm o problema de coordenação entre times que microsserviços resolvem; velocidade de iteração importa mais que escala nesse estágio. <strong>(b) Microsserviços</strong> (ou pelo menos um monolito modular bem preparado para split) — 15 times fazendo deploy do mesmo artefato monolítico geraria gargalos de coordenação constantes. <strong>(c) Monolito</strong>, sem dúvida — baixo tráfego e escopo bem definido não justificam a complexidade operacional extra de serviços distribuídos.</p>\n        </div>\n      </div>"},{"id":"arquitetura-comparison","type":"comparison","authorship":"authored","title":"Monólito modular antes de microserviços por moda","criteria":["bom quando","risco","pergunta de decisão"],"alternatives":[{"name":"Monólito modular","values":["um deploy, módulos internos claros","fronteiras podem apodrecer sem disciplina","consigo manter dependências internas direcionadas?"],"useWhen":"produto ainda aprende regras e equipe precisa de feedback rápido","avoidWhen":"fronteiras e escalas já exigem independência real"},{"name":"Microserviços","values":["deploy/escala independentes","rede, observabilidade e dados distribuídos","tenho maturidade operacional para falha parcial?"],"useWhen":"domínios e times têm autonomia necessária","avoidWhen":"é só tentativa de organizar código bagunçado"}]},{"id":"arquitetura-quiz","type":"quiz","authorship":"authored","conceptId":"hexagonal-port-adapter","prompt":"Qual evidência mostra arquitetura hexagonal funcionando na prática?","options":[{"id":"arq-q-a","label":"Caso de uso testado com porta fake, sem banco, HTTP ou framework reais.","correct":true,"explanation":"O núcleo depende de portas; adapters externos podem ser substituídos."},{"id":"arq-q-b","label":"Todas as classes estão em pacote chamado hexagonal.","correct":false,"explanation":"Nome de pacote não garante direção de dependência."},{"id":"arq-q-c","label":"Controller chama SQL diretamente para reduzir camadas.","correct":false,"explanation":"Isso atravessa fronteiras e acopla entrada a persistência."}]}],"resources":[{"id":"hexagonal-cockburn","type":"guide","title":"Hexagonal architecture","url":"https://alistair.cockburn.us/hexagonal-architecture/","reinforces":"Origem e intenção de ports and adapters.","language":"en","publisher":"Alistair Cockburn","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"monolith-first-fowler","type":"guide","title":"Monolith First","url":"https://martinfowler.com/bliki/MonolithFirst.html","reinforces":"Trade-off para começar modular antes de distribuir prematuramente.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A architecture software operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a architecture software operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this architecture software chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"com.loja.orders          // Orders module","instruction":"A architecture software operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this architecture software chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"ddd","moduleId":"application-design","order":8,"title":"Domain-Driven Design (DDD)","summary":"DDD é uma abordagem para modelar sistemas complexos a partir da linguagem do próprio negócio, não a partir da estrutura conveniente de tabelas de banco de dados. É a resposta para quando \"CRUD simples\" já não descreve mais a complexidade real do domínio.","objectives":["Usar linguagem ubíqua para aproximar código e negócio","Distinguir Entity, Value Object e DTO/entidade JPA futura","Definir agregado como fronteira de consistência","Reconhecer bounded contexts sem virar microserviços automaticamente"],"whyItExists":"Depois de arquitetura e projetos com dados/integração, DDD entra para melhorar modelagem do domínio. Ele não começa por tabela, controller ou microserviço: começa por linguagem, invariantes e fronteiras de consistência.","prerequisiteChapterIds":["arquitetura-software","encapsulamento","mini-financas-jdbc"],"conceptIds":["ubiquitous-language-o-vocabulario-compartilhado","entity-vs-value-object-uma-distincao-que-vai-alem-da-entity-jpa","aggregate-a-fronteira-de-consistencia","bounded-context-fronteira-de-linguagem-nao-sinonimo-de-microsservico"],"introducedConceptIds":["linguagem-ubiqua","entidade-value-object","agregado-consistencia","bounded-context-fronteira"],"usedConceptIds":["nome-intencao-codigo","encapsulamento-invariante","financas-ledger-invariante","sql-transacao-atomicidade"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"ddd-intuition","type":"intuition","authorship":"authored","title":"DDD começa na linguagem, não no framework","body":"Se pessoas do domínio dizem “lançamento”, “saldo disponível” e “estorno”, o código deve preservar esses termos quando eles carregam regra. DDD tenta impedir que o modelo vire só CRUD genérico.","analogyLimit":"Dicionário ajuda a alinhar palavras, mas DDD também trata invariantes, transações, fronteiras, colaboração e mudança organizacional."},{"id":"ddd-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Engenharia</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Multi-Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h30</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#arquitetura-software\">75 · Arquitetura de Software</a>, <a class=\"prereq-tag\" href=\"#encapsulamento\">04 · Encapsulamento</a></div>\n      </div>","fidelityText":"Engenharia Dificuldade: Multi-Avançado ⏱ ~2h30 de estudo Pré-requisitos: 75 · Arquitetura de Software, 04 · Encapsulamento"},{"id":"ddd-content-2","type":"html","authorship":"legacy-preserved","html":"<p>DDD é uma abordagem para modelar sistemas complexos <strong>a partir da linguagem do próprio negócio</strong>, não a partir da estrutura conveniente de tabelas de banco de dados. É a resposta para quando \"CRUD simples\" já não descreve mais a complexidade real do domínio.</p>","fidelityText":"DDD é uma abordagem para modelar sistemas complexos a partir da linguagem do próprio negócio, não a partir da estrutura conveniente de tabelas de banco de dados. É a resposta para quando \"CRUD simples\" já não descreve mais a complexidade real do domínio."},{"id":"ddd-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Ubiquitous Language — o vocabulário compartilhado</h2>","fidelityText":"Ubiquitous Language — o vocabulário compartilhado"},{"id":"ddd-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Se o time de negócio chama de \"sócio\" o que o código chama de <code>Usuario</code> com um campo <code>tipo=\"premium\"</code>, toda conversa entre eles exige tradução mental constante — e traduções acumulam mal-entendidos. DDD prega que o <strong>código deveria usar exatamente os mesmos termos que as pessoas de negócio usam</strong> em conversa: se o negócio fala em \"Sócio\", o código deveria ter uma classe <code>Socio</code>, não um <code>Usuario</code> genérico disfarçado.</div>","fidelityText":"Se o time de negócio chama de \"sócio\" o que o código chama de Usuario com um campo tipo=\"premium\", toda conversa entre eles exige tradução mental constante — e traduções acumulam mal-entendidos. DDD prega que o código deveria usar exatamente os mesmos termos que as pessoas de negócio usam em conversa: se o negócio fala em \"Sócio\", o código deveria ter uma classe Socio, não um Usuario genérico disfarçado."},{"id":"ddd-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Entity vs Value Object — uma distinção que vai além da @Entity JPA</h2>","fidelityText":"Entity vs Value Object — uma distinção que vai além da @Entity JPA"},{"id":"ddd-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Entity (no sentido DDD)</th><th>Value Object</th></tr>\n        <tr><td>Identidade</td><td>Tem um ID único que persiste ao longo do tempo</td><td>Não tem identidade — dois com os mesmos valores são <strong>iguais</strong></td></tr>\n        <tr><td>Mutabilidade</td><td>Pode mudar de estado ao longo da vida</td><td>Idealmente imutável (capítulo 04!)</td></tr>\n        <tr><td>Exemplo</td><td><code>Pedido</code> (mesmo pedido, mesmo se todos os itens mudarem)</td><td><code>Dinheiro</code>, <code>Endereco</code>, <code>Cpf</code> (exercício 5.2!) — comparados por valor</td></tr>\n      </tbody></table>","fidelityText":"Entity (no sentido DDD)Value Object IdentidadeTem um ID único que persiste ao longo do tempoNão tem identidade — dois com os mesmos valores são iguais MutabilidadePode mudar de estado ao longo da vidaIdealmente imutável (capítulo 04!) ExemploPedido (mesmo pedido, mesmo se todos os itens mudarem)Dinheiro, Endereco, Cpf (exercício 5.2!) — comparados por valor"},{"id":"ddd-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Você já implementou um Value Object perfeito no exercício 4.2 (<code>Dinheiro</code>, imutável) e no exercício 5.2 (<code>Cpf</code>, com <code>equals</code>/<code>hashCode</code> baseados em conteúdo) — sem saber que estava praticando DDD. A diferença entre \"aprender DDD\" e \"já ter praticado os padrões que DDD formaliza\" é justamente ter um nome e uma justificativa teórica para uma prática que já fazia sentido intuitivamente.</div>","fidelityText":"Você já implementou um Value Object perfeito no exercício 4.2 (Dinheiro, imutável) e no exercício 5.2 (Cpf, com equals/hashCode baseados em conteúdo) — sem saber que estava praticando DDD. A diferença entre \"aprender DDD\" e \"já ter praticado os padrões que DDD formaliza\" é justamente ter um nome e uma justificativa teórica para uma prática que já fazia sentido intuitivamente."},{"id":"ddd-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Aggregate — a fronteira de consistência</h2>","fidelityText":"Aggregate — a fronteira de consistência"},{"id":"ddd-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Um <strong>Aggregate</strong> é um grupo de objetos relacionados tratados como uma única unidade para fins de consistência — só o \"objeto raiz\" (Aggregate Root) pode ser referenciado de fora, e toda mudança passa por ele.</p>","fidelityText":"Um Aggregate é um grupo de objetos relacionados tratados como uma única unidade para fins de consistência — só o \"objeto raiz\" (Aggregate Root) pode ser referenciado de fora, e toda mudança passa por ele."},{"id":"ddd-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"public class Order { // Aggregate Root\n    private Long id;\n    private List<ItemOrder> items; // parte do agregado, NUNCA acessado diretamente de fora\n    private StatusOrder status;\n\n    // toda mudança passa por um método do Pedido -- nunca \"pedido.getItens().add(...)\" direto de fora\n    public void addItem(Product product, int quantity) {\n        if (status != StatusOrder.OPEN) {\n            throw new OrderClosedException(); // a regra de negócio VIVE aqui, não espalhada\n        }\n        items.add(new ItemOrder(product, quantity));\n    }\n}","fidelityText":"public class Pedido { // Aggregate Root private Long id; private List<ItemPedido> itens; // parte do agregado, NUNCA acessado diretamente de fora private StatusPedido status; // toda mudança passa por um método do Pedido -- nunca \"pedido.getItens().add(...)\" direto de fora public void adicionarItem(Produto produto, int qtd) { if (status != StatusPedido.ABERTO) { throw new PedidoFechadoException(); // a regra de negócio VIVE aqui, não espalhada } itens.add(new ItemPedido(produto, qtd)); } }","highlightedHtml":"<span class=\"kw\">public class</span> <span class=\"cls\">Order</span> { <span class=\"com\">// Aggregate Root</span>\n    <span class=\"kw\">private</span> <span class=\"kw\">Long</span> id;\n    <span class=\"kw\">private</span> List&lt;<span class=\"cls\">ItemOrder</span>&gt; items; <span class=\"com\">// parte do agregado, NUNCA acessado diretamente de fora</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">StatusOrder</span> status;\n\n    <span class=\"com\">// toda mudança passa por um método do Pedido -- nunca \"pedido.getItens().add(...)\" direto de fora</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">addItem</span>(<span class=\"cls\">Product</span> product, <span class=\"kw\">int</span> quantity) {\n        <span class=\"kw\">if</span> (status != <span class=\"cls\">StatusOrder</span>.OPEN) {\n            <span class=\"kw\">throw new</span> <span class=\"cls\">OrderClosedException</span>(); <span class=\"com\">// a regra de negócio VIVE aqui, não espalhada</span>\n        }\n        items.add(<span class=\"kw\">new</span> <span class=\"cls\">ItemOrder</span>(product, quantity));\n    }\n}","caption":"Exemplo executável de ddd.","explanation":["O exemplo separa identidade de valor e evita tratar tudo como entidade mutável.","Value Objects tornam regras pequenas explícitas e testáveis."],"commonMistakes":["Confundir Entity de DDD com @Entity JPA","Usar DTO externo como domínio"]},{"id":"ddd-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um Aggregate é como o gerente de uma equipe: você nunca dá uma ordem direto para um funcionário específico de outro departamento — passa pelo gerente responsável, que garante que a ordem faz sentido dentro das regras daquele departamento antes de repassar. Isso é exatamente o mesmo espírito do encapsulamento do capítulo 04, aplicado a um <em>grupo</em> de objetos relacionados, não a um único.</div>","fidelityText":"Um Aggregate é como o gerente de uma equipe: você nunca dá uma ordem direto para um funcionário específico de outro departamento — passa pelo gerente responsável, que garante que a ordem faz sentido dentro das regras daquele departamento antes de repassar. Isso é exatamente o mesmo espírito do encapsulamento do capítulo 04, aplicado a um grupo de objetos relacionados, não a um único."},{"id":"ddd-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Bounded Context — fronteira de linguagem, não sinônimo de microsserviço</h2>","fidelityText":"Bounded Context — fronteira de linguagem, não sinônimo de microsserviço"},{"id":"ddd-content-13","type":"html","authorship":"legacy-preserved","html":"<p>Em domínios grandes, a mesma palavra pode significar coisas diferentes em partes diferentes do sistema — um \"Cliente\" no contexto de Vendas tem atributos diferentes de um \"Cliente\" no contexto de Suporte. Um <strong>Bounded Context</strong> é a fronteira explícita onde um modelo e sua linguagem fazem sentido. Essa fronteira é semântica primeiro — ela pode existir dentro do mesmo monólito modular do capítulo anterior, sem virar automaticamente um deploy separado.</p>","fidelityText":"Em domínios grandes, a mesma palavra pode significar coisas diferentes em partes diferentes do sistema — um \"Cliente\" no contexto de Vendas tem atributos diferentes de um \"Cliente\" no contexto de Suporte. Um Bounded Context é a fronteira explícita onde um modelo e sua linguagem fazem sentido. Essa fronteira é semântica primeiro — ela pode existir dentro do mesmo monólito modular do capítulo anterior, sem virar automaticamente um deploy separado."},{"id":"ddd-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"// Bounded Context \"Sales\" -- Customer = whoever buys\npackage com.loja.sales;\npublic class Customer {\n    private String name;\n    private HistoryPurchases history;\n}\n\n// Bounded Context \"Support\" -- same name, DIFFERENT model, with different concerns\npackage com.loja.support;\npublic class Customer {\n    private String name;\n    private List<Ticket> tickets;\n}\n// nothing here requires two microservices -- both \"Customer\" classes coexist in the same\n// modular monolith, as long as no module imports the other one's class directly","fidelityText":"// Bounded Context \"Vendas\" -- Cliente = quem compra package com.loja.vendas; public class Cliente { private String nome; private HistoricoCompras historico; } // Bounded Context \"Suporte\" -- mesmo nome, modelo DIFERENTE, com preocupações diferentes package com.loja.suporte; public class Cliente { private String nome; private List<Chamado> chamados; } // nada aqui exige dois microsserviços -- os dois \"Cliente\" convivem no mesmo // monólito modular, desde que nenhum módulo importe a classe do outro direto","highlightedHtml":"<span class=\"com\">// Bounded Context \"Sales\" -- Customer = whoever buys</span>\n<span class=\"kw\">package</span> com.loja.sales;\n<span class=\"kw\">public class</span> <span class=\"cls\">Customer</span> {\n    <span class=\"kw\">private</span> <span class=\"kw\">String</span> name;\n    <span class=\"kw\">private</span> <span class=\"cls\">HistoryPurchases</span> history;\n}\n\n<span class=\"com\">// Bounded Context \"Support\" -- same name, DIFFERENT model, with different concerns</span>\n<span class=\"kw\">package</span> com.loja.support;\n<span class=\"kw\">public class</span> <span class=\"cls\">Customer</span> {\n    <span class=\"kw\">private</span> <span class=\"kw\">String</span> name;\n    <span class=\"kw\">private</span> List&lt;<span class=\"cls\">Ticket</span>&gt; tickets;\n}\n<span class=\"com\">// nothing here requires two microservices -- both \"Customer\" classes coexist in the same\n// modular monolith, as long as no module imports the other one's class directly</span>","caption":"Exemplo executável de ddd.","explanation":["Os dois `Cliente` são modelos legitimamente diferentes -- o mesmo nome carrega atributos e regras diferentes em cada contexto.","Nenhum dos dois módulos importa a classe do outro; a fronteira semântica não exige deploy separado."],"commonMistakes":["Forçar uma única classe Cliente compartilhada entre contextos diferentes","Achar que Bounded Context é só um pacote Java sem consequência de modelagem"]},{"id":"ddd-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Bounded Context não é pacote Java nem microsserviço automático:</b> o que define a fronteira é a linguagem e a consistência do modelo, não a topologia de deploy. Forçar um único <code>Cliente</code> para Vendas e Suporte apaga a diferença real entre os dois; extrair um microsserviço por contexto antes de precisar de escala ou times independentes só importa a complexidade do capítulo anterior sem o benefício que a justificaria.</div>","fidelityText":"Bounded Context não é pacote Java nem microsserviço automático: o que define a fronteira é a linguagem e a consistência do modelo, não a topologia de deploy. Forçar um único Cliente para Vendas e Suporte apaga a diferença real entre os dois; extrair um microsserviço por contexto antes de precisar de escala ou times independentes só importa a complexidade do capítulo anterior sem o benefício que a justificaria."},{"id":"ddd-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">DDD tem um vocabulário técnico denso (Aggregate, Bounded Context, Ubiquitous Language, Anti-Corruption Layer) que intimida à primeira leitura. A forma mais produtiva de aprender é sempre de trás para frente: pegue um problema real do seu domínio, modele com classes ricas em comportamento (não anêmicas, só com getters/setters) e regras de negócio protegidas dentro delas — e só depois mapeie o que você fez para o vocabulário formal de DDD.</div>","fidelityText":"DDD tem um vocabulário técnico denso (Aggregate, Bounded Context, Ubiquitous Language, Anti-Corruption Layer) que intimida à primeira leitura. A forma mais produtiva de aprender é sempre de trás para frente: pegue um problema real do seu domínio, modele com classes ricas em comportamento (não anêmicas, só com getters/setters) e regras de negócio protegidas dentro delas — e só depois mapeie o que você fez para o vocabulário formal de DDD."},{"id":"ddd-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>DDD não é obrigatório em todo projeto.</b> Para um CRUD simples sem regras de negócio complexas, aplicar todo o aparato de DDD é over-engineering puro — a mesma lição do capítulo anterior sobre arquitetura hexagonal. DDD compensa quando o domínio em si é genuinamente complexo, com muitas regras de negócio interconectadas, não quando a complexidade é só técnica (muitos serviços, muita infraestrutura).</div>","fidelityText":"DDD não é obrigatório em todo projeto. Para um CRUD simples sem regras de negócio complexas, aplicar todo o aparato de DDD é over-engineering puro — a mesma lição do capítulo anterior sobre arquitetura hexagonal. DDD compensa quando o domínio em si é genuinamente complexo, com muitas regras de negócio interconectadas, não quando a complexidade é só técnica (muitos serviços, muita infraestrutura)."},{"id":"ddd-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Exercício 76.1 — Identificando Entity vs Value Object","prompt":"Para o sistema de biblioteca, classifique cada conceito como Entity ou Value Object, justificando: Livro, Isbn, Emprestimo, PeriodoEmprestimo (data início + data fim).","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 76.1 — Identificando Entity vs Value Objectmédio Para o sistema de biblioteca, classifique cada conceito como Entity ou Value Object, justificando: Livro, Isbn, Emprestimo, PeriodoEmprestimo (data início + data fim). Ver solução Livro: Entity — tem identidade própria que persiste (o mesmo exemplar físico, mesmo que o título mude numa correção de cadastro). Isbn: Value Object — dois ISBNs com o mesmo número são o mesmo ISBN, sem identidade própria além do valor. Emprestimo: Entity — cada empréstimo é um evento distinto, com identidade própria ao longo do tempo (mesmo que o livro seja devolvido e emprestado de novo, é um novo empréstimo). PeriodoEmprestimo: Value Object — dois períodos com as mesmas datas são iguais, sem identidade própria, e deveria ser imutável (capítulo 04).","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 76.1 — Identificando Entity vs Value Object</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Para o sistema de biblioteca, classifique cada conceito como Entity ou Value Object, justificando: <code>Livro</code>, <code>Isbn</code>, <code>Emprestimo</code>, <code>PeriodoEmprestimo</code> (data início + data fim).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong><code>Livro</code>: Entity</strong> — tem identidade própria que persiste (o mesmo exemplar físico, mesmo que o título mude numa correção de cadastro). <strong><code>Isbn</code>: Value Object</strong> — dois ISBNs com o mesmo número são o mesmo ISBN, sem identidade própria além do valor. <strong><code>Emprestimo</code>: Entity</strong> — cada empréstimo é um evento distinto, com identidade própria ao longo do tempo (mesmo que o livro seja devolvido e emprestado de novo, é um <em>novo</em> empréstimo). <strong><code>PeriodoEmprestimo</code>: Value Object</strong> — dois períodos com as mesmas datas são iguais, sem identidade própria, e deveria ser imutável (capítulo 04).</p>\n        </div>\n      </div>"},{"id":"ddd-table","type":"table","authorship":"authored","title":"Modelo de domínio não é modelo de transporte","headers":["Elemento","Identidade","Exemplo"],"rows":[["Entity","continua a mesma ao mudar atributos","Conta, Pedido"],["Value Object","igualdade pelo valor","Dinheiro, Periodo"],["DTO","contrato de fronteira","CriarPedidoRequest"],["Aggregate","fronteira de consistência","Pedido com itens e regras de fechamento"]]},{"id":"ddd-quiz","type":"quiz","authorship":"authored","conceptId":"agregado-consistencia","prompt":"O que define melhor um agregado?","options":[{"id":"ddd-q-a","label":"Uma fronteira onde invariantes precisam ser mantidas de forma consistente numa unidade.","correct":true,"explanation":"Agregado protege regras que devem ser confirmadas juntas."},{"id":"ddd-q-b","label":"Qualquer tabela que tem chave primária.","correct":false,"explanation":"Tabela é persistência; agregado é modelo de consistência do domínio."},{"id":"ddd-q-c","label":"Um pacote Java com classes parecidas.","correct":false,"explanation":"Pacote pode organizar código, mas não define invariantes nem transação."}]}],"resources":[{"id":"fowler-ddd-aggregate","type":"guide","title":"DDD Aggregate","url":"https://martinfowler.com/bliki/DDD_Aggregate.html","reinforces":"Fronteira de consistência e papel do aggregate root.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"microsoft-ddd-microservices","type":"guide","title":"Tactical DDD patterns","url":"https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/","reinforces":"Entity, Value Object, aggregate e bounded context com exemplos arquiteturais.","language":"en","publisher":"Microsoft","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A ddd operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a ddd operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this ddd chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"public class Order { // Aggregate Root","instruction":"A ddd operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this ddd chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"ddd-estrategico","moduleId":"synchronous-integration","order":1,"title":"DDD estratégico, context mapping e integração entre domínios","summary":"DDD tático organiza entidades e agregados; DDD estratégico decide onde um modelo é válido, como times se relacionam e quais integrações merecem proteção. Um bounded context é uma fronteira semântica e de consistência, não sinônimo automático de microsserviço.","objectives":["Diferenciar modelo interno de contrato de integração","Desenhar bounded contexts e context map","Usar anti-corruption layer quando o contrato externo ameaça o domínio","Limitar transações ao agregado/contexto correto"],"whyItExists":"DDD tático já mostrou entidades, agregados e linguagem. Agora a pergunta é entre sistemas: quem é dono do modelo, como contextos se relacionam e que tradução protege o domínio de contratos externos.","prerequisiteChapterIds":["ddd","arquitetura-software","dto-mapping"],"conceptIds":["primeiro-modelo-linguagem-e-fronteira","context-map","agregados-e-transacoes","servicos-fabricas-e-repositorios"],"introducedConceptIds":["bounded-context-context-map","anti-corruption-integration-contract","aggregate-transaction-boundary"],"usedConceptIds":["abstracao-modelagem","dto-entity-boundary-spring","encapsulamento-invariante","transaction-proxy-boundary"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"ddd-estrategico-intuition","type":"intuition","authorship":"authored","title":"Contexto é fronteira de significado e ownership","body":"A mesma palavra pode significar coisas diferentes para vendas, financeiro e suporte. DDD estratégico existe para mapear essas fronteiras antes de integrar APIs e bancos como se todo mundo falasse a mesma língua.","analogyLimit":"Mapa ajuda, mas contexto não é só desenho: ele define equipe dona, linguagem, contrato e mudança."},{"id":"ddd-estrategico-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-devops\">Produção</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~5h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#ddd\">DDD</a>, <a class=\"prereq-tag\" href=\"#arquitetura-software\">Arquitetura</a></div></div>","fidelityText":"ProduçãoDificuldade: Avançado⏱ ~5h de estudo e práticaPré-requisitos: DDD, Arquitetura"},{"id":"ddd-estrategico-content-2","type":"html","authorship":"legacy-preserved","html":"<p>DDD tático organiza entidades e agregados; DDD estratégico decide onde um modelo é válido, como times se relacionam e quais integrações merecem proteção. Um bounded context é uma fronteira semântica e de consistência, não sinônimo automático de microsserviço.</p>","fidelityText":"DDD tático organiza entidades e agregados; DDD estratégico decide onde um modelo é válido, como times se relacionam e quais integrações merecem proteção. Um bounded context é uma fronteira semântica e de consistência, não sinônimo automático de microsserviço."},{"id":"ddd-estrategico-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Primeiro: modelo, linguagem e fronteira</h2></div>\n    <p>DDD é uma forma de tornar conhecimento de negócio explícito no modelo e na colaboração, não uma coleção obrigatória de classes.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>DDD</dt><dd><em>Domain-Driven Design</em>: abordagem para software complexo guiada pelo conhecimento e pela linguagem do domínio.</dd></div><div class=\"concept-card\"><dt>Linguagem ubíqua</dt><dd>Vocabulário compartilhado por especialistas e equipe, usado em conversa, código e testes dentro de um contexto.</dd></div><div class=\"concept-card\"><dt>Bounded context</dt><dd>Fronteira dentro da qual termos e regras de um modelo têm significado consistente.</dd></div><div class=\"concept-card\"><dt>Upstream</dt><dd>Contexto fornecedor que influencia um contrato ou dado consumido por outro.</dd></div><div class=\"concept-card\"><dt>Downstream</dt><dd>Contexto consumidor afetado pelas decisões do fornecedor.</dd></div><div class=\"concept-card\"><dt>Context map</dt><dd>Mapa das fronteiras e das relações técnicas, organizacionais e de poder entre contextos.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoPrimeiro: modelo, linguagem e fronteira DDD é uma forma de tornar conhecimento de negócio explícito no modelo e na colaboração, não uma coleção obrigatória de classes. DDDDomain-Driven Design: abordagem para software complexo guiada pelo conhecimento e pela linguagem do domínio.Linguagem ubíquaVocabulário compartilhado por especialistas e equipe, usado em conversa, código e testes dentro de um contexto.Bounded contextFronteira dentro da qual termos e regras de um modelo têm significado consistente.UpstreamContexto fornecedor que influencia um contrato ou dado consumido por outro.DownstreamContexto consumidor afetado pelas decisões do fornecedor.Context mapMapa das fronteiras e das relações técnicas, organizacionais e de poder entre contextos."},{"id":"ddd-estrategico-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Context map</h2>","fidelityText":"Context map"},{"id":"ddd-estrategico-content-5","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Relação</th><th>Quando ocorre</th></tr><tr><td>Customer/Supplier — cliente/fornecedor</td><td>downstream negocia necessidades com upstream</td></tr><tr><td>Conformist — conformista</td><td>downstream adota o modelo externo conscientemente</td></tr><tr><td>Anti-Corruption Layer (ACL)</td><td>camada traduz o modelo externo e impede que ele contamine o domínio interno</td></tr><tr><td>Published Language</td><td>linguagem/contrato de integração estável e documentado</td></tr><tr><td>Open Host Service</td><td>serviço com protocolo público atende múltiplos consumidores</td></tr></tbody></table>","fidelityText":"RelaçãoQuando ocorreCustomer/Supplier — cliente/fornecedordownstream negocia necessidades com upstreamConformist — conformistadownstream adota o modelo externo conscientementeAnti-Corruption Layer (ACL)camada traduz o modelo externo e impede que ele contamine o domínio internoPublished Languagelinguagem/contrato de integração estável e documentadoOpen Host Serviceserviço com protocolo público atende múltiplos consumidores"},{"id":"ddd-estrategico-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Agregados e transações</h2>","fidelityText":"Agregados e transações"},{"id":"ddd-estrategico-content-7","type":"html","authorship":"legacy-preserved","html":"<p>Um agregado protege invariantes que precisam ser consistentes imediatamente e é alterado pela raiz. Referencie outro agregado por identidade; coordene mudanças entre agregados com caso de uso, evento e consistência explicitamente escolhida. Agregados enormes geram contenção e carregamento excessivo.</p>","fidelityText":"Um agregado protege invariantes que precisam ser consistentes imediatamente e é alterado pela raiz. Referencie outro agregado por identidade; coordene mudanças entre agregados com caso de uso, evento e consistência explicitamente escolhida. Agregados enormes geram contenção e carregamento excessivo."},{"id":"ddd-estrategico-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Serviços, fábricas e repositórios</h2>","fidelityText":"Serviços, fábricas e repositórios"},{"id":"ddd-estrategico-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Comportamento que não pertence naturalmente a uma entidade pode viver em um domain service, desde que continue expressando linguagem do negócio. Fábricas encapsulam criação complexa e repositórios oferecem a ilusão de coleção de raízes de agregado. Application services orquestram entrada, transação e portas externas; não concentram regras do domínio.</p>","fidelityText":"Comportamento que não pertence naturalmente a uma entidade pode viver em um domain service, desde que continue expressando linguagem do negócio. Fábricas encapsulam criação complexa e repositórios oferecem a ilusão de coleção de raízes de agregado. Application services orquestram entrada, transação e portas externas; não concentram regras do domínio."},{"id":"ddd-estrategico-exercise-10","type":"exercise","authorship":"legacy-preserved","title":"Workshop — event storming e mapa","prompt":"Event storming é uma dinâmica visual que parte de eventos de domínio para descobrir comandos, políticas, dúvidas e fronteiras. Mapeie Pedido, Estoque, Pagamento e Entrega: comandos, eventos, políticas, invariantes, contextos, donos e relações. Escolha onde uma ACL é necessária e registre a decisão em um ADR, documento curto de decisão arquitetural com contexto, escolha e consequências.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Workshop — event storming e mapadifícilEvent storming é uma dinâmica visual que parte de eventos de domínio para descobrir comandos, políticas, dúvidas e fronteiras. Mapeie Pedido, Estoque, Pagamento e Entrega: comandos, eventos, políticas, invariantes, contextos, donos e relações. Escolha onde uma ACL é necessária e registre a decisão em um ADR, documento curto de decisão arquitetural com contexto, escolha e consequências.Ver critériosNão comece por tabelas ou serviços. O mapa deve revelar linguagens diferentes, limite transacional e responsabilidade de cada contrato.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Workshop — event storming e mapa</h2><span class=\"exercise-tag d\">difícil</span></div><p><strong>Event storming</strong> é uma dinâmica visual que parte de eventos de domínio para descobrir comandos, políticas, dúvidas e fronteiras. Mapeie Pedido, Estoque, Pagamento e Entrega: comandos, eventos, políticas, invariantes, contextos, donos e relações. Escolha onde uma ACL é necessária e registre a decisão em um <strong>ADR</strong>, documento curto de decisão arquitetural com contexto, escolha e consequências.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>Não comece por tabelas ou serviços. O mapa deve revelar linguagens diferentes, limite transacional e responsabilidade de cada contrato.</p></div></div>"},{"id":"ddd-estrategico-quiz","type":"quiz","authorship":"authored","conceptId":"anti-corruption-integration-contract","prompt":"Quando uma anti-corruption layer é útil?","options":[{"id":"ddd-a","label":"Quando o contrato externo não deve vazar diretamente para o modelo interno.","correct":true,"explanation":"A camada traduz linguagem e protege invariantes do domínio."},{"id":"ddd-b","label":"Quando dois serviços devem compartilhar a mesma Entity JPA.","correct":false,"explanation":"Isso aumenta acoplamento e mistura ownership."},{"id":"ddd-c","label":"Quando todo contexto deve usar a mesma palavra com o mesmo significado.","correct":false,"explanation":"Bounded contexts aceitam significados diferentes por fronteira."}]}],"resources":[{"id":"fowler-bounded-context","type":"reference","title":"Martin Fowler: Bounded Context","url":"https://martinfowler.com/bliki/BoundedContext.html","reinforces":"Fronteira linguística e modelo contextual em DDD.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"ddd-reference-context-mapping","type":"reference","title":"Context Mapping pattern summary","url":"https://www.domainlanguage.com/ddd/reference/","reinforces":"Vocabulário de DDD estratégico e padrões de relacionamento entre contextos.","language":"en","publisher":"Domain Language","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The ddd strategic component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to ddd strategic. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible ddd strategic failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"// Observe how names reveal the ddd strategic contract.","instruction":"The ddd strategic component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible ddd strategic failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"sistemas-distribuidos","moduleId":"distributed-consistency","order":0,"title":"Sistemas Distribuídos: CAP & trade-offs de arquitetura sênior","summary":"Este é o capítulo que justifica a existência do nível \"Além do Impossível\". Não existe resposta certa aqui — só trade-offs, e reconhecer quais trade-offs um sistema real está fazendo é o tipo de julgamento que distingue um arquiteto sênior experiente.","objectives":["Explicar por que falha parcial muda o desenho do sistema","Usar CAP somente no contexto correto de partição","Escolher CP/AP por operação e impacto de negócio","Introduzir consistência eventual e Saga sem prometer rollback global"],"whyItExists":"Depois de mensageria, resiliência, banco e deploy, o aluno já viu os ingredientes que tornam um sistema distribuído real: rede, processos, dados independentes e falha parcial. Este capítulo organiza o vocabulário antes de discutir PACELC, outbox e saga.","prerequisiteChapterIds":["arquitetura-software","mensageria","hospedagem-db","resilience"],"conceptIds":["o-teorema-cap","consistencia-eventual-a-resposta-pratica-mais-comum","padrao-saga-transacoes-atraves-de-multiplos-servicos"],"introducedConceptIds":["distributed-system-failure-boundary","cap-partition-tradeoff","consistency-availability-scope","eventual-consistency-reconciliation"],"usedConceptIds":["partial-failure-unknown-state","temporal-coupling-sync","broker-backlog-backpressure","managed-database-operations"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"sistemas-distribuidos-intuition","type":"intuition","authorship":"authored","title":"Distribuído significa que a verdade atravessa tempo, rede e donos diferentes","body":"Em uma aplicação local, chamada, dado e transação parecem estar no mesmo lugar. Em um sistema distribuído, cada serviço pode estar vivo, lento, particionado ou com uma versão diferente do estado. O desenho bom não tenta esconder isso: ele declara qual dado precisa ser forte, qual pode atrasar e como reparar divergência.","analogyLimit":"Comparar com filiais de uma loja ajuda, mas software precisa de IDs, logs, relógios, retries e provas de recuperação."},{"id":"sistemas-distribuidos-cap-table","type":"table","authorship":"authored","title":"CAP sem caricatura","headers":["Situação","Decisão real","Erro comum"],"rows":[["Não há partição observável","latência, custo e consistência ainda importam","usar CAP para explicar qualquer lentidão"],["Há partição e dado crítico","preferir recusar/esperar em vez de responder falso","dizer que disponibilidade nunca importa"],["Há partição e leitura tolera atraso","responder com versão possivelmente antiga e reconciliar","chamar dado atrasado de bug automaticamente"]]},{"id":"sistemas-distribuidos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-security\">Além do Impossível</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Além do Impossível</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo — releia quantas vezes precisar</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#arquitetura-software\">75 · Arquitetura de Software</a>, <a class=\"prereq-tag\" href=\"#mensageria\">40 · Mensageria</a>, <a class=\"prereq-tag\" href=\"#hospedagem-db\">39 · Onde hospedar o banco</a>, <a class=\"prereq-tag\" href=\"#resilience\">55 · Resilience4j</a></div>\n      </div>","fidelityText":"Além do Impossível Dificuldade: Além do Impossível ⏱ ~3h de estudo — releia quantas vezes precisar Pré-requisitos: 75 · Arquitetura de Software, 40 · Mensageria, 39 · Onde hospedar o banco, 55 · Resilience4j"},{"id":"sistemas-distribuidos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Este é o capítulo que justifica a existência do nível \"Além do Impossível\". Não existe resposta certa aqui — só trade-offs, e reconhecer <em>quais</em> trade-offs um sistema real está fazendo é o tipo de julgamento que distingue um arquiteto sênior experiente.</p>","fidelityText":"Este é o capítulo que justifica a existência do nível \"Além do Impossível\". Não existe resposta certa aqui — só trade-offs, e reconhecer quais trade-offs um sistema real está fazendo é o tipo de julgamento que distingue um arquiteto sênior experiente."},{"id":"sistemas-distribuidos-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>O Teorema CAP</h2>","fidelityText":"O Teorema CAP"},{"id":"sistemas-distribuidos-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Imagine três amigos tentando manter cópias idênticas de uma lista de compras em cadernos separados, se comunicando por bilhetes que às vezes se perdem no correio (a rede real nunca é 100% confiável). O teorema CAP diz: você não pode garantir simultaneamente que (C) todos os cadernos sempre mostrem exatamente a mesma lista, (A) qualquer amigo sempre possa ler/escrever no próprio caderno sem esperar confirmação dos outros, <strong>e</strong> (P) o sistema continue funcionando mesmo se um bilhete se perder no meio do caminho. Na prática, bilhetes <em>vão</em> se perder de vez em quando — então a escolha real é sempre entre C e A quando isso acontece.</div>","fidelityText":"Imagine três amigos tentando manter cópias idênticas de uma lista de compras em cadernos separados, se comunicando por bilhetes que às vezes se perdem no correio (a rede real nunca é 100% confiável). O teorema CAP diz: você não pode garantir simultaneamente que (C) todos os cadernos sempre mostrem exatamente a mesma lista, (A) qualquer amigo sempre possa ler/escrever no próprio caderno sem esperar confirmação dos outros, e (P) o sistema continue funcionando mesmo se um bilhete se perder no meio do caminho. Na prática, bilhetes vão se perder de vez em quando — então a escolha real é sempre entre C e A quando isso acontece."},{"id":"sistemas-distribuidos-content-5","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Letra</th><th>Significa</th></tr>\n        <tr><td><strong>C</strong> — Consistency</td><td>Toda leitura recebe a escrita mais recente (ou um erro), nunca dado desatualizado</td></tr>\n        <tr><td><strong>A</strong> — Availability</td><td>Todo pedido recebe uma resposta (não uma garantia de que seja o dado mais recente)</td></tr>\n        <tr><td><strong>P</strong> — Partition Tolerance</td><td>O sistema continua funcionando mesmo que a rede entre nós falhe parcialmente</td></tr>\n      </tbody></table>","fidelityText":"LetraSignifica C — ConsistencyToda leitura recebe a escrita mais recente (ou um erro), nunca dado desatualizado A — AvailabilityTodo pedido recebe uma resposta (não uma garantia de que seja o dado mais recente) P — Partition ToleranceO sistema continua funcionando mesmo que a rede entre nós falhe parcialmente"},{"id":"sistemas-distribuidos-content-6","type":"html","authorship":"legacy-preserved","html":"<p>Como falhas de rede (P) são uma realidade inevitável em qualquer sistema distribuído de verdade, a escolha prática do dia a dia é entre <strong>CP</strong> (prefere recusar responder a responder com dado desatualizado) e <strong>AP</strong> (prefere responder sempre, mesmo que com dado potencialmente desatualizado).</p>","fidelityText":"Como falhas de rede (P) são uma realidade inevitável em qualquer sistema distribuído de verdade, a escolha prática do dia a dia é entre CP (prefere recusar responder a responder com dado desatualizado) e AP (prefere responder sempre, mesmo que com dado potencialmente desatualizado)."},{"id":"sistemas-distribuidos-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>Não classifique componentes isolados por analogia.</b> Um PostgreSQL executando em um único nó não é CP ou AP por causa de suas transações; CAP exige replicação e uma partição de rede. Cache com TTL descreve frescor e Kafka at-least-once descreve entrega — eixos diferentes de CAP. Para classificar um sistema, identifique nós, réplicas, operação analisada e comportamento observável durante a partição.</div>","fidelityText":"Não classifique componentes isolados por analogia. Um PostgreSQL executando em um único nó não é CP ou AP por causa de suas transações; CAP exige replicação e uma partição de rede. Cache com TTL descreve frescor e Kafka at-least-once descreve entrega — eixos diferentes de CAP. Para classificar um sistema, identifique nós, réplicas, operação analisada e comportamento observável durante a partição."},{"id":"sistemas-distribuidos-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Consistência eventual — a resposta prática mais comum</h2>","fidelityText":"Consistência eventual — a resposta prática mais comum"},{"id":"sistemas-distribuidos-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Na prática, a maioria dos sistemas reais não escolhe CP ou AP de forma absoluta e única — escolhe <strong>por parte do sistema</strong>. Pagamento e estoque (capítulo 33, transações) tendem a exigir consistência forte. Contadores de curtidas, notificações, feeds de atividade toleram <strong>consistência eventual</strong> — o sistema garante que, dado tempo suficiente sem novas escritas, todas as réplicas convergem para o mesmo estado, mas não instantaneamente.</p>","fidelityText":"Na prática, a maioria dos sistemas reais não escolhe CP ou AP de forma absoluta e única — escolhe por parte do sistema. Pagamento e estoque (capítulo 33, transações) tendem a exigir consistência forte. Contadores de curtidas, notificações, feeds de atividade toleram consistência eventual — o sistema garante que, dado tempo suficiente sem novas escritas, todas as réplicas convergem para o mesmo estado, mas não instantaneamente."},{"id":"sistemas-distribuidos-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Padrão Saga — transações através de múltiplos serviços</h2>","fidelityText":"Padrão Saga — transações através de múltiplos serviços"},{"id":"sistemas-distribuidos-content-11","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 33 mostrou uma transação SQL garantindo atomicidade <em>dentro de um único banco</em>. Mas e quando uma operação de negócio precisa coordenar múltiplos microsserviços (capítulo 75), cada um com seu próprio banco? Não existe <code>BEGIN`/`COMMIT</code> distribuído simples e eficiente em escala — o padrão <strong>Saga</strong> resolve isso com uma sequência de transações locais, cada uma publicando um evento (capítulo 40) que dispara a próxima, com <strong>compensações</strong> explícitas para desfazer passos já feitos se algo falhar no meio do caminho.</p>","fidelityText":"O capítulo 33 mostrou uma transação SQL garantindo atomicidade dentro de um único banco. Mas e quando uma operação de negócio precisa coordenar múltiplos microsserviços (capítulo 75), cada um com seu próprio banco? Não existe BEGIN`/`COMMIT distribuído simples e eficiente em escala — o padrão Saga resolve isso com uma sequência de transações locais, cada uma publicando um evento (capítulo 40) que dispara a próxima, com compensações explícitas para desfazer passos já feitos se algo falhar no meio do caminho."},{"id":"sistemas-distribuidos-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"1. Service de Order: cria order (status PENDING) -- publica \"PedidoCriado\"\n2. Service de Inventory: reservation o item -- publica \"EstoqueReservado\"\n   (se fail: public \"InventoryUnavailable\" -> Order e CANCELADO, sem next step)\n3. Service de Payment: snake o customer -- publica \"PagamentoAprovado\"\n   (se fail: public \"PaymentDeclined\" -> COMPENSATION: Inventory e released de volta)\n4. Service de Order: marks order as CONFIRMED","fidelityText":"1. Serviço de Pedido: cria pedido (status PENDENTE) -- publica \"PedidoCriado\" 2. Serviço de Estoque: reserva o item -- publica \"EstoqueReservado\" (se falhar: publica \"EstoqueIndisponivel\" -> Pedido é CANCELADO, sem próximo passo) 3. Serviço de Pagamento: cobra o cliente -- publica \"PagamentoAprovado\" (se falhar: publica \"PagamentoRecusado\" -> COMPENSAÇÃO: Estoque é liberado de volta) 4. Serviço de Pedido: marca pedido como CONFIRMADO","highlightedHtml":"<span class=\"com\">1. Serviço de Pedido: cria pedido (status PENDENTE) -- publica \"PedidoCriado\"\n2. Serviço de Estoque: reserva o item -- publica \"EstoqueReservado\"\n   (se falhar: publica \"EstoqueIndisponivel\" -&gt; Pedido é CANCELADO, sem próximo passo)\n3. Serviço de Pagamento: cobra o cliente -- publica \"PagamentoAprovado\"\n   (se falhar: publica \"PagamentoRecusado\" -&gt; COMPENSAÇÃO: Estoque é liberado de volta)\n4. Serviço de Pedido: marca pedido como CONFIRMADO</span>","caption":"Exemplo executável de sistemas-distribuidos.","explanation":["O fluxo descreve uma Saga: cada serviço confirma sua transação local e publica o próximo fato ou comando.","Quando uma etapa falha, a correção é uma ação compensatória de domínio, não rollback ACID entre bancos diferentes."],"commonMistakes":["Misturar evento passado com comando futuro","Achar que compensar estoque é apagar histórico","Não persistir estado intermediário do pedido"]},{"id":"sistemas-distribuidos-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Não existe \"a arquitetura certa\" aqui, só trade-offs conscientes.</b> Sagas trocam a simplicidade de uma transação ACID local (capítulo 33) por escalabilidade e desacoplamento entre serviços — em troca, você assume a complexidade de escrever lógica de compensação explícita para cada passo que pode falhar, algo que uma transação de banco único resolve de graça com <code>ROLLBACK</code>.</div>","fidelityText":"Não existe \"a arquitetura certa\" aqui, só trade-offs conscientes. Sagas trocam a simplicidade de uma transação ACID local (capítulo 33) por escalabilidade e desacoplamento entre serviços — em troca, você assume a complexidade de escrever lógica de compensação explícita para cada passo que pode falhar, algo que uma transação de banco único resolve de graça com ROLLBACK."},{"id":"sistemas-distribuidos-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Você não precisa \"resolver\" este capítulo como resolveu os exercícios anteriores do curso — o objetivo aqui é reconhecimento, não domínio operacional. Se, ao ler sobre um sistema real em produção (uma notícia técnica, um post de engenharia de alguma empresa), você conseguir identificar \"ah, isso é uma escolha AP\" ou \"isso parece um padrão Saga\", o capítulo cumpriu seu papel. A maestria operacional vem só com anos de experiência real em sistemas de escala — este capítulo é o mapa, não o território.</div>","fidelityText":"Você não precisa \"resolver\" este capítulo como resolveu os exercícios anteriores do curso — o objetivo aqui é reconhecimento, não domínio operacional. Se, ao ler sobre um sistema real em produção (uma notícia técnica, um post de engenharia de alguma empresa), você conseguir identificar \"ah, isso é uma escolha AP\" ou \"isso parece um padrão Saga\", o capítulo cumpriu seu papel. A maestria operacional vem só com anos de experiência real em sistemas de escala — este capítulo é o mapa, não o território."},{"id":"sistemas-distribuidos-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 84.1 — Classificando escolhas CAP","prompt":"Para cada cenário, primeiro proponha uma arquitetura replicada e uma partição de rede concreta. Então discuta o comportamento desejado: (a) confirmar a disponibilidade antes do empréstimo; (b) exibir contador histórico; (c) entregar notificação de reserva. Separe CAP de frescor do cache e semântica de entrega.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 84.1 — Classificando escolhas CAPdifícil Para cada cenário, primeiro proponha uma arquitetura replicada e uma partição de rede concreta. Então discuta o comportamento desejado: (a) confirmar a disponibilidade antes do empréstimo; (b) exibir contador histórico; (c) entregar notificação de reserva. Separe CAP de frescor do cache e semântica de entrega. Ver solução (a) em um registro de empréstimos com líderes separados por partição, prefira impedir confirmações conflitantes no lado sem autoridade; isso favorece consistência durante a partição. (b) uma réplica pode continuar respondendo com um contador antigo se o contrato aceitar eventual consistency; isso favorece disponibilidade. (c) a fila pode persistir localmente e reconciliar depois, mas at-least-once e atraso devem ser analisados separadamente de CAP. Em todos os casos, declare o que é resposta válida e onde estão as réplicas.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 84.1 — Classificando escolhas CAP</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Para cada cenário, primeiro proponha uma arquitetura replicada e uma partição de rede concreta. Então discuta o comportamento desejado: (a) confirmar a disponibilidade antes do empréstimo; (b) exibir contador histórico; (c) entregar notificação de reserva. Separe CAP de frescor do cache e semântica de entrega.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p><strong>(a)</strong> em um registro de empréstimos com líderes separados por partição, prefira impedir confirmações conflitantes no lado sem autoridade; isso favorece consistência durante a partição. <strong>(b)</strong> uma réplica pode continuar respondendo com um contador antigo se o contrato aceitar eventual consistency; isso favorece disponibilidade. <strong>(c)</strong> a fila pode persistir localmente e reconciliar depois, mas at-least-once e atraso devem ser analisados separadamente de CAP. Em todos os casos, declare o que é resposta válida e onde estão as réplicas.</p>\n        </div>\n      </div>"},{"id":"sistemas-distribuidos-quiz","type":"quiz","authorship":"authored","conceptId":"cap-partition-tradeoff","prompt":"Qual leitura de CAP é correta para um sistema distribuído real?","options":[{"id":"sd-a","label":"Quando há partição, uma parte do sistema precisa escolher entre preservar consistência ou continuar disponível para aquela operação.","correct":true,"explanation":"CAP fala do trade-off sob partição; a escolha é contextual, não slogan global."},{"id":"sd-b","label":"É possível escolher C, A e P completos se o broker for Kafka.","correct":false,"explanation":"Nenhuma tecnologia remove partição e falha parcial como possibilidade física."},{"id":"sd-c","label":"P é opcional porque rede moderna quase nunca falha.","correct":false,"explanation":"Em sistemas distribuídos, partição/atraso/indisponibilidade precisam ser tratados como realidade."}]}],"resources":[{"id":"gilbert-lynch-cap-proof","type":"reference","title":"Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services","url":"https://groups.csail.mit.edu/tds/papers/Gilbert/Brewer2.pdf","reinforces":"Formalização original do trade-off CAP sob partição.","language":"en","publisher":"MIT","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"fowler-event-driven-phase17","type":"reference","title":"Martin Fowler: What do you mean by “Event-Driven”?","url":"https://martinfowler.com/articles/201701-event-driven.html","reinforces":"Estilos event-driven, riscos de coreografia e trade-offs de arquitetura.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the systems distributed flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for systems distributed. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for systems distributed with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"1. Service de Order: cria order (status PENDING) -- publica \"PedidoCriado\"","instruction":"Design the systems distributed flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for systems distributed with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"consistencia-distribuida","moduleId":"distributed-consistency","order":1,"title":"Consistência distribuída: CAP correto, PACELC, tempo e resiliência","summary":"Comece com duas cópias do mesmo dado em máquinas diferentes. Se a rede entre elas falhar, cada lado precisa decidir entre recusar algumas operações para preservar uma visão única ou continuar respondendo mesmo sem confirmar o estado do outro lado. É nesse cenário específico que CAP ajuda a raciocinar.","objectives":["Diferenciar CAP de PACELC","Escolher modelos de leitura conforme UX e custo","Tratar timeout como incerteza, não prova de falha","Combinar consistência, resiliência e observabilidade sem esconder risco"],"whyItExists":"CAP abre a conversa, mas não basta para operar sistemas reais. Mesmo sem partição, há trade-off entre latência e consistência. Este capítulo dá nomes para as garantias de leitura e para a incerteza causada por tempo, retry e falha parcial.","prerequisiteChapterIds":["sistemas-distribuidos","resilience"],"conceptIds":["base-para-falar-de-consistencia","pacelc-e-modelos-de-consistencia","tempo-falha-parcial-e-retries","protecoes-complementares"],"introducedConceptIds":["pacelc-latency-consistency","read-consistency-models","time-timeout-uncertainty"],"usedConceptIds":["cap-partition-tradeoff","partial-failure-unknown-state","synchronous-deadline-budget","circuit-breaker-state-machine"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"consistencia-distribuida-intuition","type":"intuition","authorship":"authored","title":"Consistência é promessa observável, não sentimento de segurança","body":"Dizer que um sistema é consistente só ajuda quando você declara para quem, quando e em qual leitura. Um usuário pode precisar ler a própria escrita imediatamente, enquanto analytics pode aceitar atraso. A promessa precisa caber em latência, custo, falha e reparo.","analogyLimit":"Fila de atendimento ajuda a pensar em ordem, mas sistemas distribuídos têm clocks imperfeitos, caches, réplicas e consumidores independentes."},{"id":"consistencia-distribuida-models","type":"comparison","authorship":"authored","title":"Modelos de consistência como contrato de UX","criteria":["Garantia percebida","Custo típico","Quando usar","Armadilha"],"alternatives":[{"name":"Forte","values":["leitura reflete escrita confirmada conforme a fronteira escolhida","maior coordenação/latência"],"useWhen":"saldo, pagamento, estoque crítico ou permissão recém-alterada","avoidWhen":"leitura analítica ou feed tolera atraso e custo importa"},{"name":"Read-your-writes","values":["o usuário lê o que acabou de alterar","sessão/roteamento/cache precisam colaborar"],"useWhen":"perfil, pedido recém-criado, tela de confirmação","avoidWhen":"você não consegue identificar sessão/autor da escrita"},{"name":"Eventual","values":["réplicas/con consumidores convergem depois","precisa reconciliação e UX honesta"],"useWhen":"notificação, busca, analytics, projeções e feeds","avoidWhen":"decisão irreversível depende do dado fresco"}]},{"id":"consistencia-distribuida-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-devops\">Produção</span><div class=\"meta-item\">Dificuldade: <b>Avançado</b></div><div class=\"time-est\">⏱ <b>~5h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#sistemas-distribuidos\">Sistemas distribuídos</a>, <a class=\"prereq-tag\" href=\"#resilience\">Resiliência</a></div></div>","fidelityText":"ProduçãoDificuldade: Avançado⏱ ~5h de estudo e práticaPré-requisitos: Sistemas distribuídos, Resiliência"},{"id":"consistencia-distribuida-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Comece com duas cópias do mesmo dado em máquinas diferentes. Se a rede entre elas falhar, cada lado precisa decidir entre recusar algumas operações para preservar uma visão única ou continuar respondendo mesmo sem confirmar o estado do outro lado. É nesse cenário específico que CAP ajuda a raciocinar.</p>","fidelityText":"Comece com duas cópias do mesmo dado em máquinas diferentes. Se a rede entre elas falhar, cada lado precisa decidir entre recusar algumas operações para preservar uma visão única ou continuar respondendo mesmo sem confirmar o estado do outro lado. É nesse cenário específico que CAP ajuda a raciocinar."},{"id":"consistencia-distribuida-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Base para falar de consistência</h2></div>\n    <p>As palavras abaixo descrevem falhas ou garantias distintas. Não use “consistente” sem dizer qual delas.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Sistema distribuído</dt><dd>Componentes em processos ou máquinas diferentes que se comunicam por rede e podem falhar separadamente.</dd></div><div class=\"concept-card\"><dt>Réplica</dt><dd>Cópia coordenada de dados mantida em outro nó para disponibilidade, leitura ou recuperação.</dd></div><div class=\"concept-card\"><dt>Partição de rede</dt><dd>Falha de comunicação que impede grupos de nós de trocar mensagens, mesmo que continuem executando.</dd></div><div class=\"concept-card\"><dt>Disponibilidade em CAP</dt><dd>Toda requisição recebida por um nó não falho obtém resposta, ainda que ela não contenha a gravação mais recente.</dd></div><div class=\"concept-card\"><dt>Linearizabilidade</dt><dd>Cada operação parece ocorrer em um único ponto entre chamada e resposta, respeitando a ordem observada no mundo real.</dd></div><div class=\"concept-card\"><dt>Latência</dt><dd>Tempo entre iniciar uma operação e receber sua resposta.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoBase para falar de consistência As palavras abaixo descrevem falhas ou garantias distintas. Não use “consistente” sem dizer qual delas. Sistema distribuídoComponentes em processos ou máquinas diferentes que se comunicam por rede e podem falhar separadamente.RéplicaCópia coordenada de dados mantida em outro nó para disponibilidade, leitura ou recuperação.Partição de redeFalha de comunicação que impede grupos de nós de trocar mensagens, mesmo que continuem executando.Disponibilidade em CAPToda requisição recebida por um nó não falho obtém resposta, ainda que ela não contenha a gravação mais recente.LinearizabilidadeCada operação parece ocorrer em um único ponto entre chamada e resposta, respeitando a ordem observada no mundo real.LatênciaTempo entre iniciar uma operação e receber sua resposta."},{"id":"consistencia-distribuida-content-4","type":"html","authorship":"legacy-preserved","html":"<p><strong>CAP</strong> descreve a decisão durante uma partição em um sistema replicado: preservar consistência linearizável ou manter disponibilidade. Não classifique um banco local, um cache TTL ou uma entrega <em>at-least-once</em> isoladamente como CP/AP; são eixos diferentes.</p>","fidelityText":"CAP descreve a decisão durante uma partição em um sistema replicado: preservar consistência linearizável ou manter disponibilidade. Não classifique um banco local, um cache TTL ou uma entrega at-least-once isoladamente como CP/AP; são eixos diferentes."},{"id":"consistencia-distribuida-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>PACELC e modelos de consistência</h2>","fidelityText":"PACELC e modelos de consistência"},{"id":"consistencia-distribuida-content-6","type":"html","authorship":"legacy-preserved","html":"<p><strong>PACELC</strong> acrescenta a decisão em operação normal: se houver partição (<em>Partition</em>), disponibilidade ou consistência; caso contrário (<em>Else</em>), latência ou consistência.</p>","fidelityText":"PACELC acrescenta a decisão em operação normal: se houver partição (Partition), disponibilidade ou consistência; caso contrário (Else), latência ou consistência."},{"id":"consistencia-distribuida-content-7","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\"><tbody><tr><th>Garantia</th><th>O que promete</th></tr><tr><td>linearizabilidade</td><td>leituras respeitam a ordem real das operações concluídas</td></tr><tr><td>serializabilidade</td><td>transações concorrentes equivalem a alguma execução sequencial válida</td></tr><tr><td>consistência causal</td><td>operações relacionadas por causa são vistas nessa ordem</td></tr><tr><td>leitura monotônica</td><td>depois de ver uma versão, o mesmo cliente não volta a uma versão mais antiga</td></tr><tr><td>consistência eventual</td><td>sem novas escritas, réplicas convergem; não define quão recente é uma leitura durante a convergência</td></tr></tbody></table>","fidelityText":"GarantiaO que prometelinearizabilidadeleituras respeitam a ordem real das operações concluídasserializabilidadetransações concorrentes equivalem a alguma execução sequencial válidaconsistência causaloperações relacionadas por causa são vistas nessa ordemleitura monotônicadepois de ver uma versão, o mesmo cliente não volta a uma versão mais antigaconsistência eventualsem novas escritas, réplicas convergem; não define quão recente é uma leitura durante a convergência"},{"id":"consistencia-distribuida-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Uma transação serializável não implica automaticamente leitura linearizável entre réplicas: uma garantia ordena transações; a outra também relaciona operações ao tempo real observado.</p>","fidelityText":"Uma transação serializável não implica automaticamente leitura linearizável entre réplicas: uma garantia ordena transações; a outra também relaciona operações ao tempo real observado."},{"id":"consistencia-distribuida-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Tempo, falha parcial e retries</h2>","fidelityText":"Tempo, falha parcial e retries"},{"id":"consistencia-distribuida-content-10","type":"html","authorship":"legacy-preserved","html":"<p><strong>Timeout</strong> limita quanto uma etapa espera, mas não prova que a operação falhou; prova apenas que a resposta não chegou no prazo. O servidor pode ter confirmado o pagamento. Uma <strong>deadline</strong> é o instante limite para toda a operação e deve ser propagada às dependências. <strong>Backoff exponencial</strong> aumenta a pausa entre tentativas; <strong>jitter</strong> adiciona variação aleatória para evitar repetição sincronizada. Operações repetíveis também precisam de idempotency key, consulta de estado e limite total.</p>","fidelityText":"Timeout limita quanto uma etapa espera, mas não prova que a operação falhou; prova apenas que a resposta não chegou no prazo. O servidor pode ter confirmado o pagamento. Uma deadline é o instante limite para toda a operação e deve ser propagada às dependências. Backoff exponencial aumenta a pausa entre tentativas; jitter adiciona variação aleatória para evitar repetição sincronizada. Operações repetíveis também precisam de idempotency key, consulta de estado e limite total."},{"id":"consistencia-distribuida-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Proteções complementares</h2>","fidelityText":"Proteções complementares"},{"id":"consistencia-distribuida-content-12","type":"html","authorship":"legacy-preserved","html":"<ul><li>Circuit breaker reduz pressão sobre dependência doente.</li><li>Bulkhead impede que um recurso esgotado consuma toda a aplicação.</li><li>Rate limiter protege capacidade, mas precisa de política por identidade.</li><li>Lease possui validade temporal e não é um mutex perfeito sob pausas e relógios.</li><li>Fencing token permite ao recurso rejeitar dono antigo de lock distribuído.</li></ul>","fidelityText":"Circuit breaker reduz pressão sobre dependência doente.Bulkhead impede que um recurso esgotado consuma toda a aplicação.Rate limiter protege capacidade, mas precisa de política por identidade.Lease possui validade temporal e não é um mutex perfeito sob pausas e relógios.Fencing token permite ao recurso rejeitar dono antigo de lock distribuído."},{"id":"consistencia-distribuida-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>Fencing token na prática: o rebalance do Kafka (capítulo 41) já é isso.</b> Um consumer sofre uma pausa longa de GC, o coordinator marca o grupo como morto e dispara rebalance — a partição é reatribuída a outro consumer, que assume e começa a processar. O consumer pausado acorda, ainda acreditando que é dono da partição, e tenta escrever no banco com base em uma leitura antiga. O <code>generation.id</code> do consumer group funciona como fencing token: cada rebalance incrementa esse número, e uma escrita que carregue o generation antigo pode ser rejeitada por quem detém o recurso (ex.: uma tabela de \"dono atual da partição X, generation Y\" checada antes de aplicar a escrita) — sem isso, os dois consumers escrevem como se ambos fossem donos legítimos.</div>","fidelityText":"Fencing token na prática: o rebalance do Kafka (capítulo 41) já é isso. Um consumer sofre uma pausa longa de GC, o coordinator marca o grupo como morto e dispara rebalance — a partição é reatribuída a outro consumer, que assume e começa a processar. O consumer pausado acorda, ainda acreditando que é dono da partição, e tenta escrever no banco com base em uma leitura antiga. O generation.id do consumer group funciona como fencing token: cada rebalance incrementa esse número, e uma escrita que carregue o generation antigo pode ser rejeitada por quem detém o recurso (ex.: uma tabela de \"dono atual da partição X, generation Y\" checada antes de aplicar a escrita) — sem isso, os dois consumers escrevem como se ambos fossem donos legítimos."},{"id":"consistencia-distribuida-exercise-14","type":"exercise","authorship":"legacy-preserved","title":"Laboratório mental — resposta perdida","prompt":"Modele o que acontece quando um pagamento é confirmado, mas a resposta se perde. Defina estados, idempotência, consulta, reconciliação e métricas. Depois repita o raciocínio para reserva de estoque.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório mental — resposta perdidadifícilModele o que acontece quando um pagamento é confirmado, mas a resposta se perde. Defina estados, idempotência, consulta, reconciliação e métricas. Depois repita o raciocínio para reserva de estoque.Ver critériosA solução não pode inferir falha pelo timeout nem cobrar novamente sem chave. Deve haver estado desconhecido, reconciliação e trilha auditável.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório mental — resposta perdida</h2><span class=\"exercise-tag d\">difícil</span></div><p>Modele o que acontece quando um pagamento é confirmado, mas a resposta se perde. Defina estados, idempotência, consulta, reconciliação e métricas. Depois repita o raciocínio para reserva de estoque.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>A solução não pode inferir falha pelo timeout nem cobrar novamente sem chave. Deve haver estado desconhecido, reconciliação e trilha auditável.</p></div></div>"},{"id":"consistencia-distribuida-quiz","type":"quiz","authorship":"authored","conceptId":"pacelc-latency-consistency","prompt":"O que PACELC acrescenta à conversa de CAP?","options":[{"id":"cd-a","label":"Mesmo sem partição, o sistema costuma escolher entre menor latência e consistência mais forte.","correct":true,"explanation":"PACELC lembra que o trade-off existe também no caminho normal, não só durante incidentes."},{"id":"cd-b","label":"PACELC prova que CAP está errado e não deve ser usado.","correct":false,"explanation":"PACELC complementa a análise, não remove o raciocínio sob partição."},{"id":"cd-c","label":"PACELC é uma configuração do KafkaTemplate.","correct":false,"explanation":"É um modelo conceitual de trade-off, não API de framework."}]}],"resources":[{"id":"abadi-pacelc-paper","type":"reference","title":"Consistency Tradeoffs in Modern Distributed Database System Design: CAP is Only Part of the Story","url":"https://www.cs.umd.edu/~abadi/papers/abadi-pacelc.pdf","reinforces":"PACELC e trade-offs de latência/consistência em bancos distribuídos.","language":"en","publisher":"University of Maryland","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"jepsen-consistency-models","type":"reference","title":"Jepsen: Consistency Models","url":"https://jepsen.io/consistency","reinforces":"Vocabulário de modelos de consistência e garantias observáveis.","language":"en","publisher":"Jepsen","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the consistency distributed flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for consistency distributed. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for consistency distributed with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"// Observe how names reveal the consistency distributed contract.","instruction":"Design the consistency distributed flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for consistency distributed with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"event-driven-profundo","moduleId":"messaging-eda","order":6,"title":"Arquitetura Event-Driven & Internals do Kafka — Aprofundamento","summary":"Os capítulos 40-42 ensinaram a usar Kafka. Este capítulo abre a \"caixa preta\" — como tópicos e partições realmente funcionam por dentro, e o que significa desenhar um sistema inteiro em torno de eventos, não só usar uma fila pontualmente.","objectives":["Modelar EDA como ownership de tempo e estado","Escolher key de partição por contrato de ordenação","Planejar replay sem corromper efeitos externos","Diferenciar evento de comando em processos longos"],"whyItExists":"Depois dos blocos práticos, EDA profunda consolida arquitetura: evento muda a fronteira de tempo, ownership e observabilidade. A discussão agora é quando usar, como evoluir e como operar sem criar coreografia invisível.","prerequisiteChapterIds":["kafka","mensageria","arquitetura-software"],"conceptIds":["event-driven-architecture-uma-mudanca-de-mentalidade-nao-so-de-tecnologi","anatomia-interna-de-um-topico-kafka","como-a-chave-da-mensagem-decide-a-particao","replicacao-como-kafka-sobrevive-a-queda-de-um-servidor","isr-quorum-e-eleicao-de-lider-o-mecanismo-real-nao-so-automatico","rebalance-protocol-eager-vs-cooperative-sticky-por-dentro","log-compaction-retencao-por-chave-nao-so-por-tempo","por-que-kafka-e-rapido-zero-copy-e-page-cache","consumer-offset-como-kafka-lembra-ate-onde-voce-ja-leu","kafka-streams-processar-dentro-do-proprio-kafka-sem-cluster-separado"],"introducedConceptIds":["kafka-log-replication-ordering","partition-key-ordering-contract","kafka-offset-commit-replay"],"usedConceptIds":["event-command-message-intent","bounded-context-context-map"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"event-driven-profundo-intuition","type":"intuition","authorship":"authored","title":"EDA muda ownership do tempo","body":"Em EDA, um serviço declara fatos e outros reagem no próprio tempo. Isso pode reduzir acoplamento temporal, mas aumenta necessidade de contrato, replay, versionamento, correlação, limites de ordenação e governança de fluxo.","analogyLimit":"Eventos lembram notícias, mas consumidores podem reprocessar, atrasar, deduplicar e gerar novos eventos com causalidade própria."},{"id":"event-driven-profundo-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-kafka\">Kafka Interno</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Multi-Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#kafka\">41 · Kafka na prática</a>, <a class=\"prereq-tag\" href=\"#mensageria\">40 · Mensageria conceitos</a>, <a class=\"prereq-tag\" href=\"#arquitetura-software\">75 · Arquitetura de Software</a></div>\n      </div>","fidelityText":"Kafka Interno Dificuldade: Multi-Avançado ⏱ ~3h de estudo Pré-requisitos: 41 · Kafka na prática, 40 · Mensageria conceitos, 75 · Arquitetura de Software"},{"id":"event-driven-profundo-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Os capítulos 40-42 ensinaram a <em>usar</em> Kafka. Este capítulo abre a \"caixa preta\" — como tópicos e partições realmente funcionam por dentro, e o que significa desenhar um sistema inteiro em torno de eventos, não só usar uma fila pontualmente.</p>","fidelityText":"Os capítulos 40-42 ensinaram a usar Kafka. Este capítulo abre a \"caixa preta\" — como tópicos e partições realmente funcionam por dentro, e o que significa desenhar um sistema inteiro em torno de eventos, não só usar uma fila pontualmente."},{"id":"event-driven-profundo-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Event-Driven Architecture: uma mudança de mentalidade, não só de tecnologia</h2>","fidelityText":"Event-Driven Architecture: uma mudança de mentalidade, não só de tecnologia"},{"id":"event-driven-profundo-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Em uma arquitetura tradicional request-response (capítulo 44), um serviço <strong>pergunta ativamente</strong> a outro por informação, e espera a resposta. Em uma arquitetura orientada a eventos, um serviço <strong>anuncia que algo aconteceu</strong> (\"PedidoCriado\", \"PagamentoAprovado\"), sem saber (nem se importar) quem está ouvindo — quem precisar reagir, reage, de forma completamente desacoplada.</p>","fidelityText":"Em uma arquitetura tradicional request-response (capítulo 44), um serviço pergunta ativamente a outro por informação, e espera a resposta. Em uma arquitetura orientada a eventos, um serviço anuncia que algo aconteceu (\"PedidoCriado\", \"PagamentoAprovado\"), sem saber (nem se importar) quem está ouvindo — quem precisar reagir, reage, de forma completamente desacoplada."},{"id":"event-driven-profundo-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"MODEL REQUEST-RESPONSE (coupling direto):\n┌───────────┐  \"processe this payment\"  ┌───────────────┐\n│  Service   │ ──────────────────────────► │  Service de     │\n│  Order    │ ◄────────────────────────── │  Payment      │\n└───────────┘        response direta       └───────────────┘\n    Service Order PRECISA SABER que o Service de Payment exists,\n    seu address, e espera SINCRONA e ATIVAMENTE pela response\n\nMODEL EVENT-DRIVEN (decoupling via events):\n┌───────────┐   public \"OrderCreated\"    ┌───────────────┐\n│  Service   │ ──────────────────────────► │     TOPIC      │\n│  Order    │      (nao knows quem ouve)   │  \"orders\"      │\n└───────────┘                              └───────┬───────┘\n                                                     │\n                          ┌──────────────────────────┼──────────────────────┐\n                          ▼                          ▼                      ▼\n                 ┌───────────────┐        ┌───────────────┐      ┌───────────────┐\n                 │  Service de    │        │  Service de    │      │  Service de    │\n                 │  Payment     │        │  Inventory        │      │  Notification    │\n                 └───────────────┘        └───────────────┘      └───────────────┘\n    Service Order NAO KNOWS que esses tres services exist --\n    cada um decide, de shape independente, se e as react ao event","fidelityText":"MODELO REQUEST-RESPONSE (acoplamento direto): ┌───────────┐ \"processe este pagamento\" ┌───────────────┐ │ Serviço │ ──────────────────────────► │ Serviço de │ │ Pedido │ ◄────────────────────────── │ Pagamento │ └───────────┘ resposta direta └───────────────┘ Serviço Pedido PRECISA SABER que o Serviço de Pagamento existe, seu endereço, e espera SÍNCRONA e ATIVAMENTE pela resposta MODELO EVENT-DRIVEN (desacoplamento via eventos): ┌───────────┐ publica \"PedidoCriado\" ┌───────────────┐ │ Serviço │ ──────────────────────────► │ TÓPICO │ │ Pedido │ (não sabe quem ouve) │ \"pedidos\" │ └───────────┘ └───────┬───────┘ │ ┌──────────────────────────┼──────────────────────┐ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Serviço de │ │ Serviço de │ │ Serviço de │ │ Pagamento │ │ Estoque │ │ Notificação │ └───────────────┘ └───────────────┘ └───────────────┘ Serviço Pedido NÃO SABE que esses três serviços existem -- cada um decide, de forma independente, se e como reagir ao evento","highlightedHtml":"MODEL REQUEST-RESPONSE (coupling direto):\n┌───────────┐  \"processe this payment\"  ┌───────────────┐\n│  Service   │ ──────────────────────────► │  Service de     │\n│  Order    │ ◄────────────────────────── │  Payment      │\n└───────────┘        response direta       └───────────────┘\n    Service Order PRECISA SABER que o Service de Payment exists,\n    seu address, e espera SINCRONA e ATIVAMENTE pela response\n\nMODEL EVENT-DRIVEN (decoupling via events):\n┌───────────┐   public \"OrderCreated\"    ┌───────────────┐\n│  Service   │ ──────────────────────────► │     TOPIC      │\n│  Order    │      (nao knows quem ouve)   │  \"orders\"      │\n└───────────┘                              └───────┬───────┘\n                                                     │\n                          ┌──────────────────────────┼──────────────────────┐\n                          ▼                          ▼                      ▼\n                 ┌───────────────┐        ┌───────────────┐      ┌───────────────┐\n                 │  Service de    │        │  Service de    │      │  Service de    │\n                 │  Payment     │        │  Inventory        │      │  Notification    │\n                 └───────────────┘        └───────────────┘      └───────────────┘\n    Service Order NAO KNOWS que esses tres services exist --\n    cada um decide, de shape independente, se e as react ao event","caption":"Exemplo executável de event-driven-profundo.","explanation":["O diagrama contrasta request-response (acoplamento direto, espera síncrona) com event-driven (publicação sem saber quem consome).","No modelo request-response, o serviço de pedido precisa conhecer o endereço do serviço de pagamento; no event-driven, ele só conhece o tópico."],"commonMistakes":["Achar que event-driven elimina toda dependência, quando na verdade move o acoplamento para o contrato do evento","Implementar publish/subscribe mas ainda esperar uma resposta síncrona do consumidor, recriando o acoplamento que se queria evitar"]},{"id":"event-driven-profundo-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Request-response é como fazer uma ligação telefônica direta para pedir algo — você precisa saber exatamente o número de quem vai atender, e fica esperando na linha até a resposta. Event-driven é como postar um anúncio em um mural público de bairro (\"alguém encontrou uma chave\") — você não sabe (nem precisa saber) quem vai ler, quantas pessoas vão reagir, ou quando. Isso desacopla completamente quem publica de quem consome, permitindo adicionar novos \"leitores do mural\" (novos serviços reagindo ao mesmo evento) sem NUNCA precisar tocar no código de quem publica.</div>","fidelityText":"Request-response é como fazer uma ligação telefônica direta para pedir algo — você precisa saber exatamente o número de quem vai atender, e fica esperando na linha até a resposta. Event-driven é como postar um anúncio em um mural público de bairro (\"alguém encontrou uma chave\") — você não sabe (nem precisa saber) quem vai ler, quantas pessoas vão reagir, ou quando. Isso desacopla completamente quem publica de quem consome, permitindo adicionar novos \"leitores do mural\" (novos serviços reagindo ao mesmo evento) sem NUNCA precisar tocar no código de quem publica."},{"id":"event-driven-profundo-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Anatomia interna de um tópico Kafka</h2>","fidelityText":"Anatomia interna de um tópico Kafka"},{"id":"event-driven-profundo-content-8","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 41 já introduziu tópicos e partições superficialmente. Vamos abrir isso completamente.</p>","fidelityText":"O capítulo 41 já introduziu tópicos e partições superficialmente. Vamos abrir isso completamente."},{"id":"event-driven-profundo-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"TOPIC: \"orders-completed\"  (3 partitions)\n\n┌─────────────────────────────────────────────────────────────┐\n│ PARTITION 0                                                     │\n│ offset:  0     1     2     3     4     5                       │\n│        ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐                    │\n│        │msg│ │msg│ │msg│ │msg│ │msg│ │msg│  ← APPEND-ONLY --   │\n│        └───┘ └───┘ └───┘ └───┘ └───┘ └───┘    NEVER editada,   │\n│                                    ▲            so cresce       │\n│                          next offset a write              │\n├─────────────────────────────────────────────────────────────┤\n│ PARTITION 1                                                     │\n│ offset:  0     1     2     3                                   │\n│        ┌───┐ ┌───┐ ┌───┐ ┌───┐                                 │\n│        │msg│ │msg│ │msg│ │msg│                                 │\n│        └───┘ └───┘ └───┘ └───┘                                 │\n├─────────────────────────────────────────────────────────────┤\n│ PARTITION 2                                                     │\n│ offset:  0     1     2     3     4                              │\n│        ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐                            │\n│        │msg│ │msg│ │msg│ │msg│ │msg│                            │\n│        └───┘ └───┘ └───┘ └───┘ └───┘                            │\n└─────────────────────────────────────────────────────────────┘","fidelityText":"TÓPICO: \"pedidos-finalizados\" (3 partições) ┌─────────────────────────────────────────────────────────────┐ │ PARTIÇÃO 0 │ │ offset: 0 1 2 3 4 5 │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │ │msg│ │msg│ │msg│ │msg│ │msg│ │msg│ ← APPEND-ONLY -- │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ NUNCA editada, │ │ ▲ só cresce │ │ próximo offset a escrever │ ├─────────────────────────────────────────────────────────────┤ │ PARTIÇÃO 1 │ │ offset: 0 1 2 3 │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │ │msg│ │msg│ │msg│ │msg│ │ │ └───┘ └───┘ └───┘ └───┘ │ ├─────────────────────────────────────────────────────────────┤ │ PARTIÇÃO 2 │ │ offset: 0 1 2 3 4 │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │ │msg│ │msg│ │msg│ │msg│ │msg│ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ │ └─────────────────────────────────────────────────────────────┘","highlightedHtml":"TOPIC: \"orders-completed\"  (3 partitions)\n\n┌─────────────────────────────────────────────────────────────┐\n│ PARTITION 0                                                     │\n│ offset:  0     1     2     3     4     5                       │\n│        ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐                    │\n│        │msg│ │msg│ │msg│ │msg│ │msg│ │msg│  ← APPEND-ONLY --   │\n│        └───┘ └───┘ └───┘ └───┘ └───┘ └───┘    NEVER editada,   │\n│                                    ▲            so cresce       │\n│                          next offset a write              │\n├─────────────────────────────────────────────────────────────┤\n│ PARTITION 1                                                     │\n│ offset:  0     1     2     3                                   │\n│        ┌───┐ ┌───┐ ┌───┐ ┌───┐                                 │\n│        │msg│ │msg│ │msg│ │msg│                                 │\n│        └───┘ └───┘ └───┘ └───┘                                 │\n├─────────────────────────────────────────────────────────────┤\n│ PARTITION 2                                                     │\n│ offset:  0     1     2     3     4                              │\n│        ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐                            │\n│        │msg│ │msg│ │msg│ │msg│ │msg│                            │\n│        └───┘ └───┘ └───┘ └───┘ └───┘                            │\n└─────────────────────────────────────────────────────────────┘","caption":"Exemplo executável de event-driven-profundo.","explanation":["Cada partição é um log append-only independente, com seu próprio contador de offset sequencial.","Mensagens só são adicionadas ao final -- nunca editadas nem reordenadas dentro da partição."],"commonMistakes":["Achar que existe uma ordem global entre partições diferentes do mesmo tópico","Esperar poder editar ou remover uma mensagem específica do meio do log"]},{"id":"event-driven-profundo-content-10","type":"html","authorship":"legacy-preserved","html":"<p>Cada partição é, estruturalmente, um <strong>log append-only</strong> — uma estrutura de dados extremamente simples (e por isso extremamente rápida): novas mensagens só são adicionadas ao final, nunca inseridas no meio, nunca editadas. Cada mensagem recebe um <strong>offset</strong> sequencial, único dentro daquela partição — como o número de uma página em um livro que só cresce, nunca é reordenado.</p>","fidelityText":"Cada partição é, estruturalmente, um log append-only — uma estrutura de dados extremamente simples (e por isso extremamente rápida): novas mensagens só são adicionadas ao final, nunca inseridas no meio, nunca editadas. Cada mensagem recebe um offset sequencial, único dentro daquela partição — como o número de uma página em um livro que só cresce, nunca é reordenado."},{"id":"event-driven-profundo-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>Como a chave da mensagem decide a partição</h2>","fidelityText":"Como a chave da mensagem decide a partição"},{"id":"event-driven-profundo-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"producer.send(new ProducerRecord<>(\"orders-completed\", \"customer-42\", dataOrder));\n                                                    ↑ key\n\nPor baixo dos panos: partition = hash(key) % numberOfPartitions\n\n\"customer-42\" sempre produces o SAME hash, entao ALL as messages\ncom essa key SEMPRE vao para a SAME partition -- garantindo que,\npara that customer especifico, a ORDER de processamento e preserved\n(messages inside de uma same partition are sempre processadas em\norder de offset). Messages de customers DIFFERENT podem ir para\npartitions different, processadas EM PARALLEL por consumers\ndifferent -- é assim que Kafka escala: paralelismo entre partições,\norder garantida inside de cada uma.","fidelityText":"producer.send(new ProducerRecord<>(\"pedidos-finalizados\", \"cliente-42\", dadosPedido)); ↑ chave Por baixo dos panos: partição = hash(chave) % numeroDePartições \"cliente-42\" sempre produz o MESMO hash, então TODAS as mensagens com essa chave SEMPRE vão para a MESMA partição -- garantindo que, para aquele cliente específico, a ORDEM de processamento é preservada (mensagens dentro de uma mesma partição são sempre processadas em ordem de offset). Mensagens de clientes DIFERENTES podem ir para partições diferentes, processadas EM PARALELO por consumidores diferentes -- é assim que Kafka escala: paralelismo entre partições, ordem garantida dentro de cada uma.","highlightedHtml":"producer.send(<span class=\"kw\">new</span> ProducerRecord&lt;&gt;(<span class=\"str\">\"orders-completed\"</span>, <span class=\"str\">\"customer-42\"</span>, dataOrder));\n                                                    <span class=\"com\">↑ chave</span>\n\nPor baixo dos panos: partition = hash(key) % numberOfPartitions\n\n\"customer-42\" sempre produces o SAME hash, entao ALL as messages\ncom essa key SEMPRE vao para a SAME partition -- garantindo que,\npara that customer especifico, a ORDER de processamento e preserved\n(messages inside de uma same partition are sempre processadas em\norder de offset). Messages de customers DIFFERENT podem ir para\npartitions different, processadas EM PARALLEL por consumers\ndifferent -- é assim que Kafka escala: paralelismo entre partições,\norder garantida inside de cada uma.","caption":"Exemplo executável de event-driven-profundo.","explanation":["partition = hash(key) % numeroDePartições -- a mesma chave sempre calcula o mesmo hash e cai na mesma partição.","Isso é o que garante ordem entre mensagens da mesma chave, mesmo com múltiplas partições processadas em paralelo."],"commonMistakes":["Usar uma chave aleatória ou nula quando a ordem entre mensagens relacionadas importa","Achar que a chave de partição garante ordem entre chaves diferentes -- ela só garante dentro da mesma chave"]},{"id":"event-driven-profundo-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Essa é a razão técnica exata pela qual \"ordem garantida\" no Kafka é uma <strong>garantia por partição, não global no tópico</strong> — um detalhe que confunde bastante gente. Se você precisa que todos os eventos de um mesmo cliente sejam processados estritamente em ordem (ex: \"PedidoCriado\" antes de \"PedidoCancelado\" para o mesmo pedido), a chave da mensagem <strong>precisa</strong> ser algo que identifique esse cliente/pedido de forma consistente — se você usar uma chave aleatória ou nula, mensagens relacionadas podem cair em partições diferentes e serem processadas fora de ordem por consumidores diferentes, rodando em paralelo.</div>","fidelityText":"Essa é a razão técnica exata pela qual \"ordem garantida\" no Kafka é uma garantia por partição, não global no tópico — um detalhe que confunde bastante gente. Se você precisa que todos os eventos de um mesmo cliente sejam processados estritamente em ordem (ex: \"PedidoCriado\" antes de \"PedidoCancelado\" para o mesmo pedido), a chave da mensagem precisa ser algo que identifique esse cliente/pedido de forma consistente — se você usar uma chave aleatória ou nula, mensagens relacionadas podem cair em partições diferentes e serem processadas fora de ordem por consumidores diferentes, rodando em paralelo."},{"id":"event-driven-profundo-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Replicação — como Kafka sobrevive à queda de um servidor</h2>","fidelityText":"Replicação — como Kafka sobrevive à queda de um servidor"},{"id":"event-driven-profundo-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"PARTITION 0, com replication-factor=3 (replicada em 3 brokers):\n\n  Broker 1 (LEADER)         Broker 2 (replica)      Broker 3 (replica)\n  ┌─────────────┐          ┌─────────────┐         ┌─────────────┐\n  │ [0][1][2][3] │ ──copy──► [0][1][2][3] │ ──copy─► [0][1][2][3] │\n  └─────────────┘          └─────────────┘         └─────────────┘\n   TODA writing e            replica PASSIVA         replica PASSIVA\n   reading passes por          (so copy do leader)     (so copy do leader)\n   este broker\n\n  Se o Broker 1 (leader) CAIR:\n  Kafka elects AUTOMATICALLY um new leader entre as replicas\n  remaining (ex: Broker 2 vira o new leader) -- a aplicação cliente\n  nem percebe, alem de uma pequena pausa durante a reeleicao","fidelityText":"PARTIÇÃO 0, com replication-factor=3 (replicada em 3 brokers): Broker 1 (LÍDER) Broker 2 (réplica) Broker 3 (réplica) ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ [0][1][2][3] │ ──copia──► [0][1][2][3] │ ──copia─► [0][1][2][3] │ └─────────────┘ └─────────────┘ └─────────────┘ TODA escrita e réplica PASSIVA réplica PASSIVA leitura passa por (só copia do líder) (só copia do líder) este broker Se o Broker 1 (líder) CAIR: Kafka elege AUTOMATICAMENTE um novo líder entre as réplicas restantes (ex: Broker 2 vira o novo líder) -- a aplicação cliente nem percebe, além de uma pequena pausa durante a reeleição","highlightedHtml":"PARTITION 0, com replication-factor=3 (replicada em 3 brokers):\n\n  Broker 1 (LEADER)         Broker 2 (replica)      Broker 3 (replica)\n  ┌─────────────┐          ┌─────────────┐         ┌─────────────┐\n  │ [0][1][2][3] │ ──copy──► [0][1][2][3] │ ──copy─► [0][1][2][3] │\n  └─────────────┘          └─────────────┘         └─────────────┘\n   TODA writing e            replica PASSIVA         replica PASSIVA\n   reading passes por          (so copy do leader)     (so copy do leader)\n   este broker\n\n  Se o Broker 1 (leader) CAIR:\n  Kafka elects AUTOMATICALLY um new leader entre as replicas\n  remaining (ex: Broker 2 vira o new leader) -- a aplicação cliente\n  nem percebe, alem de uma pequena pausa durante a reeleicao","caption":"Exemplo executável de event-driven-profundo.","explanation":["Com replication-factor=3, toda escrita e leitura passam pelo broker líder; as réplicas apenas copiam passivamente.","Se o líder cair, uma das réplicas assume automaticamente -- a aplicação cliente só percebe uma pequena pausa na reeleição."],"commonMistakes":["Achar que réplicas atendem leitura/escrita em paralelo com o líder por padrão","Configurar replication-factor=1 e perder a partição inteira na queda de um único broker"]},{"id":"event-driven-profundo-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Pense em replicação como três funcionários de um cartório, cada um com uma cópia idêntica de um livro de registros. Um deles (o \"líder\") é quem realmente atende o público e escreve novos registros; os outros dois copiam cada novo registro dele em tempo real, como espectadores atentos. Se o funcionário líder sair de férias inesperadamente (o broker cai), um dos outros dois — que já tinha uma cópia atualizada — assume o balcão imediatamente, sem que o público perceba interrupção significativa no atendimento.</div>","fidelityText":"Pense em replicação como três funcionários de um cartório, cada um com uma cópia idêntica de um livro de registros. Um deles (o \"líder\") é quem realmente atende o público e escreve novos registros; os outros dois copiam cada novo registro dele em tempo real, como espectadores atentos. Se o funcionário líder sair de férias inesperadamente (o broker cai), um dos outros dois — que já tinha uma cópia atualizada — assume o balcão imediatamente, sem que o público perceba interrupção significativa no atendimento."},{"id":"event-driven-profundo-content-17","type":"html","authorship":"legacy-preserved","html":"<h2>ISR, quórum e eleição de líder — o mecanismo real, não só \"automático\"</h2>","fidelityText":"ISR, quórum e eleição de líder — o mecanismo real, não só \"automático\""},{"id":"event-driven-profundo-content-18","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 41/42 já usou o termo <strong>ISR</strong> (<em>in-sync replica set</em>): o subconjunto de réplicas que está genuinamente em dia com o líder, não apenas \"existe\". Uma réplica sai do ISR quando fica atrás por tempo demais (<code>replica.lag.time.max.ms</code>) — nesse momento ela conta como réplica, mas <strong>não</strong> conta para <code>acks=all</code> nem é elegível para virar líder sem perda de dado.</p>","fidelityText":"O capítulo 41/42 já usou o termo ISR (in-sync replica set): o subconjunto de réplicas que está genuinamente em dia com o líder, não apenas \"existe\". Uma réplica sai do ISR quando fica atrás por tempo demais (replica.lag.time.max.ms) — nesse momento ela conta como réplica, mas não conta para acks=all nem é elegível para virar líder sem perda de dado."},{"id":"event-driven-profundo-code-19","type":"code","authorship":"legacy-preserved","language":"java","source":"TOPIC com replication-factor=3, min.insync.replicas=2:\n\n  ISR = {Broker 1 (leader), Broker 2, Broker 3}   -- todos em dia\n  Broker 3 stays para tras (network lenta) -----------► sai do ISR\n  ISR = {Broker 1 (leader), Broker 2}             -- ainda 2, acks=all continua funcionando\n  Broker 2 tambem falls -----------------------------► ISR = {Broker 1}\n  min.insync.replicas=2 nao e mais satisfeito -----► broker REJECTS novas escritas acks=all","fidelityText":"TÓPICO com replication-factor=3, min.insync.replicas=2: ISR = {Broker 1 (líder), Broker 2, Broker 3} -- todos em dia Broker 3 fica para trás (rede lenta) -----------► sai do ISR ISR = {Broker 1 (líder), Broker 2} -- ainda 2, acks=all continua funcionando Broker 2 também cai -----------------------------► ISR = {Broker 1} min.insync.replicas=2 não é mais satisfeito -----► broker REJEITA novas escritas acks=all","highlightedHtml":"TOPIC com replication-factor=3, min.insync.replicas=2:\n\n  ISR = {Broker 1 (leader), Broker 2, Broker 3}   -- todos em dia\n  Broker 3 stays para tras (network lenta) -----------► sai do ISR\n  ISR = {Broker 1 (leader), Broker 2}             -- ainda 2, acks=all continua funcionando\n  Broker 2 tambem falls -----------------------------► ISR = {Broker 1}\n  min.insync.replicas=2 nao e mais satisfeito -----► broker REJECTS novas escritas acks=all","caption":"Exemplo executável de event-driven-profundo.","explanation":["ISR é o subconjunto de réplicas genuinamente em dia com o líder -- uma réplica atrasada sai do ISR mesmo continuando a existir.","min.insync.replicas exige um número mínimo de réplicas no ISR para aceitar escritas com acks=all; abaixo disso, o broker rejeita a escrita em vez de fingir durabilidade."],"commonMistakes":["Confundir número de réplicas configuradas com número de réplicas atualmente no ISR","Não monitorar quando o ISR encolhe, perdendo o aviso antecipado de um problema de replicação"]},{"id":"event-driven-profundo-content-20","type":"html","authorship":"legacy-preserved","html":"<p>Quem decide qual réplica do ISR vira o novo líder quando o líder atual cai? Desde o Kafka moderno (modo KRaft, sem Zookeeper — o mesmo <code>docker-compose.yml</code> do capítulo 41), essa decisão não é feita por votação entre todos os brokers do cluster: um pequeno grupo de brokers dedicados ao papel de <strong>controller</strong> mantém o metadado do cluster (que tópicos existem, quem é líder de cada partição) via um protocolo de <strong>quórum</strong> (Raft) — só esse grupo precisa concordar, o que torna a eleição rápida mesmo em clusters grandes.</p>","fidelityText":"Quem decide qual réplica do ISR vira o novo líder quando o líder atual cai? Desde o Kafka moderno (modo KRaft, sem Zookeeper — o mesmo docker-compose.yml do capítulo 41), essa decisão não é feita por votação entre todos os brokers do cluster: um pequeno grupo de brokers dedicados ao papel de controller mantém o metadado do cluster (que tópicos existem, quem é líder de cada partição) via um protocolo de quórum (Raft) — só esse grupo precisa concordar, o que torna a eleição rápida mesmo em clusters grandes."},{"id":"event-driven-profundo-content-21","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Isso é uma simplificação real de arquitetura, não um detalhe cosmético: antes do KRaft, o Zookeeper era esse \"cartório de metadados\" externo ao cluster Kafka — um sistema distribuído a mais para operar. O KRaft moveu esse papel para dentro do próprio Kafka (brokers dedicados ao papel de controller), eliminando uma dependência externa inteira do design de produção.</div>","fidelityText":"Isso é uma simplificação real de arquitetura, não um detalhe cosmético: antes do KRaft, o Zookeeper era esse \"cartório de metadados\" externo ao cluster Kafka — um sistema distribuído a mais para operar. O KRaft moveu esse papel para dentro do próprio Kafka (brokers dedicados ao papel de controller), eliminando uma dependência externa inteira do design de produção."},{"id":"event-driven-profundo-content-22","type":"html","authorship":"legacy-preserved","html":"<h2>Rebalance protocol — eager vs. cooperative-sticky por dentro</h2>","fidelityText":"Rebalance protocol — eager vs. cooperative-sticky por dentro"},{"id":"event-driven-profundo-content-23","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 41 já nomeou as estratégias de atribuição de partição. Por dentro, existem dois <strong>protocolos</strong> de rebalance diferentes, não só estratégias de distribuição:</p>","fidelityText":"O capítulo 41 já nomeou as estratégias de atribuição de partição. Por dentro, existem dois protocolos de rebalance diferentes, não só estratégias de distribuição:"},{"id":"event-driven-profundo-code-24","type":"code","authorship":"legacy-preserved","language":"java","source":"EAGER (range, round-robin):\n  1. ALL os consumers do group revogam ALL as suas partitions\n  2. Group coordinator redistribui do zero\n  3. ALL os consumers retomam -- inclusive os que não mudaram de partição\n  \"stop-the-world\": processamento para por complete durante o rebalance\n\nCOOPERATIVE-STICKY:\n  1. Coordinator calcula a new assignment\n  2. So os consumers cujas partitions MUDARAM as revogam\n  3. So essas partitions are redistribuidas -- em uma segunda rodada\n  Consumers que mantiveram suas partitions NEVER param de process","fidelityText":"EAGER (range, round-robin): 1. TODOS os consumers do grupo revogam TODAS as suas partições 2. Group coordinator redistribui do zero 3. TODOS os consumers retomam -- inclusive os que não mudaram de partição \"stop-the-world\": processamento para por completo durante o rebalance COOPERATIVE-STICKY: 1. Coordinator calcula a nova atribuição 2. Só os consumers cujas partições MUDARAM as revogam 3. Só essas partições são redistribuídas -- em uma segunda rodada Consumers que mantiveram suas partições NUNCA param de processar","highlightedHtml":"EAGER (range, round-robin):\n  1. ALL os consumers do group revogam ALL as suas partitions\n  2. Group coordinator redistribui do zero\n  3. ALL os consumers retomam -- inclusive os que não mudaram de partição\n  \"stop-the-world\": processamento para por complete durante o rebalance\n\nCOOPERATIVE-STICKY:\n  1. Coordinator calcula a new assignment\n  2. So os consumers cujas partitions MUDARAM as revogam\n  3. So essas partitions are redistribuidas -- em uma segunda rodada\n  Consumers que mantiveram suas partitions NEVER param de process","caption":"Exemplo executável de event-driven-profundo.","explanation":["Estratégias eager (range, round-robin) revogam todas as partições de todos os consumers a cada rebalance, mesmo dos que não mudaram.","Cooperative-sticky faz o rebalance em duas fases, movendo só as partições que realmente precisam trocar de dono."],"commonMistakes":["Manter a estratégia eager padrão em grupos com entrada/saída frequente de consumers, pagando o custo de stop-the-world a cada rebalance","Achar que qualquer estratégia de assignment evita completamente pausas durante o rebalance"]},{"id":"event-driven-profundo-content-25","type":"html","authorship":"legacy-preserved","html":"<p>O <code>session.timeout.ms</code> define quanto tempo o group coordinator espera um heartbeat antes de considerar um consumer morto (disparando rebalance); <code>group.instance.id</code> (static membership) evita rebalance desnecessário em reinícios rápidos e previsíveis (deploy, restart de container) — o consumer reconecta com a mesma identidade estática em vez de ser tratado como um membro novo do grupo.</p>","fidelityText":"O session.timeout.ms define quanto tempo o group coordinator espera um heartbeat antes de considerar um consumer morto (disparando rebalance); group.instance.id (static membership) evita rebalance desnecessário em reinícios rápidos e previsíveis (deploy, restart de container) — o consumer reconecta com a mesma identidade estática em vez de ser tratado como um membro novo do grupo."},{"id":"event-driven-profundo-content-26","type":"html","authorship":"legacy-preserved","html":"<h2>Log compaction — retenção por chave, não só por tempo</h2>","fidelityText":"Log compaction — retenção por chave, não só por tempo"},{"id":"event-driven-profundo-content-27","type":"html","authorship":"legacy-preserved","html":"<p>Até aqui, todo tópico foi tratado como retido por <strong>tempo</strong> (<code>retention.ms</code>) ou <strong>tamanho</strong>. Existe uma terceira política: <code>cleanup.policy=compact</code>, que mantém apenas o <strong>registro mais recente por chave</strong>, descartando versões antigas da mesma chave — útil para um tópico que representa \"estado atual\" (ex: <code>usuario-perfil</code>, onde só o último perfil de cada <code>usuarioId</code> importa, não o histórico completo).</p>","fidelityText":"Até aqui, todo tópico foi tratado como retido por tempo (retention.ms) ou tamanho. Existe uma terceira política: cleanup.policy=compact, que mantém apenas o registro mais recente por chave, descartando versões antigas da mesma chave — útil para um tópico que representa \"estado atual\" (ex: usuario-perfil, onde só o último perfil de cada usuarioId importa, não o histórico completo)."},{"id":"event-driven-profundo-code-28","type":"code","authorship":"legacy-preserved","language":"java","source":"# topic config -- retenção por chave, não por tempo\nkafka-configs.sh --bootstrap-server localhost:9092 \\\n    --alter --topic user-perfil \\\n    --add-config cleanup.policy=compact","fidelityText":"# topic config -- retenção por chave, não por tempo kafka-configs.sh --bootstrap-server localhost:9092 \\ --alter --topic usuario-perfil \\ --add-config cleanup.policy=compact","highlightedHtml":"<span class=\"com\"># topic config -- retenção por chave, não por tempo</span>\nkafka-configs.sh --bootstrap-server localhost:9092 \\\n    --alter --topic user-perfil \\\n    --add-config cleanup.policy=compact","caption":"Exemplo executável de event-driven-profundo.","explanation":["cleanup.policy=compact faz o tópico reter só o registro mais recente por chave, em vez de reter tudo por tempo/tamanho.","É a política certa para um tópico que representa estado atual (como um perfil), não um histórico de eventos completo."],"commonMistakes":["Aplicar compactação em um tópico que precisa manter o histórico completo de eventos","Esquecer que apagar uma chave exige publicar uma tombstone (valor null), não simplesmente parar de publicar"]},{"id":"event-driven-profundo-content-29","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Apagar uma chave em um tópico compactado exige uma <em>tombstone</em>:</b> publicar um registro com a chave e valor <code>null</code>. O compactor remove a chave de verdade só depois de um período de retenção da tombstone (<code>delete.retention.ms</code>) — tempo suficiente para consumidores atrasados verem a remoção antes dela sumir de vez.</div>","fidelityText":"Apagar uma chave em um tópico compactado exige uma tombstone: publicar um registro com a chave e valor null. O compactor remove a chave de verdade só depois de um período de retenção da tombstone (delete.retention.ms) — tempo suficiente para consumidores atrasados verem a remoção antes dela sumir de vez."},{"id":"event-driven-profundo-content-30","type":"html","authorship":"legacy-preserved","html":"<h2>Por que Kafka é rápido — zero-copy e page cache</h2>","fidelityText":"Por que Kafka é rápido — zero-copy e page cache"},{"id":"event-driven-profundo-content-31","type":"html","authorship":"legacy-preserved","html":"<p>Cada partição é um log append-only dividido internamente em <strong>segmentos</strong> (arquivos no disco). Duas decisões de storage explicam a performance: primeiro, o broker delega leitura de disco para a rede via <strong>zero-copy</strong> (<code>sendfile</code> do sistema operacional) — os dados não passam pelo espaço de memória da JVM do broker no caminho de leitura, evitando cópias desnecessárias. Segundo, escritas e leituras recentes tendem a viver na <strong>page cache</strong> do sistema operacional (RAM), não no disco — sequencial append-only significa que o padrão de acesso é exatamente o que a page cache otimiza melhor.</p>","fidelityText":"Cada partição é um log append-only dividido internamente em segmentos (arquivos no disco). Duas decisões de storage explicam a performance: primeiro, o broker delega leitura de disco para a rede via zero-copy (sendfile do sistema operacional) — os dados não passam pelo espaço de memória da JVM do broker no caminho de leitura, evitando cópias desnecessárias. Segundo, escritas e leituras recentes tendem a viver na page cache do sistema operacional (RAM), não no disco — sequencial append-only significa que o padrão de acesso é exatamente o que a page cache otimiza melhor."},{"id":"event-driven-profundo-content-32","type":"html","authorship":"legacy-preserved","html":"<h2>Consumer Offset — como Kafka lembra \"até onde você já leu\"</h2>","fidelityText":"Consumer Offset — como Kafka lembra \"até onde você já leu\""},{"id":"event-driven-profundo-code-33","type":"code","authorship":"legacy-preserved","language":"java","source":"Consumer Group \"service-notification\" reading a PARTITION 0:\n\n  offset:  0     1     2     3     4     5\n         ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐\n         │ ✓ │ │ ✓ │ │ ✓ │ │ ✓ │ │ →  │ │   │\n         └───┘ └───┘ └───┘ └───┘ └───┘ └───┘\n                              ▲\n                    \"committed offset\" = 3\n                    (o consumer already confirmou ter processed\n                     ate a message de offset 3 -- se ele cair\n                     e return, retoma exactly do offset 4,\n                     NEVER reprocessa o que already foi confirmed\n                     ... exceto se o commit em si fail, dai\n                     entramos de new em \"at-least-once\",\n                     chapter 40!)","fidelityText":"Consumer Group \"servico-notificacao\" lendo a PARTIÇÃO 0: offset: 0 1 2 3 4 5 ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ ✓ │ │ ✓ │ │ ✓ │ │ ✓ │ │ → │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ ▲ \"committed offset\" = 3 (o consumer já confirmou ter processado até a mensagem de offset 3 -- se ele cair e voltar, retoma exatamente do offset 4, NUNCA reprocessa o que já foi confirmado ... exceto se o commit em si falhar, daí entramos de novo em \"at-least-once\", capítulo 40!)","highlightedHtml":"Consumer Group \"service-notification\" reading a PARTITION 0:\n\n  offset:  0     1     2     3     4     5\n         ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐\n         │ ✓ │ │ ✓ │ │ ✓ │ │ ✓ │ │ →  │ │   │\n         └───┘ └───┘ └───┘ └───┘ └───┘ └───┘\n                              ▲\n                    \"committed offset\" = 3\n                    (o consumer already confirmou ter processed\n                     ate a message de offset 3 -- se ele cair\n                     e return, retoma exactly do offset 4,\n                     NEVER reprocessa o que already foi confirmed\n                     ... exceto se o commit em si fail, dai\n                     entramos de new em \"at-least-once\",\n                     chapter 40!)","caption":"Exemplo executável de event-driven-profundo.","explanation":["O offset commitado marca até onde o consumer group já confirmou processamento -- uma queda antes do commit reprocessa a partir do último offset confirmado.","Processar antes de commitar (at-least-once) nunca perde mensagem, mas pode reprocessar; commitar antes de processar (at-most-once) pode perder mensagem silenciosamente."],"commonMistakes":["Comitar o offset antes de garantir que o processamento terminou, arriscando perda silenciosa","Achar que o offset commitado por si só prova que o efeito de negócio ocorreu"]},{"id":"event-driven-profundo-content-34","type":"html","authorship":"legacy-preserved","html":"<p>O <strong>offset commitado</strong> é armazenado pelo próprio Kafka (em um tópico interno especial). Isso é o que permite a um consumer group retomar exatamente de onde parou depois de uma queda — e é também a raiz técnica exata da garantia \"at-least-once\" discutida no capítulo 40: se o consumer processar a mensagem mas cair <em>antes</em> de confirmar o commit do offset, ao voltar ele vai reprocessar aquela mesma mensagem — daí a necessidade de idempotência no lado do consumidor.</p>","fidelityText":"O offset commitado é armazenado pelo próprio Kafka (em um tópico interno especial). Isso é o que permite a um consumer group retomar exatamente de onde parou depois de uma queda — e é também a raiz técnica exata da garantia \"at-least-once\" discutida no capítulo 40: se o consumer processar a mensagem mas cair antes de confirmar o commit do offset, ao voltar ele vai reprocessar aquela mesma mensagem — daí a necessidade de idempotência no lado do consumidor."},{"id":"event-driven-profundo-content-35","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Ordem de operações importa:</b> processar a mensagem <strong>antes</strong> de commitar o offset (o padrão mais comum, chamado \"at-least-once\") significa que uma falha entre as duas etapas causa reprocessamento — mas nunca perda de mensagem. Commitar o offset <strong>antes</strong> de processar (\"at-most-once\") é mais raro e mais arriscado: uma falha entre as duas etapas significa que a mensagem é considerada \"processada\" pelo Kafka, mesmo que na prática nunca tenha sido — resultando em perda silenciosa. A escolha padrão do Spring Kafka (capítulo 42) tende para at-least-once, exatamente por essa razão de segurança.</div>","fidelityText":"Ordem de operações importa: processar a mensagem antes de commitar o offset (o padrão mais comum, chamado \"at-least-once\") significa que uma falha entre as duas etapas causa reprocessamento — mas nunca perda de mensagem. Commitar o offset antes de processar (\"at-most-once\") é mais raro e mais arriscado: uma falha entre as duas etapas significa que a mensagem é considerada \"processada\" pelo Kafka, mesmo que na prática nunca tenha sido — resultando em perda silenciosa. A escolha padrão do Spring Kafka (capítulo 42) tende para at-least-once, exatamente por essa razão de segurança."},{"id":"event-driven-profundo-content-36","type":"html","authorship":"legacy-preserved","html":"<h2>Kafka Streams — processar dentro do próprio Kafka, sem cluster separado</h2>","fidelityText":"Kafka Streams — processar dentro do próprio Kafka, sem cluster separado"},{"id":"event-driven-profundo-content-37","type":"html","authorship":"legacy-preserved","html":"<p>Tudo até aqui tratou consumer e producer como pontas separadas de um pipeline manual. <strong>Kafka Streams</strong> é uma biblioteca Java (não um cluster de processamento à parte, como Spark/Flink) que lê de um tópico, transforma <strong>registro a registro</strong>, e escreve em outro tópico — a topologia roda dentro da própria aplicação Java, usando o consumer/producer que você já conhece por baixo.</p>","fidelityText":"Tudo até aqui tratou consumer e producer como pontas separadas de um pipeline manual. Kafka Streams é uma biblioteca Java (não um cluster de processamento à parte, como Spark/Flink) que lê de um tópico, transforma registro a registro, e escreve em outro tópico — a topologia roda dentro da própria aplicação Java, usando o consumer/producer que você já conhece por baixo."},{"id":"event-driven-profundo-code-38","type":"code","authorship":"legacy-preserved","language":"java","source":"StreamsBuilder builder = new StreamsBuilder();\n\nKStream<String, String> orders = builder.stream(\"orders-completed\");\n\norders\n    .filter((key, value) -> value.contains(\"\\\"total\\\":\")) // só pedidos com total presente\n    .mapValues(value -> value.toUpperCase())               // transforma cada registro\n    .to(\"orders-processed\");                          // escreve no tópico de saída\n\nKafkaStreams streams = new KafkaStreams(builder.build(), propertiesStreams());\nstreams.start();","fidelityText":"StreamsBuilder builder = new StreamsBuilder(); KStream<String, String> pedidos = builder.stream(\"pedidos-finalizados\"); pedidos .filter((chave, valor) -> valor.contains(\"\\\"total\\\":\")) // só pedidos com total presente .mapValues(valor -> valor.toUpperCase()) // transforma cada registro .to(\"pedidos-processados\"); // escreve no tópico de saída KafkaStreams streams = new KafkaStreams(builder.build(), propriedadesStreams()); streams.start();","highlightedHtml":"StreamsBuilder builder = <span class=\"kw\">new</span> StreamsBuilder();\n\nKStream&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; orders = builder.stream(<span class=\"str\">\"orders-completed\"</span>);\n\norders\n    .filter((key, value) -&gt; value.contains(<span class=\"str\">\"\\\"total\\\":\"</span>)) <span class=\"com\">// só pedidos com total presente</span>\n    .mapValues(value -&gt; value.toUpperCase())               <span class=\"com\">// transforma cada registro</span>\n    .to(<span class=\"str\">\"orders-processed\"</span>);                          <span class=\"com\">// escreve no tópico de saída</span>\n\nKafkaStreams streams = <span class=\"kw\">new</span> KafkaStreams(builder.build(), propertiesStreams());\nstreams.start();","caption":"Exemplo executável de event-driven-profundo.","explanation":["StreamsBuilder monta uma topologia que lê de um tópico, transforma registro a registro (filter/mapValues) e escreve em outro tópico.","A topologia roda dentro da própria aplicação Java -- não existe um cluster de processamento Kafka Streams separado do cluster Kafka."],"commonMistakes":["Achar que Kafka Streams precisa de uma infraestrutura de cluster própria, à parte do Kafka","Confundir KStream (fluxo de eventos) com KTable (visão do último valor por chave) e aplicar a operação errada para o caso"]},{"id":"event-driven-profundo-content-39","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A diferença central para o consumer/producer manual não é a API fluente — é que Kafka Streams gerencia sozinho o particionamento da topologia, o estado local (<code>KTable</code>, uma visão \"tabela\" de um tópico que representa o último valor por chave — a mesma ideia de log compaction, agora como abstração de programação) e o reprocessamento em caso de falha, tudo usando o próprio Kafka como armazenamento e coordenação — não existe um \"cluster Kafka Streams\" separado do cluster Kafka que você já opera.</div>","fidelityText":"A diferença central para o consumer/producer manual não é a API fluente — é que Kafka Streams gerencia sozinho o particionamento da topologia, o estado local (KTable, uma visão \"tabela\" de um tópico que representa o último valor por chave — a mesma ideia de log compaction, agora como abstração de programação) e o reprocessamento em caso de falha, tudo usando o próprio Kafka como armazenamento e coordenação — não existe um \"cluster Kafka Streams\" separado do cluster Kafka que você já opera."},{"id":"event-driven-profundo-content-40","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Ao desenhar um sistema orientado a eventos do zero, comece sempre respondendo três perguntas antes de escrever qualquer código: \"qual é a chave certa para garantir ordem onde ela importa?\", \"meu consumidor é genuinamente idempotente (capítulo 40)?\" e \"o que acontece se este evento for processado duas vezes, ou nunca?\" Essas três perguntas cobrem a esmagadora maioria dos bugs sutis que sistemas orientados a eventos costumam introduzir em produção.</div>","fidelityText":"Ao desenhar um sistema orientado a eventos do zero, comece sempre respondendo três perguntas antes de escrever qualquer código: \"qual é a chave certa para garantir ordem onde ela importa?\", \"meu consumidor é genuinamente idempotente (capítulo 40)?\" e \"o que acontece se este evento for processado duas vezes, ou nunca?\" Essas três perguntas cobrem a esmagadora maioria dos bugs sutis que sistemas orientados a eventos costumam introduzir em produção."},{"id":"event-driven-profundo-exercise-41","type":"exercise","authorship":"legacy-preserved","title":"Exercício 89.1 — Desenhando particionamento correto","prompt":"Para um tópico Kafka \"eventos-conta-bancaria\", publicando eventos de depósito e saque, explique qual deveria ser a chave de particionamento correta e por quê, considerando que a ordem de processamento entre depósitos e saques de uma mesma conta é crítica (processar um saque antes de um depósito anterior daria saldo incorreto), mas a ordem entre contas diferentes é irrelevante.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 89.1 — Desenhando particionamento corretodifícil Para um tópico Kafka \"eventos-conta-bancaria\", publicando eventos de depósito e saque, explique qual deveria ser a chave de particionamento correta e por quê, considerando que a ordem de processamento entre depósitos e saques de uma mesma conta é crítica (processar um saque antes de um depósito anterior daria saldo incorreto), mas a ordem entre contas diferentes é irrelevante. Ver solução A chave deveria ser o ID da conta bancária (ex: numeroConta), nunca um ID de transação individual ou uma chave nula/aleatória. Como Kafka garante ordem dentro de uma partição, usar o ID da conta como chave garante que todos os eventos daquela conta específica sempre caiam na mesma partição e sejam processados estritamente na ordem em que foram publicados — resolvendo exatamente o requisito de negócio. Ao mesmo tempo, contas diferentes (chaves diferentes) podem ser distribuídas entre partições diferentes e processadas em paralelo por múltiplos consumidores do mesmo consumer group, aproveitando o paralelismo sem sacrificar a correção onde ela realmente importa.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 89.1 — Desenhando particionamento correto</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Para um tópico Kafka <code>\"eventos-conta-bancaria\"</code>, publicando eventos de depósito e saque, explique qual deveria ser a chave de particionamento correta e por quê, considerando que a ordem de processamento entre depósitos e saques de uma <strong>mesma conta</strong> é crítica (processar um saque antes de um depósito anterior daria saldo incorreto), mas a ordem entre contas <strong>diferentes</strong> é irrelevante.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n          <p>A chave deveria ser o <strong>ID da conta bancária</strong> (ex: <code>numeroConta</code>), nunca um ID de transação individual ou uma chave nula/aleatória. Como Kafka garante ordem <strong>dentro</strong> de uma partição, usar o ID da conta como chave garante que <strong>todos</strong> os eventos daquela conta específica sempre caiam na mesma partição e sejam processados estritamente na ordem em que foram publicados — resolvendo exatamente o requisito de negócio. Ao mesmo tempo, contas diferentes (chaves diferentes) podem ser distribuídas entre partições diferentes e processadas em paralelo por múltiplos consumidores do mesmo consumer group, aproveitando o paralelismo sem sacrificar a correção onde ela realmente importa.</p>\n        </div>\n      </div>"},{"id":"event-driven-profundo-quiz","type":"quiz","authorship":"authored","conceptId":"partition-key-ordering-contract","prompt":"Quando uma partition key é boa?","options":[{"id":"edp-a","label":"Quando agrupa o fluxo que realmente exige ordem, como aggregateId de Pedido.","correct":true,"explanation":"A key deve representar o contrato de ordenação necessário."},{"id":"edp-b","label":"Quando força todos os eventos do sistema na mesma partição.","correct":false,"explanation":"Isso cria gargalo e falsa ordenação global."},{"id":"edp-c","label":"Quando muda aleatoriamente a cada mensagem para facilitar replay.","correct":false,"explanation":"Isso destrói ordenação por fluxo."}]}],"resources":[{"id":"apache-kafka-design-deep-phase16","type":"official-docs","title":"Apache Kafka: Design","url":"https://kafka.apache.org/40/documentation.html#design","reinforces":"Partições, log, retenção, ordering e consumidores.","language":"en","publisher":"Apache Kafka","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"martin-fowler-event-driven-phase16","type":"reference","title":"Martin Fowler: What do you mean by “Event-Driven”?","url":"https://martinfowler.com/articles/201701-event-driven.html","reinforces":"Diferenças de estilos event-driven e armadilhas de arquitetura.","language":"en","publisher":"Martin Fowler","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the event driven deep flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for event driven deep. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for event driven deep with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"MODEL REQUEST-RESPONSE (coupling direto):","instruction":"Design the event driven deep flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for event driven deep with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"testes-integracao-avancados","moduleId":"messaging-eda","order":11,"title":"Testes de Integração Avançados: @SpringBootTest, @DataJpaTest & Testcontainers com Kafka","summary":"O capítulo 54 já mostrou Testcontainers com Postgres. Este capítulo completa o quadro: as diferentes \"fatias\" de teste que o Spring Boot oferece, cada uma carregando só a parte do contexto necessária — e testes de integração reais com Kafka rodando em container, não simulado.","objectives":["Escolher escopo de teste para banco, Spring e broker","Subir Kafka/Testcontainers com isolamento","Aguardar efeito assíncrono sem sleep fixo","Gerar evidência útil quando o teste falha"],"whyItExists":"Depois de Kafka/Spring Kafka confiáveis, testes avançados validam fronteiras reais. A pergunta não é 'mock ou container?', mas qual risco cada camada prova: regra pura, adapter, banco, broker, listener, retry e efeito final.","prerequisiteChapterIds":["testcontainers","mockito","spring-kafka"],"conceptIds":["a-piramide-de-testes-do-spring-boot-do-mais-rapido-ao-mais-completo","datajpatest-com-testcontainers-testando-o-repository-de-verdade","testando-kafka-de-verdade-com-testcontainers"],"introducedConceptIds":["springboot-test-slice-contract","kafka-testcontainer-determinism"],"usedConceptIds":["spring-kafka-listener-template","async-awaitility-eventual-assertion","deterministic-integration-test"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"testes-integracao-avancados-intuition","type":"intuition","authorship":"authored","title":"Teste de integração prova fronteira, não sorte de timing","body":"Um teste assíncrono bom sobe dependência real quando ela importa, publica estímulo controlado e espera um efeito observável com deadline. Sleep fixo é loteria: às vezes passa lento, às vezes falha sem explicar.","analogyLimit":"É ensaio com cenário real, mas ainda precisa roteiro, isolamento e critério claro de aprovação."},{"id":"testes-integracao-avancados-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-spring\">Testes</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Multi-Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#testcontainers\">54 · Testcontainers</a>, <a class=\"prereq-tag\" href=\"#mockito\">90 · Mockito</a>, <a class=\"prereq-tag\" href=\"#spring-kafka\">42 · Spring Kafka</a></div>\n      </div>","fidelityText":"Testes Dificuldade: Multi-Avançado ⏱ ~3h de estudo + prática Pré-requisitos: 54 · Testcontainers, 90 · Mockito, 42 · Spring Kafka"},{"id":"testes-integracao-avancados-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O capítulo 54 já mostrou Testcontainers com Postgres. Este capítulo completa o quadro: as diferentes \"fatias\" de teste que o Spring Boot oferece, cada uma carregando só a parte do contexto necessária — e testes de integração reais com <strong>Kafka</strong> rodando em container, não simulado.</p>","fidelityText":"O capítulo 54 já mostrou Testcontainers com Postgres. Este capítulo completa o quadro: as diferentes \"fatias\" de teste que o Spring Boot oferece, cada uma carregando só a parte do contexto necessária — e testes de integração reais com Kafka rodando em container, não simulado."},{"id":"testes-integracao-avancados-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>A pirâmide de testes do Spring Boot — do mais rápido ao mais completo</h2>","fidelityText":"A pirâmide de testes do Spring Boot — do mais rápido ao mais completo"},{"id":"testes-integracao-avancados-code-4","type":"code","authorship":"legacy-preserved","language":"java","source":"┌─────────────────────────────────────────────────────────┐\n│  @SpringBootTest                                            │\n│  Carrega o CONTEXT INTEGER da application (all os beans)   │\n│  -- mais lento, mas testa a integração completa              │\n├─────────────────────────────────────────────────────────┤\n│  @WebMvcTest(BookController.class)                          │\n│  Carrega SO a layer web -- controllers, sem banco real       │\n│  (services/repositories geralmente mocked com @MockBean)   │\n├─────────────────────────────────────────────────────────┤\n│  @DateJpaTest                                                │\n│  Carrega SO a layer de persistence -- repositories,         │\n│  configura um bank em memory (H2) por padrao, OU pode be   │\n│  combinado com Testcontainers para um bank REAL              │\n├─────────────────────────────────────────────────────────┤\n│  Test unit plain (@ExtendWith(MockitoExtension.class))   │\n│  NENHUM context Spring carregado -- o MAIS rápido de todos    │\n│  (chapter 90) -- ideal para testar lógica de negócio isolada │\n└─────────────────────────────────────────────────────────┘\n    ↑ mais slow, mais complete          mais fast, mais isolado ↓","fidelityText":"┌─────────────────────────────────────────────────────────┐ │ @SpringBootTest │ │ Carrega o CONTEXTO INTEIRO da aplicação (todos os beans) │ │ -- mais lento, mas testa a integração completa │ ├─────────────────────────────────────────────────────────┤ │ @WebMvcTest(LivroController.class) │ │ Carrega SÓ a camada web -- controllers, sem banco real │ │ (services/repositories geralmente mockados com @MockBean) │ ├─────────────────────────────────────────────────────────┤ │ @DataJpaTest │ │ Carrega SÓ a camada de persistência -- repositories, │ │ configura um banco em memória (H2) por padrão, OU pode ser │ │ combinado com Testcontainers para um banco REAL │ ├─────────────────────────────────────────────────────────┤ │ Teste unitário puro (@ExtendWith(MockitoExtension.class)) │ │ NENHUM contexto Spring carregado -- o MAIS rápido de todos │ │ (capítulo 90) -- ideal para testar lógica de negócio isolada │ └─────────────────────────────────────────────────────────┘ ↑ mais lento, mais completo mais rápido, mais isolado ↓","highlightedHtml":"┌─────────────────────────────────────────────────────────┐\n│  @SpringBootTest                                            │\n│  Carrega o CONTEXT INTEGER da application (all os beans)   │\n│  -- mais lento, mas testa a integração completa              │\n├─────────────────────────────────────────────────────────┤\n│  @WebMvcTest(BookController.class)                          │\n│  Carrega SO a layer web -- controllers, sem banco real       │\n│  (services/repositories geralmente mocked com @MockBean)   │\n├─────────────────────────────────────────────────────────┤\n│  @DateJpaTest                                                │\n│  Carrega SO a layer de persistence -- repositories,         │\n│  configura um bank em memory (H2) por padrao, OU pode be   │\n│  combinado com Testcontainers para um bank REAL              │\n├─────────────────────────────────────────────────────────┤\n│  Test unit plain (@ExtendWith(MockitoExtension.class))   │\n│  NENHUM context Spring carregado -- o MAIS rápido de todos    │\n│  (chapter 90) -- ideal para testar lógica de negócio isolada │\n└─────────────────────────────────────────────────────────┘\n    ↑ mais slow, mais complete          mais fast, mais isolado ↓","caption":"Exemplo executável de testes-integracao-avancados.","explanation":["O setup do container deve esperar readiness antes do teste executar.","Imagem e ciclo de vida precisam ser previsíveis para a suíte."],"commonMistakes":["Assumir que porta aberta significa broker pronto","Reutilizar estado sem limpeza"]},{"id":"testes-integracao-avancados-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>@DataJpaTest com Testcontainers — testando o repository de verdade</h2>","fidelityText":"@DataJpaTest com Testcontainers — testando o repository de verdade"},{"id":"testes-integracao-avancados-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"@DateJpaTest\n@Testcontainers\n@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // NÃO usa H2 --\n                                                                                     usa o Testcontainers real\nclass BookRepositoryDataJpaTest {\n\n    @Container\n    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(\"postgres:16\");\n\n    @DynamicPropertySource\n    static void props(DynamicPropertyRegistry registry) {\n        registry.add(\"spring.datasource.url\", postgres::getJdbcUrl);\n        registry.add(\"spring.datasource.username\", postgres::getUsername);\n        registry.add(\"spring.datasource.password\", postgres::getPassword);\n    }\n\n    @Autowired\n    private BookRepository bookRepository;\n\n    @Autowired\n    private TestEntityManager entityManager; // ferramenta específica de @DataJpaTest,\n                                                    // para preparar dados de teste diretamente\n\n    @Test\n    void shouldFindBooksByAuthor() {\n        Author author = entityManager.persist(new Author(\"Orwell\"));\n        entityManager.persist(new Book(\"1984\", author));\n        entityManager.flush(); // força a escrita real no banco ANTES do teste continuar\n\n        List<Book> result = bookRepository.findByAuthorName(\"Orwell\");\n\n        assertEquals(1, result.size());\n    }\n}","fidelityText":"@DataJpaTest @Testcontainers @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // NÃO usa H2 -- usa o Testcontainers real class LivroRepositoryDataJpaTest { @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(\"postgres:16\"); @DynamicPropertySource static void props(DynamicPropertyRegistry registry) { registry.add(\"spring.datasource.url\", postgres::getJdbcUrl); registry.add(\"spring.datasource.username\", postgres::getUsername); registry.add(\"spring.datasource.password\", postgres::getPassword); } @Autowired private LivroRepository livroRepository; @Autowired private TestEntityManager entityManager; // ferramenta específica de @DataJpaTest, // para preparar dados de teste diretamente @Test void deveEncontrarLivrosPorAutor() { Autor autor = entityManager.persist(new Autor(\"Orwell\")); entityManager.persist(new Livro(\"1984\", autor)); entityManager.flush(); // força a escrita real no banco ANTES do teste continuar List<Livro> resultado = livroRepository.findByAutorNome(\"Orwell\"); assertEquals(1, resultado.size()); } }","highlightedHtml":"<span class=\"annotation\">@DateJpaTest</span>\n<span class=\"annotation\">@Testcontainers</span>\n<span class=\"annotation\">@AutoConfigureTestDatabase</span>(replace = AutoConfigureTestDatabase.Replace.NONE) <span class=\"com\">// NÃO usa H2 --\n                                                                                     usa o Testcontainers real</span>\n<span class=\"kw\">class</span> <span class=\"cls\">BookRepositoryDataJpaTest</span> {\n\n    <span class=\"annotation\">@Container</span>\n    <span class=\"kw\">static</span> PostgreSQLContainer&lt;?&gt; postgres = <span class=\"kw\">new</span> PostgreSQLContainer&lt;&gt;(<span class=\"str\">\"postgres:16\"</span>);\n\n    <span class=\"annotation\">@DynamicPropertySource</span>\n    <span class=\"kw\">static void</span> <span class=\"fn\">props</span>(DynamicPropertyRegistry registry) {\n        registry.add(<span class=\"str\">\"spring.datasource.url\"</span>, postgres::getJdbcUrl);\n        registry.add(<span class=\"str\">\"spring.datasource.username\"</span>, postgres::getUsername);\n        registry.add(<span class=\"str\">\"spring.datasource.password\"</span>, postgres::getPassword);\n    }\n\n    <span class=\"annotation\">@Autowired</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">BookRepository</span> bookRepository;\n\n    <span class=\"annotation\">@Autowired</span>\n    <span class=\"kw\">private</span> TestEntityManager entityManager; <span class=\"com\">// ferramenta específica de @DataJpaTest,\n                                                    // para preparar dados de teste diretamente</span>\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldFindBooksByAuthor</span>() {\n        <span class=\"cls\">Author</span> author = entityManager.persist(<span class=\"kw\">new</span> <span class=\"cls\">Author</span>(<span class=\"str\">\"Orwell\"</span>));\n        entityManager.persist(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"1984\"</span>, author));\n        entityManager.flush(); <span class=\"com\">// força a escrita real no banco ANTES do teste continuar</span>\n\n        List&lt;<span class=\"cls\">Book</span>&gt; result = bookRepository.findByAuthorName(<span class=\"str\">\"Orwell\"</span>);\n\n        assertEquals(1, result.size());\n    }\n}","caption":"Exemplo executável de testes-integracao-avancados.","explanation":["A configuração dinâmica injeta bootstrap servers ou URLs reais do container no contexto Spring.","Isso evita hardcode de localhost/porta que falha em CI."],"commonMistakes":["Fixar porta aleatória no código","Subir contexto inteiro quando um slice bastaria"]},{"id":"testes-integracao-avancados-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Repare a diferença de <code>@DataJpaTest</code> em relação a <code>@SpringBootTest</code> completo do capítulo 54: aqui, <strong>só</strong> os beans relacionados à camada JPA são carregados (repositories, <code>EntityManager</code>) — nenhum <code>@Controller</code>, nenhum <code>@Service</code>. Isso torna a suíte de testes significativamente mais rápida quando você só precisa validar comportamento de persistência, sem pagar o custo de subir a aplicação inteira para cada teste.</div>","fidelityText":"Repare a diferença de @DataJpaTest em relação a @SpringBootTest completo do capítulo 54: aqui, só os beans relacionados à camada JPA são carregados (repositories, EntityManager) — nenhum @Controller, nenhum @Service. Isso torna a suíte de testes significativamente mais rápida quando você só precisa validar comportamento de persistência, sem pagar o custo de subir a aplicação inteira para cada teste."},{"id":"testes-integracao-avancados-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Testando Kafka de verdade com Testcontainers</h2>","fidelityText":"Testando Kafka de verdade com Testcontainers"},{"id":"testes-integracao-avancados-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"@SpringBootTest\n@Testcontainers\nclass OrderEventPublisherTest {\n\n    @Container\n    static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"apache/kafka:3.7.0\"));\n\n    @DynamicPropertySource\n    static void props(DynamicPropertyRegistry registry) {\n        registry.add(\"spring.kafka.bootstrap-servers\", kafka::getBootstrapServers);\n    }\n\n    @Autowired\n    private OrderEventPublisher publisher; // capítulo 42\n\n    @Test\n    void shouldPublishEventQueUmConsumerConsegueRead() throws Exception {\n        CountDownLatch latch = new CountDownLatch(1); // sincroniza a espera pela mensagem assíncrona\n        List<String> messagesRecebidas = new ArrayList<>();\n\n        // cria um CONSUMER REAL, conectado ao mesmo Kafka em container:\n        Properties consumerProps = new Properties();\n        consumerProps.put(\"bootstrap.servers\", kafka.getBootstrapServers());\n        consumerProps.put(\"group.id\", \"test\");\n        consumerProps.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n        consumerProps.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n        consumerProps.put(\"auto.offset.reset\", \"earliest\");\n\n        try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps)) {\n            consumer.subscribe(List.of(\"orders-completed\"));\n\n            publisher.publish(\"order-99\"); // AÇÃO: publica de verdade\n\n            var records = consumer.poll(Duration.ofSeconds(5)); // espera de verdade pela mensagem\n            assertEquals(1, records.count());\n            assertTrue(records.iterator().next().value().contains(\"order-99\"));\n        }\n    }\n}","fidelityText":"@SpringBootTest @Testcontainers class PedidoEventPublisherTest { @Container static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"apache/kafka:3.7.0\")); @DynamicPropertySource static void props(DynamicPropertyRegistry registry) { registry.add(\"spring.kafka.bootstrap-servers\", kafka::getBootstrapServers); } @Autowired private PedidoEventPublisher publisher; // capítulo 42 @Test void devePublicarEventoQueUmConsumerConsegueLer() throws Exception { CountDownLatch latch = new CountDownLatch(1); // sincroniza a espera pela mensagem assíncrona List<String> mensagensRecebidas = new ArrayList<>(); // cria um CONSUMER REAL, conectado ao mesmo Kafka em container: Properties consumerProps = new Properties(); consumerProps.put(\"bootstrap.servers\", kafka.getBootstrapServers()); consumerProps.put(\"group.id\", \"teste\"); consumerProps.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\"); consumerProps.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\"); consumerProps.put(\"auto.offset.reset\", \"earliest\"); try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerProps)) { consumer.subscribe(List.of(\"pedidos-finalizados\")); publisher.publicar(\"pedido-99\"); // AÇÃO: publica de verdade var registros = consumer.poll(Duration.ofSeconds(5)); // espera de verdade pela mensagem assertEquals(1, registros.count()); assertTrue(registros.iterator().next().value().contains(\"pedido-99\")); } } }","highlightedHtml":"<span class=\"annotation\">@SpringBootTest</span>\n<span class=\"annotation\">@Testcontainers</span>\n<span class=\"kw\">class</span> <span class=\"cls\">OrderEventPublisherTest</span> {\n\n    <span class=\"annotation\">@Container</span>\n    <span class=\"kw\">static</span> KafkaContainer kafka = <span class=\"kw\">new</span> KafkaContainer(DockerImageName.parse(<span class=\"str\">\"apache/kafka:3.7.0\"</span>));\n\n    <span class=\"annotation\">@DynamicPropertySource</span>\n    <span class=\"kw\">static void</span> <span class=\"fn\">props</span>(DynamicPropertyRegistry registry) {\n        registry.add(<span class=\"str\">\"spring.kafka.bootstrap-servers\"</span>, kafka::getBootstrapServers);\n    }\n\n    <span class=\"annotation\">@Autowired</span>\n    <span class=\"kw\">private</span> <span class=\"cls\">OrderEventPublisher</span> publisher; <span class=\"com\">// capítulo 42</span>\n\n    <span class=\"annotation\">@Test</span>\n    <span class=\"kw\">void</span> <span class=\"fn\">shouldPublishEventQueUmConsumerConsegueRead</span>() <span class=\"kw\">throws</span> <span class=\"cls\">Exception</span> {\n        <span class=\"cls\">CountDownLatch</span> latch = <span class=\"kw\">new</span> <span class=\"cls\">CountDownLatch</span>(1); <span class=\"com\">// sincroniza a espera pela mensagem assíncrona</span>\n        List&lt;<span class=\"kw\">String</span>&gt; messagesRecebidas = <span class=\"kw\">new</span> ArrayList&lt;&gt;();\n\n        <span class=\"com\">// cria um CONSUMER REAL, conectado ao mesmo Kafka em container:</span>\n        Properties consumerProps = <span class=\"kw\">new</span> Properties();\n        consumerProps.put(<span class=\"str\">\"bootstrap.servers\"</span>, kafka.getBootstrapServers());\n        consumerProps.put(<span class=\"str\">\"group.id\"</span>, <span class=\"str\">\"test\"</span>);\n        consumerProps.put(<span class=\"str\">\"key.deserializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringDeserializer\"</span>);\n        consumerProps.put(<span class=\"str\">\"value.deserializer\"</span>, <span class=\"str\">\"org.apache.kafka.common.serialization.StringDeserializer\"</span>);\n        consumerProps.put(<span class=\"str\">\"auto.offset.reset\"</span>, <span class=\"str\">\"earliest\"</span>);\n\n        <span class=\"kw\">try</span> (KafkaConsumer&lt;<span class=\"kw\">String</span>, <span class=\"kw\">String</span>&gt; consumer = <span class=\"kw\">new</span> KafkaConsumer&lt;&gt;(consumerProps)) {\n            consumer.subscribe(List.of(<span class=\"str\">\"orders-completed\"</span>));\n\n            publisher.publish(<span class=\"str\">\"order-99\"</span>); <span class=\"com\">// AÇÃO: publica de verdade</span>\n\n            <span class=\"kw\">var</span> records = consumer.poll(Duration.ofSeconds(5)); <span class=\"com\">// espera de verdade pela mensagem</span>\n            assertEquals(1, records.count());\n            assertTrue(records.iterator().next().value().contains(<span class=\"str\">\"order-99\"</span>));\n        }\n    }\n}","caption":"Exemplo executável de testes-integracao-avancados.","explanation":["A assertiva deve esperar efeito final com deadline e mensagem de falha útil.","O teste fica mais confiável quando observa banco, evento consumido ou estado persistido."],"commonMistakes":["Usar sleep fixo","Assertar apenas que publish não lançou exceção"]},{"id":"testes-integracao-avancados-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Testes de integração com Kafka são inerentemente mais lentos e complexos</b> — envolvem timing assíncrono real (não simulado), exigindo esperas com timeout em vez de asserções instantâneas. Reserve esse tipo de teste para os fluxos mais críticos de mensageria, e confie em testes unitários com Mockito (capítulo 90) para a maior parte da lógica de negócio ao redor da publicação/consumo de eventos.</div>","fidelityText":"Testes de integração com Kafka são inerentemente mais lentos e complexos — envolvem timing assíncrono real (não simulado), exigindo esperas com timeout em vez de asserções instantâneas. Reserve esse tipo de teste para os fluxos mais críticos de mensageria, e confie em testes unitários com Mockito (capítulo 90) para a maior parte da lógica de negócio ao redor da publicação/consumo de eventos."},{"id":"testes-integracao-avancados-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O exemplo acima usa <code>@DynamicPropertySource</code> manual. Desde o Spring Boot 3.1, <code>@ServiceConnection</code> no campo do container (o mesmo padrão já visto com Testcontainers Postgres) elimina esse boilerplate — o Spring Boot reconhece o <code>KafkaContainer</code> e injeta <code>spring.kafka.bootstrap-servers</code> sozinho. Prefira <code>@ServiceConnection</code> como padrão moderno; caia para <code>@DynamicPropertySource</code> só quando o tipo de container não for reconhecido automaticamente.</div>","fidelityText":"O exemplo acima usa @DynamicPropertySource manual. Desde o Spring Boot 3.1, @ServiceConnection no campo do container (o mesmo padrão já visto com Testcontainers Postgres) elimina esse boilerplate — o Spring Boot reconhece o KafkaContainer e injeta spring.kafka.bootstrap-servers sozinho. Prefira @ServiceConnection como padrão moderno; caia para @DynamicPropertySource só quando o tipo de container não for reconhecido automaticamente."},{"id":"testes-integracao-avancados-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Ao decidir qual \"fatia\" de teste do Spring usar, pergunte: \"o que exatamente eu preciso validar?\" Se é lógica de negócio pura, teste unitário com Mockito. Se é uma query JPA específica, <code>@DataJpaTest</code>. Se é o comportamento HTTP de um controller (status codes, serialização), <code>@WebMvcTest</code>. Só recorra a <code>@SpringBootTest</code> completo (o mais lento) quando genuinamente precisar validar a integração de múltiplas camadas juntas.</div>","fidelityText":"Ao decidir qual \"fatia\" de teste do Spring usar, pergunte: \"o que exatamente eu preciso validar?\" Se é lógica de negócio pura, teste unitário com Mockito. Se é uma query JPA específica, @DataJpaTest. Se é o comportamento HTTP de um controller (status codes, serialização), @WebMvcTest. Só recorra a @SpringBootTest completo (o mais lento) quando genuinamente precisar validar a integração de múltiplas camadas juntas."},{"id":"testes-integracao-avancados-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Exercício 91.1 — @DataJpaTest com Testcontainers","prompt":"Escreva um teste @DataJpaTest com Testcontainers Postgres para o LivroRepository, usando TestEntityManager para persistir um autor e dois livros, e confirmando que findByAutorNome retorna exatamente os dois livros esperados, não mais nem menos.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 91.1 — @DataJpaTest com Testcontainersdifícil Escreva um teste @DataJpaTest com Testcontainers Postgres para o LivroRepository, usando TestEntityManager para persistir um autor e dois livros, e confirmando que findByAutorNome retorna exatamente os dois livros esperados, não mais nem menos. Ver solução @Test void deveEncontrarTodosOsLivrosDoAutor() { Autor orwell = entityManager.persist(new Autor(\"Orwell\")); entityManager.persist(new Livro(\"1984\", orwell)); entityManager.persist(new Livro(\"A Revolução dos Bichos\", orwell)); Autor outroAutor = entityManager.persist(new Autor(\"Machado de Assis\")); entityManager.persist(new Livro(\"Dom Casmurro\", outroAutor)); entityManager.flush(); List<Livro> resultado = livroRepository.findByAutorNome(\"Orwell\"); assertEquals(2, resultado.size()); }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 91.1 — @DataJpaTest com Testcontainers</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Escreva um teste <code>@DataJpaTest</code> com Testcontainers Postgres para o <code>LivroRepository</code>, usando <code>TestEntityManager</code> para persistir um autor e dois livros, e confirmando que <code>findByAutorNome</code> retorna exatamente os dois livros esperados, não mais nem menos.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Test</span>\n<span class=\"kw\">void</span> <span class=\"fn\">shouldFindAllOsBooksOfAuthor</span>() {\n    <span class=\"cls\">Author</span> orwell = entityManager.persist(<span class=\"kw\">new</span> <span class=\"cls\">Author</span>(<span class=\"str\">\"Orwell\"</span>));\n    entityManager.persist(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"1984\"</span>, orwell));\n    entityManager.persist(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"The Revolucao of the Bichos\"</span>, orwell));\n    <span class=\"cls\">Author</span> otherAuthor = entityManager.persist(<span class=\"kw\">new</span> <span class=\"cls\">Author</span>(<span class=\"str\">\"Machado de Assis\"</span>));\n    entityManager.persist(<span class=\"kw\">new</span> <span class=\"cls\">Book</span>(<span class=\"str\">\"Dom Casmurro\"</span>, otherAuthor));\n    entityManager.flush();\n\n    List&lt;<span class=\"cls\">Book</span>&gt; result = bookRepository.findByAuthorName(<span class=\"str\">\"Orwell\"</span>);\n\n    assertEquals(2, result.size());\n}</pre>\n        </div>\n      </div>"},{"id":"testes-integracao-avancados-quiz","type":"quiz","authorship":"authored","conceptId":"kafka-testcontainer-determinism","prompt":"Qual prática deixa teste Kafka mais determinístico?","options":[{"id":"tia-a","label":"Topic/grupo isolados por teste, readiness explícito e assertiva eventual do efeito.","correct":true,"explanation":"Isso reduz vazamento entre testes e espera comportamento, não tempo arbitrário."},{"id":"tia-b","label":"Thread.sleep alto depois de publicar.","correct":false,"explanation":"Sleep não prova condição e torna falha opaca."},{"id":"tia-c","label":"Compartilhar o mesmo topic com toda suíte sem limpeza.","correct":false,"explanation":"Estado compartilhado causa flakiness."}]}],"resources":[{"id":"testcontainers-kafka-phase16","type":"official-docs","title":"Testcontainers Kafka Module","url":"https://java.testcontainers.org/modules/kafka/","reinforces":"Kafka container, classes atuais e configuração para testes.","language":"en","publisher":"Testcontainers","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-boot-testing-phase16","type":"official-docs","title":"Spring Boot: Testing","url":"https://docs.spring.io/spring-boot/reference/testing/index.html","reinforces":"Test slices, @SpringBootTest e escopo de teste Spring.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the tests integration advanced flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for tests integration advanced. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for tests integration advanced with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"↑ mais slow, mais complete          mais fast, mais isolado ↓","instruction":"Design the tests integration advanced flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for tests integration advanced with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mini-pedidos-eventos","moduleId":"distributed-consistency","order":4,"title":"Mini-projeto: pedidos orientados a eventos","summary":"Separe pedidos, estoque e notificações usando Kafka como broker. A criação de pedido publica evento; estoque reserva de forma idempotente; falhas vão para estratégia de retentativa e dead-letter (retry topic + DLT, capítulo 42).","objectives":["Entregar um fluxo de pedidos orientado a eventos com evidência","Provar outbox/inbox, idempotência e reprocessamento","Escolher Saga por coreografia ou orquestração e justificar","Registrar observabilidade, falhas e critérios de aceite reproduzíveis"],"whyItExists":"O projeto fecha a trilha distribuída exigindo provas, não só arquitetura no papel. O aluno precisa demonstrar caminho feliz, duplicação, atraso, crash, compensação, replay, métricas e documentação de decisão.","prerequisiteChapterIds":["saga-schema-ordering","async-integration-tests","event-driven-profundo"],"conceptIds":["provas-exigidas","checkpoint-antes-de-concluir","execucao-guiada-e-evidencias"],"introducedConceptIds":["event-driven-project-evidence"],"usedConceptIds":["outbox-atomic-publish-intent","inbox-dedup-effect-transaction","saga-choreography-orchestration","compensating-action-domain-state","async-awaitility-eventual-assertion","eda-correlation-causality"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"mini-pedidos-eventos-intuition","type":"intuition","authorship":"authored","title":"Projeto distribuído bom entrega evidência de falha, não só caminho feliz","body":"Um fluxo de pedidos por eventos só fica convincente quando outra pessoa consegue derrubar o relay, duplicar mensagem, atrasar consumidor, quebrar schema e ainda ver o sistema terminar em estado explicado. A arquitetura vale pelo comportamento observável sob pressão.","analogyLimit":"Checklist de entrega ajuda, mas aqui cada item precisa de teste, log, métrica ou runbook reproduzível."},{"id":"mini-pedidos-eventos-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><div class=\"meta-item\">Objetivo: <b>Kafka e consistência</b></div><div class=\"time-est\">Tempo: <b>20–35 horas</b></div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#spring-kafka\">Spring Kafka</a>, <a class=\"prereq-tag\" href=\"#event-driven-profundo\">Event-driven</a></div></div>","fidelityText":"Objetivo: Kafka e consistênciaTempo: 20–35 horasPré-requisitos: Spring Kafka, Event-driven"},{"id":"mini-pedidos-eventos-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Separe pedidos, estoque e notificações usando Kafka como broker. A criação de pedido publica evento; estoque reserva de forma idempotente; falhas vão para estratégia de retentativa e dead-letter (retry topic + DLT, capítulo 42).</p>","fidelityText":"Separe pedidos, estoque e notificações usando Kafka como broker. A criação de pedido publica evento; estoque reserva de forma idempotente; falhas vão para estratégia de retentativa e dead-letter (retry topic + DLT, capítulo 42)."},{"id":"mini-pedidos-eventos-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Provas exigidas</h2>","fidelityText":"Provas exigidas"},{"id":"mini-pedidos-eventos-checklist-4","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"mini-pedidos-eventos-checklist-0","label":"Todos os eventos do mesmo pedido usam o pedidoId como chave de partição — a ordem entre eles é preservada, sem exigir ordem global entre pedidos diferentes."},{"id":"mini-pedidos-eventos-checklist-1","label":"O consumer commita offset manualmente só depois de aplicar o efeito (nunca antes) — matando o processo entre \"aplicar efeito\" e \"commitar offset\" reprocessa sem duplicar (idempotência real, não sorte)."},{"id":"mini-pedidos-eventos-checklist-2","label":"Consumir o mesmo evento duas vezes não duplica reserva."},{"id":"mini-pedidos-eventos-checklist-3","label":"Evento contém versão e identificador de correlação."},{"id":"mini-pedidos-eventos-checklist-4","label":"Falha temporária é diferente de mensagem inválida permanente — mensagem inválida vai para DLT, não fica em retry infinito."},{"id":"mini-pedidos-eventos-checklist-5","label":"Teste de integração sobe um broker Kafka real via Testcontainers (capítulo 54), não mock."},{"id":"mini-pedidos-eventos-checklist-6","label":"Documento descreve consistência eventual e como o usuário percebe estados intermediários."}]},{"id":"mini-pedidos-eventos-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\"><b>Questão central:</b> como evitar gravar o pedido e falhar antes de publicar o evento? Pesquise e compare transactional outbox com transação distribuída antes de decidir.</div>","fidelityText":"Questão central: como evitar gravar o pedido e falhar antes de publicar o evento? Pesquise e compare transactional outbox com transação distribuída antes de decidir."},{"id":"mini-pedidos-eventos-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Checkpoint antes de concluir</h2>","fidelityText":"Checkpoint antes de concluir"},{"id":"mini-pedidos-eventos:0","type":"quiz","authorship":"legacy-preserved","conceptId":"como-impedir-efeito-duplicado-no-consumidor","prompt":"Como impedir efeito duplicado no consumidor?","options":[{"id":"mini-pedidos-eventos:0:option:0","label":"Deduplicação e efeito na mesma transação local, usando event ID.","correct":true,"explanation":"A mesma transação local deve proteger a marca de processamento e o efeito para absorver redelivery."},{"id":"mini-pedidos-eventos:0:option:1","label":"Confiar que at-least-once nunca repete.","correct":false,"explanation":"At-least-once permite repetição; confiar no contrário é justamente o erro que o projeto precisa provar."},{"id":"mini-pedidos-eventos:0:option:2","label":"Remover a chave da mensagem.","correct":false,"explanation":"Remover a key destrói rastreabilidade e não protege o efeito de domínio."}],"sourceIndex":7},{"id":"mini-pedidos-eventos:1","type":"quiz","authorship":"legacy-preserved","conceptId":"todos-os-eventos-de-um-mesmo-pedido-usam-o-pedidoid-como-chave-de-partic","prompt":"Todos os eventos de um mesmo pedido usam o pedidoId como chave de partição. Dois pedidos diferentes, A e B, têm seus eventos publicados em partições diferentes. O que isso garante e o que NÃO garante?","options":[{"id":"mini-pedidos-eventos:1:option:0","label":"Garante ordem entre eventos do mesmo pedido (mesma partição); não garante nenhuma ordem relativa entre eventos de pedidos diferentes (partições diferentes).","correct":true,"explanation":"Kafka garante ordem apenas dentro de uma partição; usar pedidoId como chave mantém todos os eventos do mesmo pedido na mesma partição, preservando a ordem entre eles."},{"id":"mini-pedidos-eventos:1:option:1","label":"Garante ordem global entre todos os pedidos do tópico, independente da partição.","correct":false,"explanation":"Não existe ordem global entre partições diferentes -- essa é exatamente a limitação que o projeto precisa reconhecer, não ignorar."},{"id":"mini-pedidos-eventos:1:option:2","label":"Não garante nada, porque Kafka nunca preserva ordem dentro de uma partição.","correct":false,"explanation":"Kafka preserva ordem dentro da partição; a chave de partição é o mecanismo usado precisamente para explorar essa garantia."}],"sourceIndex":8},{"id":"mini-pedidos-eventos:2","type":"quiz","authorship":"legacy-preserved","conceptId":"uma-implementacao-grava-o-pedido-no-banco-e-em-seguida-chama-o-broker-ka","prompt":"Uma implementação grava o pedido no banco e, em seguida, chama o broker Kafka diretamente para publicar o evento, na mesma requisição HTTP. O processo cai exatamente entre o commit do banco e a chamada ao broker. O que acontece?","options":[{"id":"mini-pedidos-eventos:2:option:0","label":"O pedido existe no banco, mas o evento nunca foi publicado -- um estado inconsistente que o padrão outbox existe justamente para evitar, publicando a partir de uma tabela lida na mesma transação do banco.","correct":true,"explanation":"Esse é o problema clássico de dual-write que o outbox resolve: publicar a partir de uma tabela lida na mesma transação do banco elimina a janela entre \"gravou\" e \"publicou\"."},{"id":"mini-pedidos-eventos:2:option:1","label":"Nada, já que o Kafka sempre recebe a mensagem mesmo se o processo cair logo depois de enviá-la.","correct":false,"explanation":"Uma chamada de rede ao broker pode falhar ou nunca ser executada se o processo cair antes dela -- não há garantia implícita de entrega."},{"id":"mini-pedidos-eventos:2:option:2","label":"O problema só existe se o broker estiver fora do ar no momento exato da chamada.","correct":false,"explanation":"O problema existe mesmo com o broker saudável, porque a falha está no processo da aplicação entre as duas operações, não no broker."}],"sourceIndex":9},{"id":"mini-pedidos-eventos:3","type":"quiz","authorship":"legacy-preserved","conceptId":"uma-mensagem-malformada-schema-invalido-entra-em-loop-de-retry-porque-o-","prompt":"Uma mensagem malformada (schema inválido) entra em loop de retry porque o consumidor sempre lança exceção ao tentar desserializá-la. O que o projeto exige nesse caso?","options":[{"id":"mini-pedidos-eventos:3:option:0","label":"Distinguir falha temporária (retry) de mensagem permanentemente inválida (vai para dead-letter topic), evitando retry infinito que bloqueia o processamento das próximas mensagens.","correct":true,"explanation":"Separar falha temporária de falha permanente é o que evita que uma mensagem malformada trave o processamento de todas as mensagens seguintes na mesma partição."},{"id":"mini-pedidos-eventos:3:option:1","label":"Aumentar o número máximo de tentativas até a mensagem eventualmente ser processada com sucesso.","correct":false,"explanation":"Mais tentativas não ajudam quando o problema é o formato da mensagem, não uma indisponibilidade passageira -- ela vai continuar falhando."},{"id":"mini-pedidos-eventos:3:option:2","label":"Ignorar a exceção silenciosamente e avançar o offset sem processar a mensagem.","correct":false,"explanation":"Avançar o offset silenciosamente descarta a mensagem sem registro nem possibilidade de diagnóstico ou reprocessamento posterior."}],"sourceIndex":10},{"id":"mini-pedidos-eventos-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"mini-pedidos-eventos-exercise-prova","type":"exercise","authorship":"authored","title":"Antes de codificar: prova de consistência antes de escrever a primeira linha","prompt":"Antes de implementar, escreva por escrito: (1) que chave de partição cada tipo de evento vai usar e por que isso preserva a ordem necessária; (2) o design do outbox -- como garantir que o pedido gravado e o evento a publicar entram na mesma transação local; (3) o teste de crash que você vai rodar (em que ponto exato o processo é morto) para provar que nenhum evento se perde; (4) o critério que distingue uma mensagem que deve ir para retry de uma que deve ir direto para dead-letter.","difficulty":"advanced","criteria":["A resposta 1 justifica a chave de partição pela necessidade de ordem por pedido, não por conveniência arbitrária.","A resposta 2 descreve o outbox como parte da mesma transação de banco que grava o pedido, não uma chamada separada ao broker.","A resposta 3 nomeia um ponto de crash específico e testável (ex.: após commit do banco, antes do relay publicar), não uma descrição vaga de \"testar falha\".","A resposta 4 declara explicitamente o que torna uma falha \"temporária\" (retry) vs \"permanente\" (DLT)."]},{"id":"mini-pedidos-eventos-project","type":"project","authorship":"authored","title":"Pedidos orientados a eventos com provas de consistência","brief":"Construa um fluxo de pedido, reserva de estoque, pagamento simulado e confirmação/cancelamento usando eventos, outbox/inbox e uma Saga explícita. Entregue testes e relatório de evidência.","requirements":["Criar pedido com estado inicial persistido","Publicar intenção por outbox e relay recuperável","Consumidores idempotentes com inbox/dedup local","Saga escolhida e justificada como coreografia ou orquestração","Compensação para falha de pagamento ou estoque","Testes de duplicação, crash, schema inválido e replay","Logs/metrics com eventId, correlationId e estado final"],"guidance":"independent","acceptanceCriteria":["Crash após gravar pedido não perde evento.","Mensagem duplicada não duplica reserva, cobrança simulada ou confirmação.","Falha permanente vai para caminho diagnosticável e não retry infinito.","Relatório mostra comandos, evidências e trade-offs sem exigir conhecimento fora da trilha."],"knowledgeMatrix":[{"requirement":"Publicação sem janela perdida","conceptIds":["outbox-atomic-publish-intent","relay-retry-duplication-window"],"chapterIds":["outbox-inbox","mini-pedidos-eventos"],"expectedEvidence":"Kill test derruba o relay em janelas críticas e prova republicação sem perda."},{"requirement":"Consumo idempotente","conceptIds":["inbox-dedup-effect-transaction","delivery-at-least-once-idempotency"],"chapterIds":["delivery-failure-lab","outbox-inbox"],"expectedEvidence":"Reentrega do mesmo eventId não duplica efeito no banco."},{"requirement":"Processo distribuído explícito","conceptIds":["saga-choreography-orchestration","compensating-action-domain-state"],"chapterIds":["saga-schema-ordering","mini-pedidos-eventos"],"expectedEvidence":"Diagrama e testes cobrem estados intermediários, compensação e falha de compensação."},{"requirement":"Evidência operacional","conceptIds":["event-driven-project-evidence","eda-correlation-causality","async-awaitility-eventual-assertion"],"chapterIds":["eda-observability-security","async-integration-tests","mini-pedidos-eventos"],"expectedEvidence":"Testes sem sleep fixo, logs correlacionáveis e métricas/runbook reproduzíveis."}]},{"id":"mini-pedidos-eventos-quiz","type":"quiz","authorship":"authored","conceptId":"event-driven-project-evidence","prompt":"O que aprova o mini-projeto de pedidos por eventos?","options":[{"id":"mpe-a","label":"Provas reproduzíveis de caminho feliz, duplicação, crash, compensação, replay e observabilidade.","correct":true,"explanation":"Projeto distribuído precisa demonstrar comportamento sob falha, não apenas código compilando."},{"id":"mpe-b","label":"Um diagrama bonito com Kafka no centro.","correct":false,"explanation":"Diagrama sem teste/evidência não prova comportamento distribuído."},{"id":"mpe-c","label":"Remover retries para nunca duplicar mensagem.","correct":false,"explanation":"Isso pode perder trabalho e não resolve falha parcial."}]}],"resources":[{"id":"microservices-outbox-phase17-project","type":"reference","title":"Transactional Outbox pattern","url":"https://microservices.io/patterns/data/transactional-outbox.html","reinforces":"Padrão central do projeto para publicar eventos sem janela de perda entre banco e broker.","language":"en","publisher":"microservices.io","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"microservices-saga-phase17-project","type":"reference","title":"Saga pattern","url":"https://microservices.io/patterns/data/saga.html","reinforces":"Coordenação de transações locais com coreografia/orquestração e compensações.","language":"en","publisher":"microservices.io","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the event-driven orders project flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for event-driven orders project. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for event-driven orders project with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"// Observe how names reveal the event-driven orders project contract.","instruction":"Design the event-driven orders project flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for event-driven orders project with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"},"editorialReview":{"requiredTopics":["ordenacao-por-chave-de-particao","outbox-elimina-janela-de-dual-write","commit-de-offset-apos-efeito-idempotente","dead-letter-vs-retry-infinito","evidencia-de-falha-sob-pressao"],"evidenceBlocks":{"ordenacao-por-chave-de-particao":["mini-pedidos-eventos-checklist-4","mini-pedidos-eventos:1","mini-pedidos-eventos-exercise-prova"],"outbox-elimina-janela-de-dual-write":["mini-pedidos-eventos-content-5","mini-pedidos-eventos:2","mini-pedidos-eventos-project"],"commit-de-offset-apos-efeito-idempotente":["mini-pedidos-eventos-checklist-4","mini-pedidos-eventos:0","mini-pedidos-eventos-project"],"dead-letter-vs-retry-infinito":["mini-pedidos-eventos-checklist-4","mini-pedidos-eventos:3","mini-pedidos-eventos-exercise-prova"],"evidencia-de-falha-sob-pressao":["mini-pedidos-eventos-content-11","mini-pedidos-eventos-quiz","mini-pedidos-eventos-project"]},"primarySources":["Transactional Outbox pattern -- https://microservices.io/patterns/data/transactional-outbox.html","Saga pattern -- https://microservices.io/patterns/data/saga.html"],"factualReviewedAt":"2026-08-24","pedagogicalReviewedAt":"2026-08-24","openIssues":[]}},{"id":"estruturas-avancadas","moduleId":"algorithms-data-structures","order":4,"title":"Estruturas de Dados Avançadas","summary":"Você domina List, Set e Map desde o capítulo 11. Este capítulo cobre as estruturas por trás delas e outras que aparecem constantemente em entrevistas técnicas e em problemas reais que ArrayList/HashMap não resolvem elegantemente.","objectives":["Escolher pilha, fila, árvore ou grafo pelo contrato semântico","Relacionar cada estrutura ao custo e ao caso de uso","Implementar travessias sem perder invariantes","Explicar limites: árvore desbalanceada, fila vazia e grafo com ciclos"],"whyItExists":"Depois de Big-O, recursão e Collections, o aluno já consegue estudar estruturas não como nomes decorados, mas como contratos de acesso. Pilha, fila, árvore e grafo aparecem aqui para expandir o repertório antes de projetos maiores e arquitetura.","prerequisiteChapterIds":["big-o","recursao","colecoes"],"conceptIds":["pilha-stack-ultimo-a-entrar-primeiro-a-sair-lifo","fila-queue-primeiro-a-entrar-primeiro-a-sair-fifo","arvore-binaria-a-base-de-indices-de-banco-e-buscas-eficientes","grafo-modelando-relacionamentos-que-nao-sao-hierarquicos"],"introducedConceptIds":["lifo-stack-discipline","fifo-queue-discipline","tree-invariant-search-cost","graph-adjacency-traversal"],"usedConceptIds":["complexidade-assintotica","recursao-caso-base","contrato-collection-map","equals-hashcode-contrato"],"estimatedMinutes":60,"englishLevel":1,"blocks":[{"id":"estruturas-avancadas-intuition","type":"intuition","authorship":"authored","title":"Estrutura de dados é contrato de acesso, não coleção com nome chique","body":"A pergunta boa não é “qual estrutura parece avançada?”, mas “como os dados precisam entrar, sair, ser buscados e se relacionar?”. Pilha protege topo, fila protege chegada, árvore protege uma ordem hierárquica e grafo protege conexões livres.","analogyLimit":"Pratos, filas e mapas ajudam a começar, mas código precisa lidar com vazio, duplicidade, ciclo, altura, custo e invariantes."},{"id":"estruturas-avancadas-compare","type":"comparison","authorship":"authored","title":"Escolha pela operação dominante","criteria":["Acesso permitido","Uso comum","Custo que costuma importar","Erro clássico"],"alternatives":[{"name":"Pilha","values":["push/pop no topo","undo, chamadas, parsing","O(1) no topo"],"useWhen":"o último item pendente deve ser resolvido primeiro","avoidWhen":"você precisa buscar/remover elemento arbitrário"},{"name":"Fila","values":["offer/poll na ordem de chegada","tarefas, BFS, atendimento","O(1) nas pontas quando a implementação ajuda"],"useWhen":"ordem de chegada é parte da regra","avoidWhen":"prioridade ou ordenação por peso é o contrato real"},{"name":"Árvore de busca","values":["decisão esquerda/direita por invariante","busca ordenada, índices conceituais","depende da altura"],"useWhen":"ordem e busca por comparação importam","avoidWhen":"você não preserva balanceamento/invariante"},{"name":"Grafo","values":["nós e arestas livres","rotas, dependências, redes","depende de V+E na travessia"],"useWhen":"relações não são hierárquicas","avoidWhen":"você ignora visited e cria ciclo infinito"}]},{"id":"estruturas-avancadas-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-java\">Algoritmos</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~3h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#recursao\">81 · Recursão</a>, <a class=\"prereq-tag\" href=\"#colecoes\">11 · Coleções</a></div>\n      </div>","fidelityText":"Algoritmos Dificuldade: Avançado ⏱ ~3h de estudo + prática Pré-requisitos: 81 · Recursão, 11 · Coleções"},{"id":"estruturas-avancadas-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Você domina <code>List</code>, <code>Set</code> e <code>Map</code> desde o capítulo 11. Este capítulo cobre as estruturas por trás delas e outras que aparecem constantemente em entrevistas técnicas e em problemas reais que <code>ArrayList</code>/<code>HashMap</code> não resolvem elegantemente.</p>","fidelityText":"Você domina List, Set e Map desde o capítulo 11. Este capítulo cobre as estruturas por trás delas e outras que aparecem constantemente em entrevistas técnicas e em problemas reais que ArrayList/HashMap não resolvem elegantemente."},{"id":"estruturas-avancadas-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Pilha (Stack) — último a entrar, primeiro a sair (LIFO)</h2>","fidelityText":"Pilha (Stack) — último a entrar, primeiro a sair (LIFO)"},{"id":"estruturas-avancadas-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Uma pilha de pratos: você só pode adicionar ou remover pelo topo. O último prato colocado é sempre o primeiro a ser retirado.</div>","fidelityText":"Uma pilha de pratos: você só pode adicionar ou remover pelo topo. O último prato colocado é sempre o primeiro a ser retirado."},{"id":"estruturas-avancadas-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"Deque<String> stack = new ArrayDeque<>(); // Java não tem uma classe \"Stack\" idiomática -- usa-se Deque\nstack.push(\"a\");\nstack.push(\"b\");\nstack.pop(); // remove \"b\" -- o último que entrou\n\n// uso real: a própria stack de chamadas de função (capítulo 01, 81) é uma pilha!\n// e é assim que o método \"undo\" de um editor de texto costuma funcionar","fidelityText":"Deque<String> pilha = new ArrayDeque<>(); // Java não tem uma classe \"Stack\" idiomática -- usa-se Deque pilha.push(\"a\"); pilha.push(\"b\"); pilha.pop(); // remove \"b\" -- o último que entrou // uso real: a própria stack de chamadas de função (capítulo 01, 81) é uma pilha! // e é assim que o método \"undo\" de um editor de texto costuma funcionar","highlightedHtml":"Deque&lt;<span class=\"kw\">String</span>&gt; stack = <span class=\"kw\">new</span> ArrayDeque&lt;&gt;(); <span class=\"com\">// Java não tem uma classe \"Stack\" idiomática -- usa-se Deque</span>\nstack.push(<span class=\"str\">\"a\"</span>);\nstack.push(<span class=\"str\">\"b\"</span>);\nstack.pop(); <span class=\"com\">// remove \"b\" -- o último que entrou</span>\n\n<span class=\"com\">// uso real: a própria stack de chamadas de função (capítulo 01, 81) é uma pilha!\n// e é assim que o método \"undo\" de um editor de texto costuma funcionar</span>","caption":"Exemplo executável de estruturas-avancadas.","explanation":["ArrayDeque é a escolha idiomática para pilha em Java moderno quando você precisa de push/pop no topo.","O exemplo mostra a disciplina LIFO: o último elemento inserido é o primeiro removido."],"commonMistakes":["Usar Stack antigo sem necessidade","Tentar remover elemento arbitrário e ainda chamar de pilha"]},{"id":"estruturas-avancadas-content-6","type":"html","authorship":"legacy-preserved","html":"<h2>Fila (Queue) — primeiro a entrar, primeiro a sair (FIFO)</h2>","fidelityText":"Fila (Queue) — primeiro a entrar, primeiro a sair (FIFO)"},{"id":"estruturas-avancadas-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Uma fila de banco: quem chegou primeiro é atendido primeiro. Você já implementou isso, sem saber o nome formal, no exercício 15.2 (<code>Fila&lt;T&gt;</code>) e viu na prática no Kafka (capítulo 41, onde consumer groups processam mensagens em ordem de chegada).</div>","fidelityText":"Uma fila de banco: quem chegou primeiro é atendido primeiro. Você já implementou isso, sem saber o nome formal, no exercício 15.2 (Fila<T>) e viu na prática no Kafka (capítulo 41, onde consumer groups processam mensagens em ordem de chegada)."},{"id":"estruturas-avancadas-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"Queue<String> queue = new LinkedList<>();\nqueue.offer(\"a\");\nqueue.offer(\"b\");\nqueue.poll(); // remove \"a\" -- o primeiro que entrou","fidelityText":"Queue<String> fila = new LinkedList<>(); fila.offer(\"a\"); fila.offer(\"b\"); fila.poll(); // remove \"a\" -- o primeiro que entrou","highlightedHtml":"Queue&lt;<span class=\"kw\">String</span>&gt; queue = <span class=\"kw\">new</span> LinkedList&lt;&gt;();\nqueue.offer(<span class=\"str\">\"a\"</span>);\nqueue.offer(<span class=\"str\">\"b\"</span>);\nqueue.poll(); <span class=\"com\">// remove \"a\" -- o primeiro que entrou</span>","caption":"Exemplo executável de estruturas-avancadas.","explanation":["Queue expõe offer/poll para entrada e saída na ordem FIFO.","Em produção, ArrayDeque costuma ser melhor fila geral que LinkedList quando não há requisito específico."],"commonMistakes":["Ignorar retorno null de poll em fila vazia","Confundir FIFO com fila de prioridade"]},{"id":"estruturas-avancadas-content-9","type":"html","authorship":"legacy-preserved","html":"<h2>Árvore Binária — a base de índices de banco e buscas eficientes</h2>","fidelityText":"Árvore Binária — a base de índices de banco e buscas eficientes"},{"id":"estruturas-avancadas-code-10","type":"code","authorship":"legacy-preserved","language":"java","source":"class No {\n    int value;\n    No left, right;\n    No(int value) { this.value = value; }\n}\n\n// árvore de busca: esquerda menor, direita maior.\n// Busca é O(log n) quando a altura é logarítmica; sem balanceamento,\n// inserções ordenadas podem formar uma lista e degradar para O(n).\nboolean find(No root, int alvo) {\n    if (root == null) return false;              // caso base da recursão\n    if (root.value == alvo) return true;\n    return alvo < root.value\n        ? find(root.left, alvo)      // descarta a metade direita inteira\n        : find(root.right, alvo);      // descarta a metade esquerda inteira\n}","fidelityText":"class No { int valor; No esquerda, direita; No(int valor) { this.valor = valor; } } // árvore de busca: esquerda menor, direita maior. // Busca é O(log n) quando a altura é logarítmica; sem balanceamento, // inserções ordenadas podem formar uma lista e degradar para O(n). boolean buscar(No raiz, int alvo) { if (raiz == null) return false; // caso base da recursão if (raiz.valor == alvo) return true; return alvo < raiz.valor ? buscar(raiz.esquerda, alvo) // descarta a metade direita inteira : buscar(raiz.direita, alvo); // descarta a metade esquerda inteira }","highlightedHtml":"<span class=\"kw\">class</span> <span class=\"cls\">No</span> {\n    <span class=\"kw\">int</span> value;\n    <span class=\"cls\">No</span> left, right;\n    <span class=\"fn\">No</span>(<span class=\"kw\">int</span> value) { <span class=\"kw\">this</span>.value = value; }\n}\n\n<span class=\"com\">// árvore de busca: esquerda menor, direita maior.\n// Busca é O(log n) quando a altura é logarítmica; sem balanceamento,\n// inserções ordenadas podem formar uma lista e degradar para O(n).</span>\n<span class=\"kw\">boolean</span> <span class=\"fn\">find</span>(<span class=\"cls\">No</span> root, <span class=\"kw\">int</span> alvo) {\n    <span class=\"kw\">if</span> (root == <span class=\"kw\">null</span>) <span class=\"kw\">return false</span>;              <span class=\"com\">// caso base da recursão</span>\n    <span class=\"kw\">if</span> (root.value == alvo) <span class=\"kw\">return true</span>;\n    <span class=\"kw\">return</span> alvo &lt; root.value\n        ? find(root.left, alvo)      <span class=\"com\">// descarta a metade direita inteira</span>\n        : find(root.right, alvo);      <span class=\"com\">// descarta a metade esquerda inteira</span>\n}","caption":"Exemplo executável de estruturas-avancadas.","explanation":["A busca recursiva depende do invariante: menores à esquerda, maiores à direita.","O custo só fica logarítmico se a altura permanecer pequena; inserção ordenada pode degradar para lista."],"commonMistakes":["Dizer que toda árvore binária é O(log n)","Alterar nó sem preservar o invariante"]},{"id":"estruturas-avancadas-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Este é o mesmo raciocínio da lista telefônica ordenada do capítulo 80 — a cada passo, metade dos dados restantes é descartada sem nunca ser examinada. É também, estruturalmente, muito próximo de como um índice B-tree do Postgres (capítulo 34) funciona por baixo dos panos: uma árvore balanceada, permitindo que <code>EXPLAIN ANALYZE</code> mostre <code>Index Scan</code> em vez de <code>Seq Scan</code>.</div>","fidelityText":"Este é o mesmo raciocínio da lista telefônica ordenada do capítulo 80 — a cada passo, metade dos dados restantes é descartada sem nunca ser examinada. É também, estruturalmente, muito próximo de como um índice B-tree do Postgres (capítulo 34) funciona por baixo dos panos: uma árvore balanceada, permitindo que EXPLAIN ANALYZE mostre Index Scan em vez de Seq Scan."},{"id":"estruturas-avancadas-content-12","type":"html","authorship":"legacy-preserved","html":"<h2>Grafo — modelando relacionamentos que não são hierárquicos</h2>","fidelityText":"Grafo — modelando relacionamentos que não são hierárquicos"},{"id":"estruturas-avancadas-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Uma árvore tem uma hierarquia clara (um pai, vários filhos). Um <strong>grafo</strong> é mais livre: qualquer nó pode se conectar a qualquer outro, em qualquer direção — como o mapa de conexões de uma rede social, ou o mapa de rotas entre cidades.</div>","fidelityText":"Uma árvore tem uma hierarquia clara (um pai, vários filhos). Um grafo é mais livre: qualquer nó pode se conectar a qualquer outro, em qualquer direção — como o mapa de conexões de uma rede social, ou o mapa de rotas entre cidades."},{"id":"estruturas-avancadas-code-14","type":"code","authorship":"legacy-preserved","language":"java","source":"// representação simples: lista de adjacência\nMap<String, List<String>> graph = new HashMap<>();\ngraph.put(\"A\", List.of(\"B\", \"C\"));\ngraph.put(\"B\", List.of(\"D\"));\ngraph.put(\"C\", List.of(\"D\"));\ngraph.put(\"D\", List.of());\n\n// busca em largura (BFS) -- percorre \"camada por camada\", usando uma Fila:\nvoid bfs(String start) {\n    Queue<String> queue = new LinkedList<>();\n    Set<String> visited = new HashSet<>();\n    queue.offer(start);\n    visited.add(start);\n\n    while (!queue.isEmpty()) {\n        String current = queue.poll();\n        System.out.println(current);\n        for (String neighbor : graph.get(current)) {\n            if (!visited.contains(neighbor)) {\n                visited.add(neighbor);\n                queue.offer(neighbor);\n            }\n        }\n    }\n}","fidelityText":"// representação simples: lista de adjacência Map<String, List<String>> grafo = new HashMap<>(); grafo.put(\"A\", List.of(\"B\", \"C\")); grafo.put(\"B\", List.of(\"D\")); grafo.put(\"C\", List.of(\"D\")); grafo.put(\"D\", List.of()); // busca em largura (BFS) -- percorre \"camada por camada\", usando uma Fila: void bfs(String inicio) { Queue<String> fila = new LinkedList<>(); Set<String> visitados = new HashSet<>(); fila.offer(inicio); visitados.add(inicio); while (!fila.isEmpty()) { String atual = fila.poll(); System.out.println(atual); for (String vizinho : grafo.get(atual)) { if (!visitados.contains(vizinho)) { visitados.add(vizinho); fila.offer(vizinho); } } } }","highlightedHtml":"<span class=\"com\">// representação simples: lista de adjacência</span>\nMap&lt;<span class=\"kw\">String</span>, List&lt;<span class=\"kw\">String</span>&gt;&gt; graph = <span class=\"kw\">new</span> HashMap&lt;&gt;();\ngraph.put(<span class=\"str\">\"A\"</span>, List.of(<span class=\"str\">\"B\"</span>, <span class=\"str\">\"C\"</span>));\ngraph.put(<span class=\"str\">\"B\"</span>, List.of(<span class=\"str\">\"D\"</span>));\ngraph.put(<span class=\"str\">\"C\"</span>, List.of(<span class=\"str\">\"D\"</span>));\ngraph.put(<span class=\"str\">\"D\"</span>, List.of());\n\n<span class=\"com\">// busca em largura (BFS) -- percorre \"camada por camada\", usando uma Fila:</span>\n<span class=\"kw\">void</span> <span class=\"fn\">bfs</span>(<span class=\"kw\">String</span> start) {\n    Queue&lt;<span class=\"kw\">String</span>&gt; queue = <span class=\"kw\">new</span> LinkedList&lt;&gt;();\n    Set&lt;<span class=\"kw\">String</span>&gt; visited = <span class=\"kw\">new</span> HashSet&lt;&gt;();\n    queue.offer(start);\n    visited.add(start);\n\n    <span class=\"kw\">while</span> (!queue.isEmpty()) {\n        <span class=\"kw\">String</span> current = queue.poll();\n        System.out.println(current);\n        <span class=\"kw\">for</span> (<span class=\"kw\">String</span> neighbor : graph.get(current)) {\n            <span class=\"kw\">if</span> (!visited.contains(neighbor)) {\n                visited.add(neighbor);\n                queue.offer(neighbor);\n            }\n        }\n    }\n}","caption":"Exemplo executável de estruturas-avancadas.","explanation":["Lista de adjacência representa cada nó e seus vizinhos; BFS usa fila para avançar por camadas.","O conjunto visited evita ciclo infinito e repetição de processamento."],"commonMistakes":["Não registrar o nó como visitado antes de enfileirar","Assumir que todo nó existe como chave no mapa"]},{"id":"estruturas-avancadas-content-15","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Grafos são a estrutura conceitual por trás de problemas de \"menor caminho\"</b> (rotas de entrega, redes de dependência de pacotes Maven, capítulo 19) — reconhecer quando um problema real é \"na verdade um grafo\" é frequentemente mais difícil do que implementar o algoritmo em si.</div>","fidelityText":"Grafos são a estrutura conceitual por trás de problemas de \"menor caminho\" (rotas de entrega, redes de dependência de pacotes Maven, capítulo 19) — reconhecer quando um problema real é \"na verdade um grafo\" é frequentemente mais difícil do que implementar o algoritmo em si."},{"id":"estruturas-avancadas-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não decore implementações de estruturas de dados de cor — foque em reconhecer <em>qual estrutura combina com qual formato de problema</em>. Pilha para \"desfazer/histórico\". Fila para \"processar na ordem de chegada\" (o próprio Kafka, capítulo 41). Árvore para \"hierarquia com busca eficiente\". Grafo para \"relacionamentos livres, não hierárquicos\". Essa associação problema→estrutura é o que realmente é cobrado, na prática e em entrevistas.</div>","fidelityText":"Não decore implementações de estruturas de dados de cor — foque em reconhecer qual estrutura combina com qual formato de problema. Pilha para \"desfazer/histórico\". Fila para \"processar na ordem de chegada\" (o próprio Kafka, capítulo 41). Árvore para \"hierarquia com busca eficiente\". Grafo para \"relacionamentos livres, não hierárquicos\". Essa associação problema→estrutura é o que realmente é cobrado, na prática e em entrevistas."},{"id":"estruturas-avancadas-exercise-17","type":"exercise","authorship":"legacy-preserved","title":"Exercício 82.1 — Verificador de parênteses balanceados","prompt":"Usando uma Deque como pilha, escreva um método boolean balanceado(String expressao) que verifica se todos os parênteses/colchetes/chaves de uma expressão estão corretamente balanceados (ex: \"(a[b]{c})\" → true, \"(a[b)]\" → false).","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 82.1 — Verificador de parênteses balanceadosdifícil Usando uma Deque como pilha, escreva um método boolean balanceado(String expressao) que verifica se todos os parênteses/colchetes/chaves de uma expressão estão corretamente balanceados (ex: \"(a[b]{c})\" → true, \"(a[b)]\" → false). Ver solução boolean balanceado(String expressao) { Deque<Character> pilha = new ArrayDeque<>(); Map<Character, Character> pares = Map.of(')', '(', ']', '[', '}', '{'); for (char c : expressao.toCharArray()) { if (c == '(' || c == '[' || c == '{') { pilha.push(c); } else if (pares.containsKey(c)) { if (pilha.isEmpty() || pilha.pop() != pares.get(c)) return false; } } return pilha.isEmpty(); // tudo que abriu precisa ter fechado }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 82.1 — Verificador de parênteses balanceados</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Usando uma <code>Deque</code> como pilha, escreva um método <code>boolean balanceado(String expressao)</code> que verifica se todos os parênteses/colchetes/chaves de uma expressão estão corretamente balanceados (ex: <code>\"(a[b]{c})\"</code> → true, <code>\"(a[b)]\"</code> → false).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">boolean</span> <span class=\"fn\">balanceado</span>(<span class=\"kw\">String</span> expressao) {\n    Deque&lt;<span class=\"kw\">Character</span>&gt; stack = <span class=\"kw\">new</span> ArrayDeque&lt;&gt;();\n    Map&lt;<span class=\"kw\">Character</span>, <span class=\"kw\">Character</span>&gt; pares = Map.of(<span class=\"str\">')'</span>, <span class=\"str\">'('</span>, <span class=\"str\">']'</span>, <span class=\"str\">'['</span>, <span class=\"str\">'}'</span>, <span class=\"str\">'{'</span>);\n\n    <span class=\"kw\">for</span> (<span class=\"kw\">char</span> c : expressao.toCharArray()) {\n        <span class=\"kw\">if</span> (c == <span class=\"str\">'('</span> || c == <span class=\"str\">'['</span> || c == <span class=\"str\">'{'</span>) {\n            stack.push(c);\n        } <span class=\"kw\">else if</span> (pares.containsKey(c)) {\n            <span class=\"kw\">if</span> (stack.isEmpty() || stack.pop() != pares.get(c)) <span class=\"kw\">return false</span>;\n        }\n    }\n    <span class=\"kw\">return</span> stack.isEmpty(); <span class=\"com\">// tudo que abriu precisa ter fechado</span>\n}</pre>\n        </div>\n      </div>"},{"id":"estruturas-avancadas-quiz","type":"quiz","authorship":"authored","conceptId":"graph-adjacency-traversal","prompt":"Por que BFS em grafo precisa de conjunto de visitados?","options":[{"id":"ea-a","label":"Para não revisitar nós em ciclos e para limitar a travessia ao que ainda não foi explorado.","correct":true,"explanation":"Grafo pode ter ciclos; visited transforma a travessia em processo finito e previsível."},{"id":"ea-b","label":"Para transformar qualquer grafo em árvore binária balanceada.","correct":false,"explanation":"Visited controla travessia; não muda a estrutura do grafo."},{"id":"ea-c","label":"Para garantir que toda busca em grafo seja O(1).","correct":false,"explanation":"Travessia ainda depende de nós e arestas percorridos."}]}],"resources":[{"id":"oracle-deque-api-phase18","type":"official-docs","title":"Java Deque API","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Deque.html","reinforces":"Contrato de pilha/fila dupla, push, pop, offer e poll em Java.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"oracle-map-api-phase18","type":"official-docs","title":"Java Map API","url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html","reinforces":"Representação de lista de adjacência por chave e coleção de vizinhos.","language":"en","publisher":"Oracle","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":1,"label":"Guided reading","readPassage":"A structures advanced operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","comprehensionQuestion":"What does the operation receive, and under which condition does it fail?","contextSupport":"Procure primeiro sujeito, ação, condição e resultado. Consulte palavras isoladas somente depois de formular uma hipótese.","documentationTask":"In the official reference, find one sentence that describes a structures advanced operation. Use its example to confirm your interpretation.","productionTask":"Write an English search query about one real doubt from this structures advanced chapter, then record the answer in one sentence.","successCriterion":"The query names the technology, behavior, and failure or result you need to understand.","codeContext":"Deque<String> stack = new ArrayDeque<>(); // Java não tem uma classe \"Stack\" idiomática -- usa-se Deque","instruction":"A structures advanced operation receives input, checks a condition, and returns an observable result. Invalid input should fail explicitly instead of hiding the problem.","prompt":"Write an English search query about one real doubt from this structures advanced chapter, then record the answer in one sentence."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"code-review-adr","moduleId":"professional-final","order":0,"title":"Code Review & Architecture Decision Records","summary":"Escrever código bom sozinho é só metade da engenharia de software profissional — a outra metade é comunicar decisões técnicas de forma que outras pessoas (incluindo você mesmo, seis meses depois) entendam o porquê, não só o o quê.","objectives":["Revisar código por intenção, risco e contrato","Separar comentário útil de preferência pessoal","Registrar decisões arquiteturais em ADR curto","Conectar ADR a testes, operação e consequências"],"whyItExists":"Depois de Git, Clean Code, arquitetura e projetos, o aluno precisa aprender a trabalhar como parte de um time: explicar mudança, revisar risco e preservar memória técnica. Code review e ADR transformam conhecimento individual em decisão compartilhada.","prerequisiteChapterIds":["git","clean-code","arquitetura-software"],"conceptIds":["code-review-revisando-com-intencao-nao-so-procurando-erro","architecture-decision-records-adr-a-memoria-da-arquitetura"],"introducedConceptIds":["review-intent-risk-check","adr-context-decision-consequence"],"usedConceptIds":["git-snapshot-index","nome-intencao-codigo","hexagonal-port-adapter","bounded-context-context-map"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"code-review-adr-intuition","type":"intuition","authorship":"authored","title":"Review bom protege o futuro do código sem virar disputa de gosto","body":"Uma revisão útil pergunta: a mudança faz o que promete? O risco está testado? O contrato público mudou? A decisão ficará compreensível daqui a seis meses? O objetivo não é humilhar nem carimbar PR; é aumentar a chance de o sistema continuar operável.","analogyLimit":"Checklist de piloto ajuda, mas software inclui comportamento, contrato, deploy, dados e manutenção futura."},{"id":"code-review-adr-table","type":"table","authorship":"authored","title":"Checklist de revisão que vale comentário","headers":["Pergunta","Comentário útil","Comentário fraco"],"rows":[["Comportamento","qual caso falha e como reproduzir?","não gostei"],["Contrato","API/payload/status mudou? há compatibilidade?","renomeia porque sim"],["Teste","qual risco não está coberto?","faltou teste genérico"],["Operação","log, métrica, fallback ou migração mudaram?","parece perigoso"],["Decisão","isso merece ADR? quais alternativas foram descartadas?","documenta tudo sempre"]]},{"id":"code-review-adr-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Engenharia</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i><i></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#git\">29 · Git</a>, <a class=\"prereq-tag\" href=\"#clean-code\">74 · Clean Code</a></div>\n      </div>","fidelityText":"Engenharia Dificuldade: Intermediário ⏱ ~1h30 de estudo Pré-requisitos: 29 · Git, 74 · Clean Code"},{"id":"code-review-adr-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Escrever código bom sozinho é só metade da engenharia de software profissional — a outra metade é comunicar decisões técnicas de forma que outras pessoas (incluindo você mesmo, seis meses depois) entendam o <em>porquê</em>, não só o <em>o quê</em>.</p>","fidelityText":"Escrever código bom sozinho é só metade da engenharia de software profissional — a outra metade é comunicar decisões técnicas de forma que outras pessoas (incluindo você mesmo, seis meses depois) entendam o porquê, não só o o quê."},{"id":"code-review-adr-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Code Review — revisando com intenção, não só procurando erro</h2>","fidelityText":"Code Review — revisando com intenção, não só procurando erro"},{"id":"code-review-adr-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Nível</th><th>O que revisar</th></tr>\n        <tr><td>Corretude</td><td>O código faz o que deveria? Cobre os casos-limite discutidos no capítulo 15?</td></tr>\n        <tr><td>Design</td><td>Segue SOLID (capítulo 16)? Está no lugar certo da arquitetura (capítulo 75)?</td></tr>\n        <tr><td>Legibilidade</td><td>Nomes claros (capítulo 74)? Um novo desenvolvedor entenderia sem perguntar?</td></tr>\n        <tr><td>Segurança</td><td>Alguma validação faltando (capítulo 48)? Secret vazando (capítulo 57)?</td></tr>\n      </tbody></table>","fidelityText":"NívelO que revisar CorretudeO código faz o que deveria? Cobre os casos-limite discutidos no capítulo 15? DesignSegue SOLID (capítulo 16)? Está no lugar certo da arquitetura (capítulo 75)? LegibilidadeNomes claros (capítulo 74)? Um novo desenvolvedor entenderia sem perguntar? SegurançaAlguma validação faltando (capítulo 48)? Secret vazando (capítulo 57)?"},{"id":"code-review-adr-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um bom code review é como um piloto e copiloto revisando o checklist antes da decolagem juntos — não é desconfiança de que o piloto errou, é reconhecer que uma segunda perspectiva pega coisas que a primeira, sozinha e cansada de tanto olhar o mesmo código, deixa passar. O objetivo nunca é \"pegar\" quem escreveu, é elevar a qualidade coletiva do que vai para produção.</div>","fidelityText":"Um bom code review é como um piloto e copiloto revisando o checklist antes da decolagem juntos — não é desconfiança de que o piloto errou, é reconhecer que uma segunda perspectiva pega coisas que a primeira, sozinha e cansada de tanto olhar o mesmo código, deixa passar. O objetivo nunca é \"pegar\" quem escreveu, é elevar a qualidade coletiva do que vai para produção."},{"id":"code-review-adr-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"rules-box\">\n        <h2>Regras de ouro ao revisar código de outra pessoa</h2>\n        <ul>\n          <li>Comente o <em>código</em>, nunca a <em>pessoa</em> — \"esse método poderia ficar mais claro assim\" em vez de \"você escreveu isso errado\".</li>\n          <li>Separe sugestões obrigatórias (\"isso vai quebrar em produção\") de preferências estéticas (\"eu preferiria assim, mas não é bloqueante\") — muitas ferramentas de PR permitem marcar isso explicitamente.</li>\n          <li>Revise em blocos pequenos — um Pull Request de 2000 linhas recebe revisão superficial por fadiga; PRs pequenos e frequentes (capítulo 29) recebem revisão de verdade.</li>\n        </ul>\n      </div>","fidelityText":"Regras de ouro ao revisar código de outra pessoa Comente o código, nunca a pessoa — \"esse método poderia ficar mais claro assim\" em vez de \"você escreveu isso errado\". Separe sugestões obrigatórias (\"isso vai quebrar em produção\") de preferências estéticas (\"eu preferiria assim, mas não é bloqueante\") — muitas ferramentas de PR permitem marcar isso explicitamente. Revise em blocos pequenos — um Pull Request de 2000 linhas recebe revisão superficial por fadiga; PRs pequenos e frequentes (capítulo 29) recebem revisão de verdade."},{"id":"code-review-adr-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Architecture Decision Records (ADR) — a memória da arquitetura</h2>","fidelityText":"Architecture Decision Records (ADR) — a memória da arquitetura"},{"id":"code-review-adr-content-8","type":"html","authorship":"legacy-preserved","html":"<p>Meses depois de uma decisão técnica importante (\"por que escolhemos MongoDB em vez de Postgres para esse módulo?\"), ninguém lembra o raciocínio — só o resultado. Um <strong>ADR</strong> é um documento curto, versionado junto com o código, registrando <em>por que</em> uma decisão arquitetural foi tomada.</p>","fidelityText":"Meses depois de uma decisão técnica importante (\"por que escolhemos MongoDB em vez de Postgres para esse módulo?\"), ninguém lembra o raciocínio — só o resultado. Um ADR é um documento curto, versionado junto com o código, registrando por que uma decisão arquitetural foi tomada."},{"id":"code-review-adr-code-9","type":"code","authorship":"legacy-preserved","language":"java","source":"# docs/adr/0003-usar-mongodb-para-catalogo.md\n\n## Status\nAccepted\n\n## Contexto\nO catalog de products tem atributos que variam muito entre categories\n(chapter 36) -- roupas têm tamanho/cor, eletrônicos têm voltagem/garantia.\nModel isso em tables relational exigiria uma estrutura de atributos\ngenerics difficult de query e manter.\n\n## Decisão\nUse MongoDB para o service de catalog, mantendo Postgres para orders\ne payments (que exigem transactions fortes, chapter 33).\n\n## Consequências\n+ Schema flexible sem migrations a cada new category de product\n- Introduz um second bank no stack, aumentando complexidade operational\n- Team precisa de familiaridade com queries MongoDB (chapter 37)","fidelityText":"# docs/adr/0003-usar-mongodb-para-catalogo.md ## Status Aceito ## Contexto O catálogo de produtos tem atributos que variam muito entre categorias (capítulo 36) -- roupas têm tamanho/cor, eletrônicos têm voltagem/garantia. Modelar isso em tabelas relacionais exigiria uma estrutura de atributos genéricos difícil de consultar e manter. ## Decisão Usar MongoDB para o serviço de catálogo, mantendo Postgres para pedidos e pagamentos (que exigem transações fortes, capítulo 33). ## Consequências + Esquema flexível sem migrations a cada nova categoria de produto - Introduz um segundo banco no stack, aumentando complexidade operacional - Equipe precisa de familiaridade com consultas MongoDB (capítulo 37)","highlightedHtml":"<span class=\"com\"># docs/adr/0003-usar-mongodb-para-catalogo.md</span>\n\n## Status\nAccepted\n\n## Contexto\nO catalog de products tem atributos que variam muito entre categories\n(chapter 36) -- roupas têm tamanho/cor, eletrônicos têm voltagem/garantia.\nModel isso em tables relational exigiria uma estrutura de atributos\ngenerics difficult de query e manter.\n\n## Decisão\nUse MongoDB para o service de catalog, mantendo Postgres para orders\ne payments (que exigem transactions fortes, chapter 33).\n\n## Consequências\n+ Schema flexible sem migrations a cada new category de product\n- Introduz um second bank no stack, aumentando complexidade operational\n- Team precisa de familiaridade com queries MongoDB (chapter 37)","caption":"Exemplo executável de code-review-adr.","explanation":["O ADR usa estrutura curta: status, contexto, decisão e consequências.","A decisão MongoDB/Postgres é justificada por shape de dados, transações e custo operacional, não por preferência solta."],"commonMistakes":["Registrar decisão sem alternativa descartada","Esconder consequência negativa","Transformar ADR em documentação longa que ninguém revisa"]},{"id":"code-review-adr-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">ADRs não documentam <em>o que</em> foi decidido (isso o próprio código já mostra) — documentam o <strong>raciocínio e o contexto</strong> no momento da decisão, incluindo as alternativas descartadas e por quê. Isso evita o problema clássico de alguém, anos depois, \"corrigir\" uma decisão arquitetural sem saber que ela já foi cuidadosamente avaliada e descartada por um motivo específico que não estava mais visível em lugar nenhum.</div>","fidelityText":"ADRs não documentam o que foi decidido (isso o próprio código já mostra) — documentam o raciocínio e o contexto no momento da decisão, incluindo as alternativas descartadas e por quê. Isso evita o problema clássico de alguém, anos depois, \"corrigir\" uma decisão arquitetural sem saber que ela já foi cuidadosamente avaliada e descartada por um motivo específico que não estava mais visível em lugar nenhum."},{"id":"code-review-adr-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Comece pequeno: escreva seu primeiro ADR para a próxima decisão não-trivial que você tomar em qualquer projeto pessoal (por que Redis e não Memcached, por que REST e não GraphQL). O formato não precisa ser elaborado — um arquivo Markdown com Contexto/Decisão/Consequências, como no exemplo, já captura o essencial.</div>","fidelityText":"Comece pequeno: escreva seu primeiro ADR para a próxima decisão não-trivial que você tomar em qualquer projeto pessoal (por que Redis e não Memcached, por que REST e não GraphQL). O formato não precisa ser elaborado — um arquivo Markdown com Contexto/Decisão/Consequências, como no exemplo, já captura o essencial."},{"id":"code-review-adr-exercise-12","type":"exercise","authorship":"legacy-preserved","title":"Exercício 77.1 — Escrevendo seu primeiro ADR","prompt":"Escreva um ADR documentando a decisão de usar JWT em vez de sessão tradicional (capítulo 51) para autenticação da API da biblioteca, incluindo contexto, decisão e pelo menos duas consequências (uma positiva, uma negativa).","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 77.1 — Escrevendo seu primeiro ADRfácil Escreva um ADR documentando a decisão de usar JWT em vez de sessão tradicional (capítulo 51) para autenticação da API da biblioteca, incluindo contexto, decisão e pelo menos duas consequências (uma positiva, uma negativa). Ver solução # ADR 0001: Usar JWT em vez de sessão tradicional ## Status Aceito ## Contexto A API da biblioteca é consumida por um front-end React separado (SPA) e, potencialmente, um app mobile no futuro -- múltiplos clientes independentes do back-end. ## Decisão Usar autenticação stateless via JWT (capítulo 51), em vez de sessão tradicional com cookie. ## Consequências + Não exige compartilhar estado de sessão entre múltiplas instâncias do back-end (capítulo 43, escopo Singleton) -- escala horizontalmente sem configuração extra de sessão distribuída. - Revogar um token antes da expiração é mais complexo que simplesmente apagar uma sessão no servidor -- exige lista de revogação se necessário.","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 77.1 — Escrevendo seu primeiro ADR</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Escreva um ADR documentando a decisão de usar JWT em vez de sessão tradicional (capítulo 51) para autenticação da API da biblioteca, incluindo contexto, decisão e pelo menos duas consequências (uma positiva, uma negativa).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"># ADR 0001: Usar JWT em vez de sessão tradicional\n\n## Status\nAccepted\n\n## Contexto\nA API da library e consumida por um front-end React separate (SPA)\ne, potencialmente, um app mobile no future -- múltiplos clientes\nindependentes do back-end.\n\n## Decisão\nUse authentication stateless via JWT (chapter 51), em vez de session\ntradicional com cookie.\n\n## Consequências\n+ Nao requires compartilhar state de session entre multiplas instances\n  do back-end (chapter 43, scope Singleton) -- escala horizontalmente\n  sem configuration extra de session distribuida.\n- Revogar um token before da expiracao e mais complexo que simply\n  apagar uma session no server -- exige lista de revogação se necessário.</pre>\n        </div>\n      </div>"},{"id":"code-review-adr-quiz","type":"quiz","authorship":"authored","conceptId":"adr-context-decision-consequence","prompt":"Quando um ADR é realmente útil?","options":[{"id":"cra-a","label":"Quando uma decisão técnica relevante tem contexto, alternativas, escolha e consequências que o time precisará lembrar.","correct":true,"explanation":"ADR registra raciocínio e trade-off, não só o resultado."},{"id":"cra-b","label":"Quando qualquer variável local é renomeada.","correct":false,"explanation":"Isso é detalhe de implementação; ADR deve focar decisão arquitetural relevante."},{"id":"cra-c","label":"Quando o time quer evitar testes e conversas de review.","correct":false,"explanation":"ADR complementa revisão e evidência, não substitui validação."}]}],"resources":[{"id":"google-eng-practices-review","type":"reference","title":"Google Engineering Practices: Code Review Developer Guide","url":"https://google.github.io/eng-practices/review/","reinforces":"Critérios práticos de revisão, comentários e aprovação de mudanças.","language":"en","publisher":"Google","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"adr-github-io","type":"reference","title":"Architecture Decision Records","url":"https://adr.github.io/","reinforces":"Formato, propósito e exemplos de ADRs.","language":"en","publisher":"ADR GitHub organization","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the code review adr flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for code review adr. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for code review adr with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"# docs/adr/0003-usar-mongodb-para-catalogo.md","instruction":"Design the code review adr flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for code review adr with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"agile","moduleId":"professional-final","order":1,"title":"Metodologias Ágeis: Scrum & Kanban","summary":"Todo o resto deste curso ensinou como escrever software. Metodologias ágeis organizam como um time decide o que construir, em que ordem, e como acompanha o progresso — o contexto de processo em que praticamente todo trabalho profissional acontece.","objectives":["Entender ágil como feedback e adaptação, não cerimônia","Diferenciar Scrum e Kanban pelo tipo de fluxo","Usar WIP para reduzir trabalho parado","Transformar backlog em incremento demonstrável com evidência"],"whyItExists":"Depois de projetos e review, processo entra como ferramenta de aprendizado e entrega. Scrum e Kanban não são religião: são formas de tornar trabalho visível, limitar desperdício e colher feedback antes que uma decisão errada fique cara.","prerequisiteChapterIds":["git","projeto","code-review-adr"],"conceptIds":["por-que-agil-existe-o-problema-do-waterfall","scrum-trabalho-em-ciclos-fixos-sprints","kanban-fluxo-continuo-sem-ciclos-fixos"],"introducedConceptIds":["scrum-feedback-cadence","kanban-wip-flow"],"usedConceptIds":["project-evidence-readme","branch-merge-rebase","review-intent-risk-check"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"agile-intuition","type":"intuition","authorship":"authored","title":"Ágil é reduzir atraso de feedback, não colecionar cerimônia","body":"O problema não é escolher nomes bonitos para reunião. O problema é descobrir cedo se estamos construindo a coisa certa, se o trabalho está bloqueado e se o incremento pode ser demonstrado. Scrum organiza cadência; Kanban organiza fluxo contínuo e WIP.","analogyLimit":"Quadro de tarefas ajuda, mas fluxo real depende de critérios de pronto, revisão, testes e bloqueios visíveis."},{"id":"agile-comparison","type":"comparison","authorship":"authored","title":"Scrum e Kanban por problema resolvido","criteria":["Unidade de planejamento","Sinal principal","Quando ajuda","Risco comum"],"alternatives":[{"name":"Scrum","values":["Sprint/timebox","objetivo do sprint e incremento revisável"],"useWhen":"o time precisa de cadência forte de planejamento, review e retrospectiva","avoidWhen":"a sprint vira teatro sem entrega demonstrável"},{"name":"Kanban","values":["Fluxo contínuo","WIP, tempo de ciclo e bloqueios"],"useWhen":"trabalho chega continuamente e gargalos precisam ficar visíveis","avoidWhen":"o quadro vira depósito infinito de tarefas em progresso"}]},{"id":"agile-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-devops\">Engenharia</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i><i></i><i></i></span> <b>Iniciante</b></div>\n        <div class=\"time-est\">⏱ <b>~1h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#git\">29 · Git</a></div>\n      </div>","fidelityText":"Engenharia Dificuldade: Iniciante ⏱ ~1h de estudo Pré-requisitos: 29 · Git"},{"id":"agile-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Todo o resto deste curso ensinou <em>como</em> escrever software. Metodologias ágeis organizam <em>como um time decide o que construir, em que ordem, e como acompanha o progresso</em> — o contexto de processo em que praticamente todo trabalho profissional acontece.</p>","fidelityText":"Todo o resto deste curso ensinou como escrever software. Metodologias ágeis organizam como um time decide o que construir, em que ordem, e como acompanha o progresso — o contexto de processo em que praticamente todo trabalho profissional acontece."},{"id":"agile-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Por que \"ágil\" existe: o problema do Waterfall</h2>","fidelityText":"Por que \"ágil\" existe: o problema do Waterfall"},{"id":"agile-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">O modelo tradicional (Waterfall) é como construir uma casa inteira a partir de uma planta fixa, sem revisitar nada até a entrega final — se a planta estava errada, você só descobre depois de gastar todo o material. Ágil é construir e mostrar um cômodo de cada vez, permitindo ajustar a planta dos próximos cômodos com base no que já foi visto funcionando (ou não) na prática.</div>","fidelityText":"O modelo tradicional (Waterfall) é como construir uma casa inteira a partir de uma planta fixa, sem revisitar nada até a entrega final — se a planta estava errada, você só descobre depois de gastar todo o material. Ágil é construir e mostrar um cômodo de cada vez, permitindo ajustar a planta dos próximos cômodos com base no que já foi visto funcionando (ou não) na prática."},{"id":"agile-content-5","type":"html","authorship":"legacy-preserved","html":"<h2>Scrum — trabalho em ciclos fixos (sprints)</h2>","fidelityText":"Scrum — trabalho em ciclos fixos (sprints)"},{"id":"agile-content-6","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Papel/Cerimônia</th><th>Função</th></tr>\n        <tr><td><strong>Product Owner</strong></td><td>Decide <em>o quê</em> e <em>em que ordem de prioridade</em> construir</td></tr>\n        <tr><td><strong>Scrum Master</strong></td><td>Remove obstáculos do time, garante que o processo funcione</td></tr>\n        <tr><td><strong>Sprint</strong></td><td>Ciclo fixo de trabalho (geralmente 1-2 semanas) com um objetivo definido</td></tr>\n        <tr><td><strong>Daily Standup</strong></td><td>Reunião curta e diária: o que fiz, o que farei, o que está bloqueando</td></tr>\n        <tr><td><strong>Sprint Review</strong></td><td>Demonstração do que foi entregue ao final do sprint</td></tr>\n        <tr><td><strong>Retrospectiva</strong></td><td>O que funcionou bem no processo, o que precisa melhorar no próximo ciclo</td></tr>\n      </tbody></table>","fidelityText":"Papel/CerimôniaFunção Product OwnerDecide o quê e em que ordem de prioridade construir Scrum MasterRemove obstáculos do time, garante que o processo funcione SprintCiclo fixo de trabalho (geralmente 1-2 semanas) com um objetivo definido Daily StandupReunião curta e diária: o que fiz, o que farei, o que está bloqueando Sprint ReviewDemonstração do que foi entregue ao final do sprint RetrospectivaO que funcionou bem no processo, o que precisa melhorar no próximo ciclo"},{"id":"agile-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Kanban — fluxo contínuo, sem ciclos fixos</h2>","fidelityText":"Kanban — fluxo contínuo, sem ciclos fixos"},{"id":"agile-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"A FAZER  →  EM PROGRESSO  →  EM REVIEW  →  CONCLUIDO\n[task A]   [task B]        [task C]     [task D]\n[task E]                                    [task F]","fidelityText":"A FAZER → EM PROGRESSO → EM REVISÃO → CONCLUÍDO [tarefa A] [tarefa B] [tarefa C] [tarefa D] [tarefa E] [tarefa F]","highlightedHtml":"A FAZER  →  EM PROGRESSO  →  EM REVIEW  →  CONCLUIDO\n[task A]   [task B]        [task C]     [task D]\n[task E]                                    [task F]","caption":"Exemplo executável de agile.","explanation":["O quadro mostra estados de fluxo; a informação importante é quantos itens estão parados ou acumulados em cada coluna.","Sem limite de WIP e critério de pronto, o quadro vira decoração e não sistema de gestão."],"commonMistakes":["Mover card para concluído sem evidência","Começar muitas tarefas e terminar poucas","Confundir coluna com valor entregue"]},{"id":"agile-content-9","type":"html","authorship":"legacy-preserved","html":"<p>Diferente do Scrum, Kanban não organiza trabalho em ciclos de tempo fixo — o foco é <strong>limitar o trabalho em progresso</strong> (WIP — Work In Progress) simultâneo, para reduzir contexto trocado e acelerar o fluxo de entrega item por item.</p>","fidelityText":"Diferente do Scrum, Kanban não organiza trabalho em ciclos de tempo fixo — o foco é limitar o trabalho em progresso (WIP — Work In Progress) simultâneo, para reduzir contexto trocado e acelerar o fluxo de entrega item por item."},{"id":"agile-content-10","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Scrum</th><th>Kanban</th></tr>\n        <tr><td>Estrutura de tempo</td><td>Sprints fixos</td><td>Fluxo contínuo, sem ciclos</td></tr>\n        <tr><td>Papéis definidos</td><td>Sim (PO, Scrum Master)</td><td>Não formalmente exigidos</td></tr>\n        <tr><td>Bom para</td><td>Times com escopo previsível, produto em evolução planejada</td><td>Times de suporte/manutenção, fluxo de trabalho mais imprevisível</td></tr>\n      </tbody></table>","fidelityText":"ScrumKanban Estrutura de tempoSprints fixosFluxo contínuo, sem ciclos Papéis definidosSim (PO, Scrum Master)Não formalmente exigidos Bom paraTimes com escopo previsível, produto em evolução planejadaTimes de suporte/manutenção, fluxo de trabalho mais imprevisível"},{"id":"agile-content-11","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Nenhuma dessas metodologias é \"a certa\" de forma universal — assim como arquitetura de software (capítulo 75), a escolha depende do contexto real do time e do produto. Times sêniores frequentemente adotam versões híbridas e adaptadas, em vez de seguir um framework de processo \"por livro\" de forma rígida — a métrica que realmente importa é se o time está entregando valor de forma sustentável, não se está seguindo a cerimônia à risca.</div>","fidelityText":"Nenhuma dessas metodologias é \"a certa\" de forma universal — assim como arquitetura de software (capítulo 75), a escolha depende do contexto real do time e do produto. Times sêniores frequentemente adotam versões híbridas e adaptadas, em vez de seguir um framework de processo \"por livro\" de forma rígida — a métrica que realmente importa é se o time está entregando valor de forma sustentável, não se está seguindo a cerimônia à risca."},{"id":"agile-content-12","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Independente da metodologia formal do seu time, o hábito individual mais valioso que você pode carregar de qualquer uma delas é: <strong>quebrar trabalho grande em pedaços pequenos e entregáveis</strong> — a mesma lição de commits atômicos (capítulo 29) e PRs pequenos (capítulo 77), agora aplicada no nível de planejamento de funcionalidades inteiras.</div>","fidelityText":"Independente da metodologia formal do seu time, o hábito individual mais valioso que você pode carregar de qualquer uma delas é: quebrar trabalho grande em pedaços pequenos e entregáveis — a mesma lição de commits atômicos (capítulo 29) e PRs pequenos (capítulo 77), agora aplicada no nível de planejamento de funcionalidades inteiras."},{"id":"agile-exercise-13","type":"exercise","authorship":"legacy-preserved","title":"Exercício 78.1 — Quebrando uma funcionalidade em itens de sprint","prompt":"Para a funcionalidade \"sistema de reserva de livros\" (usuário reserva um livro emprestado, é notificado quando ele for devolvido), quebre em pelo menos 4 itens menores, entregáveis independentemente, que caberiam em um board Kanban ou backlog de sprint.","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 78.1 — Quebrando uma funcionalidade em itens de sprintfácil Para a funcionalidade \"sistema de reserva de livros\" (usuário reserva um livro emprestado, é notificado quando ele for devolvido), quebre em pelo menos 4 itens menores, entregáveis independentemente, que caberiam em um board Kanban ou backlog de sprint. Ver solução 1. Modelar e migrar a tabela \"reservas\" (capítulo 35) 2. Endpoint para criar uma reserva (POST /livros/{id}/reserva) 3. Lógica de negócio: impedir reserva duplicada do mesmo usuário 4. Evento Kafka disparado na devolução, verificando fila de reservas (capítulo 41) 5. Notificação ao usuário quando o livro reservado ficar disponível (capítulo 64, WebSocket) 6. Endpoint para o usuário cancelar sua própria reserva","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 78.1 — Quebrando uma funcionalidade em itens de sprint</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Para a funcionalidade \"sistema de reserva de livros\" (usuário reserva um livro emprestado, é notificado quando ele for devolvido), quebre em pelo menos 4 itens menores, entregáveis independentemente, que caberiam em um board Kanban ou backlog de sprint.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">1. Model e migrar a table \"reservations\" (chapter 35)\n2. Endpoint para create uma reservation (POST /books/{id}/reservation)\n3. Logic de business: impedir reservation duplicada do same user\n4. Event Kafka disparado na return, verificando queue de reservations (chapter 41)\n5. Notification ao user when o book reserved ficar available (chapter 64, WebSocket)\n6. Endpoint para o user cancel sua propria reservation</pre>\n        </div>\n      </div>"},{"id":"agile-quiz","type":"quiz","authorship":"authored","conceptId":"kanban-wip-flow","prompt":"Por que limitar WIP em Kanban costuma melhorar fluxo?","options":[{"id":"ag-a","label":"Porque reduz troca de contexto, expõe gargalos e força o time a terminar antes de começar demais.","correct":true,"explanation":"WIP menor torna bloqueios visíveis e aumenta foco em conclusão."},{"id":"ag-b","label":"Porque impede code review e testes para acelerar entrega.","correct":false,"explanation":"Isso só empurra defeito para depois; fluxo bom preserva qualidade."},{"id":"ag-c","label":"Porque Kanban exige sprint de duas semanas sempre.","correct":false,"explanation":"Sprint fixa é característica do Scrum, não do Kanban."}]}],"resources":[{"id":"scrum-guide-2020","type":"reference","title":"The Scrum Guide","url":"https://scrumguides.org/scrum-guide.html","reinforces":"Definição oficial de Scrum, papéis, eventos e artefatos.","language":"en","publisher":"Scrum Guides","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"kanban-guide","type":"reference","title":"The Kanban Guide","url":"https://kanbanguides.org/english/","reinforces":"Kanban, fluxo, WIP, políticas explícitas e melhoria contínua.","language":"en","publisher":"Kanban Guides","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the agile flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for agile. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for agile with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"A FAZER  →  EM PROGRESSO  →  EM REVIEW  →  CONCLUIDO","instruction":"Design the agile flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for agile with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"conectando-front-back","moduleId":"synchronous-integration","order":2,"title":"Conectando front-end e back-end","summary":"O momento em que todo o curso converge: o front-end fazendo requisições reais para a API Spring Boot que você construiu, com CORS já configurado (capítulo 50) para permitir a conversa entre os dois.","objectives":["Testar API antes do front-end","Usar fetch/axios respeitando status, headers e JSON","Tratar CORS como política do navegador","Alinhar contrato de erro, autenticação e payload com a UI"],"whyItExists":"A API já existe e o front-end já conhece autenticação. Agora a ponte precisa ser explícita: contrato HTTP, shape JSON, CORS, token, estados de carregamento e erro compreensível para o usuário.","prerequisiteChapterIds":["cors-rate-limit","json","http","frontend-aplicacao","auth-front-ts"],"conceptIds":["testando-manualmente-antes-do-front-existir-postman-insomnia","fetch-requisicao-nativa-do-navegador","lendo-o-erro-de-cors-por-que-o-catch-nao-conta-a-historia-real","axios-a-alternativa-mais-usada-em-projetos-maiores"],"introducedConceptIds":["frontend-backend-contract","browser-fetch-lifecycle"],"usedConceptIds":["json-formato-contrato","mvc-response-status-contract","cors-browser-policy","frontend-auth-state","browser-token-storage-risk"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"conectando-front-back-intuition","type":"intuition","authorship":"authored","title":"Front e back se conectam por contrato observável","body":"O front-end não chama controller; ele envia uma mensagem HTTP e recebe status, headers e corpo. A experiência do usuário depende de tratar sucesso, validação, autenticação expirada, CORS e falha de rede como estados diferentes.","analogyLimit":"Formulário enviando pedido ajuda, mas browser impõe CORS, cache, credentials e regras próprias."},{"id":"conectando-front-back-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-front\">Front-end</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#cors-rate-limit\">50 · CORS &amp; rate limiting</a>, <a class=\"prereq-tag\" href=\"#json\">25 · JSON &amp; serialização</a>, <a class=\"prereq-tag\" href=\"#http\">26 · HTTP &amp; REST</a></div>\n      </div>","fidelityText":"Front-end Dificuldade: Intermediário ⏱ ~2h de estudo + prática Pré-requisitos: 50 · CORS & rate limiting, 25 · JSON & serialização, 26 · HTTP & REST"},{"id":"conectando-front-back-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O momento em que todo o curso converge: o front-end fazendo requisições reais para a API Spring Boot que você construiu, com CORS já configurado (capítulo 50) para permitir a conversa entre os dois.</p>","fidelityText":"O momento em que todo o curso converge: o front-end fazendo requisições reais para a API Spring Boot que você construiu, com CORS já configurado (capítulo 50) para permitir a conversa entre os dois."},{"id":"conectando-front-back-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>Testando manualmente antes do front existir: Postman/Insomnia</h2>","fidelityText":"Testando manualmente antes do front existir: Postman/Insomnia"},{"id":"conectando-front-back-content-4","type":"html","authorship":"legacy-preserved","html":"<p>Antes mesmo do front-end tocar na API, é assim que qualquer desenvolvedor back-end valida seus endpoints — e é exatamente por isso que \"funciona no Postman mas não no front\" quase sempre aponta para CORS (capítulo 50), não para um bug na lógica da API.</p>","fidelityText":"Antes mesmo do front-end tocar na API, é assim que qualquer desenvolvedor back-end valida seus endpoints — e é exatamente por isso que \"funciona no Postman mas não no front\" quase sempre aponta para CORS (capítulo 50), não para um bug na lógica da API."},{"id":"conectando-front-back-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"1. Cria uma new request: POST http://localhost:8080/auth/login\n2. Tab \"Body\" → JSON: {\"email\": \"test@test.com\", \"password\": \"123456\"}\n3. Sends e confere a response: {\"token\": \"eyJhbGc...\"}\n4. Copy o token, usa em requests seguintes na tab \"Authorization\" → Bearer Token","fidelityText":"1. Cria uma nova requisição: POST http://localhost:8080/auth/login 2. Aba \"Body\" → JSON: {\"email\": \"teste@teste.com\", \"senha\": \"123456\"} 3. Envia e confere a resposta: {\"token\": \"eyJhbGc...\"} 4. Copia o token, usa em requisições seguintes na aba \"Authorization\" → Bearer Token","highlightedHtml":"<span class=\"com\">1. Cria uma nova requisição: POST http://localhost:8080/auth/login\n2. Aba \"Body\" → JSON: {\"email\": \"teste@teste.com\", \"senha\": \"123456\"}\n3. Envia e confere a resposta: {\"token\": \"eyJhbGc...\"}\n4. Copia o token, usa em requisições seguintes na aba \"Authorization\" → Bearer Token</span>","caption":"Exemplo executável de conectando-front-back.","explanation":["Testar com Postman/Insomnia/curl isola contrato da API antes de envolver UI.","Isso separa bug de backend, rede, CORS e estado visual."],"commonMistakes":["Debugar tudo pelo browser primeiro","Ignorar status e corpo de erro"]},{"id":"conectando-front-back-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Monte uma \"coleção\" no Postman com todos os endpoints da sua API assim que os escrever — isso vira, na prática, uma documentação viva e testável, complementando (não substituindo) o Swagger automático do capítulo 49.</div>","fidelityText":"Monte uma \"coleção\" no Postman com todos os endpoints da sua API assim que os escrever — isso vira, na prática, uma documentação viva e testável, complementando (não substituindo) o Swagger automático do capítulo 49."},{"id":"conectando-front-back-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>fetch — requisição nativa do navegador</h2>","fidelityText":"fetch — requisição nativa do navegador"},{"id":"conectando-front-back-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"// buscando a lista de livros:\nasync function findBooks() {\n  const response = await fetch(`${import.meta.env.VITE_API_URL}/books`);\n  if (!response.ok) {\n    throw new Error(`Error ${response.status}`); // resposta.ok é false para 4xx/5xx, capítulo 26\n  }\n  return response.json(); // desserializa o JSON, capítulo 25\n}\n\n// enviando dados com autenticação:\nasync function createBook(data, token) {\n  const response = await fetch(`${import.meta.env.VITE_API_URL}/books`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      'Authorization': `Bearer ${token}` // o JWT do capítulo 52\n    },\n    body: JSON.stringify(data)\n  });\n  return response.json();\n}","fidelityText":"// buscando a lista de livros: async function buscarLivros() { const resposta = await fetch(`${import.meta.env.VITE_API_URL}/livros`); if (!resposta.ok) { throw new Error(`Erro ${resposta.status}`); // resposta.ok é false para 4xx/5xx, capítulo 26 } return resposta.json(); // desserializa o JSON, capítulo 25 } // enviando dados com autenticação: async function criarLivro(dados, token) { const resposta = await fetch(`${import.meta.env.VITE_API_URL}/livros`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` // o JWT do capítulo 52 }, body: JSON.stringify(dados) }); return resposta.json(); }","highlightedHtml":"<span class=\"com\">// buscando a lista de livros:</span>\n<span class=\"kw\">async function</span> <span class=\"fn\">findBooks</span>() {\n  <span class=\"kw\">const</span> response = <span class=\"kw\">await</span> fetch(<span class=\"str\">`${import.meta.env.VITE_API_URL}/books`</span>);\n  <span class=\"kw\">if</span> (!response.ok) {\n    <span class=\"kw\">throw new</span> Error(<span class=\"str\">`Error ${response.status}`</span>); <span class=\"com\">// resposta.ok é false para 4xx/5xx, capítulo 26</span>\n  }\n  <span class=\"kw\">return</span> response.json(); <span class=\"com\">// desserializa o JSON, capítulo 25</span>\n}\n\n<span class=\"com\">// enviando dados com autenticação:</span>\n<span class=\"kw\">async function</span> <span class=\"fn\">createBook</span>(data, token) {\n  <span class=\"kw\">const</span> response = <span class=\"kw\">await</span> fetch(<span class=\"str\">`${import.meta.env.VITE_API_URL}/books`</span>, {\n    method: <span class=\"str\">'POST'</span>,\n    headers: {\n      <span class=\"str\">'Content-Type'</span>: <span class=\"str\">'application/json'</span>,\n      <span class=\"str\">'Authorization'</span>: <span class=\"str\">`Bearer ${token}`</span> <span class=\"com\">// o JWT do capítulo 52</span>\n    },\n    body: JSON.stringify(data)\n  });\n  <span class=\"kw\">return</span> response.json();\n}","caption":"Exemplo executável de conectando-front-back.","explanation":["fetch envia requisição e devolve Response; o código precisa decidir quando parsear e quando tratar erro.","Credentials, headers e token devem seguir o modelo de autenticação escolhido."],"commonMistakes":["Assumir que todo erro cai no catch","Guardar token sem considerar XSS/expiração"]},{"id":"conectando-front-back-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Se o back-end é a cozinha e o Postman é você mesmo indo pedir na janela do balcão, o front-end usando <code>fetch</code> é o garçom fazendo esse mesmo pedido em nome do cliente — só que de forma automatizada, integrada à interface visual que o usuário realmente vê e interage.</div>","fidelityText":"Se o back-end é a cozinha e o Postman é você mesmo indo pedir na janela do balcão, o front-end usando fetch é o garçom fazendo esse mesmo pedido em nome do cliente — só que de forma automatizada, integrada à interface visual que o usuário realmente vê e interage."},{"id":"conectando-front-back-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Lendo o erro de CORS — por que o <code>catch</code> não conta a história real</h2>","fidelityText":"Lendo o erro de CORS — por que o catch não conta a história real"},{"id":"conectando-front-back-content-11","type":"html","authorship":"legacy-preserved","html":"<p>CORS não é uma regra que o back-end Java aplica sozinho — é o <strong>navegador</strong> quem bloqueia a resposta antes de entregá-la ao seu JavaScript. O servidor pode ter respondido <code>200</code> perfeitamente (visível na aba Network); o navegador é quem decide não repassar o corpo para o seu código.</p>","fidelityText":"CORS não é uma regra que o back-end Java aplica sozinho — é o navegador quem bloqueia a resposta antes de entregá-la ao seu JavaScript. O servidor pode ter respondido 200 perfeitamente (visível na aba Network); o navegador é quem decide não repassar o corpo para o seu código."},{"id":"conectando-front-back-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"// in the catch of your fetch, this is all the JavaScript sees:\nTypeError: Failed to fetch\n\n// the real reason only shows up in the browser console, never inside your try/catch:\nAccess to fetch at 'http://localhost:8080/books' from origin 'http://localhost:5173'\nhas been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present\non the requested resource.","fidelityText":"// no catch do seu fetch, é só isso que o JavaScript enxerga: TypeError: Failed to fetch // o motivo real aparece SÓ no console do navegador, nunca dentro do seu try/catch: Access to fetch at 'http://localhost:8080/livros' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.","highlightedHtml":"<span class=\"com\">// in the catch of your fetch, this is all the JavaScript sees:</span>\nTypeError: Failed to fetch\n\n<span class=\"com\">// the real reason only shows up in the browser console, never inside your try/catch:</span>\nAccess to fetch at 'http://localhost:8080/books' from origin 'http://localhost:5173'\nhas been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present\non the requested resource.","caption":"Exemplo executável de conectando-front-back.","explanation":["O catch do fetch só recebe um TypeError genérico -- o navegador nunca repassa o motivo real de um bloqueio de CORS para o JavaScript.","O texto real do erro (qual origem, qual header faltou) só existe no console/aba Network do navegador, nunca dentro do try/catch."],"commonMistakes":["Debugar CORS lendo só a mensagem do catch","Achar que CORS é validado pelo Java antes de chegar ao navegador"]},{"id":"conectando-front-back-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>\"Vi 200 no Network tab, mas o front trava\" é o sintoma clássico de CORS</b> — não de bug na lógica da API. Como o <code>catch</code> nunca recebe o motivo real, debugar direto no código do front é perder tempo: confira a resposta da requisição <code>OPTIONS</code> (preflight) e o header <code>Access-Control-Allow-Origin</code> na aba Network antes de suspeitar do <code>fetch</code> ou do <code>axios</code>.</div>","fidelityText":"\"Vi 200 no Network tab, mas o front trava\" é o sintoma clássico de CORS — não de bug na lógica da API. Como o catch nunca recebe o motivo real, debugar direto no código do front é perder tempo: confira a resposta da requisição OPTIONS (preflight) e o header Access-Control-Allow-Origin na aba Network antes de suspeitar do fetch ou do axios."},{"id":"conectando-front-back-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>axios — a alternativa mais usada em projetos maiores</h2>","fidelityText":"axios — a alternativa mais usada em projetos maiores"},{"id":"conectando-front-back-code-15","type":"code","authorship":"legacy-preserved","language":"java","source":"import axios from 'axios';\n\nconst api = axios.create({\n  baseURL: import.meta.env.VITE_API_URL,\n});\n\n// interceptor: adiciona o token em TODA requisição automaticamente,\n// sem precisar repetir o header manualmente em cada chamada\napi.interceptors.request.use((config) => {\n  const token = localStorage.getItem('token');\n  if (token) config.headers.Authorization = `Bearer ${token}`;\n  return config;\n});\n\nconst { date } = await api.get('/books'); // já vem parseado, sem precisar de .json()","fidelityText":"import axios from 'axios'; const api = axios.create({ baseURL: import.meta.env.VITE_API_URL, }); // interceptor: adiciona o token em TODA requisição automaticamente, // sem precisar repetir o header manualmente em cada chamada api.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }); const { data } = await api.get('/livros'); // já vem parseado, sem precisar de .json()","highlightedHtml":"<span class=\"kw\">import</span> axios <span class=\"kw\">from</span> <span class=\"str\">'axios'</span>;\n\n<span class=\"kw\">const</span> api = axios.create({\n  baseURL: import.meta.env.VITE_API_URL,\n});\n\n<span class=\"com\">// interceptor: adiciona o token em TODA requisição automaticamente,\n// sem precisar repetir o header manualmente em cada chamada</span>\napi.interceptors.request.use((config) =&gt; {\n  <span class=\"kw\">const</span> token = localStorage.getItem(<span class=\"str\">'token'</span>);\n  <span class=\"kw\">if</span> (token) config.headers.Authorization = <span class=\"str\">`Bearer ${token}`</span>;\n  <span class=\"kw\">return</span> config;\n});\n\n<span class=\"kw\">const</span> { date } = <span class=\"kw\">await</span> api.get(<span class=\"str\">'/books'</span>); <span class=\"com\">// já vem parseado, sem precisar de .json()</span>","caption":"Exemplo executável de conectando-front-back.","explanation":["Axios simplifica interceptors, baseURL e tratamento padronizado, mas continua usando HTTP e browser por baixo.","A escolha da biblioteca não remove contrato de status, CORS e JSON."],"commonMistakes":["Trocar fetch por axios para mascarar contrato ruim","Criar interceptor que engole erro sem contexto"]},{"id":"conectando-front-back-content-16","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O padrão de <em>interceptor</em> do axios é a mesma ideia do <code>@ControllerAdvice</code> (capítulo 48) e do dynamic proxy (capítulo 20), só que do lado do cliente: um ponto único que intercepta <strong>toda</strong> requisição para aplicar uma regra transversal (adicionar token de autenticação), em vez de repetir esse código em cada chamada individual à API.</div>","fidelityText":"O padrão de interceptor do axios é a mesma ideia do @ControllerAdvice (capítulo 48) e do dynamic proxy (capítulo 20), só que do lado do cliente: um ponto único que intercepta toda requisição para aplicar uma regra transversal (adicionar token de autenticação), em vez de repetir esse código em cada chamada individual à API."},{"id":"conectando-front-back-exercise-17","type":"exercise","authorship":"legacy-preserved","title":"Exercício 62.1 — Cliente completo com autenticação","prompt":"Usando axios, crie um cliente configurado com baseURL apontando para a API da biblioteca, com um interceptor que adiciona o token JWT (guardado em algum lugar — veremos onde no próximo capítulo) em toda requisição. Escreva funções listarLivros(), emprestarLivro(codigo) e login(email, senha).","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 62.1 — Cliente completo com autenticaçãomédio Usando axios, crie um cliente configurado com baseURL apontando para a API da biblioteca, com um interceptor que adiciona o token JWT (guardado em algum lugar — veremos onde no próximo capítulo) em toda requisição. Escreva funções listarLivros(), emprestarLivro(codigo) e login(email, senha). Ver solução const api = axios.create({ baseURL: import.meta.env.VITE_API_URL }); api.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }); export async function listarLivros() { const { data } = await api.get('/livros'); return data; } export async function emprestarLivro(codigo) { const { data } = await api.post(`/livros/${codigo}/emprestimo`); return data; } export async function login(email, senha) { const { data } = await api.post('/auth/login', { email, senha }); localStorage.setItem('token', data.token); return data; }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 62.1 — Cliente completo com autenticação</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Usando axios, crie um cliente configurado com <code>baseURL</code> apontando para a API da biblioteca, com um interceptor que adiciona o token JWT (guardado em algum lugar — veremos onde no próximo capítulo) em toda requisição. Escreva funções <code>listarLivros()</code>, <code>emprestarLivro(codigo)</code> e <code>login(email, senha)</code>.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">const</span> api = axios.create({ baseURL: import.meta.env.VITE_API_URL });\n\napi.interceptors.request.use((config) =&gt; {\n  <span class=\"kw\">const</span> token = localStorage.getItem(<span class=\"str\">'token'</span>);\n  <span class=\"kw\">if</span> (token) config.headers.Authorization = <span class=\"str\">`Bearer ${token}`</span>;\n  <span class=\"kw\">return</span> config;\n});\n\n<span class=\"kw\">export async function</span> <span class=\"fn\">listarBooks</span>() {\n  <span class=\"kw\">const</span> { date } = <span class=\"kw\">await</span> api.get(<span class=\"str\">'/books'</span>);\n  <span class=\"kw\">return</span> date;\n}\n\n<span class=\"kw\">export async function</span> <span class=\"fn\">borrowBook</span>(code) {\n  <span class=\"kw\">const</span> { date } = <span class=\"kw\">await</span> api.post(<span class=\"str\">`/books/${code}/loan`</span>);\n  <span class=\"kw\">return</span> date;\n}\n\n<span class=\"kw\">export async function</span> <span class=\"fn\">login</span>(email, password) {\n  <span class=\"kw\">const</span> { date } = <span class=\"kw\">await</span> api.post(<span class=\"str\">'/auth/login'</span>, { email, password });\n  localStorage.setItem(<span class=\"str\">'token'</span>, date.token);\n  <span class=\"kw\">return</span> date;\n}</pre>\n        </div>\n      </div>"},{"id":"conectando-front-back-quiz","type":"quiz","authorship":"authored","conceptId":"browser-fetch-lifecycle","prompt":"Qual detalhe costuma surpreender ao usar fetch?","options":[{"id":"cfb-a","label":"Resposta HTTP 400/500 normalmente resolve a Promise; você precisa checar status/ok.","correct":true,"explanation":"Falha de rede rejeita; status de erro ainda é resposta HTTP."},{"id":"cfb-b","label":"CORS é desativado pelo Java se o controller existir.","correct":false,"explanation":"CORS é política do navegador baseada em headers."},{"id":"cfb-c","label":"JSON inválido vira automaticamente DTO válido.","correct":false,"explanation":"Parsing e validação precisam ser tratados."}]}],"resources":[{"id":"mdn-fetch","type":"reference","title":"MDN: Fetch API","url":"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API","reinforces":"Modelo de request/response no navegador e tratamento de Promise.","language":"en","publisher":"MDN","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"mdn-cors","type":"reference","title":"MDN: Cross-Origin Resource Sharing","url":"https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS","reinforces":"Headers, preflight e política do navegador.","language":"en","publisher":"MDN","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The connecting front back component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to connecting front back. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible connecting front back failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"1. Cria uma new request: POST http://localhost:8080/auth/login","instruction":"The connecting front back component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible connecting front back failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"auth-front-ts","moduleId":"api-security-quality","order":6,"title":"Autenticação no front & pincelada de TypeScript","summary":"Onde guardar o JWT (capítulo 51/52) no navegador é uma decisão de segurança real, não um detalhe de implementação — errar aqui abre a porta para roubo de sessão via XSS.","objectives":["Comparar armazenamento de tokens no navegador","Usar TypeScript para contratos de API","Representar login, expiração e erro como estados","Entender que frontend não substitui autorização server-side"],"whyItExists":"Depois de proteger a API, o aluno precisa consumir autenticação no navegador sem transformar localStorage em cofre nem TypeScript em segurança ilusória.","prerequisiteChapterIds":["mini-auth-rbac","autenticacao-conceitos"],"conceptIds":["localstorage-vs-cookie-httponly","typescript-por-que-vale-a-pena-mesmo-sem-precisar","representando-o-estado-de-autenticacao-e-por-que-ele-nao-e-autorizacao"],"introducedConceptIds":["browser-token-storage-risk","frontend-auth-state"],"usedConceptIds":["jwt-claims-signature","tls-certificate-chain","rbac-permission-boundary"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"auth-front-intuition","type":"intuition","authorship":"authored","title":"O navegador é ambiente hostil, mas ainda precisa boa UX","body":"Tokens no navegador vivem perto de JavaScript, extensões, abas e rede. O frontend deve representar estado de autenticação com clareza, mas a autorização real continua no servidor.","analogyLimit":"Guardar chave na mochila ajuda a imaginar risco, mas XSS, CSRF, httpOnly, SameSite e refresh token têm detalhes próprios."},{"id":"auth-front-ts-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-front\">Front-end</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~2h</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#conectando-front-back\">62 · Conectando front e back</a>, <a class=\"prereq-tag\" href=\"#autenticacao-conceitos\">51 · Autenticação conceitos</a></div>\n      </div>","fidelityText":"Front-end Dificuldade: Avançado ⏱ ~2h de estudo Pré-requisitos: 62 · Conectando front e back, 51 · Autenticação conceitos"},{"id":"auth-front-ts-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Onde guardar o JWT (capítulo 51/52) no navegador é uma decisão de segurança real, não um detalhe de implementação — errar aqui abre a porta para roubo de sessão via XSS.</p>","fidelityText":"Onde guardar o JWT (capítulo 51/52) no navegador é uma decisão de segurança real, não um detalhe de implementação — errar aqui abre a porta para roubo de sessão via XSS."},{"id":"auth-front-ts-content-3","type":"html","authorship":"legacy-preserved","html":"<h2>localStorage vs cookie httpOnly</h2>","fidelityText":"localStorage vs cookie httpOnly"},{"id":"auth-front-ts-content-4","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th><code>localStorage</code></th><th>Cookie <code>httpOnly</code></th></tr>\n        <tr><td>Acessível via JavaScript</td><td>Sim — <code>localStorage.getItem(...)</code></td><td>Não — invisível para JS, só o navegador o envia</td></tr>\n        <tr><td>Risco de XSS</td><td>Alto — script malicioso injetado pode roubar o token diretamente</td><td>Baixo — mesmo com XSS, o script não consegue ler o cookie</td></tr>\n        <tr><td>Precisa enviar manualmente</td><td>Sim, no header <code>Authorization</code> (capítulo 62)</td><td>Não — o navegador envia automaticamente em toda requisição para o domínio</td></tr>\n        <tr><td>Risco de CSRF</td><td>Baixo (não é enviado automaticamente)</td><td>Precisa de proteção CSRF adicional</td></tr>\n      </tbody></table>","fidelityText":"localStorageCookie httpOnly Acessível via JavaScriptSim — localStorage.getItem(...)Não — invisível para JS, só o navegador o envia Risco de XSSAlto — script malicioso injetado pode roubar o token diretamenteBaixo — mesmo com XSS, o script não consegue ler o cookie Precisa enviar manualmenteSim, no header Authorization (capítulo 62)Não — o navegador envia automaticamente em toda requisição para o domínio Risco de CSRFBaixo (não é enviado automaticamente)Precisa de proteção CSRF adicional"},{"id":"auth-front-ts-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Guardar o token em <code>localStorage</code> é como deixar a chave de casa embaixo do tapete — qualquer um que descubra onde procurar (nesse caso, qualquer script rodando na sua página, inclusive um malicioso injetado via XSS) consegue pegar. Um cookie <code>httpOnly</code> é como um chaveiro eletrônico que só a fechadura em si consegue ler — mesmo que alguém entre na sala, não consegue copiar a chave manualmente.</div>","fidelityText":"Guardar o token em localStorage é como deixar a chave de casa embaixo do tapete — qualquer um que descubra onde procurar (nesse caso, qualquer script rodando na sua página, inclusive um malicioso injetado via XSS) consegue pegar. Um cookie httpOnly é como um chaveiro eletrônico que só a fechadura em si consegue ler — mesmo que alguém entre na sala, não consegue copiar a chave manualmente."},{"id":"auth-front-ts-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>O exemplo do capítulo 62 usando <code>localStorage</code> foi simplificado para fins didáticos.</b> Em uma aplicação de produção real, especialmente uma que lida com dados sensíveis, cookie <code>httpOnly</code> + <code>Secure</code> + <code>SameSite=Strict</code> é a escolha mais segura — exige que o back-end (Spring Security, capítulo 52) defina o cookie na resposta de login, em vez do front-end guardar o token manualmente.</div>","fidelityText":"O exemplo do capítulo 62 usando localStorage foi simplificado para fins didáticos. Em uma aplicação de produção real, especialmente uma que lida com dados sensíveis, cookie httpOnly + Secure + SameSite=Strict é a escolha mais segura — exige que o back-end (Spring Security, capítulo 52) defina o cookie na resposta de login, em vez do front-end guardar o token manualmente."},{"id":"auth-front-ts-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Para projetos de aprendizado, começar com <code>localStorage</code> é aceitável — é mais simples de implementar e entender o fluxo completo primeiro. Migrar para cookie <code>httpOnly</code> é o passo natural assim que o projeto sair do estágio de portfólio pessoal para algo que lida com dados reais de usuários reais.</div>","fidelityText":"Para projetos de aprendizado, começar com localStorage é aceitável — é mais simples de implementar e entender o fluxo completo primeiro. Migrar para cookie httpOnly é o passo natural assim que o projeto sair do estágio de portfólio pessoal para algo que lida com dados reais de usuários reais."},{"id":"auth-front-ts-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>TypeScript — por que vale a pena mesmo sem \"precisar\"</h2>","fidelityText":"TypeScript — por que vale a pena mesmo sem \"precisar\""},{"id":"auth-front-ts-content-9","type":"html","authorship":"legacy-preserved","html":"<p>JavaScript puro deixa passar erros de contrato que só aparecem em runtime — exatamente o problema que a tipagem estática do Java (capítulo 01) já te ensinou a evitar.</p>","fidelityText":"JavaScript puro deixa passar erros de contrato que só aparecem em runtime — exatamente o problema que a tipagem estática do Java (capítulo 01) já te ensinou a evitar."},{"id":"auth-front-ts-comparison-10","type":"html","html":"<div class=\"code-comparison\"><div class=\"code-toggle-bar\" role=\"tablist\">\n        <button class=\"ct-btn bad active\" role=\"tab\" aria-selected=\"true\" type=\"button\">❌ JavaScript puro</button>\n        <button class=\"ct-btn good\" role=\"tab\" aria-selected=\"false\" type=\"button\" tabindex=\"-1\">✅ Com TypeScript</button>\n      </div><div class=\"ct-panel active\">\n<pre class=\"code\"><span class=\"kw\">function</span> <span class=\"fn\">displayBook</span>(book) {\n  <span class=\"kw\">return</span> <span class=\"str\">`${book.title} (${book.pages} pages)`</span>;\n  <span class=\"com\">// se a API mudar \"paginas\" para \"totalPaginas\" sem avisar,\n  // isso só quebra em RUNTIME, silenciosamente, como \"undefined\" na tela</span>\n}</pre>\n      </div><div class=\"ct-panel\">\n<pre class=\"code\"><span class=\"kw\">interface</span> <span class=\"cls\">Book</span> {\n  title: <span class=\"kw\">string</span>;\n  pages: <span class=\"kw\">number</span>;\n}\n\n<span class=\"kw\">function</span> <span class=\"fn\">displayBook</span>(book: <span class=\"cls\">Book</span>): <span class=\"kw\">string</span> {\n  <span class=\"kw\">return</span> <span class=\"str\">`${book.title} (${book.pages} pages)`</span>;\n  <span class=\"com\">// se a API mudar o campo, o editor avisa AGORA, em tempo de\n  // desenvolvimento -- exatamente como o compilador Java (capítulo 00) faz</span>\n}</pre>\n      </div></div>","sourceIndexes":[10,11,12],"fidelityText":"❌ JavaScript puro ✅ Com TypeScript function exibirLivro(livro) { return `${livro.titulo} (${livro.paginas} páginas)`; // se a API mudar \"paginas\" para \"totalPaginas\" sem avisar, // isso só quebra em RUNTIME, silenciosamente, como \"undefined\" na tela } interface Livro { titulo: string; paginas: number; } function exibirLivro(livro: Livro): string { return `${livro.titulo} (${livro.paginas} páginas)`; // se a API mudar o campo, o editor avisa AGORA, em tempo de // desenvolvimento -- exatamente como o compilador Java (capítulo 00) faz }"},{"id":"auth-front-ts-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">O <code>interface Livro</code> do TypeScript deveria, idealmente, espelhar exatamente o <code>LivroDTO</code> (capítulo 47) do back-end — na prática, times maduros geram esses tipos automaticamente a partir do contrato OpenAPI/Swagger (capítulo 49), garantindo que front e back nunca fiquem dessincronizados quanto ao formato dos dados trocados.</div>","fidelityText":"O interface Livro do TypeScript deveria, idealmente, espelhar exatamente o LivroDTO (capítulo 47) do back-end — na prática, times maduros geram esses tipos automaticamente a partir do contrato OpenAPI/Swagger (capítulo 49), garantindo que front e back nunca fiquem dessincronizados quanto ao formato dos dados trocados."},{"id":"auth-front-ts-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Representando o estado de autenticação — e por que ele não é autorização</h2>","fidelityText":"Representando o estado de autenticação — e por que ele não é autorização"},{"id":"auth-front-ts-content-15","type":"html","authorship":"legacy-preserved","html":"<p>\"Autenticado\" não é um booleano. O front-end precisa distinguir carregando, anônimo, autenticado, expirado e negado — cada um pede uma tela diferente. E nenhum desses estados, por si só, autoriza nada: quem decide é sempre o servidor.</p>","fidelityText":"\"Autenticado\" não é um booleano. O front-end precisa distinguir carregando, anônimo, autenticado, expirado e negado — cada um pede uma tela diferente. E nenhum desses estados, por si só, autoriza nada: quem decide é sempre o servidor."},{"id":"auth-front-ts-code-16","type":"code","authorship":"legacy-preserved","language":"java","source":"type StateAuth =\n  | { type: 'loading' }\n  | { type: 'anonymous' }\n  | { type: 'authenticated'; papel: 'USER' | 'ADMIN' }\n  | { type: 'expired' }\n  | { type: 'denied' };\n\n// ❌ hiding the button is not authorization -- it's just UX:\nfunction DeleteButton({ state }: { state: StateAuth }) {\n  if (state.type !== 'authenticated' || state.papel !== 'ADMIN') return null;\n  return <button onClick={deleteBook}>Delete</button>;\n}\n// anyone can open DevTools and remove this check from the\n// bundle that's already downloaded, or just call the API directly with curl/Postman\n\nasync function deleteBook(id: number, token: string) {\n  const response = await fetch(`${import.meta.env.VITE_API_URL}/books/${id}`, {\n    method: 'DELETE',\n    headers: { Authorization: `Bearer ${token}` }\n  });\n  // the SERVER -- not the hidden button -- decides: 403 if the real role\n  // (validated from the token, chapter 52) isn't ADMIN, even if\n  // the front lied or the button was never really hidden at all\n  if (response.status === 403) throw new Error('Server denied: insufficient role');\n}","fidelityText":"type EstadoAuth = | { tipo: 'carregando' } | { tipo: 'anonimo' } | { tipo: 'autenticado'; papel: 'USER' | 'ADMIN' } | { tipo: 'expirado' } | { tipo: 'negado' }; // ❌ esconder o botão não é autorização -- é só UX: function BotaoExcluir({ estado }: { estado: EstadoAuth }) { if (estado.tipo !== 'autenticado' || estado.papel !== 'ADMIN') return null; return <button onClick={excluirLivro}>Excluir</button>; } // qualquer pessoa pode abrir o DevTools e remover essa checagem do // bundle já baixado, ou simplesmente chamar a API direto com curl/Postman async function excluirLivro(id: number, token: string) { const resposta = await fetch(`${import.meta.env.VITE_API_URL}/livros/${id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` } }); // o SERVIDOR -- não o botão escondido -- decide: 403 se o papel real // (validado a partir do token, capítulo 52) não for ADMIN, mesmo que // o front tenha mentido ou o botão nunca tenha sido escondido de verdade if (resposta.status === 403) throw new Error('Servidor negou: papel insuficiente'); }","highlightedHtml":"<span class=\"kw\">type</span> <span class=\"cls\">StateAuth</span> =\n  | { type: <span class=\"str\">'loading'</span> }\n  | { type: <span class=\"str\">'anonymous'</span> }\n  | { type: <span class=\"str\">'authenticated'</span>; papel: <span class=\"str\">'USER'</span> | <span class=\"str\">'ADMIN'</span> }\n  | { type: <span class=\"str\">'expired'</span> }\n  | { type: <span class=\"str\">'denied'</span> };\n\n<span class=\"com\">// ❌ hiding the button is not authorization -- it's just UX:</span>\n<span class=\"kw\">function</span> <span class=\"fn\">DeleteButton</span>({ state }: { state: <span class=\"cls\">StateAuth</span> }) {\n  <span class=\"kw\">if</span> (state.type !== <span class=\"str\">'authenticated'</span> || state.papel !== <span class=\"str\">'ADMIN'</span>) <span class=\"kw\">return</span> <span class=\"kw\">null</span>;\n  <span class=\"kw\">return</span> &lt;button onClick={deleteBook}&gt;Delete&lt;/button&gt;;\n}\n<span class=\"com\">// anyone can open DevTools and remove this check from the\n// bundle that's already downloaded, or just call the API directly with curl/Postman</span>\n\n<span class=\"kw\">async function</span> <span class=\"fn\">deleteBook</span>(id: <span class=\"kw\">number</span>, token: <span class=\"kw\">string</span>) {\n  <span class=\"kw\">const</span> response = <span class=\"kw\">await</span> fetch(<span class=\"str\">`${import.meta.env.VITE_API_URL}/books/${id}`</span>, {\n    method: <span class=\"str\">'DELETE'</span>,\n    headers: { Authorization: <span class=\"str\">`Bearer ${token}`</span> }\n  });\n  <span class=\"com\">// the SERVER -- not the hidden button -- decides: 403 if the real role\n  // (validated from the token, chapter 52) isn't ADMIN, even if\n  // the front lied or the button was never really hidden at all</span>\n  <span class=\"kw\">if</span> (response.status === 403) <span class=\"kw\">throw new</span> Error(<span class=\"str\">'Server denied: insufficient role'</span>);\n}","caption":"Exemplo executável de auth-front-ts.","explanation":["EstadoAuth modela carregando, anônimo, autenticado, expirado e negado como estados distintos, não como um booleano.","O front decide o que MOSTRAR a partir do estado; o servidor decide o que é PERMITIDO a cada requisição, independentemente do que a tela exibe."],"commonMistakes":["Tratar autenticado como true/false","Confiar no estado do front para pular a checagem de 403 no servidor"]},{"id":"auth-front-ts-content-17","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Estado visual não é autorização:</b> <code>EstadoAuth</code> serve para decidir <em>o que mostrar na tela</em>. A decisão de <em>o que é permitido fazer</em> continua inteiramente no back-end (RBAC, capítulo 52) — o front que confia no próprio estado para pular a checagem de <code>403</code> está tratando UX como segurança.</div>","fidelityText":"Estado visual não é autorização: EstadoAuth serve para decidir o que mostrar na tela. A decisão de o que é permitido fazer continua inteiramente no back-end (RBAC, capítulo 52) — o front que confia no próprio estado para pular a checagem de 403 está tratando UX como segurança."},{"id":"auth-front-ts-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Exercício 63.1 — Tipando o cliente da API","prompt":"Reescreva as funções listarLivros() e login(email, senha) do exercício 62.1 em TypeScript, definindo interfaces Livro e RespostaLogin correspondentes ao formato retornado pela API.","difficulty":"intermediate","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 63.1 — Tipando o cliente da APImédio Reescreva as funções listarLivros() e login(email, senha) do exercício 62.1 em TypeScript, definindo interfaces Livro e RespostaLogin correspondentes ao formato retornado pela API. Ver solução interface Livro { id: number; titulo: string; paginas: number; nomeAutor: string; } interface RespostaLogin { token: string; } export async function listarLivros(): Promise<Livro[]> { const { data } = await api.get<Livro[]>('/livros'); return data; } export async function login(email: string, senha: string): Promise<RespostaLogin> { const { data } = await api.post<RespostaLogin>('/auth/login', { email, senha }); return data; }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 63.1 — Tipando o cliente da API</h2><span class=\"exercise-tag m\">médio</span></div>\n        <p>Reescreva as funções <code>listarLivros()</code> e <code>login(email, senha)</code> do exercício 62.1 em TypeScript, definindo interfaces <code>Livro</code> e <code>RespostaLogin</code> correspondentes ao formato retornado pela API.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"kw\">interface</span> <span class=\"cls\">Book</span> {\n  id: <span class=\"kw\">number</span>;\n  title: <span class=\"kw\">string</span>;\n  pages: <span class=\"kw\">number</span>;\n  nameAuthor: <span class=\"kw\">string</span>;\n}\n\n<span class=\"kw\">interface</span> <span class=\"cls\">ResponseLogin</span> {\n  token: <span class=\"kw\">string</span>;\n}\n\n<span class=\"kw\">export async function</span> <span class=\"fn\">listarBooks</span>(): <span class=\"cls\">Promise</span>&lt;<span class=\"cls\">Book</span>[]&gt; {\n  <span class=\"kw\">const</span> { date } = <span class=\"kw\">await</span> api.get&lt;<span class=\"cls\">Book</span>[]&gt;(<span class=\"str\">'/books'</span>);\n  <span class=\"kw\">return</span> date;\n}\n\n<span class=\"kw\">export async function</span> <span class=\"fn\">login</span>(email: <span class=\"kw\">string</span>, password: <span class=\"kw\">string</span>): <span class=\"cls\">Promise</span>&lt;<span class=\"cls\">ResponseLogin</span>&gt; {\n  <span class=\"kw\">const</span> { date } = <span class=\"kw\">await</span> api.post&lt;<span class=\"cls\">ResponseLogin</span>&gt;(<span class=\"str\">'/auth/login'</span>, { email, password });\n  <span class=\"kw\">return</span> date;\n}</pre>\n        </div>\n      </div>"},{"id":"auth-front-quiz","type":"quiz","authorship":"authored","conceptId":"browser-token-storage-risk","prompt":"Qual comparação entre localStorage e cookie httpOnly é mais correta?","options":[{"id":"front-auth-a","label":"localStorage é exposto a JavaScript; cookie httpOnly reduz leitura por XSS, mas exige cuidado com CSRF/SameSite.","correct":true,"explanation":"Não existe cofre perfeito; riscos mudam conforme mecanismo."},{"id":"front-auth-b","label":"localStorage criptografa automaticamente o token.","correct":false,"explanation":"localStorage guarda string acessível ao JavaScript da origem."},{"id":"front-auth-c","label":"Cookie httpOnly autoriza qualquer endpoint sozinho.","correct":false,"explanation":"Servidor ainda valida sessão/token e permissão."}]},{"id":"auth-front-state-quiz","type":"quiz","authorship":"authored","conceptId":"frontend-auth-state","prompt":"Um botão de excluir só aparece na tela quando o estado local diz papel: 'ADMIN'. Isso é suficiente para proteger a exclusão?","options":[{"id":"front-state-a","label":"Não -- é só UX; o servidor precisa validar o papel a partir do token e recusar com 403 se necessário.","correct":true,"explanation":"Estado visual não é autorização: DevTools, bundle já baixado ou chamada direta à API ignoram a checagem do front."},{"id":"front-state-b","label":"Sim -- se o botão não aparece, a ação não pode ser executada.","correct":false,"explanation":"Esconder o botão não impede uma chamada HTTP direta à API."},{"id":"front-state-c","label":"Sim, desde que o estado venha de um token JWT válido.","correct":false,"explanation":"Um token válido ainda precisa ser revalidado no servidor a cada requisição sensível; o front não decide autorização sozinho."}]}],"resources":[{"id":"owasp-html5-storage","type":"guide","title":"OWASP HTML5 Security Cheat Sheet","url":"https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html","reinforces":"Riscos de Web Storage e recomendações de segurança no navegador.","language":"en","publisher":"OWASP","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"typescript-handbook-everyday-types","type":"reference","title":"TypeScript Handbook: Everyday Types","url":"https://www.typescriptlang.org/docs/handbook/2/everyday-types.html","reinforces":"Tipos básicos para contratos de dados entre frontend e API.","language":"en","publisher":"TypeScript","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The auth front ts component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to auth front ts. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible auth front ts failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"function displayBook(book) {","instruction":"The auth front ts component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible auth front ts failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"frontend-aplicacao","moduleId":"api-security-quality","order":7,"title":"Front-end aplicado: estado, formulários, acessibilidade e testes","summary":"Uma integração front-end robusta distingue estado remoto, estado de formulário, estado de navegação e estado puramente visual. Misturar tudo em variáveis globais cria telas inconsistentes e difíceis de testar.","objectives":["Modelar estado local e remoto explicitamente","Construir formulários acessíveis com erro e loading","Consumir API autenticada sem esconder falhas","Testar comportamento principal da tela"],"whyItExists":"O curso não vira curso de frontend, mas uma API profissional precisa ser consumida por uma tela real. Este capítulo mostra o mínimo necessário para não tratar navegador como pós-pensamento.","prerequisiteChapterIds":["auth-front-ts","api-design-avancado"],"conceptIds":["primeiro-contato-com-a-tela-reativa","camada-1-um-estado-local-simples","camada-2-estados-remotos-explicitos","formularios-e-acessibilidade","autenticacao-no-navegador","testes"],"introducedConceptIds":["accessible-form-state","frontend-api-error-state"],"usedConceptIds":["frontend-auth-state","controller-advice-problem-details","bean-validation-boundary"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"frontend-app-intuition","type":"intuition","authorship":"authored","title":"Tela boa mostra estado, não só dados felizes","body":"Carregando, vazio, erro, expirado, negado e pronto são estados diferentes. Uma tela que só renderiza quando tudo dá certo esconde justamente os casos que uma API profissional precisa suportar.","analogyLimit":"Painel de controle ajuda a imaginar estados, mas acessibilidade, foco e contrato HTTP precisam ser testados."},{"id":"frontend-aplicacao-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\"><span class=\"tech-badge tech-front\">Front-end</span><div class=\"meta-item\">Dificuldade: <b>Intermediário</b></div><div class=\"time-est\">⏱ <b>~6h</b> de estudo e prática</div><div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#auth-front-ts\">Autenticação no front</a>, <a class=\"prereq-tag\" href=\"#api-design-avancado\">Contratos HTTP</a></div></div>","fidelityText":"Front-endDificuldade: Intermediário⏱ ~6h de estudo e práticaPré-requisitos: Autenticação no front, Contratos HTTP"},{"id":"frontend-aplicacao-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Uma integração front-end robusta distingue estado remoto, estado de formulário, estado de navegação e estado puramente visual. Misturar tudo em variáveis globais cria telas inconsistentes e difíceis de testar.</p>","fidelityText":"Uma integração front-end robusta distingue estado remoto, estado de formulário, estado de navegação e estado puramente visual. Misturar tudo em variáveis globais cria telas inconsistentes e difíceis de testar."},{"id":"frontend-aplicacao-content-3","type":"html","authorship":"legacy-preserved","html":"<aside class=\"concept-foundation\">\n    <div class=\"concept-foundation-head\"><span class=\"concept-level\">base antes do aprofundamento</span><h2>Primeiro contato com a tela reativa</h2></div>\n    <p>O exemplo usa React com TypeScript, mas os princípios de estado, efeito e acessibilidade valem para outros frameworks.</p>\n    <dl class=\"concept-grid\"><div class=\"concept-card\"><dt>Componente</dt><dd>Função ou unidade de interface que recebe dados e descreve o que deve aparecer.</dd></div><div class=\"concept-card\"><dt>Estado</dt><dd>Dados que podem mudar e alterar a interface, como valor do campo ou resultado carregado.</dd></div><div class=\"concept-card\"><dt>Renderização</dt><dd>Produção da interface correspondente ao estado atual.</dd></div><div class=\"concept-card\"><dt>Hook</dt><dd>Função do React, como useState, que conecta o componente a recursos do framework.</dd></div><div class=\"concept-card\"><dt>Efeito</dt><dd>Sincronização com algo externo à renderização, como rede, timer ou evento do navegador.</dd></div><div class=\"concept-card\"><dt>Cleanup</dt><dd>Função de limpeza que cancela ou desfaz um efeito quando ele deixa de valer.</dd></div></dl>\n  </aside>","fidelityText":"base antes do aprofundamentoPrimeiro contato com a tela reativa O exemplo usa React com TypeScript, mas os princípios de estado, efeito e acessibilidade valem para outros frameworks. ComponenteFunção ou unidade de interface que recebe dados e descreve o que deve aparecer.EstadoDados que podem mudar e alterar a interface, como valor do campo ou resultado carregado.RenderizaçãoProdução da interface correspondente ao estado atual.HookFunção do React, como useState, que conecta o componente a recursos do framework.EfeitoSincronização com algo externo à renderização, como rede, timer ou evento do navegador.CleanupFunção de limpeza que cancela ou desfaz um efeito quando ele deixa de valer."},{"id":"frontend-aplicacao-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Camada 1 — um estado local simples</h2>","fidelityText":"Camada 1 — um estado local simples"},{"id":"frontend-aplicacao-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"import { useState } from 'react';\n\nfunction Counter() {\n  const [total, setTotal] = useState(0);\n  return <button onClick={() => setTotal(total + 1)}>\n    Clicks: {total}\n  </button>;\n}","fidelityText":"import { useState } from 'react'; function Contador() { const [total, setTotal] = useState(0); return <button onClick={() => setTotal(total + 1)}> Cliques: {total} </button>; }","highlightedHtml":"<span class=\"kw\">import</span> { useState } <span class=\"kw\">from</span> <span class=\"str\">'react'</span>;\n\n<span class=\"kw\">function</span> Counter() {\n  <span class=\"kw\">const</span> [total, setTotal] = useState(0);\n  <span class=\"kw\">return</span> &lt;button onClick={() =&gt; setTotal(total + 1)}&gt;\n    Clicks: {total}\n  &lt;/button&gt;;\n}","caption":"Exemplo executável de frontend-aplicacao.","explanation":["useState cria estado local e re-renderiza a UI quando o valor muda.","Esse exemplo é pequeno para mostrar o ciclo intenção -> estado -> render."],"commonMistakes":["Atualizar estado derivado de forma ambígua","Misturar regra de domínio pesada no componente"]},{"id":"frontend-aplicacao-content-6","type":"html","authorship":"legacy-preserved","html":"<p><code>useState(0)</code> guarda o valor atual e fornece uma função para atualizá-lo. Ao chamar <code>setTotal</code>, o React renderiza novamente o componente. A marcação dentro do JavaScript é <strong>JSX</strong>, uma sintaxe transformada em chamadas que descrevem elementos.</p>","fidelityText":"useState(0) guarda o valor atual e fornece uma função para atualizá-lo. Ao chamar setTotal, o React renderiza novamente o componente. A marcação dentro do JavaScript é JSX, uma sintaxe transformada em chamadas que descrevem elementos."},{"id":"frontend-aplicacao-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Camada 2 — estados remotos explícitos</h2>","fidelityText":"Camada 2 — estados remotos explícitos"},{"id":"frontend-aplicacao-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"type State<T> =\n  | { status: 'loading' }\n  | { status: 'error'; message: string }\n  | { status: 'empty' }\n  | { status: 'ready'; data: T };\n\nfunction Orders() {\n  const [state, setState] = useState<State<Order[]>>({ status: 'loading' });\n  // renderize cada estado deliberadamente; não deixe erro parecer lista vazia\n}","fidelityText":"type Estado<T> = | { status: 'carregando' } | { status: 'erro'; mensagem: string } | { status: 'vazio' } | { status: 'pronto'; dados: T }; function Pedidos() { const [estado, setEstado] = useState<Estado<Pedido[]>>({ status: 'carregando' }); // renderize cada estado deliberadamente; não deixe erro parecer lista vazia }","highlightedHtml":"<span class=\"kw\">type</span> State&lt;T&gt; =\n  | { status: <span class=\"str\">'loading'</span> }\n  | { status: <span class=\"str\">'error'</span>; message: string }\n  | { status: <span class=\"str\">'empty'</span> }\n  | { status: <span class=\"str\">'ready'</span>; data: T };\n\n<span class=\"kw\">function</span> Orders() {\n  <span class=\"kw\">const</span> [state, setState] = useState&lt;State&lt;Order[]&gt;&gt;({ status: <span class=\"str\">'loading'</span> });\n  <span class=\"com\">// renderize cada estado deliberadamente; não deixe erro parecer lista vazia</span>\n}","caption":"Exemplo executável de frontend-aplicacao.","explanation":["A união de estados força a tela a tratar loading, error, empty e ready explicitamente.","Isso reduz null ambíguo e melhora mensagens para usuário."],"commonMistakes":["Usar null para todos os estados","Renderizar erro 401 como lista vazia"]},{"id":"frontend-aplicacao-content-9","type":"html","authorship":"legacy-preserved","html":"<p><strong>Estado remoto</strong> é a cópia local de dados cuja autoridade está no servidor. Efeitos de busca precisam de cleanup e cancelamento: uma resposta antiga não deve sobrescrever a busca mais recente. Bibliotecas de <em>server state</em> ajudam com cache, deduplicação e invalidação — marcar dados antigos para nova busca — mas ainda exigem chaves e políticas corretas.</p>","fidelityText":"Estado remoto é a cópia local de dados cuja autoridade está no servidor. Efeitos de busca precisam de cleanup e cancelamento: uma resposta antiga não deve sobrescrever a busca mais recente. Bibliotecas de server state ajudam com cache, deduplicação e invalidação — marcar dados antigos para nova busca — mas ainda exigem chaves e políticas corretas."},{"id":"frontend-aplicacao-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Formulários e acessibilidade</h2>","fidelityText":"Formulários e acessibilidade"},{"id":"frontend-aplicacao-content-11","type":"html","authorship":"legacy-preserved","html":"<ul><li>Associe <code>label</code> ao controle; placeholder não substitui rótulo.</li><li>Mostre erro próximo ao campo, relacione com <code>aria-describedby</code> e mova foco para o resumo quando necessário.</li><li>Não desabilite zoom nem dependa somente de cor.</li><li>Use elementos semânticos e interação completa por teclado.</li><li>Validação do cliente melhora experiência, mas o servidor continua sendo autoridade.</li></ul>","fidelityText":"Associe label ao controle; placeholder não substitui rótulo.Mostre erro próximo ao campo, relacione com aria-describedby e mova foco para o resumo quando necessário.Não desabilite zoom nem dependa somente de cor.Use elementos semânticos e interação completa por teclado.Validação do cliente melhora experiência, mas o servidor continua sendo autoridade."},{"id":"frontend-aplicacao-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"function FieldEmail({ value, error, onChange }: { value: string; error?: string; onChange: (v: string) => void }) {\n  const idError = 'email-error';\n  return (\n    <div>\n      <label htmlFor=\"email\">Email</label>\n      <input\n        id=\"email\"\n        type=\"email\"\n        value={value}\n        onChange={(e) => onChange(e.target.value)}\n        aria-invalid={Boolean(error)}\n        aria-describedby={error ? idError : undefined}\n      />\n      {error && <p id={idError} role=\"alert\">{error}</p>}\n    </div>\n  );\n}\n// placeholder does NOT replace the label -- screen reader doesn't announce\n// placeholder as the field's name as soon as the field receives focus.\n// aria-describedby only exists when there is an error: it links the input to the\n// right message without forcing the screen reader to announce a nonexistent error.","fidelityText":"function CampoEmail({ valor, erro, onChange }: { valor: string; erro?: string; onChange: (v: string) => void }) { const idErro = 'email-erro'; return ( <div> <label htmlFor=\"email\">E-mail</label> <input id=\"email\" type=\"email\" value={valor} onChange={(e) => onChange(e.target.value)} aria-invalid={Boolean(erro)} aria-describedby={erro ? idErro : undefined} /> {erro && <p id={idErro} role=\"alert\">{erro}</p>} </div> ); } // placeholder NÃO substitui o label -- leitor de tela não anuncia // placeholder como nome do campo assim que o campo recebe foco. // aria-describedby só existe quando há erro: liga o input à mensagem // certa sem forçar o leitor de tela a anunciar um erro inexistente.","highlightedHtml":"<span class=\"kw\">function</span> <span class=\"fn\">FieldEmail</span>({ value, error, onChange }: { value: <span class=\"kw\">string</span>; error?: <span class=\"kw\">string</span>; onChange: (v: <span class=\"kw\">string</span>) =&gt; <span class=\"kw\">void</span> }) {\n  <span class=\"kw\">const</span> idError = <span class=\"str\">'email-error'</span>;\n  <span class=\"kw\">return</span> (\n    &lt;div&gt;\n      &lt;label htmlFor=<span class=\"str\">\"email\"</span>&gt;Email&lt;/label&gt;\n      &lt;input\n        id=<span class=\"str\">\"email\"</span>\n        type=<span class=\"str\">\"email\"</span>\n        value={value}\n        onChange={(e) =&gt; onChange(e.target.value)}\n        aria-invalid={<span class=\"cls\">Boolean</span>(error)}\n        aria-describedby={error ? idError : <span class=\"kw\">undefined</span>}\n      /&gt;\n      {error &amp;&amp; &lt;p id={idError} role=<span class=\"str\">\"alert\"</span>&gt;{error}&lt;/p&gt;}\n    &lt;/div&gt;\n  );\n}\n<span class=\"com\">// placeholder does NOT replace the label -- screen reader doesn't announce\n// placeholder as the field's name as soon as the field receives focus.\n// aria-describedby only exists when there is an error: it links the input to the\n// right message without forcing the screen reader to announce a nonexistent error.</span>","caption":"Exemplo executável de frontend-aplicacao.","explanation":["label associa o texto ao input pelo htmlFor/id; leitor de tela anuncia o nome do campo mesmo sem o placeholder.","aria-describedby só aponta para o erro quando ele existe -- não força o leitor de tela a anunciar uma mensagem vazia."],"commonMistakes":["Usar só placeholder no lugar de label","Mostrar erro visualmente sem ligá-lo ao campo via aria-describedby"]},{"id":"frontend-aplicacao-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Um formulário acessível não é \"mais bonito\" — é o único que realmente informa o que aconteceu para quem não enxerga a cor vermelha ou o texto flutuando perto do campo. <code>aria-describedby</code> é a ponte entre o campo e a mensagem de erro para quem usa leitor de tela; sem ela, a pessoa ouve \"campo inválido\" sem nunca saber qual regra foi quebrada.</div>","fidelityText":"Um formulário acessível não é \"mais bonito\" — é o único que realmente informa o que aconteceu para quem não enxerga a cor vermelha ou o texto flutuando perto do campo. aria-describedby é a ponte entre o campo e a mensagem de erro para quem usa leitor de tela; sem ela, a pessoa ouve \"campo inválido\" sem nunca saber qual regra foi quebrada."},{"id":"frontend-aplicacao-content-14","type":"html","authorship":"legacy-preserved","html":"<h2>Autenticação no navegador</h2>","fidelityText":"Autenticação no navegador"},{"id":"frontend-aplicacao-content-15","type":"html","authorship":"legacy-preserved","html":"<p>Retome o fluxo e as ameaças do capítulo <a href=\"#security-oidc\">OAuth 2.0 e OpenID Connect</a>. <strong>Backend for Frontend (BFF)</strong> é um backend dedicado à interface que pode guardar tokens fora do JavaScript e expor ao navegador uma sessão por cookie. Prefira Authorization Code com PKCE ou BFF conforme o risco. Se usar cookie, implemente CSRF; se usar Bearer acessível ao JavaScript, minimize vida útil e superfície XSS. Nunca coloque client secret no bundle. Renove sessão de forma serializada para evitar uma tempestade de tentativas simultâneas.</p>","fidelityText":"Retome o fluxo e as ameaças do capítulo OAuth 2.0 e OpenID Connect. Backend for Frontend (BFF) é um backend dedicado à interface que pode guardar tokens fora do JavaScript e expor ao navegador uma sessão por cookie. Prefira Authorization Code com PKCE ou BFF conforme o risco. Se usar cookie, implemente CSRF; se usar Bearer acessível ao JavaScript, minimize vida útil e superfície XSS. Nunca coloque client secret no bundle. Renove sessão de forma serializada para evitar uma tempestade de tentativas simultâneas."},{"id":"frontend-aplicacao-content-16","type":"html","authorship":"legacy-preserved","html":"<h2>Testes</h2>","fidelityText":"Testes"},{"id":"frontend-aplicacao-content-17","type":"html","authorship":"legacy-preserved","html":"<p>Teste comportamento observável com <strong>Testing Library</strong>: o usuário preenche, envia, percebe carregamento e recebe sucesso ou erro. <strong>Mock Service Worker (MSW)</strong> intercepta requisições e simula a fronteira HTTP sem acoplar o teste à implementação do cliente. Mantenha poucos testes <strong>E2E</strong>, de ponta a ponta, para jornadas críticas.</p>","fidelityText":"Teste comportamento observável com Testing Library: o usuário preenche, envia, percebe carregamento e recebe sucesso ou erro. Mock Service Worker (MSW) intercepta requisições e simula a fronteira HTTP sem acoplar o teste à implementação do cliente. Mantenha poucos testes E2E, de ponta a ponta, para jornadas críticas."},{"id":"frontend-aplicacao-exercise-18","type":"exercise","authorship":"legacy-preserved","title":"Laboratório — tela de pedidos completa","prompt":"Implemente listagem por cursor, criação idempotente, estados de carregamento/erro/vazio, formulário acessível, sessão expirada e atualização por WebSocket com reconexão.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Laboratório — tela de pedidos completadifícilImplemente listagem por cursor, criação idempotente, estados de carregamento/erro/vazio, formulário acessível, sessão expirada e atualização por WebSocket com reconexão.Ver critériosInclua testes de teclado, erro 422/409, duplicação de clique, resposta fora de ordem e reconexão. O bundle não pode conter segredo.","sourceHtml":"<div class=\"exercise\"><div class=\"exercise-head\"><h2>Laboratório — tela de pedidos completa</h2><span class=\"exercise-tag d\">difícil</span></div><p>Implemente listagem por cursor, criação idempotente, estados de carregamento/erro/vazio, formulário acessível, sessão expirada e atualização por WebSocket com reconexão.</p><button class=\"reveal-btn\">Ver critérios</button><div class=\"solution\"><p>Inclua testes de teclado, erro 422/409, duplicação de clique, resposta fora de ordem e reconexão. O bundle não pode conter segredo.</p></div></div>"},{"id":"frontend-app-quiz","type":"quiz","authorship":"authored","conceptId":"frontend-api-error-state","prompt":"Por que representar estado remoto como união de estados ajuda?","options":[{"id":"app-a","label":"Porque impede confundir carregando, erro, vazio e dados prontos como se fossem o mesmo null.","correct":true,"explanation":"Cada estado pede UI, mensagem e ação diferente."},{"id":"app-b","label":"Porque elimina a necessidade de validar resposta da API.","correct":false,"explanation":"Tipos ajudam no código, mas a resposta real ainda precisa ser tratada."},{"id":"app-c","label":"Porque substitui status HTTP no backend.","correct":false,"explanation":"Frontend representa o resultado; backend continua publicando contrato."}]},{"id":"frontend-app-a11y-quiz","type":"quiz","authorship":"authored","conceptId":"accessible-form-state","prompt":"Um campo usa só placeholder (sem label) e mostra o erro só pela cor vermelha do texto. O que está faltando?","options":[{"id":"a11y-a","label":"label associado ao campo e aria-describedby ligando o erro ao input, para quem usa leitor de tela.","correct":true,"explanation":"Placeholder some ao digitar e não é anunciado como nome do campo; cor sozinha não comunica nada a quem não a percebe."},{"id":"a11y-b","label":"Nada -- placeholder e cor já deixam o formulário acessível.","correct":false,"explanation":"Isso é exatamente o padrão que falha para leitor de tela e daltonismo."},{"id":"a11y-c","label":"Só falta aumentar o tamanho da fonte do erro.","correct":false,"explanation":"Tamanho de fonte não resolve a falta de associação semântica entre campo e erro."}]}],"resources":[{"id":"react-managing-state","type":"reference","title":"React: Managing State","url":"https://react.dev/learn/managing-state","reinforces":"Estado local, atualização e fluxo de renderização em React.","language":"en","publisher":"React","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"w3c-forms-tutorial","type":"guide","title":"WAI Forms Tutorial","url":"https://www.w3.org/WAI/tutorials/forms/","reinforces":"Labels, instruções, erros e acessibilidade em formulários.","language":"en","publisher":"W3C","official":true,"expectedLevel":"beginner","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The frontend application component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to frontend application. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible frontend application failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"import { useState } from 'react';","instruction":"The frontend application component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible frontend application failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"websockets","moduleId":"concurrency-network-tui","order":2,"title":"WebSockets — quando HTTP não basta","summary":"Todo o curso até aqui usou o modelo requisição/resposta: o cliente pergunta, o servidor responde, a conexão fecha. Isso não serve para \"o servidor avisar o cliente sozinho\" — um chat, uma notificação ao vivo, um dashboard atualizando sem o usuário recarregar a página.","objectives":["Decidir quando canal persistente é melhor que request/response","Entender WebSocket como full-duplex sem entrega durável garantida","Separar conexão, destino STOMP, payload e autorização","Evitar usar WebSocket como substituto automático de REST"],"whyItExists":"Depois de HTTP, frontend autenticado e threads, WebSocket entra como solução para interação em tempo real. O aluno já entende contrato e autorização para não confundir canal aberto com sistema confiável.","prerequisiteChapterIds":["frontend-aplicacao","threads","spring-mvc"],"conceptIds":["o-handshake-e-um-http-upgrade-nao-um-protocolo-a-parte","quando-usar-e-quando-nao-usar","spring-websocket-configuracao-basica"],"introducedConceptIds":["websocket-persistent-channel","stomp-topic-session"],"usedConceptIds":["http-mensagem-recurso","json-formato-contrato","thread-lifecycle-scheduler"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"websockets-intuition","type":"intuition","authorship":"authored","title":"WebSocket mantém uma conversa aberta","body":"HTTP tradicional é ótimo para pedir e responder. WebSocket mantém um canal para os dois lados enviarem mensagens ao longo do tempo, útil para chat, presença e notificações em tempo real.","analogyLimit":"Telefone ajuda a imaginar canal aberto, mas entrega, autenticação, reconexão e backpressure ainda precisam de contrato."},{"id":"websockets-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-front\">Tempo real</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~1h30</b> de estudo</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#conectando-front-back\">62 · Conectando front e back</a>, <a class=\"prereq-tag\" href=\"#threads\">14 · Threads &amp; concorrência</a></div>\n      </div>","fidelityText":"Tempo real Dificuldade: Avançado ⏱ ~1h30 de estudo Pré-requisitos: 62 · Conectando front e back, 14 · Threads & concorrência"},{"id":"websockets-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Todo o curso até aqui usou o modelo requisição/resposta: o cliente pergunta, o servidor responde, a conexão fecha. Isso não serve para \"o servidor avisar o cliente sozinho\" — um chat, uma notificação ao vivo, um dashboard atualizando sem o usuário recarregar a página.</p>","fidelityText":"Todo o curso até aqui usou o modelo requisição/resposta: o cliente pergunta, o servidor responde, a conexão fecha. Isso não serve para \"o servidor avisar o cliente sozinho\" — um chat, uma notificação ao vivo, um dashboard atualizando sem o usuário recarregar a página."},{"id":"websockets-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">HTTP tradicional é como mandar uma carta e esperar a resposta chegar pelo correio a cada pergunta nova — funcional, mas cada troca exige uma carta nova do zero. WebSocket é abrir uma <strong>ligação telefônica</strong> que fica aberta: depois de discar uma vez (o \"handshake\" inicial), os dois lados podem falar a qualquer momento, sem precisar disparar uma requisição nova para cada mensagem.</div>","fidelityText":"HTTP tradicional é como mandar uma carta e esperar a resposta chegar pelo correio a cada pergunta nova — funcional, mas cada troca exige uma carta nova do zero. WebSocket é abrir uma ligação telefônica que fica aberta: depois de discar uma vez (o \"handshake\" inicial), os dois lados podem falar a qualquer momento, sem precisar disparar uma requisição nova para cada mensagem."},{"id":"websockets-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>O \"handshake\" é um HTTP Upgrade, não um protocolo à parte</h2>","fidelityText":"O \"handshake\" é um HTTP Upgrade, não um protocolo à parte"},{"id":"websockets-content-5","type":"html","authorship":"legacy-preserved","html":"<p>WebSocket nasce <strong>em cima de</strong> TCP e HTTP, não ao lado deles — a mesma conexão TCP framed que o capítulo 100 (Sockets TCP) descreveu, só que com um protocolo de frames diferente depois do handshake. A conexão começa como uma requisição HTTP comum, pedindo para trocar de protocolo:</p>","fidelityText":"WebSocket nasce em cima de TCP e HTTP, não ao lado deles — a mesma conexão TCP framed que o capítulo 100 (Sockets TCP) descreveu, só que com um protocolo de frames diferente depois do handshake. A conexão começa como uma requisição HTTP comum, pedindo para trocar de protocolo:"},{"id":"websockets-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"GET /ws HTTP/1.1\nHost: api.library.com\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\nSec-WebSocket-Version: 13\n\n// resposta do servidor quando o endpoint aceita WebSocket:\nHTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=","fidelityText":"GET /ws HTTP/1.1 Host: api.biblioteca.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 // resposta do servidor quando o endpoint aceita WebSocket: HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=","highlightedHtml":"GET /ws HTTP/1.1\nHost: api.library.com\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\nSec-WebSocket-Version: 13\n\n<span class=\"com\">// resposta do servidor quando o endpoint aceita WebSocket:</span>\nHTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=","caption":"Exemplo executável de websockets.","explanation":["O handshake é uma requisição HTTP comum pedindo Upgrade; o 101 Switching Protocols é a confirmação -- a partir daí a mesma conexão TCP troca frames WebSocket, não mais requisição/resposta HTTP.","Sec-WebSocket-Key/Accept provam que o servidor entende WebSocket (não é um proxy HTTP repassando cego) -- não são mecanismo de segurança; isso continua sendo TLS (wss://)."],"commonMistakes":["Achar que Sec-WebSocket-Key/Accept são criptografia ou autenticação.","Esquecer que sem wss:// (TLS) o tráfego do handshake e das mensagens seguintes vai em texto claro."]},{"id":"websockets-content-7","type":"html","authorship":"legacy-preserved","html":"<p>O status <strong>101 Switching Protocols</strong> é o \"aceito, mudando de assunto agora\": a partir daí, a mesma conexão TCP para de falar HTTP request/response e passa a trocar <em>frames</em> WebSocket nos dois sentidos, sem fechar e reabrir. <code>Sec-WebSocket-Key</code>/<code>Sec-WebSocket-Accept</code> não são criptografia nem autenticação — só provam que o servidor (ou um proxy no meio) de fato entende WebSocket, em vez de repassar cegamente uma requisição HTTP que ele não sabe interpretar. Segurança de transporte continua sendo TLS, exatamente como HTTPS (<code>wss://</code> em vez de <code>ws://</code>, capítulo 53).</p>","fidelityText":"O status 101 Switching Protocols é o \"aceito, mudando de assunto agora\": a partir daí, a mesma conexão TCP para de falar HTTP request/response e passa a trocar frames WebSocket nos dois sentidos, sem fechar e reabrir. Sec-WebSocket-Key/Sec-WebSocket-Accept não são criptografia nem autenticação — só provam que o servidor (ou um proxy no meio) de fato entende WebSocket, em vez de repassar cegamente uma requisição HTTP que ele não sabe interpretar. Segurança de transporte continua sendo TLS, exatamente como HTTPS (wss:// em vez de ws://, capítulo 53)."},{"id":"websockets-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Quando usar (e quando NÃO usar)</h2>","fidelityText":"Quando usar (e quando NÃO usar)"},{"id":"websockets-content-9","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Cenário</th><th>Ferramenta</th></tr>\n        <tr><td>Listar livros, criar pedido, qualquer CRUD comum</td><td>REST tradicional (capítulo 26) — mais simples, cacheável, já dominado</td></tr>\n        <tr><td>Chat em tempo real</td><td>WebSocket</td></tr>\n        <tr><td>Notificação instantânea (\"seu empréstimo está atrasado\")</td><td>WebSocket (ou Server-Sent Events, mais simples para via única servidor→cliente)</td></tr>\n        <tr><td>Dashboard com métricas atualizando sozinho</td><td>WebSocket</td></tr>\n      </tbody></table>","fidelityText":"CenárioFerramenta Listar livros, criar pedido, qualquer CRUD comumREST tradicional (capítulo 26) — mais simples, cacheável, já dominado Chat em tempo realWebSocket Notificação instantânea (\"seu empréstimo está atrasado\")WebSocket (ou Server-Sent Events, mais simples para via única servidor→cliente) Dashboard com métricas atualizando sozinhoWebSocket"},{"id":"websockets-content-10","type":"html","authorship":"legacy-preserved","html":"<h2>Spring WebSocket — configuração básica</h2>","fidelityText":"Spring WebSocket — configuração básica"},{"id":"websockets-code-11","type":"code","authorship":"legacy-preserved","language":"java","source":"@Configuration\n@EnableWebSocketMessageBroker\npublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer {\n\n    @Override\n    public void registerStompEndpoints(StompEndpointRegistry registry) {\n        registry.addEndpoint(\"/ws\").setAllowedOrigins(\"http://localhost:5173\"); // CORS, capítulo 50, aplicado aqui também\n    }\n\n    @Override\n    public void configureMessageBroker(MessageBrokerRegistry registry) {\n        registry.enableSimpleBroker(\"/topic\"); // canais que o servidor publica para\n        registry.setApplicationDestinationPrefixes(\"/app\"); // prefixo para mensagens do cliente\n    }\n}\n\n@Controller\npublic class NotificationController {\n    @MessageMapping(\"/loan\")      // cliente manda para /app/emprestimo\n    @SendTo(\"/topic/notifications\")  // servidor retransmite para todos inscritos\n    public Notification notify(OrderLoan order) {\n        return new Notification(\"Book borrowed: \" + order.getTitle());\n    }\n}","fidelityText":"@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(\"/ws\").setAllowedOrigins(\"http://localhost:5173\"); // CORS, capítulo 50, aplicado aqui também } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker(\"/topico\"); // canais que o servidor publica para registry.setApplicationDestinationPrefixes(\"/app\"); // prefixo para mensagens do cliente } } @Controller public class NotificacaoController { @MessageMapping(\"/emprestimo\") // cliente manda para /app/emprestimo @SendTo(\"/topico/notificacoes\") // servidor retransmite para todos inscritos public Notificacao notificar(PedidoEmprestimo pedido) { return new Notificacao(\"Livro emprestado: \" + pedido.getTitulo()); } }","highlightedHtml":"<span class=\"annotation\">@Configuration</span>\n<span class=\"annotation\">@EnableWebSocketMessageBroker</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">WebSocketConfig</span> <span class=\"kw\">implements</span> WebSocketMessageBrokerConfigurer {\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">registerStompEndpoints</span>(StompEndpointRegistry registry) {\n        registry.addEndpoint(<span class=\"str\">\"/ws\"</span>).setAllowedOrigins(<span class=\"str\">\"http://localhost:5173\"</span>); <span class=\"com\">// CORS, capítulo 50, aplicado aqui também</span>\n    }\n\n    <span class=\"annotation\">@Override</span>\n    <span class=\"kw\">public void</span> <span class=\"fn\">configureMessageBroker</span>(MessageBrokerRegistry registry) {\n        registry.enableSimpleBroker(<span class=\"str\">\"/topic\"</span>); <span class=\"com\">// canais que o servidor publica para</span>\n        registry.setApplicationDestinationPrefixes(<span class=\"str\">\"/app\"</span>); <span class=\"com\">// prefixo para mensagens do cliente</span>\n    }\n}\n\n<span class=\"annotation\">@Controller</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">NotificationController</span> {\n    <span class=\"annotation\">@MessageMapping</span>(<span class=\"str\">\"/loan\"</span>)      <span class=\"com\">// cliente manda para /app/emprestimo</span>\n    <span class=\"annotation\">@SendTo</span>(<span class=\"str\">\"/topic/notifications\"</span>)  <span class=\"com\">// servidor retransmite para todos inscritos</span>\n    <span class=\"kw\">public</span> <span class=\"cls\">Notification</span> <span class=\"fn\">notify</span>(<span class=\"cls\">OrderLoan</span> order) {\n        <span class=\"kw\">return new</span> <span class=\"cls\">Notification</span>(<span class=\"str\">\"Book borrowed: \"</span> + order.getTitle());\n    }\n}","caption":"Exemplo executável de websockets.","explanation":["A configuração habilita broker de mensagens WebSocket/STOMP e destinos da aplicação.","Destino/tópico organiza mensagens, mas autorização precisa ser desenhada separadamente."],"commonMistakes":["Liberar inscrição em qualquer tópico","Achar que tópico substitui regra de permissão"]},{"id":"websockets-code-12","type":"code","authorship":"legacy-preserved","language":"java","source":"// front-end (usando a biblioteca STOMP sobre SockJS):\nconst socket = new SockJS(`${import.meta.env.VITE_API_URL}/ws`);\nconst stompClient = Stomp.over(socket);\n\nstompClient.connect({}, () => {\n  stompClient.subscribe('/topic/notifications', (message) => {\n    const notification = JSON.parse(message.body);\n    displayNotification(notification.text); // atualiza a UI sem recarregar a página\n  });\n});","fidelityText":"// front-end (usando a biblioteca STOMP sobre SockJS): const socket = new SockJS(`${import.meta.env.VITE_API_URL}/ws`); const stompClient = Stomp.over(socket); stompClient.connect({}, () => { stompClient.subscribe('/topico/notificacoes', (mensagem) => { const notificacao = JSON.parse(mensagem.body); exibirNotificacao(notificacao.texto); // atualiza a UI sem recarregar a página }); });","highlightedHtml":"<span class=\"com\">// front-end (usando a biblioteca STOMP sobre SockJS):</span>\n<span class=\"kw\">const</span> socket = <span class=\"kw\">new</span> SockJS(<span class=\"str\">`${import.meta.env.VITE_API_URL}/ws`</span>);\n<span class=\"kw\">const</span> stompClient = Stomp.over(socket);\n\nstompClient.connect({}, () =&gt; {\n  stompClient.subscribe(<span class=\"str\">'/topic/notifications'</span>, (message) =&gt; {\n    <span class=\"kw\">const</span> notification = JSON.parse(message.body);\n    <span class=\"fn\">displayNotification</span>(notification.text); <span class=\"com\">// atualiza a UI sem recarregar a página</span>\n  });\n});","caption":"Exemplo executável de websockets.","explanation":["O frontend abre conexão, cria cliente STOMP e assina destinos de mensagem.","Reconexão, autenticação e tratamento de erro precisam ser considerados."],"commonMistakes":["Ignorar queda de conexão","Enviar token de forma insegura ou sem expiração"]},{"id":"websockets-content-13","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Uma conexão WebSocket permanece aberta e consome socket, buffers, estado e capacidade do event loop ou runtime, mas servidores não bloqueantes não dedicam necessariamente uma thread por conexão. Em escala, planeje autenticação no handshake e nas mensagens, heartbeat, reconexão com backoff, limites, backpressure, broker relay e distribuição entre instâncias.</div>","fidelityText":"Uma conexão WebSocket permanece aberta e consome socket, buffers, estado e capacidade do event loop ou runtime, mas servidores não bloqueantes não dedicam necessariamente uma thread por conexão. Em escala, planeje autenticação no handshake e nas mensagens, heartbeat, reconexão com backoff, limites, backpressure, broker relay e distribuição entre instâncias."},{"id":"websockets-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Não introduza WebSocket \"porque é mais moderno\" — ele resolve um problema específico (servidor precisa falar primeiro) que REST não resolve bem. Se o seu projeto não tem nenhuma funcionalidade de tempo real genuína, adicionar WebSocket só aumenta complexidade sem benefício real. Pergunte sempre: \"o usuário precisa ver isso mudar na tela sem recarregar, iniciado pelo servidor?\" — só aí vale a pena.</div>","fidelityText":"Não introduza WebSocket \"porque é mais moderno\" — ele resolve um problema específico (servidor precisa falar primeiro) que REST não resolve bem. Se o seu projeto não tem nenhuma funcionalidade de tempo real genuína, adicionar WebSocket só aumenta complexidade sem benefício real. Pergunte sempre: \"o usuário precisa ver isso mudar na tela sem recarregar, iniciado pelo servidor?\" — só aí vale a pena."},{"id":"websockets-exercise-15","type":"exercise","authorship":"legacy-preserved","title":"Exercício 64.1 — Notificação de empréstimo em tempo real","prompt":"Configure um endpoint WebSocket /ws no back-end da biblioteca. Ao chamar o endpoint REST de empréstimo (capítulo 44/52), publique também uma mensagem no tópico /topico/notificacoes informando qual livro foi emprestado — de forma que qualquer cliente conectado via WebSocket receba a notificação instantaneamente, sem precisar dar refresh na página.","difficulty":"advanced","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 64.1 — Notificação de empréstimo em tempo realdifícil Configure um endpoint WebSocket /ws no back-end da biblioteca. Ao chamar o endpoint REST de empréstimo (capítulo 44/52), publique também uma mensagem no tópico /topico/notificacoes informando qual livro foi emprestado — de forma que qualquer cliente conectado via WebSocket receba a notificação instantaneamente, sem precisar dar refresh na página. Ver solução @Service public class Biblioteca { private final SimpMessagingTemplate mensageria; // injeção, capítulo 22 public Biblioteca(SimpMessagingTemplate mensageria) { this.mensageria = mensageria; } public void emprestar(String codigo) throws ItemIndisponivelException { // ... lógica de empréstimo já existente (capítulo 17/22) ... mensageria.convertAndSend(\"/topico/notificacoes\", new Notificacao(\"Livro emprestado: \" + codigo)); } }","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 64.1 — Notificação de empréstimo em tempo real</h2><span class=\"exercise-tag d\">difícil</span></div>\n        <p>Configure um endpoint WebSocket <code>/ws</code> no back-end da biblioteca. Ao chamar o endpoint REST de empréstimo (capítulo 44/52), publique também uma mensagem no tópico <code>/topico/notificacoes</code> informando qual livro foi emprestado — de forma que qualquer cliente conectado via WebSocket receba a notificação instantaneamente, sem precisar dar refresh na página.</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\"><span class=\"annotation\">@Service</span>\n<span class=\"kw\">public class</span> <span class=\"cls\">Library</span> {\n    <span class=\"kw\">private final</span> SimpMessagingTemplate mensageria; <span class=\"com\">// injeção, capítulo 22</span>\n\n    <span class=\"kw\">public</span> <span class=\"fn\">Library</span>(SimpMessagingTemplate mensageria) { <span class=\"kw\">this</span>.mensageria = mensageria; }\n\n    <span class=\"kw\">public void</span> <span class=\"fn\">borrow</span>(<span class=\"kw\">String</span> code) <span class=\"kw\">throws</span> <span class=\"cls\">ItemUnavailableException</span> {\n        <span class=\"com\">// ... lógica de empréstimo já existente (capítulo 17/22) ...</span>\n        mensageria.convertAndSend(<span class=\"str\">\"/topic/notifications\"</span>,\n            <span class=\"kw\">new</span> <span class=\"cls\">Notification</span>(<span class=\"str\">\"Book borrowed: \"</span> + code));\n    }\n}</pre>\n        </div>\n      </div>"},{"id":"websockets-quiz","type":"quiz","authorship":"authored","conceptId":"websocket-persistent-channel","prompt":"Quando WebSocket costuma fazer sentido?","options":[{"id":"ws-a","label":"Quando servidor e cliente precisam trocar mensagens frequentes/tempo real em canal persistente.","correct":true,"explanation":"Exemplos: chat, presença, dashboards ao vivo."},{"id":"ws-b","label":"Para substituir todo CRUD simples automaticamente.","correct":false,"explanation":"REST/HTTP ainda é mais simples para muitos fluxos request/response."},{"id":"ws-c","label":"Para garantir entrega durável mesmo se o cliente estiver offline.","correct":false,"explanation":"WebSocket não é fila durável por si só."}]}],"resources":[{"id":"spring-websocket","type":"reference","title":"Spring WebSocket reference","url":"https://docs.spring.io/spring-framework/reference/web/websocket.html","reinforces":"Suporte WebSocket, STOMP, broker e configuração Spring.","language":"en","publisher":"Spring","official":true,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"mdn-websocket","type":"reference","title":"MDN: WebSocket API","url":"https://developer.mozilla.org/en-US/docs/Web/API/WebSocket","reinforces":"API de navegador, eventos, envio e fechamento de conexão.","language":"en","publisher":"MDN","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The websockets component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to websockets. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible websockets failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"GET /ws HTTP/1.1","instruction":"The websockets component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible websockets failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"deploy-frontend","moduleId":"production-delivery","order":7,"title":"Deploy do front-end & monorepo vs poli-repo","summary":"O front-end (React, Vue, ou qualquer SPA) tem um perfil de deploy bem diferente do back-end: no final, é só HTML/CSS/JS estático — não precisa de um servidor Java rodando o tempo todo.","objectives":["Publicar front-end estático com build reproduzível","Diferenciar variável pública de secret","Planejar cache, roteamento e rollback de assets","Escolher monorepo ou poli-repo por ownership e CI"],"whyItExists":"Depois do backend observável, o front-end entra como outro artefato de produção: build estático, CDN/cache, rotas, variáveis públicas e organização de repositório precisam de contrato próprio.","prerequisiteChapterIds":["deploy-backend","git","cicd","auth-front-ts"],"conceptIds":["vercel-netlify-feitos-sob-medida-para-isso","build-time-vs-runtime-por-que-trocar-a-url-da-api-exige-rebuild","spa-routing-fallback-o-f5-que-quebra-em-producao","cache-o-index-html-nao-pode-ter-o-mesmo-ttl-dos-assets","monorepo-vs-poli-repo-onde-colocar-tudo"],"introducedConceptIds":["frontend-static-deploy-cache","repo-topology-monorepo-polyrepo"],"usedConceptIds":["artifact-image-provenance","http-header-body-negociacao","browser-token-storage-risk","git-snapshot-index","build-lifecycle"],"estimatedMinutes":60,"englishLevel":2,"blocks":[{"id":"deploy-frontend-intuition","type":"intuition","authorship":"authored","title":"Front-end também é artefato de produção","body":"Mesmo sem servidor próprio, o front-end publicado tem versão, cache, variáveis públicas, rotas e contrato com a API. Se o asset antigo fica no CDN, a mudança precisa ser compatível ou revertível.","analogyLimit":"Arquivos estáticos parecem simples, mas cache e browser tornam rollback e compatibilidade parte do desenho."},{"id":"deploy-frontend-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <span class=\"tech-badge tech-front\">Front-end</span>\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i></i></span> <b>Intermediário</b></div>\n        <div class=\"time-est\">⏱ <b>~1h</b> de estudo + prática</div>\n        <div class=\"meta-item\">Pré-requisitos: <a class=\"prereq-tag\" href=\"#deploy-backend\">60 · Deploy do back-end</a>, <a class=\"prereq-tag\" href=\"#git\">29 · Git</a></div>\n      </div>","fidelityText":"Front-end Dificuldade: Intermediário ⏱ ~1h de estudo + prática Pré-requisitos: 60 · Deploy do back-end, 29 · Git"},{"id":"deploy-frontend-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O front-end (React, Vue, ou qualquer SPA) tem um perfil de deploy bem diferente do back-end: no final, é só HTML/CSS/JS estático — não precisa de um servidor Java rodando o tempo todo.</p>","fidelityText":"O front-end (React, Vue, ou qualquer SPA) tem um perfil de deploy bem diferente do back-end: no final, é só HTML/CSS/JS estático — não precisa de um servidor Java rodando o tempo todo."},{"id":"deploy-frontend-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Enquanto o back-end é como um restaurante que precisa estar sempre com a cozinha funcionando para atender pedidos, o front-end depois de \"buildado\" é como um cardápio impresso — um arquivo estático que qualquer servidor de arquivos simples (ou uma CDN) pode entregar instantaneamente, sem processamento nenhum a cada visita.</div>","fidelityText":"Enquanto o back-end é como um restaurante que precisa estar sempre com a cozinha funcionando para atender pedidos, o front-end depois de \"buildado\" é como um cardápio impresso — um arquivo estático que qualquer servidor de arquivos simples (ou uma CDN) pode entregar instantaneamente, sem processamento nenhum a cada visita."},{"id":"deploy-frontend-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>Vercel / Netlify — feitos sob medida para isso</h2>","fidelityText":"Vercel / Netlify — feitos sob medida para isso"},{"id":"deploy-frontend-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"# conecta o repositório, e a cada push:\n# 1. detecta o framework (Next.js, Vite, etc)\n# 2. roda o build (npm run build)\n# 3. publica os arquivos estáticos em uma CDN global automaticamente","fidelityText":"# conecta o repositório, e a cada push: # 1. detecta o framework (Next.js, Vite, etc) # 2. roda o build (npm run build) # 3. publica os arquivos estáticos em uma CDN global automaticamente","highlightedHtml":"<span class=\"com\"># conecta o repositório, e a cada push:\n# 1. detecta o framework (Next.js, Vite, etc)\n# 2. roda o build (npm run build)\n# 3. publica os arquivos estáticos em uma CDN global automaticamente</span>","caption":"Exemplo executável de deploy-frontend.","explanation":["O build front-end gera assets estáticos que podem ser servidos por CDN/provedor.","Variáveis embutidas no build são públicas para quem recebe o JavaScript."],"commonMistakes":["Colocar secret em variável VITE_/NEXT_PUBLIC","Ignorar base path/roteamento"]},{"id":"deploy-frontend-code-6","type":"code","authorship":"legacy-preserved","language":"java","source":"# .env.production do front-end -- aponta para a API real, não localhost:\nVITE_API_URL=https://api.biblioteca.com","fidelityText":"# .env.production do front-end -- aponta para a API real, não localhost: VITE_API_URL=https://api.biblioteca.com","highlightedHtml":"<span class=\"com\"># .env.production do front-end -- aponta para a API real, não localhost:</span>\nVITE_API_URL=https://api.biblioteca.com","caption":"Exemplo executável de deploy-frontend.","explanation":["`VITE_API_URL` é substituída pelo valor literal durante o build, não lida em runtime pelo navegador.","Trocar a variável no painel do provedor sem disparar novo build não muda nada no bundle já publicado."],"commonMistakes":["Achar que reiniciar o serviço aplica uma variável de build-time nova","Misturar variável de build-time do front com variável de runtime do back (capítulo 57)"]},{"id":"deploy-frontend-content-7","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">A \"compilação\" do front-end (<code>npm run build</code>) faz algo parecido com o multi-stage build do capítulo 31: pega código-fonte (JSX, TypeScript, CSS) e produz um punhado de arquivos estáticos otimizados (minificados, com hash no nome para cache eficiente do navegador) — o \"estágio final enxuto\" equivalente ao <code>jre-alpine</code> do back-end, só que para o mundo do front-end.</div>","fidelityText":"A \"compilação\" do front-end (npm run build) faz algo parecido com o multi-stage build do capítulo 31: pega código-fonte (JSX, TypeScript, CSS) e produz um punhado de arquivos estáticos otimizados (minificados, com hash no nome para cache eficiente do navegador) — o \"estágio final enxuto\" equivalente ao jre-alpine do back-end, só que para o mundo do front-end."},{"id":"deploy-frontend-content-8","type":"html","authorship":"legacy-preserved","html":"<h2>Build-time vs runtime: por que trocar a URL da API exige rebuild</h2>","fidelityText":"Build-time vs runtime: por que trocar a URL da API exige rebuild"},{"id":"deploy-frontend-content-9","type":"html","authorship":"legacy-preserved","html":"<p><code>VITE_API_URL</code> não é lido pelo navegador em tempo real — o Vite substitui <code>import.meta.env.VITE_API_URL</code> pelo valor literal <strong>durante o build</strong>. O JavaScript final já sai do build com a URL \"gravada a fogo\" no bundle.</p>","fidelityText":"VITE_API_URL não é lido pelo navegador em tempo real — o Vite substitui import.meta.env.VITE_API_URL pelo valor literal durante o build. O JavaScript final já sai do build com a URL \"gravada a fogo\" no bundle."},{"id":"deploy-frontend-content-10","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Consequência real:</b> mudar a variável de ambiente no painel do provedor e clicar em \"restart\" não muda nada — é preciso disparar um <strong>novo build</strong>. Diferente do back-end (capítulo 57), onde a variável é lida a cada <code>System.getenv()</code> em runtime, o front-end estático já \"esqueceu\" que aquilo um dia foi uma variável.</div>","fidelityText":"Consequência real: mudar a variável de ambiente no painel do provedor e clicar em \"restart\" não muda nada — é preciso disparar um novo build. Diferente do back-end (capítulo 57), onde a variável é lida a cada System.getenv() em runtime, o front-end estático já \"esqueceu\" que aquilo um dia foi uma variável."},{"id":"deploy-frontend-content-11","type":"html","authorship":"legacy-preserved","html":"<h2>SPA routing fallback — o F5 que quebra em produção</h2>","fidelityText":"SPA routing fallback — o F5 que quebra em produção"},{"id":"deploy-frontend-content-12","type":"html","authorship":"legacy-preserved","html":"<p>Um SPA com rotas do lado do cliente (ex.: <code>/livros/42</code>) funciona perfeitamente navegando pelos links da aplicação — mas dar refresh (F5) direto nessa URL, ou compartilhar o link, faz o navegador pedir <code>/livros/42</code> ao servidor de arquivos estáticos, que não tem nenhum arquivo com esse nome e responde <strong>404</strong>.</p>","fidelityText":"Um SPA com rotas do lado do cliente (ex.: /livros/42) funciona perfeitamente navegando pelos links da aplicação — mas dar refresh (F5) direto nessa URL, ou compartilhar o link, faz o navegador pedir /livros/42 ao servidor de arquivos estáticos, que não tem nenhum arquivo com esse nome e responde 404."},{"id":"deploy-frontend-code-13","type":"code","authorship":"legacy-preserved","language":"java","source":"// vercel.json -- reescreve qualquer rota desconhecida para index.html,\n// deixando o roteador do front-end (client-side) decidir o que mostrar:\n{\n  \"rewrites\": [\n    { \"source\": \"/(.*)\", \"destination\": \"/index.html\" }\n  ]\n}","fidelityText":"// vercel.json -- reescreve qualquer rota desconhecida para index.html, // deixando o roteador do front-end (client-side) decidir o que mostrar: { \"rewrites\": [ { \"source\": \"/(.*)\", \"destination\": \"/index.html\" } ] }","highlightedHtml":"<span class=\"com\">// vercel.json -- reescreve qualquer rota desconhecida para index.html,</span>\n<span class=\"com\">// deixando o roteador do front-end (client-side) decidir o que mostrar:</span>\n{\n  \"rewrites\": [\n    { \"source\": \"/(.*)\", \"destination\": \"/index.html\" }\n  ]\n}","caption":"Exemplo executável de deploy-frontend.","explanation":["A regra de rewrite entrega index.html para qualquer rota desconhecida do servidor de arquivos, deixando o roteador client-side decidir a view.","Sem essa regra, todo link profundo do SPA quebra em refresh (F5) com 404 do servidor estático."],"commonMistakes":["Publicar um SPA com rotas client-side sem nenhuma regra de rewrite/fallback","Confundir 404 de rota client-side com bug do próprio roteador React/Vue"]},{"id":"deploy-frontend-content-14","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Isso não é um bug do seu roteador React/Vue — é uma consequência inevitável de servir arquivos estáticos. Netlify usa um arquivo <code>_redirects</code> com a mesma ideia (<code>/* /index.html 200</code>); qualquer provedor de hospedagem estática precisa dessa regra de reescrita, ou toda URL \"profunda\" do SPA quebra em refresh.</div>","fidelityText":"Isso não é um bug do seu roteador React/Vue — é uma consequência inevitável de servir arquivos estáticos. Netlify usa um arquivo _redirects com a mesma ideia (/* /index.html 200); qualquer provedor de hospedagem estática precisa dessa regra de reescrita, ou toda URL \"profunda\" do SPA quebra em refresh."},{"id":"deploy-frontend-content-15","type":"html","authorship":"legacy-preserved","html":"<h2>Cache: o index.html não pode ter o mesmo TTL dos assets</h2>","fidelityText":"Cache: o index.html não pode ter o mesmo TTL dos assets"},{"id":"deploy-frontend-content-16","type":"html","authorship":"legacy-preserved","html":"<p>Os arquivos com hash no nome (<code>app.a1b2c3.js</code>) podem ser cacheados <strong>para sempre</strong> — se o conteúdo mudar, o hash muda, então o nome do arquivo nunca é reaproveitado por engano. Mas o <code>index.html</code> (que referencia esses hashes) precisa do oposto: cache curto ou nenhum, porque é ele quem aponta para a versão mais nova.</p>","fidelityText":"Os arquivos com hash no nome (app.a1b2c3.js) podem ser cacheados para sempre — se o conteúdo mudar, o hash muda, então o nome do arquivo nunca é reaproveitado por engano. Mas o index.html (que referencia esses hashes) precisa do oposto: cache curto ou nenhum, porque é ele quem aponta para a versão mais nova."},{"id":"deploy-frontend-code-17","type":"code","authorship":"legacy-preserved","language":"java","source":"# cabeçalhos típicos configurados no provedor/CDN:\n/assets/*.js    Cache-Control: public, max-age=31536000, immutable\n/index.html     Cache-Control: no-cache","fidelityText":"# cabeçalhos típicos configurados no provedor/CDN: /assets/*.js Cache-Control: public, max-age=31536000, immutable /index.html Cache-Control: no-cache","highlightedHtml":"<span class=\"com\"># cabeçalhos típicos configurados no provedor/CDN:</span>\n/assets/*.js    Cache-Control: public, max-age=31536000, immutable\n/index.html     Cache-Control: no-cache","caption":"Exemplo executável de deploy-frontend.","explanation":["Assets com hash no nome podem ter cache longo/imutável porque o nome muda sempre que o conteúdo muda.","index.html precisa do cache oposto (curto/nenhum), porque é ele quem aponta para a versão mais nova dos assets."],"commonMistakes":["Aplicar o mesmo Cache-Control longo ao index.html e aos assets com hash","Deixar o navegador reter uma versão antiga do index.html após um deploy novo"]},{"id":"deploy-frontend-content-18","type":"html","authorship":"legacy-preserved","html":"<div class=\"warn\"><b>Erro comum: cachear o index.html por engano.</b> Se isso acontecer, um usuário pode ficar preso na versão antiga do site mesmo depois de um deploy novo — o navegador nunca volta a pedir o <code>index.html</code> atualizado até o cache expirar.</div>","fidelityText":"Erro comum: cachear o index.html por engano. Se isso acontecer, um usuário pode ficar preso na versão antiga do site mesmo depois de um deploy novo — o navegador nunca volta a pedir o index.html atualizado até o cache expirar."},{"id":"deploy-frontend-content-19","type":"html","authorship":"legacy-preserved","html":"<h2>Monorepo vs poli-repo — onde colocar tudo</h2>","fidelityText":"Monorepo vs poli-repo — onde colocar tudo"},{"id":"deploy-frontend-content-20","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th></th><th>Monorepo</th><th>Poli-repo</th></tr>\n        <tr><td>Estrutura</td><td>Front e back no mesmo repositório Git, em pastas separadas</td><td>Dois repositórios completamente independentes</td></tr>\n        <tr><td>Vantagem</td><td>Mudanças relacionadas (API muda + front se adapta) num único commit/PR</td><td>Pipelines de deploy independentes, times podem trabalhar sem interferir um no outro</td></tr>\n        <tr><td>Desvantagem</td><td>CI/CD precisa ser configurado para rodar só o que mudou</td><td>Sincronizar mudanças relacionadas exige coordenar dois PRs em dois lugares</td></tr>\n        <tr><td>Bom para</td><td>Time pequeno, projeto pessoal, forte acoplamento entre front e back</td><td>Times maiores, ciclos de release independentes, ownership claro</td></tr>\n      </tbody></table>","fidelityText":"MonorepoPoli-repo EstruturaFront e back no mesmo repositório Git, em pastas separadasDois repositórios completamente independentes VantagemMudanças relacionadas (API muda + front se adapta) num único commit/PRPipelines de deploy independentes, times podem trabalhar sem interferir um no outro DesvantagemCI/CD precisa ser configurado para rodar só o que mudouSincronizar mudanças relacionadas exige coordenar dois PRs em dois lugares Bom paraTime pequeno, projeto pessoal, forte acoplamento entre front e backTimes maiores, ciclos de release independentes, ownership claro"},{"id":"deploy-frontend-code-21","type":"code","authorship":"legacy-preserved","language":"java","source":"# estrutura típica de monorepo:\nproject-library/\n├── backend/          # projeto Spring Boot (capítulos 43-59)\n│   ├── pom.xml\n│   └── src/\n├── frontend/          # projeto React/Vite\n│   ├── package.json\n│   └── src/\n└── docker-compose.yml  # sobe os dois juntos, capítulo 32","fidelityText":"# estrutura típica de monorepo: projeto-biblioteca/ ├── backend/ # projeto Spring Boot (capítulos 43-59) │ ├── pom.xml │ └── src/ ├── frontend/ # projeto React/Vite │ ├── package.json │ └── src/ └── docker-compose.yml # sobe os dois juntos, capítulo 32","highlightedHtml":"<span class=\"com\"># estrutura típica de monorepo:</span>\nproject-library/\n├── backend/          <span class=\"com\"># projeto Spring Boot (capítulos 43-59)</span>\n│   ├── pom.xml\n│   └── src/\n├── frontend/          <span class=\"com\"># projeto React/Vite</span>\n│   ├── package.json\n│   └── src/\n└── docker-compose.yml  <span class=\"com\"># sobe os dois juntos, capítulo 32</span>","caption":"Exemplo executável de deploy-frontend.","explanation":["Monorepo e poli-repo mudam ownership, versionamento e custo de CI.","A decisão deve seguir fronteira de mudança, não preferência estética."],"commonMistakes":["Adotar monorepo sem pipeline seletivo","Achar que poli-repo elimina acoplamento de API"]},{"id":"deploy-frontend-content-22","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Para projetos de aprendizado e portfólio (o cenário mais comum neste ponto do curso), monorepo é quase sempre a escolha mais simples de gerenciar — um único <code>git clone</code>, um único <code>docker compose up</code> sobe tudo. Só considere poli-repo quando times diferentes realmente precisarem de ciclos de deploy totalmente independentes.</div>","fidelityText":"Para projetos de aprendizado e portfólio (o cenário mais comum neste ponto do curso), monorepo é quase sempre a escolha mais simples de gerenciar — um único git clone, um único docker compose up sobe tudo. Só considere poli-repo quando times diferentes realmente precisarem de ciclos de deploy totalmente independentes."},{"id":"deploy-frontend-exercise-23","type":"exercise","authorship":"legacy-preserved","title":"Exercício 61.1 — Estruturando o monorepo","prompt":"Desenhe a estrutura de pastas de um monorepo para o projeto da biblioteca, com back-end Spring Boot e um front-end React, incluindo onde ficaria o docker-compose.yml que sobe os dois juntos com o banco Postgres (capítulo 32).","difficulty":"foundation","criteria":["A solução explica decisões, casos-limite e como foi validada.","A entrega registra entrada, resultado esperado e evidência reproduzível da validação."],"fidelityText":"Exercício 61.1 — Estruturando o monorepofácil Desenhe a estrutura de pastas de um monorepo para o projeto da biblioteca, com back-end Spring Boot e um front-end React, incluindo onde ficaria o docker-compose.yml que sobe os dois juntos com o banco Postgres (capítulo 32). Ver solução biblioteca/ ├── backend/ │ ├── Dockerfile │ ├── pom.xml │ └── src/ ├── frontend/ │ ├── Dockerfile │ ├── package.json │ └── src/ ├── docker-compose.yml # define os 3 serviços: api, front, db └── .env # local, no .gitignore","sourceHtml":"<div class=\"exercise\">\n        <div class=\"exercise-head\"><h2>Exercício 61.1 — Estruturando o monorepo</h2><span class=\"exercise-tag f\">fácil</span></div>\n        <p>Desenhe a estrutura de pastas de um monorepo para o projeto da biblioteca, com back-end Spring Boot e um front-end React, incluindo onde ficaria o <code>docker-compose.yml</code> que sobe os dois juntos com o banco Postgres (capítulo 32).</p>\n        <button class=\"reveal-btn\">Ver solução</button>\n        <div class=\"solution\">\n<pre class=\"code\">library/\n├── backend/\n│   ├── Dockerfile\n│   ├── pom.xml\n│   └── src/\n├── frontend/\n│   ├── Dockerfile\n│   ├── package.json\n│   └── src/\n├── docker-compose.yml   <span class=\"com\"># define os 3 serviços: api, front, db</span>\n└── .env                  <span class=\"com\"># local, no .gitignore</span></pre>\n        </div>\n      </div>"},{"id":"deploy-frontend-quiz","type":"quiz","authorship":"authored","conceptId":"frontend-static-deploy-cache","prompt":"Por que cache importa no deploy front-end?","options":[{"id":"df-a","label":"Porque usuários podem receber assets antigos; nomes versionados e headers definem compatibilidade e atualização.","correct":true,"explanation":"Cache afeta qual código realmente roda no navegador."},{"id":"df-b","label":"Porque cache transforma variável pública em secret.","correct":false,"explanation":"Variável enviada ao browser nunca é secret."},{"id":"df-c","label":"Porque elimina necessidade de contrato com a API.","correct":false,"explanation":"Front e API continuam precisando evoluir de modo compatível."}]}],"resources":[{"id":"vite-static-deploy","type":"official-docs","title":"Vite: Deploying a Static Site","url":"https://vite.dev/guide/static-deploy.html","reinforces":"Build estático, base path e publicação de aplicações Vite.","language":"en","publisher":"Vite","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"mdn-http-caching","type":"reference","title":"MDN: HTTP caching","url":"https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching","reinforces":"Headers, cache do navegador e invalidação de assets.","language":"en","publisher":"MDN","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":2,"label":"Applied comprehension","readPassage":"The deploy frontend component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","comprehensionQuestion":"Which contract can fail, how is the failure observed, and what should the caller do next?","contextSupport":"Infer the contract from code and behavior. Use the documentation only to confirm the hypothesis you already formed.","documentationTask":"According to the official documentation, identify one precondition, return value, or failure related to deploy frontend. Cite the relevant section.","productionTask":"Write a concise bug report for a plausible deploy frontend failure with expected behavior, actual behavior, and steps to reproduce.","successCriterion":"A developer can reproduce the failure and distinguish expected from actual behavior without translation notes.","codeContext":"# conecta o repositório, e a cada push:","instruction":"The deploy frontend component must preserve its contract under invalid input and dependency failure. Callers need an explicit result, not an ambiguous null or partial state.","prompt":"Write a concise bug report for a plausible deploy frontend failure with expected behavior, actual behavior, and steps to reproduce."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"mapa-sistema","moduleId":"professional-final","order":2,"title":"Glossário & mapa do sistema completo","summary":"Antes do projeto final, um passo atrás para ver a floresta inteira, não só as árvores individuais que você plantou nos últimos capítulos.","objectives":["Seguir uma requisição ponta-a-ponta no sistema completo","Relacionar UI, HTTP, segurança, domínio, banco, cache, eventos e observabilidade","Usar glossário/mapa como ferramenta de revisão ativa","Identificar lacunas antes do projeto integrador"],"whyItExists":"Antes do projeto final, o aluno precisa enxergar o curso como sistema conectado. O mapa mostra que cada tecnologia estudada participa de uma cadeia de contrato, estado, falha e evidência; ele também ajuda a revisar lacunas sem reler tudo do zero.","prerequisiteChapterIds":["mini-pedidos-eventos","deploy-frontend","code-review-adr"],"conceptIds":["o-caminho-de-uma-requisicao-ponta-a-ponta","glossario-de-termos-chave-tambem-disponivel-no-botao-flutuante"],"introducedConceptIds":["end-to-end-request-map","system-knowledge-map"],"usedConceptIds":["frontend-backend-contract","spring-security-authorization","trace-span-boundary","event-driven-project-evidence"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"mapa-sistema-intuition","type":"intuition","authorship":"authored","title":"Mapa final transforma capítulos soltos em uma cadeia de responsabilidade","body":"Uma ação simples — clicar em emprestar livro — atravessa UI, HTTP, autenticação, controller, domínio, transação, cache, evento, consumer, WebSocket, logs e métricas. O mapa existe para você conseguir explicar essa travessia sem chamar tudo de mágica.","analogyLimit":"Mapa de metrô ajuda a visualizar estações, mas cada estação aqui tem contrato, erro, segurança e evidência operacional."},{"id":"mapa-sistema-exercise","type":"exercise","authorship":"authored","title":"Trace narrado de uma ação","prompt":"Escolha uma ação do projeto final e escreva o caminho completo: entrada do usuário, request, autorização, caso de uso, persistência, evento/cache, resposta, logs/métricas e falhas possíveis.","difficulty":"advanced","criteria":["Cada etapa aponta o capítulo/conceito usado.","Há pelo menos um erro de domínio, um erro técnico e um sinal de observabilidade.","O texto diferencia contrato externo de modelo interno."]},{"id":"mapa-sistema-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i></i><i></i></span> <b>Revisão</b></div>\n        <div class=\"time-est\">⏱ <b>~1h</b> de revisão</div>\n        <div class=\"meta-item\">Pré-requisitos: todos os capítulos anteriores</div>\n      </div>","fidelityText":"Dificuldade: Revisão ⏱ ~1h de revisão Pré-requisitos: todos os capítulos anteriores"},{"id":"mapa-sistema-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Antes do projeto final, um passo atrás para ver a floresta inteira, não só as árvores individuais que você plantou nos últimos capítulos.</p>","fidelityText":"Antes do projeto final, um passo atrás para ver a floresta inteira, não só as árvores individuais que você plantou nos últimos capítulos."},{"id":"mapa-sistema-content-3","type":"html","authorship":"legacy-preserved","html":"<div class=\"analogy\">Cada capítulo deste curso foi uma peça de um quebra-cabeça montada isoladamente. Esta seção é virar o quebra-cabeça montado de cabeça para baixo na mesa e finalmente ver a imagem completa — de onde vem uma requisição do usuário até onde ela termina, passando por cada camada que você construiu.<p></p>\n      </div>","fidelityText":"Cada capítulo deste curso foi uma peça de um quebra-cabeça montada isoladamente. Esta seção é virar o quebra-cabeça montado de cabeça para baixo na mesa e finalmente ver a imagem completa — de onde vem uma requisição do usuário até onde ela termina, passando por cada camada que você construiu."},{"id":"mapa-sistema-content-4","type":"html","authorship":"legacy-preserved","html":"<h2>O caminho de uma requisição, ponta a ponta</h2>","fidelityText":"O caminho de uma requisição, ponta a ponta"},{"id":"mapa-sistema-code-5","type":"code","authorship":"legacy-preserved","language":"java","source":"1. User clica \"Borrow\" no FRONT-END (React/TS, capitulos 62-63)\n2. axios sends POST /books/{code}/loan com JWT no header (chapter 52)\n3. Request atravessa HTTPS (chapter 53) ate o back-end\n4. CORS (chapter 50) already validou que essa source tem permission\n5. Spring Security Filter Chain (chapter 52) validates o JWT BEFORE do controller\n6. BookController (chapter 44) recebe a request -- a URL, o verbo HTTP\n   e o name de cada method already foram decididos no chapter 44A, before do code\n7. Controller delega ao Service, que aplica a rule de business\n8. Se o book already esta borrowed -> ItemUnavailableException\n   -> capturada pelo @ControllerAdvice (chapter 48) -> 409 Conflict\n9. Se available: BookRepository (Spring Data JPA, chapter 45) faz UPDATE no Postgres\n10. Cache no Redis (chapter 38) e invalidado, se existir\n11. Um event e published no Kafka (capitulos 40-43), producer com\n    acks=all e enable.idempotence=true, para nao duplicar em failure de network\n12. Um consumer group processa esse event de shape idempotente (ex: notification)\n13. Uma notification WebSocket (chapter 64) e enviada em time real ao front\n14. O Service devolve um DTO (chapter 47), never a Entity crua\n15. O Controller retorna 200 OK com o body em JSON (Jackson, chapter 25)\n16. Actuator (chapter 56) registrou metrics dessa request o time todo\n17. Se algo der muito wrong, logs estruturados (chapter 27) registram o occurred","fidelityText":"1. Usuário clica \"Emprestar\" no FRONT-END (React/TS, capítulos 62-63) 2. axios envia POST /livros/{codigo}/emprestimo com JWT no header (capítulo 52) 3. Requisição atravessa HTTPS (capítulo 53) até o back-end 4. CORS (capítulo 50) já validou que essa origem tem permissão 5. Spring Security Filter Chain (capítulo 52) valida o JWT ANTES do controller 6. LivroController (capítulo 44) recebe a requisição -- a URL, o verbo HTTP e o nome de cada método já foram decididos no capítulo 44A, antes do código 7. Controller delega ao Service, que aplica a regra de negócio 8. Se o livro já está emprestado -> ItemIndisponivelException -> capturada pelo @ControllerAdvice (capítulo 48) -> 409 Conflict 9. Se disponível: LivroRepository (Spring Data JPA, capítulo 45) faz UPDATE no Postgres 10. Cache no Redis (capítulo 38) é invalidado, se existir 11. Um evento é publicado no Kafka (capítulos 40-43), producer com acks=all e enable.idempotence=true, para não duplicar em falha de rede 12. Um consumer group processa esse evento de forma idempotente (ex: notificação) 13. Uma notificação WebSocket (capítulo 64) é enviada em tempo real ao front 14. O Service devolve um DTO (capítulo 47), nunca a Entity crua 15. O Controller retorna 200 OK com o corpo em JSON (Jackson, capítulo 25) 16. Actuator (capítulo 56) registrou métricas dessa requisição o tempo todo 17. Se algo der muito errado, logs estruturados (capítulo 27) registram o ocorrido","highlightedHtml":"1. User clica \"Borrow\" no FRONT-END (React/TS, capitulos 62-63)\n2. axios sends POST /books/{code}/loan com JWT no header (chapter 52)\n3. Request atravessa HTTPS (chapter 53) ate o back-end\n4. CORS (chapter 50) already validou que essa source tem permission\n5. Spring Security Filter Chain (chapter 52) validates o JWT BEFORE do controller\n6. BookController (chapter 44) recebe a request -- a URL, o verbo HTTP\n   e o name de cada method already foram decididos no chapter 44A, before do code\n7. Controller delega ao Service, que aplica a rule de business\n8. Se o book already esta borrowed -&gt; ItemUnavailableException\n   -&gt; capturada pelo @ControllerAdvice (chapter 48) -&gt; 409 Conflict\n9. Se available: BookRepository (Spring Data JPA, chapter 45) faz UPDATE no Postgres\n10. Cache no Redis (chapter 38) e invalidado, se existir\n11. Um event e published no Kafka (capitulos 40-43), producer com\n    acks=all e enable.idempotence=true, para nao duplicar em failure de network\n12. Um consumer group processa esse event de shape idempotente (ex: notification)\n13. Uma notification WebSocket (chapter 64) e enviada em time real ao front\n14. O Service devolve um DTO (chapter 47), never a Entity crua\n15. O Controller retorna 200 OK com o body em JSON (Jackson, chapter 25)\n16. Actuator (chapter 56) registrou metrics dessa request o time todo\n17. Se algo der muito wrong, logs estruturados (chapter 27) registram o occurred","caption":"Exemplo executável de mapa-sistema.","explanation":["O fluxo textual mostra uma requisição atravessando front-end, HTTP, segurança, domínio, banco, cache, evento e notificação.","Use-o como mapa de rastreamento: em cada etapa pergunte qual contrato pode falhar e qual evidência prova o comportamento."],"commonMistakes":["Achar que o controller é o sistema inteiro","Ignorar falha assíncrona depois da resposta HTTP","Misturar DTO público com entidade interna"]},{"id":"mapa-sistema-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"secret\">Repare que quase toda \"mágica\" nesse fluxo inteiro se resume a três mecanismos que você já domina profundamente desde os capítulos iniciais: <strong>reflection</strong> (descobrir e invocar código dinamicamente), <strong>dynamic proxy</strong> (interceptar chamadas para aplicar comportamento transversal — segurança, transação, circuit breaker, cache) e <strong>injeção de dependência</strong> (montar o grafo de objetos sem acoplamento direto). Spring Boot, Kafka, Spring Security — tudo isso são aplicações elaboradas dessas três ideias fundamentais, não conceitos completamente novos e desconectados.</div>","fidelityText":"Repare que quase toda \"mágica\" nesse fluxo inteiro se resume a três mecanismos que você já domina profundamente desde os capítulos iniciais: reflection (descobrir e invocar código dinamicamente), dynamic proxy (interceptar chamadas para aplicar comportamento transversal — segurança, transação, circuit breaker, cache) e injeção de dependência (montar o grafo de objetos sem acoplamento direto). Spring Boot, Kafka, Spring Security — tudo isso são aplicações elaboradas dessas três ideias fundamentais, não conceitos completamente novos e desconectados."},{"id":"mapa-sistema-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Glossário de termos-chave (também disponível no botão 🔍 flutuante)</h2>","fidelityText":"Glossário de termos-chave (também disponível no botão 🔍 flutuante)"},{"id":"mapa-sistema-content-8","type":"html","authorship":"legacy-preserved","html":"<table class=\"cmp\">\n        <tbody><tr><th>Termo</th><th>Definição resumida</th><th>Capítulo</th></tr>\n        <tr><td>Bean</td><td>Objeto gerenciado pelo container Spring (IoC)</td><td>43</td></tr>\n        <tr><td>DTO</td><td>Objeto usado só para transporte de dados entre camadas/API</td><td>47</td></tr>\n        <tr><td>N+1</td><td>Bug de performance: 1 query + N queries extras evitáveis</td><td>46</td></tr>\n        <tr><td>Idempotência</td><td>Repetir a operação tem o mesmo efeito de fazê-la uma vez</td><td>26, 40</td></tr>\n        <tr><td>Circuit Breaker</td><td>Interrompe chamadas a um serviço que está falhando repetidamente</td><td>55</td></tr>\n        <tr><td>JWT</td><td>Token de autenticação assinado, carregando sua própria informação</td><td>51</td></tr>\n        <tr><td>Migration</td><td>Mudança versionada e ordenada no schema do banco</td><td>35</td></tr>\n        <tr><td>Consumer Group</td><td>Conjunto de consumidores Kafka dividindo o processamento de um tópico</td><td>41</td></tr>\n        <tr><td>ISR</td><td>In-Sync Replicas: réplicas de uma partição atualizadas o suficiente para assumir liderança sem perder dado</td><td>41</td></tr>\n        <tr><td>Offset</td><td>Posição de leitura de um consumer em uma partição Kafka; committar cedo demais perde mensagem, tarde demais reprocessa</td><td>41</td></tr>\n        <tr><td>Schema Registry</td><td>Serviço que versiona e valida compatibilidade de schemas Avro/Protobuf entre producer e consumer Kafka</td><td>42-43</td></tr>\n        <tr><td>Kafka Streams</td><td>Biblioteca de processamento de stream sobre tópicos Kafka, sem exigir um cluster de processamento separado</td><td>43</td></tr>\n        <tr><td>Estrutura em camadas</td><td>Controller traduz HTTP, Service aplica regra de negócio, Repository acessa dado -- cada um com sua própria convenção de nome de método</td><td>44A</td></tr>\n      </tbody></table>","fidelityText":"TermoDefinição resumidaCapítulo BeanObjeto gerenciado pelo container Spring (IoC)43 DTOObjeto usado só para transporte de dados entre camadas/API47 N+1Bug de performance: 1 query + N queries extras evitáveis46 IdempotênciaRepetir a operação tem o mesmo efeito de fazê-la uma vez26, 40 Circuit BreakerInterrompe chamadas a um serviço que está falhando repetidamente55 JWTToken de autenticação assinado, carregando sua própria informação51 MigrationMudança versionada e ordenada no schema do banco35 Consumer GroupConjunto de consumidores Kafka dividindo o processamento de um tópico41 ISRIn-Sync Replicas: réplicas de uma partição atualizadas o suficiente para assumir liderança sem perder dado41 OffsetPosição de leitura de um consumer em uma partição Kafka; committar cedo demais perde mensagem, tarde demais reprocessa41 Schema RegistryServiço que versiona e valida compatibilidade de schemas Avro/Protobuf entre producer e consumer Kafka42-43 Kafka StreamsBiblioteca de processamento de stream sobre tópicos Kafka, sem exigir um cluster de processamento separado43 Estrutura em camadasController traduz HTTP, Service aplica regra de negócio, Repository acessa dado -- cada um com sua própria convenção de nome de método44A"},{"id":"mapa-sistema-content-9","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Use o botão de modo revisão (👁, no canto da tela) para repassar rapidamente só os títulos, avisos e segredos de todos os capítulos antes de uma entrevista técnica ou de retomar o projeto final depois de um tempo parado — é bem mais rápido que reler tudo por extenso.</div>","fidelityText":"Use o botão de modo revisão (👁, no canto da tela) para repassar rapidamente só os títulos, avisos e segredos de todos os capítulos antes de uma entrevista técnica ou de retomar o projeto final depois de um tempo parado — é bem mais rápido que reler tudo por extenso."},{"id":"mapa-sistema-quiz","type":"quiz","authorship":"authored","conceptId":"end-to-end-request-map","prompt":"O que um mapa ponta-a-ponta deve revelar?","options":[{"id":"ms-a","label":"Como uma ação atravessa contratos, estado, segurança, falha e observabilidade até produzir efeito verificável.","correct":true,"explanation":"O mapa conecta responsabilidades; não é só lista de ferramentas."},{"id":"ms-b","label":"Somente qual controller recebe a rota.","correct":false,"explanation":"Controller é uma etapa; o fluxo inclui domínio, persistência, eventos, UI e operação."},{"id":"ms-c","label":"Todas as classes privadas em ordem alfabética.","correct":false,"explanation":"Mapa final deve ajudar raciocínio e revisão, não inventariar detalhes irrelevantes."}]}],"resources":[{"id":"spring-boot-observability-phase19-map","type":"official-docs","title":"Spring Boot Actuator: Production-ready Features","url":"https://docs.spring.io/spring-boot/reference/actuator/index.html","reinforces":"Sinais operacionais no mapa final: health, metrics e observabilidade.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"opentelemetry-observability-primer-map","type":"official-docs","title":"OpenTelemetry Observability Primer","url":"https://opentelemetry.io/docs/concepts/observability-primer/","reinforces":"Logs, métricas e traces como evidência ponta-a-ponta.","language":"en","publisher":"OpenTelemetry","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the map system flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for map system. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for map system with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"1. User clica \"Borrow\" no FRONT-END (React/TS, capitulos 62-63)","instruction":"Design the map system flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for map system with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"projeto-integrador","moduleId":"professional-final","order":3,"title":"Projeto final — sistema completo de ponta a ponta","summary":"O projeto que reúne, literalmente, tudo — do Olá, POO! do capítulo 00 até WebSockets em tempo real. Um sistema de biblioteca completo, pronto para produção real.","objectives":["Entregar sistema ponta-a-ponta verificável","Escolher arquitetura por trade-off e registrar ADRs","Provar build, teste, deploy, observabilidade e recuperação","Limitar escopo sem perder evidência profissional"],"whyItExists":"O projeto integrador é a síntese do curso: não é usar todas as tecnologias por exibicionismo, mas escolher uma arquitetura coerente e provar que ela compila, testa, executa, falha de modo operável e pode ser explicada por outra pessoa.","prerequisiteChapterIds":["mapa-sistema","mini-pedidos-eventos","mini-deploy-observavel","code-review-adr"],"conceptIds":["execucao-guiada-e-evidencias","arquitetura-de-referencia-verificavel"],"introducedConceptIds":["capstone-reference-architecture","capstone-operational-evidence"],"usedConceptIds":["production-evidence-checklist","event-driven-project-evidence","health-readiness-liveness","deploy-rollback-strategy","adr-context-decision-consequence"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"projeto-integrador-intuition","type":"intuition","authorship":"authored","title":"Projeto final bom é menor do que sua ambição e mais comprovado do que sua promessa","body":"A tentação é colocar tudo: Spring, Postgres, Kafka, Redis, WebSocket, Docker, CI e deploy. O critério profissional é outro: escolher escopo suficiente, escrever ADRs, provar os fluxos centrais, operar falhas previsíveis e documentar como outra pessoa executa e revisa.","analogyLimit":"Portfólio ajuda como vitrine, mas o avaliador técnico procura evidência: testes, logs, decisões, deploy e limites."},{"id":"projeto-integrador-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Avançado</b></div>\n        <div class=\"time-est\">⏱ <b>~15-20h</b> — o projeto que fecha o curso inteiro</div>\n        <div class=\"meta-item\">Pré-requisitos: toda a trilha anterior e seus laboratórios</div>\n      </div>","fidelityText":"Dificuldade: Avançado ⏱ ~15-20h — o projeto que fecha o curso inteiro Pré-requisitos: toda a trilha anterior e seus laboratórios"},{"id":"projeto-integrador-content-2","type":"html","authorship":"legacy-preserved","html":"<p>O projeto que reúne, literalmente, tudo — do <code>Olá, POO!</code> do capítulo 00 até WebSockets em tempo real. Um sistema de biblioteca completo, pronto para produção real.</p>","fidelityText":"O projeto que reúne, literalmente, tudo — do Olá, POO! do capítulo 00 até WebSockets em tempo real. Um sistema de biblioteca completo, pronto para produção real."},{"id":"projeto-integrador-checklist-3","type":"checklist","authorship":"legacy-preserved","title":"Checklist de prática","items":[{"id":"projeto-integrador-checklist-0","label":"Modelagem: entidades Livro, Autor, Usuario com relacionamentos JPA corretos (capítulo 45), schema versionado via Flyway (capítulo 35)."},{"id":"projeto-integrador-checklist-1","label":"API REST: recursos, verbos e nomes de método decididos pelo processo do capítulo 44A, controllers completos com paginação (capítulo 49), DTOs (capítulo 47), tratamento global de erros (capítulo 48), documentados via Swagger."},{"id":"projeto-integrador-checklist-2","label":"Segurança: registro/login com JWT (capítulo 52), senhas com BCrypt, rotas protegidas por role, tudo atrás de HTTPS em produção."},{"id":"projeto-integrador-checklist-3","label":"Performance: queries livres de N+1 (capítulo 46), cache de leituras frequentes com Redis (capítulo 38)."},{"id":"projeto-integrador-checklist-4","label":"Mensageria: evento Kafka publicado ao emprestar um livro com producer idempotente (acks=all, enable.idempotence=true), consumido de forma idempotente por um serviço de notificação, com DLT para mensagem que falha repetidamente (capítulos 40-43)."},{"id":"projeto-integrador-checklist-5","label":"Resiliência: pelo menos uma dependência externa simulada protegida por circuit breaker (capítulo 55)."},{"id":"projeto-integrador-checklist-6","label":"Testes: testes unitários (capítulo 15) para lógica de negócio + testes de integração com Testcontainers (capítulo 54) para o repository."},{"id":"projeto-integrador-checklist-7","label":"CI/CD: pipeline no GitHub Actions rodando testes a cada Pull Request (capítulo 58)."},{"id":"projeto-integrador-checklist-8","label":"Observabilidade: Actuator com health check configurado (capítulo 56), secrets nunca commitados (capítulo 57)."},{"id":"projeto-integrador-checklist-9","label":"Front-end: React + TypeScript consumindo a API com axios (capítulos 62-63), autenticação persistida corretamente, notificação em tempo real via WebSocket (capítulo 64)."},{"id":"projeto-integrador-checklist-10","label":"Deploy real: back-end publicado (Railway/Render/VPS, capítulo 60), front-end publicado (Vercel/Netlify, capítulo 61), banco de produção em provedor gerenciado (capítulo 39), tudo funcionando com domínio e HTTPS de verdade."}]},{"id":"projeto-integrador-content-4","type":"html","authorship":"legacy-preserved","html":"<div class=\"callout\"><b>Isso não precisa ser feito em um único fim de semana.</b> Trate cada item do checklist como uma iteração — implemente, teste, faça commit (capítulo 29), depois avance para o próximo. Um sistema completo de produção nunca é construído tudo de uma vez; é construído camada por camada, exatamente como este curso foi estruturado.</div>","fidelityText":"Isso não precisa ser feito em um único fim de semana. Trate cada item do checklist como uma iteração — implemente, teste, faça commit (capítulo 29), depois avance para o próximo. Um sistema completo de produção nunca é construído tudo de uma vez; é construído camada por camada, exatamente como este curso foi estruturado."},{"id":"projeto-integrador-content-5","type":"html","authorship":"legacy-preserved","html":"<div class=\"study-tip\">Se precisar de um ponto de partida mais guiado, comece pelo caminho mais \"vertical\" possível: um único endpoint (ex: listar livros) funcionando de ponta a ponta — banco → repository → service → controller → front-end exibindo a lista — antes de expandir para todas as funcionalidades. É mais valioso ter uma fatia fina e completa do sistema funcionando do que todas as camadas parcialmente prontas ao mesmo tempo.</div>","fidelityText":"Se precisar de um ponto de partida mais guiado, comece pelo caminho mais \"vertical\" possível: um único endpoint (ex: listar livros) funcionando de ponta a ponta — banco → repository → service → controller → front-end exibindo a lista — antes de expandir para todas as funcionalidades. É mais valioso ter uma fatia fina e completa do sistema funcionando do que todas as camadas parcialmente prontas ao mesmo tempo."},{"id":"projeto-integrador-content-6","type":"html","authorship":"legacy-preserved","html":"<div class=\"project-quality-gate\">\n      <h2>Execução guiada e evidências</h2>\n      <p>Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir.</p>\n      <h2 class=\"sub\">Marcos obrigatórios</h2>\n      <ol>\n        <li><strong>Contrato:</strong> escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação.</li>\n        <li><strong>Caminho mínimo:</strong> entregue uma fatia vertical executável com um teste de aceitação.</li>\n        <li><strong>Falhas:</strong> implemente validação, mensagens úteis, cleanup e comportamento após interrupção.</li>\n        <li><strong>Qualidade:</strong> automatize testes, formatação e build; remova segredos e dados pessoais dos logs.</li>\n        <li><strong>Operação:</strong> documente como executar, diagnosticar, atualizar e recuperar o projeto.</li>\n      </ol>\n      <h2 class=\"sub\">Matriz mínima de testes</h2>\n      <table class=\"cmp\"><tbody><tr><th>Categoria</th><th>Evidência</th></tr><tr><td>caminho feliz</td><td>resultado e estado final esperados</td></tr><tr><td>limites</td><td>vazio, mínimo, máximo, duplicado e formato inválido</td></tr><tr><td>falha</td><td>dependência indisponível, timeout ou exceção sem corrupção de estado</td></tr><tr><td>repetição</td><td>retry ou execução duplicada não produz efeito indevido</td></tr><tr><td>regressão</td><td>bug corrigido ganha teste que falhava antes</td></tr></tbody></table>\n      <ul class=\"checklist\"><li><input type=\"checkbox\"><span>README contém requisitos, arquitetura, decisões e comandos reproduzíveis.</span></li><li><input type=\"checkbox\"><span>Build e testes executam do zero sem passos secretos.</span></li><li><input type=\"checkbox\"><span>Casos-limite e falhas foram demonstrados, não apenas descritos.</span></li><li><input type=\"checkbox\"><span>Existe uma retrospectiva com dívida técnica e próximo incremento.</span></li></ul>\n      <div class=\"warn\"><b>Regra de conclusão:</b> captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível.</div>\n      </div>","fidelityText":"Execução guiada e evidências Este projeto não termina quando o caminho feliz funciona. Construa em incrementos pequenos, mantenha o sistema executável a cada etapa e produza evidências que outra pessoa consiga reproduzir. Marcos obrigatórios Contrato: escreva regras, entradas, saídas, invariantes e casos-limite antes da implementação. Caminho mínimo: entregue uma fatia vertical executável com um teste de aceitação. Falhas: implemente validação, mensagens úteis, cleanup e comportamento após interrupção. Qualidade: automatize testes, formatação e build; remova segredos e dados pessoais dos logs. Operação: documente como executar, diagnosticar, atualizar e recuperar o projeto. Matriz mínima de testes CategoriaEvidênciacaminho felizresultado e estado final esperadoslimitesvazio, mínimo, máximo, duplicado e formato inválidofalhadependência indisponível, timeout ou exceção sem corrupção de estadorepetiçãoretry ou execução duplicada não produz efeito indevidoregressãobug corrigido ganha teste que falhava antes README contém requisitos, arquitetura, decisões e comandos reproduzíveis.Build e testes executam do zero sem passos secretos.Casos-limite e falhas foram demonstrados, não apenas descritos.Existe uma retrospectiva com dívida técnica e próximo incremento. Regra de conclusão: captura de tela não prova comportamento. Entregue código, testes, dados de exemplo, comandos e resultados verificáveis. Se uma tecnologia externa for necessária, fixe versões e forneça um ambiente reproduzível."},{"id":"projeto-integrador-content-7","type":"html","authorship":"legacy-preserved","html":"<h2>Arquitetura de referência verificável</h2>","fidelityText":"Arquitetura de referência verificável"},{"id":"projeto-integrador-code-8","type":"code","authorship":"legacy-preserved","language":"java","source":"customer web\n  └── HTTPS / OIDC\n        └── API Spring Boot\n              ├── domain + cases de uso\n              ├── PostgreSQL + Flyway + optimistic locking\n              ├── outbox ──► Kafka ──► consumer idempotente + DLT\n              ├── Redis cache-aside degradable\n              └── logs JSON + metrics + traces\n\npipeline: build → tests → SBOM/scan → image por digest → migration compativel\n          → canary/health → promotion ou rollback\noperation: SLO → alertas → runbook → backup restaurado → post-mortem","fidelityText":"cliente web └── HTTPS / OIDC └── API Spring Boot ├── domínio + casos de uso ├── PostgreSQL + Flyway + optimistic locking ├── outbox ──► Kafka ──► consumidor idempotente + DLT ├── Redis cache-aside degradável └── logs JSON + métricas + traces pipeline: build → testes → SBOM/scan → imagem por digest → migration compatível → canary/health → promoção ou rollback operação: SLO → alertas → runbook → backup restaurado → post-mortem","highlightedHtml":"customer web\n  └── HTTPS / OIDC\n        └── API Spring Boot\n              ├── domain + cases de uso\n              ├── PostgreSQL + Flyway + optimistic locking\n              ├── outbox ──► Kafka ──► consumer idempotente + DLT\n              ├── Redis cache-aside degradable\n              └── logs JSON + metrics + traces\n\npipeline: build → tests → SBOM/scan → image por digest → migration compativel\n          → canary/health → promotion ou rollback\noperation: SLO → alertas → runbook → backup restaurado → post-mortem","caption":"Exemplo executável de projeto-integrador.","explanation":["O diagrama textual é arquitetura de referência: camadas, dados, eventos, cache, pipeline e operação aparecem como responsabilidades verificáveis.","Ele não obriga usar tudo; serve para escolher o que cabe no escopo e justificar exclusões."],"commonMistakes":["Copiar a arquitetura inteira sem necessidade","Esquecer ADRs e critérios de aceite","Não provar rollback/health/logs"]},{"id":"projeto-integrador-content-9","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Fases de entrega</h2>","fidelityText":"Fases de entrega"},{"id":"projeto-integrador-content-10","type":"html","authorship":"legacy-preserved","html":"<ol><li>Domínio puro, invariantes e testes unitários.</li><li>API HTTP com Problem Details, idempotência e contrato OpenAPI.</li><li>Persistência com migrations, transações e teste de concorrência.</li><li>Resource Server OIDC, escopos, propriedade e testes negativos.</li><li>Outbox/Kafka, deduplicação, retry, DLT e reprocessamento.</li><li>Cache tolerante a falha e limites de cardinalidade.</li><li>Imagem hardened, CI, observabilidade e deploy progressivo.</li><li>Game day, restauração, revisão de segurança e apresentação dos trade-offs.</li></ol>","fidelityText":"Domínio puro, invariantes e testes unitários.API HTTP com Problem Details, idempotência e contrato OpenAPI.Persistência com migrations, transações e teste de concorrência.Resource Server OIDC, escopos, propriedade e testes negativos.Outbox/Kafka, deduplicação, retry, DLT e reprocessamento.Cache tolerante a falha e limites de cardinalidade.Imagem hardened, CI, observabilidade e deploy progressivo.Game day, restauração, revisão de segurança e apresentação dos trade-offs."},{"id":"projeto-integrador-content-11","type":"html","authorship":"legacy-preserved","html":"<h2 class=\"sub\">Critérios eliminatórios</h2>","fidelityText":"Critérios eliminatórios"},{"id":"projeto-integrador-content-12","type":"html","authorship":"legacy-preserved","html":"<ul><li>O projeto não é concluído se depender de segredo versionado, <code>ddl-auto=update</code>, testes manuais como única evidência ou banco exposto publicamente.</li><li>Falha de Kafka ou Redis não pode corromper o estado transacional.</li><li>Uma requisição repetida não pode cobrar, reservar ou publicar efeito de negócio duas vezes.</li><li>O artefato implantado precisa ser exatamente o artefato testado.</li><li>Backup precisa ter sido restaurado; health check precisa distinguir readiness e liveness.</li></ul>","fidelityText":"O projeto não é concluído se depender de segredo versionado, ddl-auto=update, testes manuais como única evidência ou banco exposto publicamente.Falha de Kafka ou Redis não pode corromper o estado transacional.Uma requisição repetida não pode cobrar, reservar ou publicar efeito de negócio duas vezes.O artefato implantado precisa ser exatamente o artefato testado.Backup precisa ter sido restaurado; health check precisa distinguir readiness e liveness."},{"id":"projeto-integrador-project","type":"project","authorship":"authored","title":"Sistema completo de ponta a ponta","brief":"Construa um sistema pequeno, mas profissionalmente verificável, com front-end, API, domínio, persistência, segurança, observabilidade, entrega e ao menos uma integração assíncrona ou justificativa explícita para não usá-la.","requirements":["Escopo fechado em 2 ou 3 fluxos de negócio centrais","README com setup, decisões, comandos, env vars e limitações","ADRs para decisões arquiteturais relevantes","Testes unitários, integração e pelo menos uma validação end-to-end/manual documentada","Pipeline/build reproduzível e artefato rastreável","Health/logs/métricas ou evidência operacional equivalente","Plano de rollback/restore ou simulação segura documentada"],"guidance":"independent","acceptanceCriteria":["Uma pessoa externa consegue rodar o projeto seguindo o README.","Cada decisão tecnológica importante possui razão e consequência.","Falhas principais têm comportamento observável, não stack trace solto.","O relatório final conecta requisitos aos capítulos/conceitos usados."],"knowledgeMatrix":[{"requirement":"Arquitetura justificada","conceptIds":["capstone-reference-architecture","adr-context-decision-consequence"],"chapterIds":["code-review-adr","mapa-sistema","projeto-integrador"],"expectedEvidence":"ADRs registram contexto, decisão, alternativa e consequência."},{"requirement":"Operação verificável","conceptIds":["capstone-operational-evidence","production-evidence-checklist"],"chapterIds":["mini-deploy-observavel","projeto-integrador"],"expectedEvidence":"Health, logs, build, deploy, rollback/restore ou simulação estão documentados."},{"requirement":"Fluxo distribuído quando necessário","conceptIds":["event-driven-project-evidence","outbox-atomic-publish-intent"],"chapterIds":["mini-pedidos-eventos","projeto-integrador"],"expectedEvidence":"Se assíncrono existir, duplicação/falha são testadas; se não existir, a decisão é justificada."},{"requirement":"Contrato ponta-a-ponta","conceptIds":["end-to-end-request-map","frontend-backend-contract"],"chapterIds":["mapa-sistema","conectando-front-back"],"expectedEvidence":"Fluxo UI/API/domínio/persistência/resposta é demonstrado e testado."}]},{"id":"projeto-integrador-quiz","type":"quiz","authorship":"authored","conceptId":"capstone-operational-evidence","prompt":"O que diferencia projeto final profissional de demo frágil?","options":[{"id":"pi-a","label":"Escopo controlado, decisões registradas, testes, operação observável e instruções reproduzíveis.","correct":true,"explanation":"Profissionalismo aparece na capacidade de executar, revisar, diagnosticar e recuperar."},{"id":"pi-b","label":"Usar o maior número possível de tecnologias mesmo sem necessidade.","correct":false,"explanation":"Tecnologia sem trade-off aumenta risco e reduz clareza."},{"id":"pi-c","label":"Funcionar uma vez na máquina do autor sem README.","correct":false,"explanation":"Isso não é reproduzível nem revisável."}]}],"resources":[{"id":"twelve-factor-config-capstone","type":"reference","title":"The Twelve-Factor App: Config","url":"https://12factor.net/config","reinforces":"Configuração por ambiente e separação de secrets/config no projeto final.","language":"en","publisher":"12factor.net","official":false,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"sre-workbook-monitoring-capstone","type":"reference","title":"Google SRE Workbook: Monitoring Distributed Systems","url":"https://sre.google/workbook/monitoring/","reinforces":"Sinais, alertas e evidência operacional para sistemas em produção.","language":"en","publisher":"Google SRE","official":false,"expectedLevel":"advanced","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the end-to-end capstone system flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for end-to-end capstone system. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for end-to-end capstone system with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"customer web","instruction":"Design the end-to-end capstone system flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for end-to-end capstone system with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}},{"id":"quiz","moduleId":"professional-final","order":4,"title":"Quiz de revisão","summary":"Vinte e duas perguntas cobrindo do básico ao avançado. Clique em uma alternativa para ver o feedback na hora.","objectives":["Usar revisão final como diagnóstico de lacunas","Revisitar fundamentos, POO, Java Core, Spring, segurança, dados, mensageria e produção","Ligar erro de quiz ao capítulo/conceito correto","Preparar uma rodada de revisão antes do projeto ou entrevista"],"whyItExists":"O quiz final não existe para decorar respostas isoladas. Ele é uma malha de diagnóstico: cada erro aponta uma lacuna conceitual e sugere onde voltar antes do projeto integrador, revisão técnica ou entrevista.","prerequisiteChapterIds":["projeto-integrador","mapa-sistema"],"conceptIds":["quiz-de-revisao"],"introducedConceptIds":["final-review-diagnostic-loop"],"usedConceptIds":["system-knowledge-map","project-evidence-readme","capstone-reference-architecture"],"estimatedMinutes":60,"englishLevel":3,"blocks":[{"id":"quiz-intuition","type":"intuition","authorship":"authored","title":"Quiz final é bússola de revisão, não placar de ego","body":"Se você errar uma questão, a pergunta útil é: qual conceito faltou? O quiz fecha o ciclo apontando tópicos que merecem releitura antes de entrevista, projeto integrador ou revisão espaçada.","analogyLimit":"Prova ajuda como metáfora, mas aqui o valor é diagnóstico acionável, não nota final."},{"id":"quiz-review-exercise","type":"exercise","authorship":"authored","title":"Plano de revisão por erro","prompt":"Após responder o quiz, escolha três erros e escreva: conceito envolvido, capítulo para revisar, exemplo mínimo que provaria a resposta correta e como isso aparece no projeto final.","difficulty":"intermediate","criteria":["Cada erro vira ação concreta de revisão.","O plano conecta resposta a capítulo/conceito, não só à alternativa certa.","Pelo menos um exemplo usa código ou cenário operacional."]},{"id":"quiz-content-1","type":"html","authorship":"legacy-preserved","html":"<div class=\"topic-meta\">\n        <div class=\"meta-item\">Dificuldade: <span class=\"dots\"><i class=\"on\"></i><i class=\"on\"></i><i class=\"on\"></i></span> <b>Revisão geral</b></div>\n        <div class=\"meta-item\">Pré-requisito: todo o curso</div>\n      </div>","fidelityText":"Dificuldade: Revisão geral Pré-requisito: todo o curso"},{"id":"quiz-content-2","type":"html","authorship":"legacy-preserved","html":"<p>Vinte e duas perguntas cobrindo do básico ao avançado. Clique em uma alternativa para ver o feedback na hora.</p>","fidelityText":"Vinte e duas perguntas cobrindo do básico ao avançado. Clique em uma alternativa para ver o feedback na hora."},{"id":"quiz:0","type":"quiz","authorship":"legacy-preserved","conceptId":"1-qual-a-diferenca-fundamental-entre-sobrecarga-overload-e-sobrescrita-o","prompt":"1. Qual a diferença fundamental entre sobrecarga (overload) e sobrescrita (override)?","options":[{"id":"quiz:0:option:0","label":"Sobrecarga é resolvida em compilação (mesma classe), sobrescrita em execução (herança).","correct":true,"explanation":"Sobrecarga escolhe assinatura em compilação; sobrescrita usa despacho dinâmico em herança."},{"id":"quiz:0:option:1","label":"São sinônimos, apenas nomes diferentes para o mesmo conceito.","correct":false,"explanation":"Confunde dois mecanismos diferentes de resolução de método."},{"id":"quiz:0:option:2","label":"Sobrecarga só existe em interfaces; sobrescrita só em classes abstratas.","correct":false,"explanation":"Interfaces/classes abstratas não definem essa diferença."}],"sourceIndex":3},{"id":"quiz:1","type":"quiz","authorship":"legacy-preserved","conceptId":"2-ao-atribuir-um-objeto-a-outra-variavel-b-a-o-que-e-copiado","prompt":"2. Ao atribuir um objeto a outra variável (b = a), o que é copiado?","options":[{"id":"quiz:1:option:0","label":"Uma cópia completa e independente do objeto no heap.","correct":false,"explanation":"Em Java, atribuição de objeto copia referência, não objeto profundo."},{"id":"quiz:1:option:1","label":"Apenas a referência (endereço) — ambas apontam para o mesmo objeto.","correct":true,"explanation":"Esta alternativa inverte a regra: não há cópia completa automática."},{"id":"quiz:1:option:2","label":"Só os atributos marcados como public.","correct":false,"explanation":"Modificador de acesso não controla cópia de objeto."}],"sourceIndex":4},{"id":"quiz:2","type":"quiz","authorship":"legacy-preserved","conceptId":"3-se-voce-sobrescreve-equals-o-que-e-obrigatorio-fazer-junto","prompt":"3. Se você sobrescreve equals(), o que é obrigatório fazer junto?","options":[{"id":"quiz:2:option:0","label":"Nada além disso — equals() funciona sozinho.","correct":false,"explanation":"equals e hashCode coerentes preservam contratos de HashMap/HashSet."},{"id":"quiz:2:option:1","label":"Sobrescrever hashCode() de forma coerente.","correct":true,"explanation":"equals isolado quebra coleções baseadas em hash."},{"id":"quiz:2:option:2","label":"Tornar a classe final.","correct":false,"explanation":"final não é requisito do contrato equals/hashCode."}],"sourceIndex":5},{"id":"quiz:3","type":"quiz","authorship":"legacy-preserved","conceptId":"4-atributos-campos-de-instancia-tem-ligacao-dinamica-polimorfismo","prompt":"4. Atributos (campos) de instância têm ligação dinâmica (polimorfismo)?","options":[{"id":"quiz:3:option:0","label":"Sim, exatamente como métodos sobrescritos.","correct":false,"explanation":"Campos não têm polimorfismo dinâmico como métodos sobrescritos."},{"id":"quiz:3:option:1","label":"Não — atributos usam o tipo da variável, resolvido em compilação.","correct":true,"explanation":"Correto: acesso a campo é resolvido pelo tipo de referência."},{"id":"quiz:3:option:2","label":"Só se forem declarados protected.","correct":false,"explanation":"protected muda visibilidade, não despacho dinâmico de campo."}],"sourceIndex":6},{"id":"quiz:4","type":"quiz","authorship":"legacy-preserved","conceptId":"5-qual-a-diferenca-principal-entre-uma-excecao-checked-e-uma-unchecked","prompt":"5. Qual a diferença principal entre uma exceção checked e uma unchecked?","options":[{"id":"quiz:4:option:0","label":"Checked obriga tratar/declarar em compilação; unchecked não.","correct":true,"explanation":"Checked aparece no contrato de compilação; unchecked não obriga declaração."},{"id":"quiz:4:option:1","label":"Checked só existe em testes unitários.","correct":false,"explanation":"Checked não é conceito de teste."},{"id":"quiz:4:option:2","label":"Unchecked não pode ser capturada com catch.","correct":false,"explanation":"Unchecked pode ser capturada, apenas não é obrigada."}],"sourceIndex":7},{"id":"quiz:5","type":"quiz","authorship":"legacy-preserved","conceptId":"6-em-generics-o-que-significa-type-erasure","prompt":"6. Em generics, o que significa \"type erasure\"?","options":[{"id":"quiz:5:option:0","label":"Que o tipo genérico é escolhido aleatoriamente em runtime.","correct":false,"explanation":"Type erasure remove informação genérica reificada em runtime na maioria dos usos."},{"id":"quiz:5:option:1","label":"Que a informação de tipo genérico existe só em compilação, não no bytecode final.","correct":true,"explanation":"Tipos não são escolhidos aleatoriamente."},{"id":"quiz:5:option:2","label":"Que classes genéricas não podem ter métodos.","correct":false,"explanation":"Classes genéricas podem ter métodos."}],"sourceIndex":8},{"id":"quiz:6","type":"quiz","authorship":"legacy-preserved","conceptId":"7-por-que-um-stream-so-pode-ser-consumido-operacao-terminal-uma-unica-ve","prompt":"7. Por que um Stream só pode ser consumido (operação terminal) uma única vez?","options":[{"id":"quiz:6:option:0","label":"É uma limitação de performance que será corrigida em versões futuras.","correct":false,"explanation":"Stream é pipeline consumível uma vez por operação terminal."},{"id":"quiz:6:option:1","label":"Streams representam um pipeline de execução única, não uma estrutura de dados reutilizável.","correct":true,"explanation":"Não é promessa de correção futura; é parte do contrato."},{"id":"quiz:6:option:2","label":"Na verdade pode ser consumido quantas vezes quiser.","correct":false,"explanation":"Reusar Stream consumido lança erro."}],"sourceIndex":9},{"id":"quiz:7","type":"quiz","authorship":"legacy-preserved","conceptId":"8-por-que-valor-nao-e-seguro-em-multiplas-threads-sem-sincronizacao","prompt":"8. Por que \"valor++\" não é seguro em múltiplas threads sem sincronização?","options":[{"id":"quiz:7:option:0","label":"Porque o operador ++ não existe de fato em Java.","correct":false,"explanation":"++ envolve leitura, cálculo e escrita; interleaving causa perda de atualização."},{"id":"quiz:7:option:1","label":"Porque envolve ler, somar e gravar em três passos separados, que podem intercalar entre threads.","correct":true,"explanation":"O operador existe; o problema é atomicidade/visibilidade."},{"id":"quiz:7:option:2","label":"Porque só funciona com tipos double.","correct":false,"explanation":"O problema vale para inteiros também."}],"sourceIndex":10},{"id":"quiz:8","type":"quiz","authorship":"legacy-preserved","conceptId":"9-qual-estrutura-de-teste-representa-o-padrao-aaa","prompt":"9. Qual estrutura de teste representa o padrão AAA?","options":[{"id":"quiz:8:option:0","label":"Assert, Assert, Assert.","correct":false,"explanation":"AAA significa organizar cenário, ação e verificação."},{"id":"quiz:8:option:1","label":"Arrange, Act, Assert.","correct":true,"explanation":"Assert três vezes não organiza comportamento."},{"id":"quiz:8:option:2","label":"API, Application, Assertion.","correct":false,"explanation":"API/Application/Assertion não é o padrão estudado."}],"sourceIndex":11},{"id":"quiz:9","type":"quiz","authorship":"legacy-preserved","conceptId":"10-o-que-o-principio-de-liskov-o-l-de-solid-afirma","prompt":"10. O que o princípio de Liskov (o \"L\" de SOLID) afirma?","options":[{"id":"quiz:9:option:0","label":"Toda classe deve implementar pelo menos uma interface.","correct":false,"explanation":"LSP exige substituição sem quebrar expectativas do cliente."},{"id":"quiz:9:option:1","label":"Uma subclasse deve poder substituir sua superclasse sem quebrar o comportamento esperado.","correct":true,"explanation":"Interface não é obrigatória para toda classe."},{"id":"quiz:9:option:2","label":"Métodos static não podem ser sobrescritos.","correct":false,"explanation":"static não ser sobrescrito é outro assunto."}],"sourceIndex":12},{"id":"quiz:10","type":"quiz","authorship":"legacy-preserved","conceptId":"11-por-que-fetchtype-lazy-sozinho-nao-resolve-o-problema-n-1","prompt":"11. Por que FetchType.LAZY sozinho não resolve o problema N+1?","options":[{"id":"quiz:10:option:0","label":"Porque LAZY na verdade piora a performance sempre.","correct":false,"explanation":"LAZY apenas adia carregamento; acessar coleção em loop ainda pode gerar N queries."},{"id":"quiz:10:option:1","label":"Porque só adia a query extra para quando o campo é acessado — ainda uma query por vez.","correct":true,"explanation":"LAZY não piora sempre; depende do acesso."},{"id":"quiz:10:option:2","label":"Porque LAZY desabilita relacionamentos completamente.","correct":false,"explanation":"LAZY não remove relacionamento."}],"sourceIndex":13},{"id":"quiz:11","type":"quiz","authorship":"legacy-preserved","conceptId":"12-um-jwt-decodificado-revela-o-conteudo-do-payload-sem-nenhuma-chave-se","prompt":"12. Um JWT decodificado revela o conteúdo do payload sem nenhuma chave secreta. Isso significa que:","options":[{"id":"quiz:11:option:0","label":"JWT é assinado, não criptografado — nunca deve carregar dados sensíveis.","correct":true,"explanation":"JWT assinado é legível; não coloque segredo/PII sensível no payload."},{"id":"quiz:11:option:1","label":"O JWT está corrompido e não deveria funcionar.","correct":false,"explanation":"Payload legível é normal."},{"id":"quiz:11:option:2","label":"Isso só acontece se o servidor estiver mal configurado.","correct":false,"explanation":"Não depende necessariamente de má configuração."}],"sourceIndex":14},{"id":"quiz:12","type":"quiz","authorship":"legacy-preserved","conceptId":"13-qual-a-principal-diferenca-entre-kafka-e-uma-fila-tradicional-como-ra","prompt":"13. Qual a principal diferença entre Kafka e uma fila tradicional como RabbitMQ?","options":[{"id":"quiz:12:option:0","label":"Kafka não suporta múltiplos consumidores.","correct":false,"explanation":"Kafka retém log e permite replay conforme retenção/offset."},{"id":"quiz:12:option:1","label":"Kafka retém mensagens por um período, permitindo replay; filas tradicionais removem ao confirmar.","correct":true,"explanation":"Kafka suporta múltiplos consumidores/grupos."},{"id":"quiz:12:option:2","label":"RabbitMQ é sempre mais rápido em qualquer cenário.","correct":false,"explanation":"Performance depende do caso."}],"sourceIndex":15},{"id":"quiz:13","type":"quiz","authorship":"legacy-preserved","conceptId":"14-por-que-consumidores-de-mensageria-deveriam-ser-idempotentes","prompt":"14. Por que consumidores de mensageria deveriam ser idempotentes?","options":[{"id":"quiz:13:option:0","label":"Porque a garantia mais comum é \"at-least-once\" — a mesma mensagem pode chegar mais de uma vez.","correct":true,"explanation":"At-least-once aceita duplicação; consumidor protege efeito por idempotência."},{"id":"quiz:13:option:1","label":"Porque idempotência é exigida por lei em sistemas de mensageria.","correct":false,"explanation":"Não é exigência legal genérica."},{"id":"quiz:13:option:2","label":"Porque só assim o Kafka aceita processar a mensagem.","correct":false,"explanation":"Kafka não exige idempotência para aceitar mensagem, mas o domínio precisa dela."}],"sourceIndex":16},{"id":"quiz:14","type":"quiz","authorship":"legacy-preserved","conceptId":"15-qual-a-diferenca-entre-guardar-um-jwt-em-localstorage-vs-em-um-cookie","prompt":"15. Qual a diferença entre guardar um JWT em localStorage vs em um cookie httpOnly?","options":[{"id":"quiz:14:option:0","label":"Cookie httpOnly é inacessível via JavaScript, reduzindo risco de roubo via XSS.","correct":true,"explanation":"httpOnly reduz roubo via XSS porque JS não lê o cookie."},{"id":"quiz:14:option:1","label":"Não há diferença prática de segurança entre os dois.","correct":false,"explanation":"Há diferença relevante de superfície de ataque."},{"id":"quiz:14:option:2","label":"localStorage é sempre mais seguro por ser mais simples.","correct":false,"explanation":"localStorage é acessível por JS e sofre em XSS."}],"sourceIndex":17},{"id":"quiz:15","type":"quiz","authorship":"legacy-preserved","conceptId":"16-por-que-testcontainers-e-preferivel-a-mocks-para-testar-um-repository","prompt":"16. Por que Testcontainers é preferível a mocks para testar um Repository JPA?","options":[{"id":"quiz:15:option:0","label":"Testcontainers é mais rápido que mocks em todos os casos.","correct":false,"explanation":"Repository real depende de SQL, driver, transação e dialeto; container valida isso."},{"id":"quiz:15:option:1","label":"Testa contra um banco real, validando comportamento que um mock não consegue simular fielmente.","correct":true,"explanation":"Containers costumam ser mais lentos que mocks, mas mais fiéis."},{"id":"quiz:15:option:2","label":"Mocks não funcionam com Spring Data JPA.","correct":false,"explanation":"Mocks funcionam, só não provam integração real."}],"sourceIndex":18},{"id":"quiz:16","type":"quiz","authorship":"legacy-preserved","conceptId":"17-o-que-um-circuit-breaker-faz-quando-um-servico-externo-falha-repetida","prompt":"17. O que um Circuit Breaker faz quando um serviço externo falha repetidamente?","options":[{"id":"quiz:16:option:0","label":"Tenta reconectar infinitamente até dar certo.","correct":false,"explanation":"Circuit breaker abre e evita pressionar dependência doente por política."},{"id":"quiz:16:option:1","label":"Abre o circuito e para de tentar chamar o serviço real, usando um fallback.","correct":true,"explanation":"Retry infinito amplifica falha."},{"id":"quiz:16:option:2","label":"Reinicia a aplicação automaticamente.","correct":false,"explanation":"Ele não reinicia aplicação automaticamente."}],"sourceIndex":19},{"id":"quiz:17","type":"quiz","authorship":"legacy-preserved","conceptId":"18-por-que-nunca-usar-ddl-auto-update-em-producao","prompt":"18. Por que nunca usar ddl-auto=update em produção?","options":[{"id":"quiz:17:option:0","label":"Porque essa opção não existe no Hibernate.","correct":false,"explanation":"Migrations versionadas são auditáveis; ddl-auto=update pode alterar schema sem revisão."},{"id":"quiz:17:option:1","label":"Porque o Hibernate pode alterar o schema de forma imprevisível; migrations versionadas (Flyway) são a fonte confiável.","correct":true,"explanation":"ddl-auto=update existe, mas delega mudanças de schema ao runtime sem revisão, histórico ou plano confiável de rollback."},{"id":"quiz:17:option:2","label":"Porque só funciona com MongoDB.","correct":false,"explanation":"Não tem relação com MongoDB."}],"sourceIndex":20},{"id":"quiz:18","type":"quiz","authorship":"legacy-preserved","conceptId":"19-por-que-uma-imagem-docker-de-aplicacao-java-deveria-usar-multi-stage-","prompt":"19. Por que uma imagem Docker de aplicação Java deveria usar multi-stage build?","options":[{"id":"quiz:18:option:0","label":"É a única forma de o Docker funcionar com Java.","correct":false,"explanation":"Multi-stage separa build pesado do runtime enxuto."},{"id":"quiz:18:option:1","label":"Separa o JDK/compilador (só necessário no build) do JRE enxuto necessário em runtime, reduzindo o tamanho final.","correct":true,"explanation":"Docker funciona com Java sem multi-stage, mas pior para imagem final."},{"id":"quiz:18:option:2","label":"Multi-stage torna o build mais lento, mas obrigatório por segurança.","correct":false,"explanation":"Não é obrigatório por si só; é prática de qualidade."}],"sourceIndex":21},{"id":"quiz:19","type":"quiz","authorship":"legacy-preserved","conceptId":"20-por-que-expor-uma-entity-jpa-diretamente-em-um-endpoint-rest-e-uma-ma","prompt":"20. Por que expor uma @Entity JPA diretamente em um endpoint REST é uma má prática?","options":[{"id":"quiz:19:option:0","label":"Entidades JPA não podem ser serializadas para JSON de forma alguma.","correct":false,"explanation":"Entity exposta acopla API ao modelo interno e pode causar problemas de lazy/loading."},{"id":"quiz:19:option:1","label":"Vaza detalhes internos do banco, acopla o contrato da API ao modelo interno, e pode causar LazyInitializationException.","correct":true,"explanation":"Entidades podem ser serializadas, mas isso não torna boa prática."},{"id":"quiz:19:option:2","label":"É proibido pelo Spring Boot em tempo de compilação.","correct":false,"explanation":"Spring não proíbe em compilação."}],"sourceIndex":22},{"id":"quiz:20","type":"quiz","authorship":"legacy-preserved","conceptId":"21-um-producer-kafka-ja-esta-configurado-com-acks-all-por-que-isso-sozin","prompt":"21. Um producer Kafka já está configurado com acks=all. Por que isso sozinho ainda não impede que uma mensagem seja duplicada no tópico após uma falha de rede?","options":[{"id":"quiz:20:option:0","label":"acks=all já garante exactly-once por padrão, duplicação não é possível.","correct":false,"explanation":"acks=all confirma que as réplicas gravaram, mas um timeout pode fazer o producer reenviar o mesmo registro sem enable.idempotence."},{"id":"quiz:20:option:1","label":"Sem enable.idempotence=true, um reenvio automático após timeout pode gravar o mesmo registro duas vezes no log da partição.","correct":true,"explanation":"acks=all sozinho não é exactly-once; garante durabilidade da réplica, não deduplicação no reenvio."},{"id":"quiz:20:option:2","label":"acks=all só afeta o consumer, nunca o comportamento do producer.","correct":false,"explanation":"acks configura o producer, não o consumer — a confirmação é sobre a escrita na partição."}],"sourceIndex":23},{"id":"quiz:21","type":"quiz","authorship":"legacy-preserved","conceptId":"22-o-que-diferencia-processar-dados-com-kafka-streams-de-simplesmente-le","prompt":"22. O que diferencia processar dados com Kafka Streams de simplesmente ler e escrever tópicos com um KafkaConsumer/KafkaProducer manuais?","options":[{"id":"quiz:21:option:0","label":"Kafka Streams precisa de um cluster de processamento separado do cluster Kafka.","correct":false,"explanation":"Kafka Streams roda embutido na sua aplicação como biblioteca, lendo/escrevendo do próprio cluster Kafka sem cluster de processamento à parte."},{"id":"quiz:21:option:1","label":"Kafka Streams oferece uma topology declarativa (filter/map/join) com estado gerenciado, sem exigir infraestrutura de processamento própria.","correct":true,"explanation":"Não existe exigência de cluster de processamento separado; essa é justamente a vantagem de Kafka Streams sobre engines como Spark/Flink."},{"id":"quiz:21:option:2","label":"Kafka Streams só funciona com mensagens em formato texto puro, nunca Avro.","correct":false,"explanation":"Kafka Streams suporta serializadores Avro/Protobuf normalmente via Schema Registry, não só texto puro."}],"sourceIndex":24},{"id":"quiz-final-meta","type":"quiz","authorship":"authored","conceptId":"final-review-diagnostic-loop","prompt":"Qual é a atitude correta diante de erro no quiz final?","options":[{"id":"qf-a","label":"Mapear o erro para conceito/capítulo, refazer um exemplo e aplicar a correção no projeto ou revisão.","correct":true,"explanation":"O erro vira diagnóstico e prática deliberada."},{"id":"qf-b","label":"Ignorar porque quiz não tem relação com projeto real.","correct":false,"explanation":"As questões cobrem conceitos que aparecem no projeto e em entrevistas."},{"id":"qf-c","label":"Decorar a alternativa sem entender por quê.","correct":false,"explanation":"Decorar não transfere para código, review ou operação."}]}],"resources":[{"id":"dev-java-get-started-final-review","type":"official-docs","title":"Dev.java: Getting Started","url":"https://dev.java/learn/getting-started/","reinforces":"Revisão oficial de ambiente, execução e fundamentos Java.","language":"en","publisher":"Oracle/Java","official":true,"expectedLevel":"foundation","verifiedAt":"2026-08-22","auditStatus":"approved"},{"id":"spring-boot-reference-final-review","type":"official-docs","title":"Spring Boot Reference Documentation","url":"https://docs.spring.io/spring-boot/index.html","reinforces":"Mapa de consulta para tópicos Spring usados no curso e no projeto final.","language":"en","publisher":"Spring","official":true,"expectedLevel":"intermediate","verifiedAt":"2026-08-22","auditStatus":"approved"}],"englishActivity":{"level":3,"label":"Professional use","readPassage":"Design the quiz flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","comprehensionQuestion":"Which constraint protects correctness, and which trade-off would you make explicit during review?","contextSupport":"Work directly from the contract, code, and official material. Record uncertainty as a technical question, not as a translation exercise.","documentationTask":"Read the official material for quiz. Extract one normative behavior, one limitation, and one operational consequence.","productionTask":"Write an implementation issue for quiz with context, constraints, acceptance criteria, failure scenarios, and trade-offs.","successCriterion":"The issue is implementable and reviewable by an international team without additional Portuguese context.","codeContext":"// Observe how names reveal the quiz contract.","instruction":"Design the quiz flow so that failures remain observable, retries are bounded, and duplicate work cannot corrupt state. Document constraints and recovery trade-offs.","prompt":"Write an implementation issue for quiz with context, constraints, acceptance criteria, failure scenarios, and trade-offs."},"audit":{"status":"approved","sourceKind":"legacy-html","reviewedAt":"2026-08-22"}}]}
