Hey folks.
Read this chapter in Spanish: ES
This series tells the story of how we built todo-cli-rs from CodeCrafters Project #1 (Rust Projects). If you want to follow code while reading, here is the repository:
The challenge looked simple (until it wasn’t) #
The initial scope was classic:
addlistdonedelete- local persistence
At this point, the quick path is obvious: throw everything into main.rs, parse args, read/write JSON, print output, done.
That works until the first meaningful change request lands.
The decision that changed everything #
Before implementing use cases, we set boundaries:
- Hexagonal Architecture to isolate business rules from infrastructure.
- Screaming Architecture so the folder structure says “tasks”, not “misc utils”.
References:
Why this instead of a flat design #
Flat approach:
- CLI parses,
- validates,
- persists,
- and formats output.
It is fast to start, expensive to evolve.
Real example: add a second persistence backend later.
- In a flat design, CLI, business logic, and tests are tangled.
- In a hexagonal design, adapters change while domain and use cases stay stable.
The first contract we made explicit #
We defined the command contract early:
add <title>list [--status <all|todo|done>]done <id>todo <id>delete <id>--output table|json
Implemented in:
This is not just UX polish. Input contract clarity prevents ambiguity across the whole system.
Layer map and responsibilities #
domain: pure business rules.application/use_cases: orchestration.ports: contracts so application does not know infrastructure details.adapters/cli: terminal input/output.adapters/persistence: concrete repositories.
Module entry:
Where this paid off immediately #
When we introduced dual output (table/json) and file persistence, use cases stayed untouched. No serde_json, no path management, no printing logic leaked into core flow.
That is exactly what good boundaries are supposed to do.
Closing #
Part 1 is about rules of the game: we did not optimize for “it runs once”, we optimized for “it survives change”.
In the next chapter, we go into the core: immutable domain transitions and typed error boundaries by layer.