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.
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
Dependencies always point inward. Outer layers depend on inner layers. Inner layers never depend on outer layers.
Interface → Application → Domain Infrastructure → Domain
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/
| 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 |
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
Purpose
Define business contracts
No implementation
No framework or DB dependency
File location:
internal/domain/repository/add_point_repository.go
<code 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
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
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
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
Always inject interfaces, not concrete implementations.
usecase := PointAddUsecase{ repo: addPointRepositoryImpl, }
Unit Tests
Test usecases with mocked repositories
No DB required
Integration Tests
Real database
Test infrastructure layer only
Use structured errors
Log errors in:
Interface layer
Application layer
❌ Business logic in handlers
❌ SQL in usecases
❌ Infrastructure importing application layer
❌ Circular dependencies
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
By following Clean Architecture:
Dependencies always point inward
Layers are clearly separated
Business logic is framework-independent
Code is easier to:
Maintain
Test
Scale