This is an old revision of the document!
Table of Contents
A Guide to Using Clean Architecture in Go
I. Introduction to Clean Architecture
Clean Architecture is a software architecture proposed by Robert C. Martin (Uncle Bob).
Its main goals are:
Clear separation of concerns
Reduced coupling between layers
Easier testing, maintenance, and evolution
This project follows Clean Architecture only. No Domain-Driven Design (DDD) or Bounded Context concepts are required.
II. Core Principles
1. Layered Separation
The system is divided into four logical layers:
Domain Layer Core business rules and contracts
Application / Usecase Layer Orchestration of business logic
Interface (Presentation) Layer gRPC / HTTP handlers
Infrastructure Layer Database and external service access
2. Dependency Rule (Most Important Rule)
Dependencies always point inward. Outer layers depend on inner layers. Inner layers never depend on outer layers.
Interface → Application → Domain Infrastructure → Domain
III. Project Folder Structure
Overall Structure (Clean Architecture Only)
internal/
├── domain/ # Domain Layer (pure business logic)
│ ├── model/ # Core business entities
│ ├── repository/ # Repository interfaces (contracts)
│ └── service/ # Domain services (optional)
│
├── application/ # Application Layer
│ ├── usecase/ # Use cases (business orchestration)
│ └── dto/ # Input / output data structures
│
├── interface/ # Interface (Presentation) Layer
│ ├── grpc/
│ │ └── handler/ # gRPC handlers
│ └── http/ # HTTP handlers (optional)
│
├── infrastructure/ # Infrastructure Layer
│ ├── repository/ # Repository implementations
│ ├── persistence/ # Database setup & transactions
│ └── client/ # External API / service clients
│
└── shared/ # Cross-cutting concerns
├── constant/
├── validator/
├── logger/
└── database/
IV. Layer Responsibilities
| Layer | Responsibility | Depends On |
|---|---|---|
| Domain | Business rules and contracts | Nothing |
| Application | Usecase orchestration | Domain |
| Interface | Request / response handling | Application |
| Infrastructure | DB and external APIs | Domain |
V. Processing Flow (Request → Response)
Client (gRPC / HTTP Request)
↓
Interface Layer (Handler)
- Receive request
- Validate input
↓ Application Layer (Usecase)
- Orchestrate business logic
↓ Domain Layer (Repository Interface)
- Define contracts
↓ Infrastructure Layer (Implementation)
- Access DB / External API
↓ Database / External Service
VI. Detailed Layer Examples
1. Domain Layer — Define Interfaces
Purpose
Define business contracts
No implementation
No framework or DB dependency
File location:
internal/domain/repository/add_point_repository.go
type AddPointRepository interface { FetchProcessingOrders( ctx context.Context, limit, offset int, ) ([]ProcessingOrder, error) SaveOrders( ctx context.Context, orders []ValidOrder, ) error }
✔ No SQL ✔ No gRPC ✔ No infrastructure dependency
2. Application Layer — Usecase Orchestration
Purpose
Coordinate business steps
Use domain interfaces only
Easy to unit test
File location:
internal/application/usecase/point_add_usecase.go
type PointAddUsecase struct { repo domain.AddPointRepository } func (u *PointAddUsecase) Execute(ctx context.Context) error { orders, err := u.repo.FetchProcessingOrders(ctx, 200, 0) if err != nil { return err } // Business calculation: // product_total - adjusted_merchandize_total_tax return nil }
✔ No DB logic ✔ No framework dependency ✔ Mockable for unit tests
3. Interface Layer — gRPC Handler
Purpose
Handle incoming requests
Validate input
Delegate logic to usecase
File location:
internal/interface/grpc/handler/sso_handler.go
type SSOHandler struct { usecase application.SSOUsecase } func (h *SSOHandler) Authenticate( ctx context.Context, req *pb.AuthRequest, ) (*pb.AuthResponse, error) { if err := validator.ValidateSSO(req); err != nil { return nil, err } return h.usecase.Authenticate(ctx, req) }
✔ Thin layer ✔ No business logic ✔ No DB access
4. Infrastructure Layer — Repository Implementation
Purpose
Implement domain interfaces
Handle SQL and external APIs
Replaceable without affecting core logic
File location:
internal/infrastructure/repository/add_point_repository.go
type AddPointRepositoryImpl struct { db *sqlx.DB } func (r *AddPointRepositoryImpl) FetchProcessingOrders( ctx context.Context, limit, offset int, ) ([]ProcessingOrder, error) { query := ` SELECT ... FROM orders LIMIT ? OFFSET ? ` var orders []ProcessingOrder err := r.db.SelectContext(ctx, &orders, query, limit, offset) return orders, err }
✔ Depends only on Domain ✔ Infrastructure-specific logic only
VII. Best Practices
1. Dependency Injection
Always inject interfaces, not concrete implementations.
usecase := PointAddUsecase{ repo: addPointRepositoryImpl, }
2. Testing Strategy
Unit Tests
Test usecases with mocked repositories
No DB required
Integration Tests
Real database
Test infrastructure layer only
3. Error Handling
Use structured errors
Log errors in:
Interface layer
Application layer
4. What NOT to Do
❌ Business logic in handlers
❌ SQL in usecases
❌ Infrastructure importing application layer
❌ Circular dependencies
VIII. Example: Adding a New Feature
Feature: Register Customer
Steps
Domain: create CustomerRepository interface
Application: create RegisterCustomerUsecase
Interface: add gRPC handler
Infrastructure: implement repository using DB
Flow
Handler → Usecase → Repository Interface → Repository Implementation → Database
IX. Conclusion
By following Clean Architecture:
Dependencies always point inward
Layers are clearly separated
Business logic is framework-independent
Code is easier to:
Maintain
Test
Scale
