# Clean Architecture Refactoring Plan ## Current State Analysis ### API Handlers (`internal/api/handlers/management/`) The current handlers have several issues: 1. **Tight coupling**: Handlers directly manipulate config, auth, and logging 2. **Mixed concerns**: Business logic mixed with HTTP transport (Gin) 3. **Inconsistent error handling**: Uses `gin.H{"error": ...}` directly 4. **No clear separation**: Domain logic embedded in HTTP handlers 5. **Large files**: `auth_files.go` is ~2200 lines with multiple responsibilities ### Auth Module (`internal/auth/`) 1. **Minimal interface**: Only `TokenStorage` interface defined 2. **Provider-specific logic**: Each provider has its own auth implementation 3. **No unified error types**: Each provider handles errors differently ### Logging Module (`internal/logging/`) 1. **Good interfaces**: `RequestLogger` and `StreamingLogWriter` already defined 2. **Mixed transport concerns**: Some Gin-specific code in logging 3. **Correlation IDs**: Basic request ID support exists but could be enhanced ## Proposed Clean Architecture ### Layer Structure ``` ┌─────────────────────────────────────────────────────────────┐ │ Transport Layer │ │ (Gin handlers, middleware, routing) │ ├─────────────────────────────────────────────────────────────┤ │ Application Layer │ │ (Use cases, DTOs, request/response mapping) │ ├─────────────────────────────────────────────────────────────┤ │ Domain Layer │ │ (Domain services, entities, business rules, interfaces) │ ├─────────────────────────────────────────────────────────────┤ │ Infrastructure Layer │ │ (Config persistence, auth storage, file system, HTTP) │ └─────────────────────────────────────────────────────────────┘ ``` ### New Directory Structure ``` internal/ ├── api/ │ ├── handlers/ # HTTP transport (thin layer) │ │ └── management/ │ │ ├── handler.go │ │ ├── config_handler.go │ │ ├── auth_handler.go │ │ └── logs_handler.go │ └── middleware/ # HTTP middleware │ ├── auth.go │ ├── logging.go │ └── error_handler.go ├── application/ # Application layer (NEW) │ ├── dto/ # Data transfer objects │ │ ├── config_dto.go │ │ ├── auth_dto.go │ │ └── response_dto.go │ ├── mapper/ # DTO <-> Domain mapping │ │ ├── config_mapper.go │ │ └── auth_mapper.go │ └── usecase/ # Use cases │ ├── config_usecase.go │ ├── auth_usecase.go │ └── logs_usecase.go ├── domain/ # Domain layer (NEW) │ ├── entity/ # Domain entities │ │ ├── config.go │ │ ├── auth.go │ │ └── log_entry.go │ ├── service/ # Domain services (business logic) │ │ ├── config_service.go │ │ ├── auth_service.go │ │ └── log_service.go │ ├── repository/ # Repository interfaces │ │ ├── config_repository.go │ │ └── auth_repository.go │ └── error/ # Domain errors │ └── errors.go ├── infrastructure/ # Infrastructure layer (NEW) │ ├── persistence/ # Repository implementations │ │ ├── config_repository.go │ │ └── auth_repository.go │ ├── auth/ # Auth provider implementations │ │ ├── provider.go │ │ └── factory.go │ └── logging/ # Logging infrastructure │ ├── file_logger.go │ └── structured_logger.go └── interfaces/ # Existing - shared interfaces └── types.go ``` ## Key Components ### 1. Domain Errors (`internal/domain/error/errors.go`) Standardized error types for the entire application: ```go package error type DomainError struct { Code string Message string Cause error } func (e *DomainError) Error() string { ... } // Specific error types var ( ErrNotFound = &DomainError{Code: "NOT_FOUND", ...} ErrUnauthorized = &DomainError{Code: "UNAUTHORIZED", ...} ErrInvalidInput = &DomainError{Code: "INVALID_INPUT", ...} ErrInternal = &DomainError{Code: "INTERNAL_ERROR", ...} ) ``` ### 2. Domain Services Domain services contain pure business logic, no HTTP concerns: ```go // internal/domain/service/config_service.go type ConfigService interface { GetConfig(ctx context.Context) (*entity.Config, error) UpdateConfig(ctx context.Context, cfg *entity.Config) error UpdateField(ctx context.Context, field string, value any) error ValidateConfig(ctx context.Context, cfg *entity.Config) error } // internal/domain/service/auth_service.go type AuthService interface { ListAuthFiles(ctx context.Context) ([]*entity.AuthFile, error) UploadAuthFile(ctx context.Context, file *entity.AuthFile) error DeleteAuthFile(ctx context.Context, id string) error RefreshToken(ctx context.Context, id string) (*entity.AuthToken, error) } ``` ### 3. Repository Interfaces ```go // internal/domain/repository/config_repository.go type ConfigRepository interface { Load(ctx context.Context) (*entity.Config, error) Save(ctx context.Context, cfg *entity.Config) error Validate(ctx context.Context, cfg *entity.Config) error } // internal/domain/repository/auth_repository.go type AuthRepository interface { List(ctx context.Context) ([]*entity.AuthFile, error) GetByID(ctx context.Context, id string) (*entity.AuthFile, error) Save(ctx context.Context, file *entity.AuthFile) error Delete(ctx context.Context, id string) error } ``` ### 4. Application Use Cases Use cases orchestrate domain services for specific operations: ```go // internal/application/usecase/config_usecase.go type ConfigUseCase struct { configService domain.ConfigService logger logging.Logger } func (uc *ConfigUseCase) GetConfig(ctx context.Context) (*dto.ConfigResponse, error) { cfg, err := uc.configService.GetConfig(ctx) if err != nil { uc.logger.Error("failed to get config", err) return nil, err } return mapper.ToConfigResponse(cfg), nil } ``` ### 5. Structured Logging with Correlation IDs ```go // internal/infrastructure/logging/structured_logger.go type StructuredLogger struct { logger *logrus.Logger } type LogEntry struct { Timestamp time.Time Level string Message string CorrelationID string Service string Operation string Fields map[string]interface{} } func (l *StructuredLogger) WithCorrelationID(ctx context.Context) *logrus.Entry { correlationID := logging.GetRequestID(ctx) return l.logger.WithField("correlation_id", correlationID) } ``` ### 6. HTTP Handlers (Thin Layer) Handlers only deal with HTTP concerns: ```go // internal/api/handlers/management/config_handler.go type ConfigHandler struct { useCase *usecase.ConfigUseCase } func (h *ConfigHandler) GetConfig(c *gin.Context) { ctx := c.Request.Context() response, err := h.useCase.GetConfig(ctx) if err != nil { h.handleError(c, err) return } c.JSON(http.StatusOK, response) } func (h *ConfigHandler) handleError(c *gin.Context, err error) { // Map domain errors to HTTP responses var domainErr *domainerror.DomainError if errors.As(err, &domainErr) { status := h.mapErrorCodeToStatus(domainErr.Code) c.JSON(status, gin.H{ "error": domainErr.Code, "message": domainErr.Message, }) return } c.JSON(http.StatusInternalServerError, gin.H{ "error": "INTERNAL_ERROR", "message": "An unexpected error occurred", }) } ``` ## Migration Strategy ### Phase 1: Foundation 1. Create domain error types 2. Define repository interfaces 3. Create domain service interfaces 4. Set up structured logging infrastructure ### Phase 2: Extract Domain Logic 1. Move business logic from handlers to domain services 2. Implement repository interfaces 3. Create use cases 4. Add comprehensive unit tests ### Phase 3: Refactor Handlers 1. Make handlers thin - delegate to use cases 2. Standardize error handling 3. Add correlation ID middleware ### Phase 4: Cleanup 1. Remove old code 2. Verify backward compatibility 3. Update documentation ## Backward Compatibility - All existing API endpoints remain unchanged - Response formats preserved - Configuration file format unchanged - Auth file format unchanged ## Testing Strategy 1. **Unit tests**: Domain services with mocked repositories 2. **Integration tests**: Use cases with real repositories 3. **E2E tests**: Full HTTP request/response cycle 4. **Mock implementations**: For all external dependencies ## Benefits 1. **Testability**: Domain logic can be tested without HTTP layer 2. **Maintainability**: Clear separation of concerns 3. **Flexibility**: Easy to swap implementations (e.g., different storage) 4. **Observability**: Structured logging with correlation IDs 5. **Scalability**: Clear boundaries for future extensions