Back to list
billybbuffum

go-clean-architecture

by billybbuffum

0🍴 0📅 Nov 2, 2025

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:

// 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:

// 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:

// 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:

// 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

// 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

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

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

// BAD: Domain importing database
import "database/sql"

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

❌ Service with HTTP/Database

// 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

// 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

// 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

func TestAccount_Validate(t *testing.T) {
    // Test entity validation
    // No mocks needed
}

Service Tests (Unit)

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

Repository Tests (Integration)

func TestRepository_Create(t *testing.T) {
    db := setupTestDB()  // Real database
    repo := NewRepository(db)
    // Test persistence
}

Handler Tests (E2E)

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

Score

Total Score

60/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon