Clean Architecture Notes
Practical notes on Clean Architecture based on implementing it in a Go blog project.
Project Reference
This analysis is based on: [https://github.com/kangko05/blog-go]
Core Concepts
Layer Structure
- Entities (innermost): Core business logic and rules
- Use Cases/Application: Application-specific business rules
- Interface Adapters: Controllers, presenters, gateways
- Frameworks & Drivers (outermost): Database, web framework, external services
Dependency Rule
- Dependencies point inward only (outer layers depend on inner layers)
- Inner layers know nothing about outer layers
- Source code dependencies must point toward higher-level policies
Dependency Inversion
- High-level modules shouldn't depend on low-level modules
- Both should depend on abstractions (interfaces)
- Abstractions shouldn't depend on details; details depend on abstractions
Implementation in Go Blog Project
Repository Pattern
// Domain layer - interface definition
type Repository interface {
SavePost(post *Post) error
GetPost(id int) (*Post, error)
// ...
}
// Infrastructure layer - implementation
type PostRepository struct {
db *Database
}
func (pr *PostRepository) SavePost(post *Post) error {
// Database-specific implementation
}
Service Layer
// Application layer
type Service struct {
repo Repository // Depends on interface, not implementation
}
func NewService(repo Repository) *Service {
if repo == nil {
// Fallback to in-memory implementation
repo = newMemoryRepository()
}
return &Service{repo: repo}
}
Practical Benefits
Testability
- Can swap real database with in-memory implementation for tests
- No external dependencies needed for unit testing business logic
- Each layer can be tested independently
Flexibility
- Easy to change database (SQLite → PostgreSQL)
- Can add new interfaces (CLI, web, gRPC) without changing business logic
- Infrastructure changes don't affect core functionality
Maintainability
- Clear separation of concerns
- Business logic isolated from external dependencies
- Changes in one layer don't cascade to others
Limitations & Trade-offs
Structural Rigidity
- Changes in core entities often cascade upward through all layers
- Common utilities and shared modules don't fit neatly into layer boundaries
- Perfect entity design is nearly impossible in practice
Entity Layer Challenges
- Requires near-perfect initial design to avoid widespread changes
- Real-world business logic is messier than theoretical examples
- Shared concerns (logging, validation, etc.) create dependency dilemmas
When Clean Architecture May Not Be Worth It
- Small projects with frequent structural changes
- Prototypes or proof-of-concepts
- Teams unfamiliar with the patterns (learning curve overhead)
- Projects with unclear or evolving business requirements
Key Takeaways
When It Works Best
- Business rules are well-understood and stable
- Team is experienced with the patterns
- Long-term maintainability is prioritized over short-term velocity
- Even small projects benefit from clear structure
Practical Reality
- Repository pattern is extremely valuable for testability
- Dependency injection doesn't have to be complex
- Interface segregation helps keep things focused
- Start simple, refactor toward clean architecture as requirements stabilize