C4 Model na prática: Component Diagram (Parte 3)
No post anterior abrimos o sistema em containers: frontend, API, banco, CDN. Agora vamos abrir um container específico e ver como ele é organizado por dentro.
O que é o Component Diagram
O Component Diagram responde: como esse container específico é organizado internamente?
Você pega um único container do diagrama anterior (a API, por exemplo) e mostra seus módulos: controllers, services, repositories, e como eles se chamam entre si. O diagrama mostra:
- Components internos do container (classes ou módulos com responsabilidade única)
- Relações internas, geralmente chamadas de método, não chamadas de rede
- Os containers externos com os quais esses components conversam (banco, cache, outro serviço, frontend)
Nem todo container precisa de um Component Diagram. Vale a pena quando o container tem complexidade interna real: um serviço de autenticação, um módulo de pagamento. Um CRUD simples não justifica o esforço.
Trocando de exemplo
Nos posts anteriores usei o exemplo do Blog. O repositório não tem um Component Diagram pra esse sistema, e faz sentido: o Blog é simples o bastante pra não precisar desse nível de detalhe.
Pra esse post vou usar outro exemplo do mesmo repositório: o Auth Service, um serviço de autenticação com JWT que aparece no Container Diagram de um sistema de e-commerce (container/jwt.puml). É um container com responsabilidade suficiente pra justificar abrir por dentro.
As macros do Component Diagram
A biblioteca usa C4_Component.puml. Rel continua igual. Três macros novas entram:
Component
Component(alias, "Label", "Technology", "Description")
Mesma estrutura de Container, um nível abaixo. Representa uma classe, um módulo, uma camada com responsabilidade única dentro do container.
Component(auth_ctrl, "Auth Controller", "Spring MVC", "Recebe e roteia requisições HTTP de autenticação")
Container_Boundary
Container_Boundary(alias, "Label") {
' components aqui dentro
}
Equivalente ao System_Boundary do nível anterior, mas desenhando a borda ao redor do container que você está detalhando, não do sistema inteiro.
Container_Boundary(auth, "Auth Service") {
Component(auth_ctrl, "Auth Controller", "Spring MVC", "...")
Component(auth_svc, "Auth Service", "Spring Service", "...")
}
Container_Ext / ContainerDb_Ext
Container_Ext(alias, "Label", "Technology", "Description")
ContainerDb_Ext(alias, "Label", "Technology", "Description")
Representam containers que já existiam no Container Diagram, mas que aqui aparecem só como referência externa: o frontend que chama esse serviço, o banco que ele usa. Você não abre eles de novo, só marca que existem fora da fronteira atual.
Container_Ext(spa, "Frontend SPA", "React", "Consome os endpoints do Auth Service")
ContainerDb_Ext(auth_db, "Auth Database", "PostgreSQL", "Contas de usuário e senhas com hash")
A distinção visual entre
Component(dentro da fronteira) e*_Ext(fora dela) é a mesma lógica deSystemvsSystem_Extdo Context Diagram, só que um nível abaixo.
Exemplo completo: Auth Service
@startuml auth
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
title "Auth Service - Component Diagram"
Container_Ext(spa, "Frontend SPA", "React", "Consome os endpoints do Auth Service")
ContainerDb_Ext(auth_db, "Auth Database", "PostgreSQL", "Contas de usuário e senhas com hash")
ContainerDb_Ext(auth_cache, "Auth Cache", "Redis", "Blacklist de tokens e refresh tokens")
Container_Boundary(auth, "Auth Service") {
Component(auth_ctrl, "Auth Controller", "Spring MVC", "Recebe e roteia requisições HTTP de autenticação")
Component(auth_svc, "Auth Service", "Spring Service", "Orquestra os fluxos de login, logout e renovação de token")
Component(token_svc, "Token Service", "Spring Service", "Emite e valida tokens JWT de acesso e refresh")
Component(password_svc, "Password Service", "Spring Service", "Gera hash e verifica senhas de usuário")
Component(user_repo, "User Repository", "Spring Data JPA", "Abstrai acesso de leitura e escrita dos dados de usuário")
Component(token_repo, "Token Repository", "Spring Data Redis", "Abstrai acesso de leitura e escrita do cache de tokens")
}
Rel(spa, auth_ctrl, "Envia requisições de login e refresh", "HTTPS/REST JSON")
Rel(auth_ctrl, auth_svc, "Delega a lógica de autenticação", "Method call")
Rel(auth_svc, password_svc, "Verifica e gera hash de senhas", "Method call")
Rel(auth_svc, token_svc, "Emite e valida tokens", "Method call")
Rel(auth_svc, user_repo, "Busca usuário pelas credenciais", "Method call")
Rel(token_svc, token_repo, "Lê e escreve blacklist e refresh tokens", "Method call")
Rel(user_repo, auth_db, "Lê e escreve dados de usuário", "TCP/SQL")
Rel(token_repo, auth_cache, "Lê e escreve cache de tokens", "TCP")
@enduml
Repare no padrão: auth_ctrl recebe a requisição HTTP e delega tudo pro auth_svc, que é o orquestrador. Ele não fala com banco, não fala com cache: quem faz isso são user_repo e token_repo. Cada component tem uma responsabilidade, e a relação entre eles vira “Method call”, porque agora estamos dentro do processo, não entre processos.
O que colocar e o que deixar de fora
Coloca:
- Components com responsabilidade clara e única
- A relação entre eles (geralmente chamada de método)
- Os containers externos que esses components consomem, como referência
Não coloca:
- Classes utilitárias, DTOs, helpers (isso é ruído, não arquitetura)
- Todo container do sistema (só o que está sendo detalhado)
- Assinatura de método, atributos, código (isso é nível de Code)
A regra prática: se dois components sempre mudam juntos e nunca são reaproveitados separadamente, considera se eles não deveriam ser um só.
E o nível Code?
O quarto nível do C4 (Code) mostra classes, interfaces e implementação, normalmente em UML. Na prática, quase ninguém mantém esse diagrama manualmente: IDEs e ferramentas de documentação geram isso sozinhas a partir do código-fonte. Por isso a série para por aqui, no Component.
Os exemplos desse post estão no repositório github.com/wander4747/c4model, na pasta component/.
Context mostra o quê. Container mostra o como, por fora. Component mostra o como, por dentro. Três diagramas, três perguntas diferentes, e nenhum deles exige que você desenhe tudo de novo quando o sistema cresce.