API / old /refactoring-architecture.md
sshinmen's picture
Update HF Spaces deployment with latest changes
2e6b65c
|
Raw
History Blame Contribute Delete
10.2 kB
# 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