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
Bottom Line
Clean Architecture isn't about rigid rules, but about managing dependencies to make code more testable and maintainable. For evolving projects, a pragmatic approach might be starting with simpler patterns and refactoring toward clean architecture as requirements stabilize.