Skip to content
Back to skills

Go Clean Architecture

ASecurity

Expert knowledge in Go clean architecture patterns and best practices

  • 666 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 8, 2026
developmentgosqltestingdatabase

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill go-clean-architecture --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Go Clean Architecture?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Go Clean Architecture
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-go-clean-architecture/badge)](https://www.skillsdirectory.com/skills/majiayu000-go-clean-architecture)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: go-clean-architecture
description: Expert knowledge in Go clean architecture patterns and best practices
triggers: [clean architecture, layer separation, domain driven, architecture, refactor]
---

# 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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.