- Published on
Clean Architecture Design Flow: A Practical Guide to Diagrams That Actually Help
9 min read
- Authors
- Name
- Shuwen
Table of Contents
- Clean Architecture Design Flow: A Practical Guide to Diagrams That Actually Help
- When to Draw Diagrams, What to Draw, and Why
- The Guiding Principle
- Stage 0: Problem Framing (No Diagrams Yet)
- Stage 1: Identify Use Cases (Mandatory)
- Stage 2: Core Workflow (Sequence Diagram)
- Stage 3: Domain Modeling (Optional but Powerful)
- Stage 4: Architecture Skeleton (Folder Tree Stage)
- Stage 5: Identify Ports (Interfaces)
- Stage 6: Implement Adapters (No Diagrams)
- Stage 7: Post-Implementation Diagrams (Optional)
- Summary: The Design Flow Cheat Sheet
- The Final Rule to Remember
- Key Takeaways
Clean Architecture Design Flow: A Practical Guide to Diagrams That Actually Help
When I applied Clean Architecture in a new project, I treated it as an engineering process, not a refactor-after-the-fact exercise. Instead of starting with frameworks or package structures, I began by explicitly identifying the system's use cases and modeling their workflows at a high level. These early diagrams helped me reason about execution flow, failure paths, and where the architectural boundaries should be. Only after those boundaries were clear did I move into the IDE to create the project structure and start coding. This approach allowed the architecture to guide implementation rather than constantly being corrected by it.
Concretely, this is what I did:
- Identified and named the core application use cases (business workflows)
- Drew use case and sequence diagrams to understand execution flow and external interactions
- Used the diagrams to identify architectural boundaries (application core vs. infrastructure)
- Created the project skeleton based on those boundaries
- Defined interfaces (ports) in the core to express required external behavior
- Implemented adapters (REST, persistence, messaging) against those interfaces
- Updated the high-level diagram and documentation and shared them with the team
When to Draw Diagrams, What to Draw, and Why
If you have ever felt overwhelmed by UML diagrams that look impressive but do not help you build better software, this guide is for you.
This document describes a practical, lightweight design flow for projects using Clean Architecture (also known as Hexagonal Architecture). The goal is to make system intent explicit, reduce cognitive load, and keep diagrams useful instead of decorative.
The Guiding Principle
Diagrams are thinking tools, not documentation artifacts.
Draw them to make decisions. Stop drawing when the code becomes clearer than the diagram.
Stage 0: Problem Framing (No Diagrams Yet)
Goal: understand why the system exists.
Write a short paragraph answering:
- Who uses the system?
- What problem does it solve?
- What outcomes matter?
Example: "This system handles order checkout, payment retries, and order expiration across REST APIs, Kafka retries, and scheduled jobs."
No UML yet. If this is unclear, diagrams will not help.
Stage 1: Identify Use Cases (Mandatory)
Goal: make system intent explicit.
What you produce:
- A list of use cases (verbs, not nouns)
- Optionally a use case diagram
Input: business requirements, product conversations, real workflows.
Output: clear, named use cases.
Example use case list:
- Checkout Order
- Retry Payment
- Expire Unpaid Orders
Use case diagram:
┌──────────┐
│ User │──────────> Checkout Order
└──────────┘
┌──────────┐
│ Kafka │──────────> Retry Payment
└──────────┘
┌──────────┐
│Scheduler │──────────> Expire Unpaid Orders
└──────────┘
Rules:
- No entities
- No methods
- No databases
- Just who triggers what
Stage 2: Core Workflow (Sequence Diagram)
Goal: understand flow and boundaries, not implementation.
When to draw: when a use case touches multiple systems, when failure handling matters.
Input: one use case, happy path plus one failure path if important.
Output: a simple sequence diagram.
Sequence diagram example: Checkout Order
Controller CheckoutOrderUseCase InventoryPort PaymentPort OrderRepo EventBus
| | | | | |
|-- execute(cmd) ->| | | | |
| | | | | |
| |-- reserve(items) ->| | | |
| |<-- reserved -------| | | |
| | | | | |
| |-- charge(amount) ----------------->| | |
| |<-- charged ------------------------| | |
| | | | | |
| |-- save(order) ---------------------------------->| |
| |<-- saved ----------------------------------------| |
| | | | | |
| |-- publish(OrderPaid) ---------------------------------------->|
| |<-- published -------------------------------------------------|
| | | | | |
|<-- result -------| | | | |
| | | | | |
Rules:
- Show ports, not implementations
- No frameworks
- No DTOs
- Stop once decisions are clear
Stage 3: Domain Modeling (Optional but Powerful)
Goal: define business language and invariants.
When to draw: complex domain, multiple developers, state transitions matter.
Input: use cases, business rules.
Output: small domain diagram (entities + relationships).
Domain model diagram:
┌─────────────────────────────┐
│ Order │
├─────────────────────────────┤
│ + id │
│ + status │
│ + total │
├─────────────────────────────┤
│ + markPaid() │
│ + expire() │
└─────────────────────────────┘
│
│ contains
│ 1 to many
▼
┌─────────────────────────────┐
│ OrderItem │
├─────────────────────────────┤
│ + productId │
│ + quantity │
│ + price │
└─────────────────────────────┘
Rules:
- No repositories
- No controllers
- No annotations
- Only business meaning
Stage 4: Architecture Skeleton (Folder Tree Stage)
Purpose: turn clear intent into a visible, enforceable structure before writing real business or framework code.
This is the stage where the architecture stops being an idea and becomes a guide.
"If I want to add a feature, I know exactly where to go."
If someone opens your repo and reads only the folder names, they should understand what the system does.
You already have:
- A clear use case list (Checkout Order, Retry Payment)
- At least one sequence diagram showing boundaries
- Basic domain concepts (Order, OrderStatus)
You do not yet have:
- Controllers
- JPA entities
- Kafka consumers
- Framework annotations
What you do in Stage 4:
- Create the top-level package boundaries:
domain/
application/
adapter/
config/
- Create one folder per use case (use verbs):
application/usecase/
├── checkout/
├── retry/
└── expire/
This answers: "What can this system do?"
- Add use-case-level placeholders (no logic yet):
application/usecase/checkout/
├── CheckoutOrderUseCase.java
├── CheckoutOrderCommand.java
└── CheckoutOrderResult.java
Rules:
- Empty or minimal methods
- No frameworks
- No infrastructure imports
- Create port folders (interfaces only):
application/port/out/
├── OrderRepositoryPort.java
├── PaymentPort.java
├── InventoryPort.java
└── EventPublisherPort.java
Rules:
- Ports exist only because a use case needs them
- No implementation classes here
- Create adapter placeholders (optional, empty):
adapter/in/
adapter/out/
Do not implement yet. This reserves space and enforces direction.
Example folder tree:
orders-service/
├── README.md
├── docs/
│ ├── architecture/
│ │ ├── use-cases.md
│ │ ├── sequence-checkout-order.md
│ │ └── domain-model.md
│ └── decisions/
│ └── ADR-001-use-case-driven-design.md
│
├── src/main/java/com/example/orders/
│
│ ├── domain/
│ │ ├── model/
│ │ │ ├── Order.java
│ │ │ ├── OrderItem.java
│ │ │ ├── OrderStatus.java
│ │ │ ├── Money.java
│ │ │ └── Discount.java
│ │ │
│ │ ├── rule/
│ │ │ ├── PricingRule.java
│ │ │ └── OrderInvariant.java
│ │ │
│ │ └── event/
│ │ ├── OrderPaidEvent.java
│ │ └── OrderExpiredEvent.java
│
│ ├── application/
│ │ ├── usecase/
│ │ │ ├── checkout/
│ │ │ │ ├── CheckoutOrderUseCase.java
│ │ │ │ ├── CheckoutOrderCommand.java
│ │ │ │ ├── CheckoutOrderResult.java
│ │ │ │ └── CheckoutOrderService.java
│ │ │ │
│ │ │ ├── retry/
│ │ │ │ ├── RetryPaymentUseCase.java
│ │ │ │ ├── RetryPaymentCommand.java
│ │ │ │ └── RetryPaymentService.java
│ │ │ │
│ │ │ └── expire/
│ │ │ ├── ExpireUnpaidOrdersUseCase.java
│ │ │ └── ExpireUnpaidOrdersService.java
│ │ │
│ │ └── port/
│ │ ├── out/
│ │ │ ├── OrderRepositoryPort.java
│ │ │ ├── PaymentPort.java
│ │ │ ├── InventoryPort.java
│ │ │ ├── FraudCheckPort.java
│ │ │ ├── EventPublisherPort.java
│ │ │ ├── IdempotencyPort.java
│ │ │ └── ClockPort.java
│ │ │
│ │ └── in/
│ │ └── (optional)
│
│ ├── adapter/
│ │ ├── in/
│ │ │ ├── web/
│ │ │ │ ├── OrderController.java
│ │ │ │ └── OrderRequestMapper.java
│ │ │ │
│ │ │ ├── messaging/
│ │ │ │ ├── CheckoutRetryConsumer.java
│ │ │ │ └── RetryMessageMapper.java
│ │ │ │
│ │ │ └── scheduler/
│ │ │ └── ExpireUnpaidOrdersJob.java
│ │ │
│ │ └── out/
│ │ ├── persistence/
│ │ │ ├── JpaOrderRepositoryAdapter.java
│ │ │ ├── JpaOrderEntity.java
│ │ │ └── SpringDataOrderRepository.java
│ │ │
│ │ ├── payment/
│ │ │ ├── StripePaymentAdapter.java
│ │ │ └── PaymentMapper.java
│ │ │
│ │ ├── inventory/
│ │ │ └── InventoryHttpAdapter.java
│ │ │
│ │ ├── fraud/
│ │ │ └── FraudApiAdapter.java
│ │ │
│ │ ├── messaging/
│ │ │ └── KafkaEventPublisherAdapter.java
│ │ │
│ │ └── idempotency/
│ │ └── RedisIdempotencyAdapter.java
│
│ └── config/
│ ├── UseCaseConfig.java
│ ├── AdapterConfig.java
│ └── ApplicationConfig.java
│
└── src/test/java/com/example/orders/
├── domain/
│ └── OrderTest.java
│
├── application/
│ ├── checkout/
│ │ └── CheckoutOrderServiceTest.java
│ └── retry/
│ └── RetryPaymentServiceTest.java
│
└── adapter/
├── in/
│ └── web/
│ └── OrderControllerTest.java
└── out/
└── persistence/
└── JpaOrderRepositoryAdapterTest.java
Stage 5: Identify Ports (Interfaces)
Goal: isolate the core from infrastructure.
What qualifies as a port? Anything that:
- Touches DB
- Touches network
- Touches time
- Sends messages
Input: sequence diagram, use case code.
Output: port interfaces.
Example:
interface PaymentPort {
PaymentResult charge(Money amount);
}
Ports emerge naturally. If you are forcing them, you started too early.
Stage 6: Implement Adapters (No Diagrams)
Goal: make the system work.
What you do:
- REST controllers
- Kafka consumers
- JPA repositories
- External API clients
Rule: adapters depend on the core, never the other way around.
No UML. If you feel lost here, go back to Stage 1 or 2.
Stage 7: Post-Implementation Diagrams (Optional)
Goal: onboarding and communication.
What to draw:
- One hexagon overview
- One key sequence diagram
High-level hexagonal view:
┌─────────────────────────────────────────────────────────────┐
│ INBOUND ADAPTERS │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ REST │ │ Kafka │ │Scheduler │ │
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ │
└────────┼──────────────┼──────────────┼──────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ APPLICATION CORE │
│ │
│ ┌──────────────────────┐ │
│ │ Use Cases │ │
│ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ OUTBOUND ADAPTERS │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Database │ │ Payment │ │Inventory │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
Never try to document everything.
Summary: The Design Flow Cheat Sheet
┌────────┬───────────────────────────────┬─────────────────────────────┐
│ STAGE │ WHAT TO DRAW │ PURPOSE │
├────────┼───────────────────────────────┼─────────────────────────────┤
│ 0 │ Nothing │ Clarify problem and intent │
│ 1 │ Use case diagram or list │ Make intent explicit │
│ 2 │ Sequence diagram │ Find boundaries and flow │
│ 3 │ Domain diagram (optional) │ Shared business language │
│ 4 │ Code structure (folders) │ Lock architecture │
│ 5 │ Interfaces (ports) │ Define dependencies │
│ 6 │ No diagrams │ Implement adapters │
│ 7 │ Overview diagrams │ Onboarding │
└────────┴───────────────────────────────┴─────────────────────────────┘
The Final Rule to Remember
If a new developer can understand what the system does by reading use case names, your architecture is working. If they must jump across controllers, services, and repositories to understand intent, it is not.
Key Takeaways
- Draw diagrams to make decisions, not to look professional.
- Start with use cases because they reveal system intent.
- Sequence diagrams help you find boundaries before coding.
- Domain models create shared language.
- Stop diagramming when code becomes clearer.
- Ports emerge naturally from use cases and workflows.
- Post-implementation diagrams are for onboarding, not design.
Clean architecture is not about following rigid rules. It is about making your system's intent obvious, your boundaries clear, and your code maintainable. Start simple. Draw only what helps you think. Let the architecture emerge from real use cases, not theoretical purity.
