go:architecture:clean_arch
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| go:architecture:clean_arch [2026/01/30 01:13] – [Overall Structure] phong2018 | go:architecture:clean_arch [2026/01/30 01:33] (current) – [2. Application Layer — Usecase Orchestration] phong2018 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== A Guide to Using Clean Architecture | + | ====== A Guide to Using Clean Architecture in Go ====== |
| ===== I. Introduction to Clean Architecture ===== | ===== I. Introduction to Clean Architecture ===== | ||
| - | Clean Architecture is a software architecture proposed by Robert C. Martin. | + | Clean Architecture is a software architecture proposed by Robert C. Martin |
| - | Its goal is to clearly separate layers so the system is easier to maintain, extend, and test. | + | |
| - | The pascalia-spec project applies Clean Architecture with 4 main layers: | + | Its main goals are: |
| - | Domain Layer: Core business logic (entities, interfaces, business rules) | + | Clear separation of concerns |
| - | Application / Usecase Layer: Application logic (orchestration, | + | Reduced coupling between layers |
| - | Presentation Layer: User interface communication (handlers, APIs) | + | Easier testing, maintenance, |
| - | Infrastructure Layer: External technical concerns | + | This project follows Clean Architecture only. |
| + | No Domain-Driven Design | ||
| - | Key Principle: | + | ===== II. Core Principles ===== |
| - | Dependencies always point inward (outer layers depend on inner layers, never the opposite). | + | ==== 1. Layered Separation ==== |
| - | ===== II. Folder Structure and Dependencies ===== | + | The system is divided into four logical layers: |
| - | ==== Overall Structure ==== | + | Domain Layer |
| + | Core business rules and contracts | ||
| - | internal/ | + | Application / Usecase Layer |
| - | ├── burton/ | + | Orchestration of business logic |
| - | │ | + | |
| - | │ | + | |
| - | │ | + | |
| - | │ | + | |
| - | │ | + | |
| - | | + | |
| - | │ | + | |
| - | │ | + | |
| - | ├── constant/ | + | |
| - | ├── infrastructure/ | + | |
| - | └── validator/ | + | |
| - | ==== Roles and Dependencies ==== | + | Interface (Presentation) Layer |
| + | gRPC / HTTP handlers | ||
| - | ^ Layer ^ Role ^ Example ^ Depends On ^ | + | Infrastructure |
| - | | Domain | Core business logic | IAddPointRepository, | + | Database and external service access |
| - | | Application / Usecase | Application logic | PointAddUsecase | Domain | | + | |
| - | | Presentation | User communication | SSOHandler | Application | | + | |
| - | | Infrastructure | External technical concerns | pointadd_repository.go | Domain (interfaces) | | + | |
| - | Dependency | + | ==== 2. Dependency |
| - | < | + | |
| - | Infrastructure → Domain | + | Dependencies always point inward. |
| + | Outer layers depend on inner layers. | ||
| + | Inner layers never depend on outer layers. | ||
| + | |||
| + | < | ||
| + | |||
| + | ===== III. Project Folder Structure ===== | ||
| + | |||
| + | ==== Overall Structure (Clean Architecture Only) ==== | ||
| + | |||
| + | <code text> | ||
| + | internal/ | ||
| + | ├── domain/ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | ├── application/ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | ├── interface/ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | ├── infrastructure/ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | └── shared/ | ||
| + | ├── constant/ | ||
| + | ├── validator/ | ||
| + | ├── logger/ | ||
| + | └── database/ | ||
| </ | </ | ||
| - | ===== III. Processing Flow (Request → Response) ===== | ||
| - | < | + | ===== IV. Layer Responsibilities ===== |
| - | ==== Detailed Examples from Source Code ==== | + | ^ 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 | | ||
| - | === 1. Domain Layer: Define | + | ===== V. Processing Flow (Request → Response) ===== |
| + | |||
| + | <code text> | ||
| + | Client (gRPC / HTTP Request) | ||
| + | ↓ | ||
| + | Interface Layer (Handler) | ||
| + | - Receive request | ||
| + | - Validate input | ||
| + | ↓ | ||
| + | Application Layer (Usecase) | ||
| + | - Orchestrate business logic | ||
| + | ↓ | ||
| + | Domain Layer (Repository Interface) | ||
| + | - Define | ||
| + | ↓ | ||
| + | Infrastructure Layer (Implementation) | ||
| + | - Access DB / External API | ||
| + | ↓ | ||
| + | Database / External Service | ||
| + | </ | ||
| + | ===== VI. Detailed Layer Examples ===== | ||
| - | <code go> // internal/ | + | ==== 1. Domain Layer — Define Interfaces ==== |
| - | Purpose: | + | Purpose |
| - | Define | + | Define business |
| No implementation | No implementation | ||
| - | No dependency on DB or frameworks | + | No framework |
| - | === 2. Application / Usecase Layer: Orchestration Logic === | + | File location: |
| + | <code text> | ||
| + | internal/ | ||
| - | <code go> | + | <code go> type AddPointRepository interface |
| + | SaveOrders( | ||
| + | ctx context.Context, | ||
| + | orders []ValidOrder, | ||
| + | ) error | ||
| - | func (u *PointAddUsecase) | + | |
| - | orders, err := u.repo.FetchProcessingOrders(ctx, | + | } |
| + | </ | ||
| + | |||
| + | ✔ 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/ | ||
| + | |||
| + | </ | ||
| + | |||
| + | <code go> | ||
| + | type PointAddUsecase struct { repo domain.AddPointRepository } | ||
| + | |||
| + | func (u *PointAddUsecase) | ||
| + | orders, err := u.repo.FetchProcessingOrders(ctx, | ||
| if err != nil { | if err != nil { | ||
| return err | return err | ||
| } | } | ||
| - | // Calculate points: | + | |
| + | // Business calculation: | ||
| // product_total - adjusted_merchandize_total_tax | // product_total - adjusted_merchandize_total_tax | ||
| - | // Generate CSV, upload to Yappli | + | |
| return nil | return nil | ||
| + | |||
| + | |||
| } | } | ||
| </ | </ | ||
| - | Purpose: | + | ✔ No DB logic |
| + | ✔ No framework dependency | ||
| + | ✔ Mockable for unit tests | ||
| - | Orchestrate business logic | + | ==== 3. Interface Layer — gRPC Handler ==== |
| - | Use interfaces, not implementations | + | Purpose |
| - | Easy to unit test using mocks | + | Handle incoming requests |
| - | === 3. Presentation Layer: gRPC Handler === | + | Validate input |
| - | < | + | Delegate logic to usecase |
| + | |||
| + | File location: | ||
| + | < | ||
| + | internal/interface/ | ||
| + | </ | ||
| + | |||
| + | <code go> | ||
| func (h *SSOHandler) Authenticate( | func (h *SSOHandler) Authenticate( | ||
| Line 102: | Line 193: | ||
| ) (*pb.AuthResponse, | ) (*pb.AuthResponse, | ||
| - | // Validate input | ||
| if err := validator.ValidateSSO(req); | if err := validator.ValidateSSO(req); | ||
| return nil, err | return nil, err | ||
| } | } | ||
| - | // Call usecase | + | return h.usecase.Authenticate(ctx, |
| - | return h.SSOService.Authenticate(ctx, | + | |
| Line 114: | Line 203: | ||
| </ | </ | ||
| - | Purpose: | + | ✔ Thin layer |
| + | ✔ No business logic | ||
| + | ✔ No DB access | ||
| - | Receive requests | + | ==== 4. Infrastructure Layer — Repository Implementation ==== |
| - | Validate input | + | Purpose |
| - | Delegate logic to usecase | + | Implement domain interfaces |
| - | No business rules here | + | Handle SQL and external APIs |
| - | === 4. Infrastructure Layer: Implementation === | + | Replaceable without affecting core logic |
| + | |||
| + | File location: | ||
| + | < | ||
| + | internal/ | ||
| + | </ | ||
| - | <code go> | + | <code go> type AddPointRepositoryImpl |
| - | func (r *AddPointRepository) FetchProcessingOrders( | + | func (r *AddPointRepositoryImpl) FetchProcessingOrders( |
| ctx context.Context, | ctx context.Context, | ||
| limit, offset int, | limit, offset int, | ||
| - | ) ([]ProcessingOrders, error) { | + | ) ([]ProcessingOrder, error) { |
| query := ` | query := ` | ||
| SELECT ... | SELECT ... | ||
| - | FROM customers c | + | FROM orders |
| - | INNER JOIN orders | + | |
| LIMIT ? OFFSET ? | LIMIT ? OFFSET ? | ||
| ` | ` | ||
| - | return | + | var orders []ProcessingOrder |
| + | err := r.db.SelectContext(ctx, | ||
| + | return orders, err | ||
| Line 145: | Line 242: | ||
| </ | </ | ||
| - | Purpose: | + | ✔ Depends only on Domain |
| + | ✔ Infrastructure-specific logic only | ||
| - | Implement domain interfaces | + | ===== VII. Best Practices ===== |
| - | Handle DB / external APIs | + | ==== 1. Dependency Injection ==== |
| - | Depends on Domain, not Usecase or Presentation | + | Always inject interfaces, not concrete implementations. |
| - | ===== IV. Best Practices When Coding ===== | + | <code go> usecase := PointAddUsecase{ repo: addPointRepositoryImpl, |
| - | ==== 1. Dependencies Point Inward ==== | + | ==== 2. Testing Strategy ==== |
| - | + | ||
| - | Domain → depends on nothing | + | |
| - | + | ||
| - | Application → depends on Domain | + | |
| - | + | ||
| - | Presentation → depends on Application | + | |
| - | + | ||
| - | Infrastructure → depends on Domain (via interfaces) | + | |
| - | + | ||
| - | ==== 2. Use Interfaces and Dependency Injection ==== | + | |
| - | + | ||
| - | Inject interfaces, not concrete structs | + | |
| - | Example: usecase.ISSOUsecase | + | |
| - | + | ||
| - | Easy to mock for testing: | + | |
| - | <code go> | + | |
| - | mockRepo := & | + | |
| - | </ | + | |
| - | + | ||
| - | ==== 3. Separate Concerns ==== | + | |
| - | + | ||
| - | Constants → internal/ | + | |
| - | Example: DefaultChunk = 200 | + | |
| - | + | ||
| - | Validation → internal/ | + | |
| - | + | ||
| - | Transactions / DB setup → internal/ | + | |
| - | + | ||
| - | ==== 4. Testing Strategy ==== | + | |
| Unit Tests | Unit Tests | ||
| - | Test each layer independently | + | Test usecases with mocked repositories |
| - | Mock dependencies | + | No DB required |
| Integration Tests | Integration Tests | ||
| - | End-to-end with real DB | + | Real database |
| - | Example: | + | Test infrastructure layer only |
| - | Test usecase with a mock repository | + | ==== 3. Error Handling ==== |
| - | ==== 5. Error Handling ==== | + | Use structured errors |
| - | Use fastmedia/ | + | Log errors |
| - | Log with slog in: | + | Interface layer |
| - | Presentation | + | Application |
| - | Usecase layer | + | ==== 4. What NOT to Do ==== |
| - | ===== V. Example: Implementing a New Feature ===== | + | ❌ Business logic in handlers |
| - | Feature: Register Customer | + | ❌ SQL in usecases |
| - | Steps: | + | ❌ Infrastructure importing application layer |
| - | Domain | + | ❌ Circular dependencies |
| - | Create ICustomerRepository interface | + | ===== VIII. Example: Adding a New Feature ===== |
| - | Application | + | Feature: Register Customer |
| - | Create RegisterCustomerUsecase | + | Steps |
| - | Presentation | + | Domain: create CustomerRepository interface |
| - | Create gRPC handler RegisterHandler | + | Application: |
| - | Infrastructure | + | Interface: add gRPC handler |
| - | Implement CustomerRepository | + | Infrastructure: |
| - | Flow: | + | Flow |
| - | < | + | |
| - | Handler → Usecase → Repository Interface → Repository Implementation → DB | + | < |
| - | </ | + | |
| - | ===== VI. Conclusion ===== | + | ===== IX. Conclusion ===== |
| - | By following Clean Architecture | + | By following Clean Architecture: |
| Dependencies always point inward | Dependencies always point inward | ||
| - | Interfaces decouple layers | + | Layers are clearly separated |
| - | Each bounded context (e.g. burton) | + | Business logic is framework-independent |
| - | Code becomes | + | Code is easier to: |
| Maintain | Maintain | ||
go/architecture/clean_arch.1769735619.txt.gz · Last modified: by phong2018
