--- name: go-backend-dev description: "Backend Agent uses this skill to implement Golang backend. Based on implementation plan and API spec, use Domain-Driven + go-zero style architecture and TDD process to produce production-ready code. Trigger: After Task Breakdown complete (Stage 9)." --- # /go-backend-dev — Golang Backend Implementation Backend Agent uses this skill to implement Golang backend. ## Responsibilities 1. Establish project structure based on implementation plan (Domain-Driven + go-zero style) 2. Implement features using TDD process (Red-Green-Refactor) 3. Deliver incrementally by vertical slices (end-to-end, not layer-by-layer) 4. Implement Domain / Usecase / Logic / Repository layers 5. Write unit tests and integration tests ## Input - Implementation plan (`./plans/{feature}.md`) - API spec (`docs/api/{date}-{feature}.yaml`) - DB Schema (`docs/db/{date}-{feature}.sql`) ## Output - Golang code structure - Test code (unit tests >= 80%, business logic >= 90%) - Protobuf definitions (if gRPC needed) ## Flow ``` Read implementation plan + API spec + DB Schema ↓ Identify vertical slices (each slice = end-to-end feature) ↓ Execute TDD loop for each slice: ├── RED: Write test → Test fails ├── GREEN: Write minimal code → Test passes └── REFACTOR: Refactor → Test still passes ↓ Build order within slice: domain (entity/value object/interface) → pkg/domain/usecase (interface) → pkg/domain/repository (interface) → pkg/usecase (implementation) → pkg/mock (mock) → internal/logic (handler logic) → pkg/repository (infrastructure implementation) ↓ All slices complete → Run integration tests ↓ Confirm deliverables checklist ``` ### Step Details **1. Read Input** Read three documents simultaneously: - Implementation plan: Understand vertical slice breakdown and priority - API spec: Understand endpoints, request/response structures - DB Schema: Understand table structures and relationships **2. Identify Vertical Slices** Not horizontal slicing (layer by layer), but vertical slicing (end-to-end): ``` ✅ Correct way (vertical): Slice 1: User registration (domain.entity + domain.usecase interface + usecase implementation + logic + repository) Slice 2: User login (same as above) Slice 3: User list (same as above) ❌ Wrong way (horizontal): Stage 1: All domain entities Stage 2: All usecases Stage 3: All logic handlers ``` **3. TDD Loop (Each Slice)** For each slice, follow Red-Green-Refactor: ``` RED: Write a test → Test fails GREEN: Write minimal code to make test pass → Test passes REFACTOR: Refactor code → Test still passes ``` Build order within slice (inside-out): 1. `pkg/domain/entity/` — Define Entity and Value Object 2. `pkg/domain/member/` — Define value objects and enums (with tests) 3. `pkg/domain/usecase/` — Define Use Case interface 4. `pkg/domain/repository/` — Define Repository interface 5. `pkg/usecase/` — Implement business logic (write tests first) 6. `pkg/mock/` — Generate mocks 7. `internal/logic/` — Handler logic 8. `pkg/repository/` — Infrastructure implementation (with DB tests) **4. Testing** After each slice completes, ensure: - Unit tests pass (`pkg/usecase/*_test.go`) - Value object tests pass (`pkg/domain/member/*_test.go`) - Repository tests pass (`pkg/repository/*_test.go`) - Integration tests pass (critical paths) - Test coverage meets requirements **5. Completion Verification** Finally confirm all deliverables are complete. ## Project Structure ``` project-root/ ├── build/ │ └── Dockerfile # Build image │ ├── etc/ │ └── {service}.example.yaml # Example config file │ ├── generate/ │ └── protobuf/ │ └── {service}.proto # Protobuf definitions (if gRPC) │ ├── internal/ # Application layer (not exposed externally) │ ├── config/ │ │ └── config.go # Application config │ ├── logic/ │ │ └── {module}/ │ │ ├── create_{entity}_logic.go # One logic file per use case │ │ ├── get_{entity}_logic.go │ │ ├── update_{entity}_logic.go │ │ └── ... │ ├── server/ │ │ └── {module}/ │ │ └── {module}_server.go # Server definition (HTTP/gRPC) │ └── svc/ │ └── service_context.go # Dependency injection container │ ├── pkg/ # Domain layer (can be exposed externally) │ ├── domain/ │ │ ├── config/ │ │ │ └── config.go # Domain config │ │ ├── entity/ │ │ │ ├── {entity}.go # Entity definition │ │ │ ├── {entity}_uid_table.go # UID mapping table │ │ │ └── auto_id.go # Auto ID generation │ │ ├── {module}/ │ │ │ ├── {value_object}.go # Value objects and enums │ │ │ └── {value_object}_test.go # Value object tests │ │ ├── repository/ │ │ │ ├── {entity}.go # Repository interface │ │ │ └── ... │ │ ├── usecase/ │ │ │ ├── {module}.go # Use Case interface │ │ │ └── ... │ │ ├── errors.go # Domain sentinel errors │ │ ├── const.go # Domain constants │ │ └── redis.go # Redis domain definitions │ ├── mock/ │ │ ├── repository/ │ │ │ ├── {entity}.go # Repository mock │ │ │ └── ... │ │ └── usecase/ │ │ └── {module}.go # Use Case mock │ ├── repository/ │ │ ├── {entity}.go # Repository implementation │ │ ├── {entity}_test.go # Repository tests │ │ ├── {entity}_uid.go # UID Repository implementation │ │ ├── {entity}_uid_test.go │ │ ├── error.go # Repository error definitions │ │ └── start_{db}_container_test.go # testcontainers startup │ └── usecase/ │ ├── {module}.go # Use Case implementation │ ├── {operation}.go # Specific operation │ ├── {operation}_test.go # Use Case tests │ └── {utils}.go # Utility functions │ ├── {service}.go # Application entry point ├── Makefile ├── go.mod ├── go.sum ├── docker-compose.yml └── readme.md ``` ## Dependency Direction Rules ``` pkg/domain/ ← No external dependencies (innermost, pure definitions) ↑ pkg/domain/usecase/ ← Use Case interface (only interface definitions) pkg/domain/repository/ ← Repository interface (only interface definitions) ↑ pkg/usecase/ ← Depends on domain interfaces (business logic implementation) pkg/mock/ ← Depends on domain interfaces (test mocks) ↑ internal/logic/ ← Depends on usecase implementations (handler logic) internal/server/ ← Depends on logic (HTTP/gRPC server) internal/svc/ ← Depends on all (DI container, assemble dependencies) ↑ pkg/repository/ ← Depends on domain interfaces (infrastructure implementation) ``` ``` ┌─────────────────────────────────┐ │ pkg/domain/ │ ← Pure definitions, no dependencies │ ├── entity/ │ │ ├── {module}/ (value objects)│ │ ├── repository/ (interfaces) │ │ ├── usecase/ (interfaces) │ │ ├── errors.go │ │ └── const.go │ ├─────────────────────────────────┤ │ pkg/usecase/ │ ← Depends on domain interfaces │ pkg/mock/ │ ← Depends on domain interfaces ├─────────────────────────────────┤ │ internal/logic/ │ ← Depends on usecase │ internal/server/ │ │ internal/config/ │ │ internal/svc/ │ ← DI container ├─────────────────────────────────┤ │ pkg/repository/ │ ← Depends on domain interfaces └─────────────────────────────────┘ ``` ## Architecture Principles ### `pkg/domain/` — Pure Domain Definitions `pkg/domain/` is the core, containing only **interfaces and definitions**, no implementations: ```go // pkg/domain/entity/user.go — Entity definition package entity type User struct { ID string `json:"id"` Email string `json:"email"` Name string `json:"name"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } ``` ```go // pkg/domain/member/status.go — Value object (with tests) package member type Status string const ( StatusActive Status = "active" StatusInactive Status = "inactive" ) func (s Status) IsValid() bool { switch s { case StatusActive, StatusInactive: return true } return false } func NewStatus(s string) (Status, error) { status := Status(s) if !status.IsValid() { return "", fmt.Errorf("invalid status: %s", s) } return status, nil } ``` ```go // pkg/domain/member/status_test.go — Value object tests package member func TestStatus_IsValid(t *testing.T) { assert.True(t, StatusActive.IsValid()) assert.True(t, StatusInactive.IsValid()) assert.False(t, Status("unknown").IsValid()) } func TestNewStatus(t *testing.T) { status, err := NewStatus("active") assert.NoError(t, err) assert.Equal(t, StatusActive, status) _, err = NewStatus("unknown") assert.Error(t, err) } ``` ```go // pkg/domain/repository/user.go — Repository interface package repository type UserRepository interface { GetByID(ctx context.Context, id string) (*entity.User, error) GetByEmail(ctx context.Context, email string) (*entity.User, error) Create(ctx context.Context, user *entity.User) error Update(ctx context.Context, user *entity.User) error Delete(ctx context.Context, id string) error } ``` ```go // pkg/domain/usecase/user.go — Use Case interface package usecase type UserUsecase interface { CreateUser(ctx context.Context, input CreateUserInput) (*entity.User, error) GetUser(ctx context.Context, id string) (*entity.User, error) UpdateUser(ctx context.Context, id string, input UpdateUserInput) (*entity.User, error) } ``` ```go // pkg/domain/errors.go — Domain sentinel errors package domain import "errors" var ( ErrUserNotFound = errors.New("user not found") ErrInvalidInput = errors.New("invalid input") ErrDuplicateEmail = errors.New("email already exists") ) ``` ### `pkg/usecase/` — Business Logic Implementation One functional domain per file, test file in same directory: ```go // pkg/usecase/account.go — Use Case entry point package usecase type AccountUsecase struct { userRepo repository.UserRepository accountRepo repository.AccountRepository redis *redis.Client } func NewAccountUsecase( userRepo repository.UserRepository, accountRepo repository.AccountRepository, redis *redis.Client, ) *AccountUsecase { return &AccountUsecase{ userRepo: userRepo, accountRepo: accountRepo, redis: redis, } } ``` ```go // pkg/usecase/create_user.go — Single operation package usecase func (uc *AccountUsecase) CreateUser(ctx context.Context, input CreateUserInput) (*entity.User, error) { if err := input.Validate(); err != nil { return nil, fmt.Errorf("validate input: %w", err) } existing, _ := uc.userRepo.GetByEmail(ctx, input.Email) if existing != nil { return nil, domain.ErrDuplicateEmail } user, err := entity.NewUser(input.Email, input.Password, input.Name) if err != nil { return nil, fmt.Errorf("create user: %w", err) } if err := uc.userRepo.Create(ctx, user); err != nil { return nil, fmt.Errorf("save user: %w", err) } return user, nil } ``` ```go // pkg/usecase/create_user_test.go — Test in same directory package usecase func TestAccountUsecase_CreateUser_Success(t *testing.T) { mockUserRepo := new(mock.UserRepository) uc := NewAccountUsecase(mockUserRepo, nil, nil) mockUserRepo.On("GetByEmail", mock.Anything, "test@example.com").Return(nil, nil) mockUserRepo.On("Create", mock.Anything, mock.AnythingOfType("*entity.User")).Return(nil) user, err := uc.CreateUser(context.Background(), input) assert.NoError(t, err) assert.NotNil(t, user) mockUserRepo.AssertExpectations(t) } ``` ### `pkg/mock/` — Auto-Generated Mocks ```go // pkg/mock/repository/user.go — Generated by mockery //go:generate mockery --name=UserRepository --output=../../mock/repository --outpkg=mock_repository package mock_repository import ( "github.com/stretchr/testify/mock" "your-project/pkg/domain/repository" ) type UserRepository struct { mock.Mock } func (m *UserRepository) GetByID(ctx context.Context, id string) (*entity.User, error) { args := m.Called(ctx, id) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(*entity.User), args.Error(1) } ``` ### `internal/logic/` — Handler Logic One logic file per use case (go-zero style): ```go // internal/logic/account/create_user_logic.go package account type CreateUserLogic struct { ctx context.Context svcCtx *svc.ServiceContext } func NewCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateUserLogic { return &CreateUserLogic{ ctx: ctx, svcCtx: svcCtx, } } func (l *CreateUserLogic) CreateUser(req *types.CreateUserReq) (*types.UserResp, error) { user, err := l.svcCtx.UserUsecase.CreateUser(l.ctx, usecase.CreateUserInput{ Email: req.Email, Password: req.Password, Name: req.Name, }) if err != nil { return nil, err } return &types.UserResp{ ID: user.ID, Email: user.Email, Name: user.Name, }, nil } ``` ### `internal/svc/` — Dependency Injection Container ```go // internal/svc/service_context.go package svc type ServiceContext struct { Config config.Config UserUsecase usecase.UserUsecase AccountUsecase usecase.AccountUsecase } func NewServiceContext(c config.Config) *ServiceContext { db := mongo.NewClient(c.Mongo.URI) redisClient := redis.NewClient(c.Redis) userRepo := repository.NewUserRepository(db) accountRepo := repository.NewAccountRepository(db) return &ServiceContext{ Config: c, UserUsecase: usecase.NewUserUsecase(userRepo, redisClient), AccountUsecase: usecase.NewAccountUsecase(userRepo, accountRepo, redisClient), } } ``` ### `pkg/repository/` — Infrastructure Implementation ```go // pkg/repository/user.go package repository type userRepository struct { db *mongo.Database } func NewUserRepository(db *mongo.Database) domain.Repository.UserRepository { return &userRepository{db: db} } func (r *userRepository) GetByID(ctx context.Context, id string) (*entity.User, error) { var user entity.User err := r.db.Collection("users").FindOne(ctx, bson.M{"_id": id}).Decode(&user) if err != nil { if err == mongo.ErrNoDocuments { return nil, domain.ErrUserNotFound } return nil, fmt.Errorf("find user by id: %w", err) } return &user, nil } ``` ```go // pkg/repository/user_test.go package repository func TestUserRepository_GetByID_Success(t *testing.T) { db := startMongoContainer(t) defer db.Client().Disconnect(context.Background()) repo := NewUserRepository(db) // ... } ``` ## Coding Standards ### File Naming ``` Value objects and enums: pkg/domain/{module}/{name}.go + _test.go Entity: pkg/domain/entity/{name}.go Repository interface: pkg/domain/repository/{name}.go Usecase interface: pkg/domain/usecase/{module}.go Usecase implementation: pkg/usecase/{operation}.go + _test.go Usecase utilities: pkg/usecase/{module}_utils.go + _test.go Repository impl: pkg/repository/{name}.go + _test.go Mock: pkg/mock/repository/{name}.go pkg/mock/usecase/{module}.go Handler logic: internal/logic/{module}/{operation}_logic.go Server: internal/server/{module}/{module}_server.go Service Context: internal/svc/service_context.go Protobuf definitions: generate/protobuf/{module}.proto Config files: etc/{service}.yaml Dockerfile: build/Dockerfile ``` ### Naming Conventions ```go // Package: lowercase, semantically clear package usecase // not usecases package repository // not repositories package entity // not entities // Entity struct: PascalCase, no suffix type User struct { ... } // not UserModel, UserEntity // Value Object: base type alias + methods type Status string // not StatusEnum // Interface: defined in pkg/domain/, semantic naming type UserRepository interface { ... } // not UserRepo or UserRepositoryI // Use Case struct: {Module}Usecase type AccountUsecase struct { ... } // Use Case methods: verb prefix func (uc *AccountUsecase) CreateUser(ctx context.Context, ...) (*entity.User, error) func (uc *AccountUsecase) GetUser(ctx context.Context, id string) (*entity.User, error) // Logic struct: {Operation}Logic type CreateUserLogic struct { ... } // Error: Err prefix var ErrUserNotFound = errors.New("user not found") // Constant: PascalCase (exported) or camelCase (internal) const MaxRetryCount = 3 const defaultPageSize = 20 ``` ### Error Handling ```go // Sentinel errors — pkg/domain/errors.go var ( ErrUserNotFound = errors.New("user not found") ErrInvalidInput = errors.New("invalid input") ErrDuplicateEmail = errors.New("email already exists") ) // Error wrapping — always use %w if err != nil { return fmt.Errorf("create user: %w", err) } // Error checking — always use errors.Is if errors.Is(err, domain.ErrUserNotFound) { // handle not found } // Repository errors — pkg/repository/error.go var ( ErrMongoConnection = errors.New("mongo connection failed") ErrRedisConnection = errors.New("redis connection failed") ) ``` ### Interface Design ```go // Interface defined in pkg/domain/ (consumer side) // Implementation defined in pkg/ (provider side) // Accept interfaces, return structs func NewUserUsecase(repo repository.UserRepository, redis *redis.Client) *UserUsecase { return &UserUsecase{repo: repo, redis: redis} } // Keep interfaces small (1-3 methods) type UserRepository interface { GetByID(ctx context.Context, id string) (*entity.User, error) Create(ctx context.Context, user *entity.User) error } ``` ## TDD Standards This skill integrates with `tdd` skill, following shared TDD principles. ### Test Pyramid ``` /\ / \ / E2E \ <- Few critical flows /--------\ /Integration\ <- DB + Redis (testcontainers) /--------------\ / Unit Tests \ <- Most, 80%+ coverage /--------------------\ ``` ### Test Location ``` Test files in same directory as source: pkg/domain/member/status_test.go ← Value object tests pkg/usecase/create_user_test.go ← Use Case tests pkg/repository/user_test.go ← Repository tests pkg/repository/start_mongo_container_test.go ← testcontainers startup ``` ### Vertical Slice TDD Each slice follows this order: ``` Slice: User registration 1. RED: Write TestStatus_IsValid (value object) 2. GREEN: Write Status.IsValid() 3. RED: Write TestAccountUsecase_CreateUser_Success 4. GREEN: Write domain/entity, domain/usecase interface, pkg/usecase implementation, mock 5. RED: Write TestAccountUsecase_CreateUser_DuplicateEmail 6. GREEN: Add duplicate check 7. RED: Write TestUserRepository_Create (DB test) 8. GREEN: Write pkg/repository/user.go 9. RED: Write TestCreateUserLogic (handler test) 10. GREEN: Write internal/logic/account/create_user_logic.go 11. REFACTOR: Clean up everything ``` ### Mock Strategy ```go // Use mockery to auto-generate mocks // Add go:generate directive in interface file //go:generate mockery --name=UserRepository --output=../../mock/repository --outpkg=mock_repository // Unit tests use mock func TestAccountUsecase_CreateUser_Success(t *testing.T) { mockRepo := new(mock_repository.UserRepository) uc := usecase.NewAccountUsecase(mockRepo, nil, nil) mockRepo.On("GetByEmail", mock.Anything, "test@example.com").Return(nil, nil) mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*entity.User")).Return(nil) user, err := uc.CreateUser(context.Background(), input) assert.NoError(t, err) assert.NotNil(t, user) } ``` ### testcontainers Strategy ```go // pkg/repository/start_mongo_container_test.go func startMongoContainer(t *testing.T) *mongo.Database { ctx := context.Background() req := testcontainers.ContainerRequest{ Image: "mongo:7", ExposedPorts: []string{"27017/tcp"}, WaitingFor: wait.ForListeningPort("27017/tcp"), } mongoC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) require.NoError(t, err) t.Cleanup(func() { mongoC.Terminate(ctx) }) // ... return connected database } ``` ### Coverage Requirements - Value objects (`pkg/domain/member/`): >= 90% - Use Case (`pkg/usecase/`): >= 90% - Repository (`pkg/repository/`): >= 80% - Logic (`internal/logic/`): >= 80% (mainly integration tests) - Critical paths: Integration tests required ## Vertical Slice Template File list for each vertical slice: ``` Slice: {operation}_{entity} New/modified files: ├── pkg/domain/entity/{entity}.go ← Entity definition ├── pkg/domain/member/{value_object}.go ← Value object (if needed) ├── pkg/domain/member/{value_object}_test.go ← Value object tests ├── pkg/domain/repository/{entity}.go ← Repository interface ├── pkg/domain/usecase/{module}.go ← Use Case interface ├── pkg/usecase/{operation}.go ← Use Case implementation ├── pkg/usecase/{operation}_test.go ← Use Case tests ├── pkg/mock/repository/{entity}.go ← Repository mock ├── pkg/repository/{entity}.go ← Repository implementation ├── pkg/repository/{entity}_test.go ← Repository tests ├── internal/logic/{module}/{operation}_logic.go ← Handler logic └── internal/svc/service_context.go ← Update DI ``` ## Completion Checklist ### After Each Slice - [ ] Value object tests pass - [ ] Use Case tests pass - [ ] Repository tests pass (with DB) - [ ] Error handling complete - [ ] Dependency direction correct (domain has no external dependencies) ### After All Complete - [ ] Project structure follows Domain-Driven + go-zero style - [ ] `pkg/domain/` contains all Entity, Value Object, interface definitions - [ ] `pkg/usecase/` contains all business logic implementations - [ ] `pkg/repository/` contains all infrastructure implementations - [ ] `internal/logic/` contains all Handler logic - [ ] `internal/svc/` contains complete dependency injection setup - [ ] Unit tests >= 80% coverage - [ ] Business logic >= 90% coverage - [ ] Integration tests pass (critical paths) - [ ] Error handling consistent and uses `%w` wrapping ## Related Skills - **Prerequisite**: `prd-to-plan` (implementation plan), `be-api-design` (API spec), `dba-schema` (DB Schema) - **辅助**: `tdd` (TDD Red-Green-Refactor process), `design-an-interface` (interface design) - **Follow-up**: `qa` (QA testing) ## Rollback Mechanism ``` QA failed (Stage 10) ↓ Orchestrator re-assigns fix task ↓ Backend Agent fixes bug + adds regression test ↓ Re-enter QA (Stage 10) Code Review rejected (Stage 10) ↓ Handle PR feedback ↓ Re-enter QA (Stage 10) for verification Implementation plan not feasible ↓ Return to Task Breakdown (Stage 8) for re-decomposition