| name | code-designing |
| description | Domain type design and architectural planning for Go code.
Use when planning new features, designing self-validating types, preventing primitive obsession, or when refactoring reveals need for new types.
Focuses on vertical slice architecture and type safety.
|
| allowed-tools | ["Skill(go-linter-driven-development:testing)"] |
Domain type design and architectural planning for Go code.
Use when planning new features or identifying need for new types during refactoring.
Reference: See reference.md for complete design principles and examples.
<skill_invocation>
CRITICAL: When this skill says "Use @skill-name" or routes to "@skill-name", you MUST use the Skill tool explicitly.
| Notation | Skill Tool Call |
|---|
| @testing | Skill(go-linter-driven-development:testing) |
DO NOT just reference the skill - actually invoke it using the Skill tool.
</skill_invocation>
<quick_start>
- Analyze Architecture: Check for vertical vs horizontal slicing
- Understand Domain: Identify problem domain, concepts, invariants
- Identify Core Types: Find primitives that need type wrappers
- Design Self-Validating Types: Create types with validating constructors
- Plan Package Structure: Vertical slices by feature
- Output Design Plan: Present structured plan before implementation
Ready to implement? Use @testing skill for test structure.
</quick_start>
<when_to_use>
- Planning a new feature (before writing code)
- Refactoring reveals need for new types (complexity extraction)
- Linter failures suggest types should be introduced
- When you need to think through domain modeling
argument-limit linter failure (>4 parameters) → Design options struct
function-result-limit linter failure (>3 returns) → Design result type
confusing-results linter failure → Design named result type
file-length-limit linter failure (>450 lines) → Analyze and split juicy types to own files
- PostToolUse package-size hook reports yellow/red zone → design-time intervention: re-model with sub-packages before the zone escalates (full decomposition playbook in @refactoring
<package_decomposition>)
</when_to_use>
Design clean, self-validating types that:
- Prevent primitive obsession
- Ensure type safety
- Make validation explicit
- Follow vertical slice architecture
<architecture_pattern_analysis priority="FIRST_STEP">
Default: Always use vertical slice architecture (feature-first, not layer-first).
Scan codebase structure:
- Vertical slicing:
internal/feature/{handler,service,repository,models}.go
- Horizontal layering:
internal/{handlers,services,domain}/feature.go
<decision_flow>
- Pure vertical → Continue pattern, implement as
internal/[new-feature]/
- Pure horizontal → Propose: Start migration with
docs/architecture/vertical-slice-migration.md, implement new feature as first vertical slice
- Mixed (migrating) → Check for migration docs, continue pattern as vertical slice
</decision_flow>
Always ask user approval with options:
- Option A: Vertical slice (recommended for cohesion/maintainability)
- Option B: Match existing pattern (if time-constrained)
- Acknowledge: Time pressure, team decisions, consistency needs are valid
If migration needed, create/update docs/architecture/vertical-slice-migration.md:
# Vertical Slice Migration Plan
## Current State: [horizontal/mixed]
## Target: Vertical slices in internal/[feature]/
## Strategy: New features vertical, migrate existing incrementally
## Progress: [x] [new-feature] (this PR), [ ] existing features
See reference.md section #3 for detailed patterns.
</architecture_pattern_analysis>
<understand_domain>
- What is the problem domain?
- What are the main concepts/entities?
- What are the invariants and rules?
- How does this fit into existing architecture?
</understand_domain>
<identify_core_types>
Ask for each concept:
- Is this currently a primitive (string, int, float)?
- Does it have validation rules?
- Does it have behavior beyond simple data?
- Is it used across multiple places?
If yes to any → Consider creating a type
</identify_core_types>
<design_self_validating_types>
For each type:
type TypeName underlyingType
func NewTypeName(input underlyingType) (TypeName, error) {
if {
return zero, errors.New("why it failed")
}
return TypeName(input), nil
}
func (t TypeName) SomeMethod() result {
}
Composed types trust their parts — never re-validate self-validating types:
func NewAddress(host Host, port Port) (Address, error) {
if host == "" { return Address{}, errors.New("host required") }
return Address{host: host, port: port}, nil
}
func NewAddress(host Host, port Port) Address {
return Address{host: host, port: port}
}
</design_self_validating_types>
<plan_package_structure>
- Vertical slices: Group by feature, not layer
- Each feature gets its own package
- Within package: separate by role (service, repository, handler)
Good structure:
user/
├── user.go # Domain types
├── service.go # Business logic
├── repository.go # Persistence
└── handler.go # HTTP/API
Bad structure:
domain/user.go
services/user_service.go
repository/user_repository.go
Package naming method (for feature and sub-package design):
- Model the real-world relationship. Ask: "What IS this system? What does it DO? What does it operate ON?"
- A worker HAS a job →
worker/ + worker/job/ (job.ID, job.Status)
- A compiler HAS tokens →
compiler/ + compiler/token/
- A scheduler HAS tasks →
scheduler/ + scheduler/task/
- The parent names the actor/system (the thing that does the work).
- The sub-package names the domain object (the thing being acted upon) — this is where your
pkg.Type call sites live.
- Test: say
pkg.Type out loud. job.ID sounds right. domain.ID sounds like Java.
Package-name anti-patterns (never use — they describe roles or act as dumping grounds):
- Role names:
handlers/, types/, model/
- Generic containers:
common/, shared/, core/, base/, util/, helpers/, domain/
Import direction (strictly downward — plan this up front to avoid cycles):
leaf types (domain) ← (nothing)
sub-packages ← leaf types
parent ← leaf types + sub-packages
cmd/ ← everything
If the parent needs sub-package logic AND the sub-package needs parent types, extract the shared types into a leaf sub-package from day one.
When decomposing an existing package (red/yellow zone), see @refactoring <package_decomposition> for the full 3-step design review and phased migration.
</plan_package_structure>
<design_orchestrating_types>
For types that coordinate others:
- Make fields private
- Validate dependencies in constructor
- No nil checks in methods (constructor guarantees validity)
type Service struct {
repo Repository
notifier Notifier
}
func NewService(repo Repository, notifier Notifier) (*Service, error) {
if repo == nil {
return nil, errors.New("repo required")
}
if notifier == nil {
return nil, errors.New("notifier required")
}
return &Service{
repo: repo,
notifier: notifier,
}, nil
}
func (s *Service) DoSomething() error {
return s.repo.Save(...)
}
</design_orchestrating_types>
<review_against_principles>
Check design against (see reference.md):
<linter_triggered_patterns>
When invoked by linter failures, apply these patterns:
```go
// BEFORE - Too many parameters
func CreateUser(name string, email string, age int, role string, dept string) (*User, error)
// AFTER - Options struct
type CreateUserOptions struct {
Name string
Email string
Age int
Role string
Dept string
}
func CreateUser(opts CreateUserOptions) (*User, error)
**Design Tip**: Add validation in a constructor: `NewCreateUserOptions(...) (CreateUserOptions, error)`
</pattern>
<pattern name="result_type" trigger="function-result-limit (>3 returns)">
```go
// BEFORE - Too many return values
func ParseConfig(path string) (config Config, warnings []string, version int, error)
// AFTER - Result type
type ParseConfigResult struct {
Config Config
Warnings []string
Version int
}
func ParseConfig(path string) (ParseConfigResult, error)
```go
// BEFORE - Confusing (string, string, error)
func ParseAddress(raw string) (string, string, error) // Which is host? Which is port?
// AFTER - Named result type
type ParsedAddress struct {
Host string
Port string
}
func ParseAddress(raw string) (ParsedAddress, error)
</pattern>
<pattern name="file_splitting" trigger="file-length-limit (revive) - file > 450 lines">
**Step 1: Analyze file structure**
| File Pattern | Action |
|--------------|--------|
| Multiple juicy types | Move each juicy type to its own file |
| Single god type | Extract method clusters via composition OR extract juicy logic from methods |
| Long functions, few types | Route to @refactoring first (storify → extract functions) |
**Step 2: Apply juiciness test**
"Juicy" types (deserve their own file):
- Types with ≥2 methods
- Types with complex validation
- Types with transformations/parsing
- Enums WITH methods (behavior makes them juicy)
"Anemic" types (can stay grouped in types.go or similar):
- Simple enums (const block only)
- DTOs with no methods
- Type aliases
**Step 3: For god types** (>15 methods)
| Option | When to Use | Pattern |
|--------|-------------|---------|
| **Storify first** | Methods are hard to read | Apply storification → reveals hidden structure |
| **Extract via composition** | Method clusters exist | Identify cluster → extract to new type → compose |
| **Extract juicy logic** | Primitive obsession inside methods | Find logic on primitives → extract to self-validating type |
**Routing**: God types require two-phase refactoring:
1. **@refactoring** (first): Storify methods to reveal structure
2. **@code-designing** (then): Design service composition
See @refactoring skill → `god_object_decomposition` pattern for detailed mechanics.
</pattern>
</linter_triggered_patterns>
</workflow>
<output_format>
After design phase:
DESIGN PLAN
Feature: [Feature Name]
Core Domain Types:
- UserID (string) - Self-validating, prevents empty IDs
- Email (string) - Self-validating, RFC 5322 validation
- Age (int) - Self-validating, range 0-150
Orchestrating Types:
- UserService - Coordinates user operations
Dependencies: Repository, Notifier
Methods: CreateUser, GetUser, UpdateUser
Package Structure:
user/
├── user.go # UserID, Email, Age, User
├── service.go # UserService
├── repository.go # Repository interface + implementations
├── notifier.go # Notifier interface + implementations
└── handler.go # HTTP handlers
Design Decisions:
- UserID is custom type to prevent passing empty/invalid IDs
- Email validation centralized in NewEmail constructor
- Vertical slice keeps all user logic in one package
- Repository as interface allows multiple backends (Postgres, in-memory for tests)
Integration Points:
- Consumed by: HTTP API (/users endpoints)
- Depends on: Database, Email service
- Events: UserCreated event published after creation
Next Steps:
- Create types with validating constructors
- Write unit tests for each type
- Implement UserService
- Write integration tests
Ready to implement? Use @testing skill for test structure.
</output_format>
<key_principles>
See reference.md for detailed principles:
- Primitive obsession prevention (Yoke design strategy)
- Self-validating types
- Vertical slice architecture
- Types around intent and behavior, not just shape
- Single responsibility per type
</key_principles>
<pre_code_review>
Before writing code, ask:
- Can logic be moved into smaller custom types?
- Is this type designed around intent and behavior?
- Have I avoided primitive obsession?
- Is validation in the right place (constructor)?
- Does this follow vertical slice architecture?
Only after satisfactory answers, proceed to implementation.
See reference.md for complete design principles and examples.
</pre_code_review>
<success_criteria>
Design phase is complete when ALL of the following are true:
- [ ] Architecture pattern analyzed (vertical/horizontal/mixed)
- [ ] Core domain types identified with validation rules
- [ ] Self-validating type design documented
- [ ] Package structure follows vertical slice pattern
- [ ] Package names reflect real-world domain concepts (not role names like `handlers/`/`types/` or containers like `common/`/`domain/`); `pkg.Type` reads like English
- [ ] Import direction is strictly downward (leaf types ← sub-packages ← parent ← cmd/)
- [ ] Design decisions documented with rationale
- [ ] Pre-code review questions answered satisfactorily
- [ ] Design plan output presented to user
</success_criteria>