Claude
Skills
Sign in
Back

code-designing

Included with Lifetime
$97 forever

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.

Design

What this skill does


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

<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>
1. **Analyze Architecture**: Check for vertical vs horizontal slicing
2. **Understand Domain**: Identify problem domain, concepts, invariants
3. **Identify Core Types**: Find primitives that need type wrappers
4. **Design Self-Validating Types**: Create types with validating constructors
5. **Plan Package Structure**: Vertical slices by feature
6. **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>

<purpose>
Design clean, self-validating types that:
- Prevent primitive obsession
- Ensure type safety
- Make validation explicit
- Follow vertical slice architecture
</purpose>

<workflow>

<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>
1. **Pure vertical** → Continue pattern, implement as `internal/[new-feature]/`
2. **Pure horizontal** → Propose: Start migration with `docs/architecture/vertical-slice-migration.md`, implement new feature as first vertical slice
3. **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`:
```markdown
# 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:
```go
// Type definition
type TypeName underlyingType

// Validating constructor
func NewTypeName(input underlyingType) (TypeName, error) {
    // Validate input
    if /* validation fails */ {
        return zero, errors.New("why it failed")
    }
    return TypeName(input), nil
}

// Methods on type (if behavior needed)
func (t TypeName) SomeMethod() result {
    // Type-specific logic
}
```

**Composed types trust their parts** — never re-validate self-validating types:
```go
// ❌ Re-validates composed types
func NewAddress(host Host, port Port) (Address, error) {
    if host == "" { return Address{}, errors.New("host required") }  // Host owns this
    return Address{host: host, port: port}, nil
}

// ✅ Trusts composed self-validating types
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):

1. **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/`
2. **The parent names the actor/system** (the thing that does the work).
3. **The sub-package names the domain object** (the thing being acted upon) — this is where your `pkg.Type` call sites live.
4. **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)

```go
type Service struct {
    repo        Repository  // private
    notifier    Notifier    // private
}

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
}

// Methods can trust fields are valid
func (s *Service) DoSomething() error {
    // No nil checks needed
    return s.repo.Save(...)
}
```
</design_orchestrating_types>

<review_against_principles>
Check design against (see reference.md):
- [ ] No primitive obsession
- [ ] Types are self-validating
- [ ] Vertical slice architecture
- [ ] Types designed around intent, not just shape
- [ ] Clear separation of concerns
- [ ] Each type owns its validation; composed self-validating types are trusted, not re-validated
</review_against_principles>

<linter_triggered_patterns>
**When invoked by linter failur

Related in Design