Estrutura de Pacotes
Nossos serviços backend seguem a organização Pacote por Funcionalidade em quatro módulos irmãos. Este documento é a fonte da verdade para onde colocar novo código.
Nota sobre a linguagem. Os exemplos abaixo estão escritos em Java/Spring Boot porque é nossa principal stack de backend, mas a estrutura em si é agnóstica em relação à linguagem e obrigatória em todas as linguagens backend que usamos (Kotlin, Go, Python, Node, etc.). Traduza as extensões de arquivo e anotações do framework; mantenha a estrutura de pastas, sufixos de nome (
*Db,*Repo,*RepoImpl,*Dao,*Controller,*ControllerImpl) e a divisão interface/implementação inalterados.
Princípio central
Agrupar por funcionalidade, não por camada técnica. Uma alteração na funcionalidade "pedidos" deve tocar em arquivos que ficam próximos uns dos outros, não em arquivos espalhados pelos pacotes controllers/, services/, repositories/. A divisão em camadas técnicas acontece no limite do módulo (db, client, capabilities, starter), e a organização por funcionalidade acontece dentro de cada módulo.
Os quatro módulos
Cada serviço é composto por estes quatro módulos irmãos:
<serviço>/
├── capabilities/ # funcionalidades/lógica de negócio — o cérebro
├── db/ # persistência: entidades, repositórios, migrações
├── client/ # integrações de saída com sistemas externos
└── starter/ # Inicializador Spring Boot — conecta tudo e executa
capabilities/ — lógica de negócio
Cada funcionalidade tem seu próprio pacote, e dentro de cada funcionalidade há um sub-pacote por endpoint de API (getById, create, updateById, getByQuery, …). Cada sub-funcionalidade possui três pastas irmãs:
service/— a classe de serviço única para aquela APIdto/—request/eresponse/(o que for aplicável)mapper/— traduz entidades*Db↔ DTOs (ambas as direções) apenas para esta sub-funcionalidade
O controlador da funcionalidade fica na raiz do pacote da funcionalidade, dividido em uma interface e uma implementação, assim como os repositórios:
capabilities/<funcionalidade>/<Funcionalidade>Controller.java— interface; declara rotas, verbos HTTP, tipos de requisição/resposta, anotações de documentação da APIcapabilities/<funcionalidade>/<Funcionalidade>ControllerImpl.java— implementação; carrega a anotação do framework (@RestController) e delega cada rota para o serviço da sub-funcionalidade correspondente
Compartilhado entre todas as sub-funcionalidades daquela funcionalidade. Consulte Controllers.
- Depende de:
db(apenas através de interfaces*Repo— consulte Regra de uso do DB),client - Nunca depende de:
starter - Não contém classe
main, nem@SpringBootApplication - Possui: controladores HTTP, DTOs de requisição/resposta da API, mapeadores, serviços
db/ — persistência
Organizado uma pasta por tabela. Cada pasta de tabela tem exatamente três sub-pastas:
db/<nomeDaTabela>/
├── model/
│ ├── <NomeDaEntidade>Db.java # entidade JPA principal
│ └── <Outro>Db.java # sub-modelos (tipos incorporados, entidades filhas, projeções)
├── dao/
│ └── <NomeDaEntidade>Dao.java # acesso a dados de baixo nível (Criteria/JPQL/consultas nativas)
└── repository/
├── <NomeDaEntidade>Repo.java # interface PÚBLICA — o único tipo que capabilities vê
└── impl/
└── <NomeDaEntidade>RepoImpl.java # compõe Spring Data + Dao por trás da interface
Convenção de nomenclatura
| Camada | Sufixo | Exemplo |
|---|---|---|
| Entidade JPA | Db | TestcaseDb |
| Interface do repositório | Repo | TestcaseRepo |
| Implementação do repositório | RepoImpl | TestcaseRepoImpl |
| Auxiliar de acesso a dados | Dao | TestcaseDao |
Regra de uso do DB
Capabilities devem depender apenas de interfaces *Repo. *RepoImpl e *Dao são internos — nunca injetados ou referenciados de fora do pacote db/<nomeDaTabela>/. Isso mantém a camada de persistência intercambiável e os testes de capabilities focados no contrato.
- Depende de: nada neste serviço
- Expõe: interfaces
*Repoe tipos de modelo*Db - Nunca contém: lógica de negócio, transformação para DTOs de API
client/ — integrações de saída
Contém o código que chama outros serviços dos quais este serviço depende. Principalmente uma sub-pasta por microsserviço dependente em nossa própria plataforma (paymentService, userService, executionService, …). Wrappers de SDKs de terceiros ou produtores de mensagem também podem ficar aqui, mas o padrão dominante — e a razão pela qual este módulo existe — são os clientes de microsserviços dependentes.
client/<nomeDoMicrosserviço>/
├── <Microsserviço>Client.java # interface — do que capabilities depende
├── impl/
│ └── <Microsserviço>ClientImpl.java # impl: chamada HTTP/gRPC/SDK, retentativas, timeouts
├── dto/ # formas de requisição/resposta para a API DESTE microsserviço
│ ├── request/
│ └── response/
└── mapper/ # opcional: traduz entre as formas deles e as nossas
Regras
-
Uma sub-pasta por microsserviço dependente, nomeada de acordo com o serviço. Não agrupe vários microsserviços em um único pacote, mesmo que sejam "relacionados".
-
Capabilities dependem apenas de interfaces
*Client.*ClientImplé interno — mesma regra de*Repo/*RepoImpl. -
A interface do cliente expõe a API do microsserviço em termos de seus próprios DTOs; capabilities podem adaptá-los ainda mais, se necessário.
-
Preocupações transversais (retentativas, circuit breaking, logging de requisição/resposta, cabeçalhos de autenticação) pertencem a
impl/— capabilities nunca devem vê-los. -
Depende de: nada neste serviço
-
Expõe: interfaces
*Cliente seus DTOs de requisição/resposta -
Nunca contém: lógica de negócio, orquestração de capabilities
starter/ — raiz de composição
O único módulo que executa. Contém @SpringBootApplication, manipuladores globais de exceção REST, configuração de segurança, filtros e application.yml. Sem controladores — eles ficam em capabilities/.
- Depende de:
capabilities(e transitivamentedb,client) - Possui: o artefato implantável, configuração web transversal (segurança, CORS, tratamento de erros)
- Nunca possui: controladores de funcionalidades ou DTOs de funcionalidades
Regra de dependência
starter → capabilities → { db, client }
As setas vão em uma direção. db e client nunca importam de capabilities ou starter. capabilities nunca importa de starter.
Controllers
Há um controlador por funcionalidade, não por sub-funcionalidade, e ele fica na raiz do pacote da funcionalidade dentro de capabilities/. Ele é dividido em dois arquivos, espelhando a divisão *Repo / *RepoImpl:
capabilities/<funcionalidade>/<Funcionalidade>Controller.java— interface. Declara as rotas, verbos HTTP, parâmetros de caminho/consulta, tipos de DTO de requisição/resposta e anotações de documentação da API (OpenAPI/Swagger). Qualquer pessoa lendo a interface deve ver todo o contrato HTTP.capabilities/<funcionalidade>/<Funcionalidade>ControllerImpl.java— implementação. Carrega a anotação do framework (@RestControllerno Spring), é o alvo de injeção para os serviços de sub-funcionalidade e contém um método curto por rota que chama oservice/correspondente e retorna o resultado. Nenhuma lógica além de delegação.
Por que a divisão? A interface é o contrato da API — estável, documentado, testável isoladamente. O impl é a fiação — pode mudar sem alterar o contrato. Mesmo raciocínio de *Repo vs *RepoImpl.
O controlador delega cada rota HTTP para o serviço da sub-funcionalidade correspondente em capabilities/<funcionalidade>/<sub-funcionalidade>/service/. Os DTOs de requisição/resposta são de propriedade da sub-funcionalidade (capabilities/<funcionalidade>/<sub-funcionalidade>/dto/); o controlador é apenas um adaptador HTTP fino.
Por que dentro de capabilities/ e não em starter/? O controlador, seu serviço e seus DTOs mudam juntos quando uma API é alterada — mantê-los em um módulo preserva a promessa de "pacote por funcionalidade" de "uma funcionalidade, um lugar". starter/ é reservado para fiação transversal (segurança, manipuladores de erro, ponto de entrada da aplicação), não para código web por funcionalidade.
Exemplo prático: uma funcionalidade testcase
A árvore abaixo usa extensões .java para ilustração. Substitua a extensão do arquivo e os idiomas do framework da sua linguagem — Kotlin .kt, pacotes Go, módulos Python, etc. — mas mantenha a estrutura de pastas e os sufixos de nome idênticos.
testcase-service/
├── capabilities/
│ └── com/example/testcase/capabilities/
│ └── testcase/ # funcionalidade principal
│ ├── TestcaseController.java # interface — contrato HTTP, declarações de rota, docs da API
│ ├── TestcaseControllerImpl.java # impl — @RestController, delega para serviços de sub-funcionalidade
│ ├── getById/ # sub-funcionalidade = uma API
│ │ ├── service/
│ │ │ └── GetTestcaseByIdService.java
│ │ ├── dto/
│ │ │ └── response/
│ │ │ └── GetTestcaseByIdResponse.java
│ │ └── mapper/
│ │ └── GetTestcaseByIdMapper.java # TestcaseDb → resposta
│ ├── create/
│ │ ├── service/
│ │ │ └── CreateTestcaseService.java
│ │ ├── dto/
│ │ │ ├── request/
│ │ │ │ └── CreateTestcaseRequest.java
│ │ │ └── response/
│ │ │ └── CreateTestcaseResponse.java
│ │ └── mapper/
│ │ └── CreateTestcaseMapper.java # requisição → TestcaseDb, TestcaseDb → resposta
│ ├── updateById/
│ │ ├── service/
│ │ │ └── UpdateTestcaseByIdService.java
│ │ ├── dto/
│ │ │ ├── request/
│ │ │ │ └── UpdateTestcaseByIdRequest.java
│ │ │ └── response/
│ │ │ └── UpdateTestcaseByIdResponse.java
│ │ └── mapper/
│ │ └── UpdateTestcaseByIdMapper.java
│ └── getByQuery/
│ ├── service/
│ │ └── GetTestcaseByQueryService.java
│ ├── dto/
│ │ ├── request/
│ │ │ └── GetTestcaseByQueryRequest.java
│ │ └── response/
│ │ └── GetTestcaseByQueryResponse.java
│ └── mapper/
│ └── GetTestcaseByQueryMapper.java
│
├── db/ # uma pasta por tabela
│ └── com/example/testcase/db/
│ └── testcase/ # nome da tabela
│ ├── model/
│ │ ├── TestcaseDb.java # entidade JPA — principal
│ │ └── TestcaseStepDb.java # sub-modelo (filho/incorporado)
│ ├── dao/
│ │ └── TestcaseDao.java # auxiliar de consulta/critério, uso privado ao pacote
│ └── repository/
│ ├── TestcaseRepo.java # interface — capabilities depende DESTA
│ └── impl/
│ └── TestcaseRepoImpl.java # compõe Spring Data + TestcaseDao
│
├── client/ # uma pasta por microsserviço dependente
│ └── com/example/testcase/client/
│ ├── executionService/ # microsserviço dependente que chamamos
│ │ ├── ExecutionServiceClient.java # interface
│ │ ├── impl/
│ │ │ └── ExecutionServiceClientImpl.java # chamada HTTP/gRPC, retentativas, timeouts
│ │ ├── dto/
│ │ │ ├── request/
│ │ │ │ └── RunTestcaseRequest.java
│ │ │ └── response/
│ │ │ └── RunTestcaseResponse.java
│ │ └── mapper/
│ │ └── ExecutionServiceMapper.java
│ └── userService/ # outro microsserviço dependente
│ ├── UserServiceClient.java
│ └── impl/
│ └── UserServiceClientImpl.java
│
└── starter/ # apenas fiação transversal — sem controladores
└── com/example/testcase/
├── TestcaseServiceApplication.java
├── config/
│ ├── SecurityConfig.java
│ └── WebConfig.java
├── error/
│ └── GlobalExceptionHandler.java # @ControllerAdvice — transversal, não por funcionalidade
└── resources/application.yml
Regras práticas
- Adicionando uma nova API a uma funcionalidade existente? Crie um novo pacote de sub-funcionalidade dentro de
capabilities/<funcionalidade>/com seu próprioservice/,dto/{request,response}/emapper/, depois adicione a declaração da rota em<Funcionalidade>Controller.java(interface) e o método delegador em<Funcionalidade>ControllerImpl.java. Não crie um segundo controlador. - Adicionando uma funcionalidade totalmente nova? Crie o pacote da funcionalidade em
capabilities/(controlador na raiz, sub-funcionalidades por API) e a pasta de tabela correspondente emdb/<nomeDaTabela>/commodel/,dao/,repository/{impl/}. Adicione um cliente apenas se você chamar um sistema externo. Não mexa emstarter/. - Sempre injete
*Repo, nunca*RepoImplou*Dao. Capabilities dependem apenas da interface.*RepoImple*Daosão internos adb/<nomeDaTabela>/— eles existem para permitir trocar a estratégia de consulta ou o armazenamento subjacente sem alterar o código de capabilities. - Sempre injete
*Client, nunca*ClientImpl. Mesma regra para chamadas de microsserviços dependentes. Capabilities dependem da interface; o transporte (HTTP, gRPC, SDK) fica por trás deimpl/. - Uma pasta por microsserviço dependente dentro de
client/. Nomeie a pasta de acordo com o microsserviço. Não agrupe "todos os serviços internos" em um pacote. - A granularidade de sub-funcionalidade para em
capabilities/.db/é organizado por tabela;client/por sistema externo. Dividir qualquer um por API espalharia código coeso. - DTOs de requisição/resposta e seu mapeador são por sub-funcionalidade, nunca compartilhados. Se
createeupdateByIdparecem semelhantes hoje, mantenha dois DTOs e dois mapeadores. Compartilhar acopla duas APIs que divergirão independentemente. - Mapeadores convertem entidade ↔ DTO diretamente. Sem camada de modelo de domínio intermediária.
<SubFuncionalidade>Mapperconverte*Db↔ DTOs de requisição/resposta daquela sub-funcionalidade. - Três sub-funcionalidades têm código semelhante? Resista à tentação de extrair um serviço ou mapeador compartilhado até ter o mesmo padrão em quatro ou mais lugares.
- Reutilização entre funcionalidades? Coloque utilitários compartilhados em um pacote
common/dentro decapabilities/, mas apenas quando o padrão aparecer em pelo menos três funcionalidades. Evite abstr