Initial commit
This commit is contained in:
221
skills/code-designing/SKILL.md
Normal file
221
skills/code-designing/SKILL.md
Normal file
@@ -0,0 +1,221 @@
|
||||
---
|
||||
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.
|
||||
---
|
||||
|
||||
# Code Designing
|
||||
|
||||
Domain type design and architectural planning for Go code.
|
||||
Use when planning new features or identifying need for new types during refactoring.
|
||||
|
||||
## 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
|
||||
|
||||
## Purpose
|
||||
Design clean, self-validating types that:
|
||||
- Prevent primitive obsession
|
||||
- Ensure type safety
|
||||
- Make validation explicit
|
||||
- Follow vertical slice architecture
|
||||
|
||||
## Workflow
|
||||
|
||||
### 0. Architecture Pattern Analysis (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
|
||||
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
### 1. 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?
|
||||
|
||||
### 2. 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
|
||||
|
||||
### 3. 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
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 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
|
||||
```
|
||||
|
||||
### 5. 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(...)
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 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
|
||||
|
||||
## 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:
|
||||
1. Create types with validating constructors
|
||||
2. Write unit tests for each type
|
||||
3. Implement UserService
|
||||
4. Write integration tests
|
||||
|
||||
Ready to implement? Use @testing skill for test structure.
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
## Pre-Code Review Questions
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user