====== 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