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