APM

>Agent Skill

@zskulcsar/go-clean-architecture

skilldevelopment

Expert knowledge in Go clean architecture patterns and best practices

apm::install
$apm install @zskulcsar/go-clean-architecture
apm::skill.md
---
name: go-clean-architecture
description: Expert knowledge in Go clean architecture patterns and best practices
---

# Go Clean Architecture Skill

## Overview

Clean Architecture in Go emphasizes separation of concerns through distinct layers, with dependencies pointing inward toward the domain.

## Layer Structure

### Domain Layer (innermost)
**Location:** `internal/domain/`

**Contains:**
- Business entities (structs)
- Repository interfaces
- Domain logic and validation
- Business rules

**Rules:**
- NO external dependencies
- NO framework dependencies
- Pure business logic
- Defines contracts for outer layers

**Example:**
```go
// internal/domain/account.go
package domain

type Account struct {
    ID      string
    Name    string
    Type    AccountType
    Balance int // cents
}

type AccountRepository interface {
    Create(account *Account) error
    GetByID(id string) (*Account, error)
    Update(account *Account) error
    Delete(id string) error
}

// Domain validation
func (a *Account) Validate() error {
    if a.Name == "" {
        return ErrInvalidName
    }
    if !a.Type.IsValid() {
        return ErrInvalidType
    }
    return nil
}
```

### Application Layer (middle)
**Location:** `internal/application/`

**Contains:**
- Business logic services
- Use case orchestration
- Service interfaces
- Cross-cutting concerns

**Rules:**
- Depends ONLY on domain interfaces
- NO HTTP dependencies
- NO database dependencies
- Orchestrates domain entities

**Example:**
```go
// internal/application/account_service.go
package application

import "internal/domain"

type AccountService struct {
    repo domain.AccountRepository // Interface, not concrete type
}

func NewAccountService(repo domain.AccountRepository) *AccountService {
    return &AccountService{repo: repo}
}

func (s *AccountService) CreateAccount(account *domain.Account) error {
    if err := account.Validate(); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }

    if err := s.repo.Create(account); err != nil {
        return fmt.Errorf("failed to create account: %w", err)
    }

    return nil
}
```

### Infrastructure Layer (outermost)
**Location:** `internal/infrastructure/`

**Contains:**
- Repository implementations
- HTTP handlers
- Database logic
- External service integrations

**Rules:**
- Implements domain interfaces
- Can have external dependencies
- Handlers should be thin (parse → service → respond)
- Repositories only handle persistence

**Example:**
```go
// internal/infrastructure/repository/account_repository.go
package repository

import (
    "database/sql"
    "internal/domain"
)

type AccountRepository struct {
    db *sql.DB
}

func NewAccountRepository(db *sql.DB) *AccountRepository {
    return &AccountRepository{db: db}
}

func (r *AccountRepository) Create(account *domain.Account) error {
    query := `INSERT INTO accounts (id, name, type, balance) VALUES (?, ?, ?, ?)`
    _, err := r.db.Exec(query, account.ID, account.Name, account.Type, account.Balance)
    return err
}

// internal/infrastructure/http/handlers/account_handler.go
package handlers

type AccountHandler struct {
    service *application.AccountService
}

func (h *AccountHandler) CreateAccount(w http.ResponseWriter, r *http.Request) {
    // 1. Parse request
    var req CreateAccountRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "invalid request", http.StatusBadRequest)
        return
    }

    // 2. Call service
    account := req.ToDomain()
    if err := h.service.CreateAccount(account); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 3. Return response
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(account)
}
```

## Dependency Injection

Wire dependencies in main.go:

```go
// cmd/server/main.go
func main() {
    // Infrastructure
    db := setupDatabase()

    // Repositories (concrete implementations)
    accountRepo := repository.NewAccountRepository(db)

    // Services (injected with interfaces)
    accountService := application.NewAccountService(accountRepo)

    // Handlers (injected with services)
    accountHandler := handlers.NewAccountHandler(accountService)

    // Router
    router := setupRouter(accountHandler)

    http.ListenAndServe(":8080", router)
}
```

