User Tools

Site Tools


go:architecture:clean_arch

This is an old revision of the document!


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)

<pre> 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/

</pre>

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

go/architecture/clean_arch.1769736341.txt.gz · Last modified: by phong2018