## Common Patterns

### Repository Pattern
```go
// Domain defines interface
type Repository interface {
    Create(entity *Entity) error
    GetByID(id string) (*Entity, error)
}

// Infrastructure implements
type SQLRepository struct {
    db *sql.DB
}

func (r *SQLRepository) Create(entity *Entity) error {
    // SQL implementation
}
```

### Service Pattern
```go
type Service struct {
    repo domain.Repository  // Depend on interface
}

func (s *Service) DoBusinessLogic(entity *domain.Entity) error {
    // Validate
    // Transform
    // Call repository
    return s.repo.Create(entity)
}
```

### Handler Pattern
```go
func (h *Handler) HandleRequest(w http.ResponseWriter, r *http.Request) {
    // Parse → Service → Respond
    req := parseRequest(r)
    result, err := h.service.Do(req)
    respond(w, result, err)
}
```

## Anti-Patterns to Avoid

### ❌ Domain with External Dependencies
```go
// BAD: Domain importing database
import "database/sql"

type Account struct {
    db *sql.DB  // ❌ Domain shouldn't know about database
}
```

### ❌ Service with HTTP/Database
```go
// BAD: Service with HTTP dependency
func (s *Service) Create(w http.ResponseWriter, r *http.Request) {
    // ❌ Service shouldn't handle HTTP
}

// BAD: Service with database dependency
func (s *Service) Create(db *sql.DB, entity *Entity) error {
    // ❌ Service should use repository interface
}
```

### ❌ Handler with Business Logic
```go
// BAD: Complex logic in handler
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
    // Parse
    // ❌ Complex validation
    // ❌ Calculations
    // ❌ Business rules
    // Direct database access
}

// GOOD: Thin handler
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
    req := parse(r)
    result := h.service.Create(req)  // Service has the logic
    respond(w, result)
}
```

### ❌ Repository with Business Logic
```go
// BAD: Business rules in repository
func (r *Repository) Create(account *Account) error {
    // ❌ Business validation in repository
    if account.Balance < 0 && account.Type != "credit" {
        return errors.New("invalid")
    }
    // Should only handle persistence
}
```

## Testing Strategy

### Domain Tests
```go
func TestAccount_Validate(t *testing.T) {
    // Test entity validation
    // No mocks needed
}
```

### Service Tests (Unit)
```go
func TestService_Create(t *testing.T) {
    mockRepo := &MockRepository{}  // Mock interface
    service := NewService(mockRepo)
    // Test business logic
}
```

### Repository Tests (Integration)
```go
func TestRepository_Create(t *testing.T) {
    db := setupTestDB()  // Real database
    repo := NewRepository(db)
    // Test persistence
}
```

### Handler Tests (E2E)
```go
func TestHandler_Create(t *testing.T) {
    mockService := &MockService{}
    handler := NewHandler(mockService)
    req := httptest.NewRequest("POST", "/", body)
    w := httptest.NewRecorder()
    handler.Create(w, req)
    // Test HTTP layer
}
```

## Benefits

**Testability**: Easy to mock dependencies
**Maintainability**: Clear separation of concerns
**Flexibility**: Easy to swap implementations
**Independence**: Domain logic independent of frameworks
**Scalability**: Easy to add features

## When to Apply

- Multi-layer applications
- Complex business logic
- Long-lived projects
- Team projects requiring clear boundaries
- Applications that may change databases/frameworks

## Quick Checklist

- [ ] Domain has no external dependencies
- [ ] Application uses interfaces, not concrete types
- [ ] Handlers are thin (parse → service → respond)
- [ ] Repositories only handle persistence
- [ ] Dependencies point inward
- [ ] Business logic in services, not handlers
- [ ] Each layer has clear responsibility