try to restructure the agents and skills given the new skills and command merge
This commit is contained in:
633
old/skills/software-architecture/SKILL.md
Normal file
633
old/skills/software-architecture/SKILL.md
Normal file
@@ -0,0 +1,633 @@
|
||||
---
|
||||
name: software-architecture
|
||||
model: haiku
|
||||
description: >
|
||||
Architectural patterns for building systems: DDD, Event Sourcing, event-driven communication.
|
||||
Use when implementing features, reviewing code, planning issues, refining architecture,
|
||||
or making design decisions. Ensures alignment with organizational beliefs about
|
||||
auditability, domain modeling, and independent evolution.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# Software Architecture
|
||||
|
||||
Architectural patterns and best practices. This skill is auto-triggered when implementing, reviewing, or planning work that involves architectural decisions.
|
||||
|
||||
## Architecture Beliefs
|
||||
|
||||
These outcome-focused beliefs (from our organization manifesto) guide architectural decisions:
|
||||
|
||||
| Belief | Why It Matters |
|
||||
|--------|----------------|
|
||||
| **Auditability by default** | Systems should remember what happened, not just current state |
|
||||
| **Business language in code** | Domain experts' words should appear in the codebase |
|
||||
| **Independent evolution** | Parts should change without breaking other parts |
|
||||
| **Explicit over implicit** | Intent and side effects should be visible and traceable |
|
||||
|
||||
## Beliefs → Patterns
|
||||
|
||||
| Belief | Primary Pattern | Supporting Patterns |
|
||||
|--------|-----------------|---------------------|
|
||||
| Auditability by default | Event Sourcing | Immutable events, temporal queries |
|
||||
| Business language in code | Domain-Driven Design | Ubiquitous language, aggregates, bounded contexts |
|
||||
| Independent evolution | Event-driven communication | Bounded contexts, published language |
|
||||
| Explicit over implicit | Commands and Events | Domain events, clear intent |
|
||||
|
||||
## Event Sourcing
|
||||
|
||||
**Achieves:** Auditability by default
|
||||
|
||||
Instead of storing current state, store the sequence of events that led to it.
|
||||
|
||||
**Core concepts:**
|
||||
- **Events** are immutable facts about what happened, named in past tense: `OrderPlaced`, `PaymentReceived`
|
||||
- **State** is derived by replaying events, not stored directly
|
||||
- **Event store** is append-only - history is never modified
|
||||
|
||||
**Why this matters:**
|
||||
- Complete audit trail for free
|
||||
- Debug by replaying history
|
||||
- Answer "what was the state at time X?"
|
||||
- Recover from bugs by fixing logic and replaying
|
||||
|
||||
**Trade-offs:**
|
||||
- More complex than CRUD for simple cases
|
||||
- Requires thinking in events, not state
|
||||
- Eventually consistent read models
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
**Achieves:** Business language in code
|
||||
|
||||
The domain model reflects how the business thinks and talks.
|
||||
|
||||
**Core concepts:**
|
||||
- **Ubiquitous language** - same terms in code, conversations, and documentation
|
||||
- **Bounded contexts** - explicit boundaries where terms have consistent meaning
|
||||
- **Aggregates** - clusters of objects that change together, with one root entity
|
||||
- **Domain events** - capture what happened in business terms
|
||||
|
||||
**Why this matters:**
|
||||
- Domain experts can read and validate the model
|
||||
- New team members learn the domain through code
|
||||
- Changes in business rules map clearly to code changes
|
||||
|
||||
**Trade-offs:**
|
||||
- Upfront investment in understanding the domain
|
||||
- Boundaries may need to shift as understanding grows
|
||||
- Overkill for pure technical/infrastructure code
|
||||
|
||||
## Event-Driven Communication
|
||||
|
||||
**Achieves:** Independent evolution
|
||||
|
||||
Services communicate by publishing events, not calling each other directly.
|
||||
|
||||
**Core concepts:**
|
||||
- **Publish events** when something important happens
|
||||
- **Subscribe to events** you care about
|
||||
- **No direct dependencies** between publisher and subscriber
|
||||
- **Eventual consistency** - accept that not everything updates instantly
|
||||
|
||||
**Why this matters:**
|
||||
- Add new services without changing existing ones
|
||||
- Services can be deployed independently
|
||||
- Natural resilience - if a subscriber is down, events queue
|
||||
|
||||
**Trade-offs:**
|
||||
- Harder to trace request flow
|
||||
- Eventual consistency requires different thinking
|
||||
- Need infrastructure for reliable event delivery
|
||||
|
||||
## Commands and Events
|
||||
|
||||
**Achieves:** Explicit over implicit
|
||||
|
||||
Distinguish between requests (commands) and facts (events).
|
||||
|
||||
**Core concepts:**
|
||||
- **Commands** express intent: `PlaceOrder`, `CancelSubscription`
|
||||
- Commands can be rejected (validation, business rules)
|
||||
- **Events** express facts: `OrderPlaced`, `SubscriptionCancelled`
|
||||
- Events are immutable - what happened, happened
|
||||
|
||||
**Why this matters:**
|
||||
- Clear separation of "trying to do X" vs "X happened"
|
||||
- Commands validate, events just record
|
||||
- Enables replay - reprocess events with new logic
|
||||
|
||||
## When to Diverge
|
||||
|
||||
These patterns are defaults, not mandates. Diverge intentionally when:
|
||||
|
||||
- **Simplicity wins** - a simple CRUD endpoint doesn't need event sourcing
|
||||
- **Performance requires it** - sometimes synchronous calls are necessary
|
||||
- **Team context** - patterns the team doesn't understand cause more harm than good
|
||||
- **Prototyping** - validate ideas before investing in full architecture
|
||||
|
||||
When diverging, document the decision in the project's `vision.md` Architecture section.
|
||||
|
||||
## Project-Level Architecture
|
||||
|
||||
Each project documents architectural choices in `vision.md`:
|
||||
|
||||
```markdown
|
||||
## Architecture
|
||||
|
||||
This project follows organization architecture patterns.
|
||||
|
||||
### Alignment
|
||||
- Event sourcing for [which aggregates/domains]
|
||||
- Bounded contexts: [list contexts and their responsibilities]
|
||||
- Event-driven communication between [which services]
|
||||
|
||||
### Intentional Divergences
|
||||
| Area | Standard Pattern | What We Do Instead | Why |
|
||||
|------|------------------|-------------------|-----|
|
||||
```
|
||||
|
||||
## Go-Specific Best Practices
|
||||
|
||||
### Package Organization
|
||||
|
||||
**Good package structure:**
|
||||
```
|
||||
project/
|
||||
├── cmd/ # Application entry points
|
||||
│ └── server/
|
||||
│ └── main.go
|
||||
├── internal/ # Private packages
|
||||
│ ├── domain/ # Core business logic
|
||||
│ │ ├── user/
|
||||
│ │ └── order/
|
||||
│ ├── service/ # Application services
|
||||
│ ├── repository/ # Data access
|
||||
│ └── handler/ # HTTP/gRPC handlers
|
||||
├── pkg/ # Public, reusable packages
|
||||
└── go.mod
|
||||
```
|
||||
|
||||
**Package naming:**
|
||||
- Short, concise, lowercase: `user`, `order`, `auth`
|
||||
- Avoid generic names: `util`, `common`, `helpers`, `misc`
|
||||
- Name after what it provides, not what it contains
|
||||
- One package per concept, not per file
|
||||
|
||||
**Package cohesion:**
|
||||
- A package should have a single, focused responsibility
|
||||
- Package internal files can use internal types freely
|
||||
- Minimize exported types - export interfaces, hide implementations
|
||||
|
||||
### Interfaces
|
||||
|
||||
**Accept interfaces, return structs:**
|
||||
```go
|
||||
// Good: Accept interface, return concrete type
|
||||
func NewUserService(repo UserRepository) *UserService {
|
||||
return &UserService{repo: repo}
|
||||
}
|
||||
|
||||
// Bad: Accept and return interface
|
||||
func NewUserService(repo UserRepository) UserService {
|
||||
return &userService{repo: repo}
|
||||
}
|
||||
```
|
||||
|
||||
**Define interfaces at point of use:**
|
||||
```go
|
||||
// Good: Interface defined where it's used (consumer owns the interface)
|
||||
package service
|
||||
|
||||
type UserRepository interface {
|
||||
FindByID(ctx context.Context, id string) (*User, error)
|
||||
}
|
||||
|
||||
// Bad: Interface defined with implementation (producer owns the interface)
|
||||
package repository
|
||||
|
||||
type UserRepository interface {
|
||||
FindByID(ctx context.Context, id string) (*User, error)
|
||||
}
|
||||
```
|
||||
|
||||
**Keep interfaces small:**
|
||||
- Prefer single-method interfaces
|
||||
- Large interfaces indicate missing abstraction
|
||||
- Compose small interfaces when needed
|
||||
|
||||
### Error Handling
|
||||
|
||||
**Wrap errors with context:**
|
||||
```go
|
||||
// Good: Wrap with context
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetching user %s: %w", id, err)
|
||||
}
|
||||
|
||||
// Bad: Return bare error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
**Use sentinel errors for expected conditions:**
|
||||
```go
|
||||
var ErrNotFound = errors.New("not found")
|
||||
var ErrConflict = errors.New("conflict")
|
||||
|
||||
// Check with errors.Is
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
// handle not found
|
||||
}
|
||||
```
|
||||
|
||||
**Error types for rich errors:**
|
||||
```go
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Field, e.Message)
|
||||
}
|
||||
|
||||
// Check with errors.As
|
||||
var valErr *ValidationError
|
||||
if errors.As(err, &valErr) {
|
||||
// handle validation error
|
||||
}
|
||||
```
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
**Constructor injection:**
|
||||
```go
|
||||
type UserService struct {
|
||||
repo UserRepository
|
||||
logger Logger
|
||||
}
|
||||
|
||||
func NewUserService(repo UserRepository, logger Logger) *UserService {
|
||||
return &UserService{
|
||||
repo: repo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Wire dependencies in main:**
|
||||
```go
|
||||
func main() {
|
||||
// Create dependencies
|
||||
db := database.Connect()
|
||||
logger := slog.Default()
|
||||
|
||||
// Wire up services
|
||||
userRepo := repository.NewUserRepository(db)
|
||||
userService := service.NewUserService(userRepo, logger)
|
||||
userHandler := handler.NewUserHandler(userService)
|
||||
|
||||
// Start server
|
||||
http.Handle("/users", userHandler)
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
```
|
||||
|
||||
**Avoid global state:**
|
||||
- No `init()` for service initialization
|
||||
- No package-level variables for dependencies
|
||||
- Pass context explicitly, don't store in structs
|
||||
|
||||
### Testing
|
||||
|
||||
**Table-driven tests:**
|
||||
```go
|
||||
func TestUserService_Create(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input CreateUserInput
|
||||
want *User
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "valid user",
|
||||
input: CreateUserInput{Email: "test@example.com"},
|
||||
want: &User{Email: "test@example.com"},
|
||||
},
|
||||
{
|
||||
name: "invalid email",
|
||||
input: CreateUserInput{Email: "invalid"},
|
||||
wantErr: ErrInvalidEmail,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// arrange, act, assert
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Test doubles:**
|
||||
- Use interfaces for test doubles
|
||||
- Prefer hand-written mocks over generated ones for simple cases
|
||||
- Use `testify/mock` or `gomock` for complex mocking needs
|
||||
|
||||
**Test package naming:**
|
||||
- `package user_test` for black-box testing (preferred)
|
||||
- `package user` for white-box testing when needed
|
||||
|
||||
## Generic Architecture Patterns
|
||||
|
||||
### Layered Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ Presentation │ HTTP handlers, CLI, gRPC
|
||||
├─────────────────────────────────┤
|
||||
│ Application │ Use cases, orchestration
|
||||
├─────────────────────────────────┤
|
||||
│ Domain │ Business logic, entities
|
||||
├─────────────────────────────────┤
|
||||
│ Infrastructure │ Database, external services
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Dependencies point downward only
|
||||
- Upper layers depend on interfaces, not implementations
|
||||
- Domain layer has no external dependencies
|
||||
|
||||
### SOLID Principles
|
||||
|
||||
**Single Responsibility (S):**
|
||||
- Each module has one reason to change
|
||||
- Split code that changes for different reasons
|
||||
|
||||
**Open/Closed (O):**
|
||||
- Open for extension, closed for modification
|
||||
- Add new behavior through new types, not changing existing ones
|
||||
|
||||
**Liskov Substitution (L):**
|
||||
- Subtypes must be substitutable for their base types
|
||||
- Interfaces should be implementable without surprises
|
||||
|
||||
**Interface Segregation (I):**
|
||||
- Clients shouldn't depend on interfaces they don't use
|
||||
- Prefer many small interfaces over few large ones
|
||||
|
||||
**Dependency Inversion (D):**
|
||||
- High-level modules shouldn't depend on low-level modules
|
||||
- Both should depend on abstractions
|
||||
|
||||
### Dependency Direction
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ Domain │
|
||||
│ (no deps) │
|
||||
└──────────────┘
|
||||
▲
|
||||
┌────────────┴────────────┐
|
||||
│ │
|
||||
┌───────┴───────┐ ┌───────┴───────┐
|
||||
│ Application │ │Infrastructure │
|
||||
│ (uses domain) │ │(implements │
|
||||
└───────────────┘ │ domain intf) │
|
||||
▲ └───────────────┘
|
||||
│
|
||||
┌───────┴───────┐
|
||||
│ Presentation │
|
||||
│(calls app) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
**Key insight:** Infrastructure implements domain interfaces, doesn't define them. This inverts the "natural" dependency direction.
|
||||
|
||||
### Module Boundaries
|
||||
|
||||
**Signs of good boundaries:**
|
||||
- Modules can be understood in isolation
|
||||
- Changes are localized within modules
|
||||
- Clear, minimal public API
|
||||
- Dependencies flow in one direction
|
||||
|
||||
**Signs of bad boundaries:**
|
||||
- Circular dependencies between modules
|
||||
- "Shotgun surgery" - small changes require many file edits
|
||||
- Modules reach into each other's internals
|
||||
- Unclear ownership of concepts
|
||||
|
||||
## Repository Health Indicators
|
||||
|
||||
### Positive Indicators
|
||||
|
||||
| Indicator | What to Look For |
|
||||
|-----------|------------------|
|
||||
| Clear structure | Obvious package organization, consistent naming |
|
||||
| Small interfaces | Most interfaces have 1-3 methods |
|
||||
| Explicit dependencies | Constructor injection, no globals |
|
||||
| Test coverage | Unit tests for business logic, integration tests for boundaries |
|
||||
| Error handling | Wrapped errors, typed errors for expected cases |
|
||||
| Documentation | CLAUDE.md accurate, code comments explain "why" |
|
||||
|
||||
### Warning Signs
|
||||
|
||||
| Indicator | What to Look For |
|
||||
|-----------|------------------|
|
||||
| God packages | `utils/`, `common/`, `helpers/` with 20+ files |
|
||||
| Circular deps | Package A imports B, B imports A |
|
||||
| Deep nesting | 4+ levels of directory nesting |
|
||||
| Huge files | Files with 500+ lines |
|
||||
| Interface pollution | Interfaces for everything, even single implementations |
|
||||
| Global state | Package-level variables, `init()` for setup |
|
||||
|
||||
### Metrics to Track
|
||||
|
||||
- **Package fan-out:** How many packages does each package import?
|
||||
- **Cyclomatic complexity:** How complex are the functions?
|
||||
- **Test coverage:** What percentage of code is tested?
|
||||
- **Import depth:** How deep is the import tree?
|
||||
|
||||
## Review Checklists
|
||||
|
||||
### Repository Audit Checklist
|
||||
|
||||
Use this when evaluating overall repository health.
|
||||
|
||||
**Structure:**
|
||||
- [ ] Clear package organization following Go conventions
|
||||
- [ ] No circular dependencies between packages
|
||||
- [ ] Appropriate use of `internal/` for private packages
|
||||
- [ ] `cmd/` for application entry points
|
||||
|
||||
**Dependencies:**
|
||||
- [ ] Dependencies flow inward (toward domain)
|
||||
- [ ] Interfaces defined at point of use (not with implementation)
|
||||
- [ ] No global state or package-level dependencies
|
||||
- [ ] Constructor injection throughout
|
||||
|
||||
**Code Quality:**
|
||||
- [ ] Consistent naming conventions
|
||||
- [ ] No "god" packages (utils, common, helpers)
|
||||
- [ ] Errors wrapped with context
|
||||
- [ ] Small, focused interfaces
|
||||
|
||||
**Testing:**
|
||||
- [ ] Unit tests for domain logic
|
||||
- [ ] Integration tests for boundaries (DB, HTTP)
|
||||
- [ ] Tests are readable and maintainable
|
||||
- [ ] Test coverage for critical paths
|
||||
|
||||
**Documentation:**
|
||||
- [ ] CLAUDE.md is accurate and helpful
|
||||
- [ ] vision.md explains the product purpose
|
||||
- [ ] Code comments explain "why", not "what"
|
||||
|
||||
### Issue Refinement Checklist
|
||||
|
||||
Use this when reviewing issues for architecture impact.
|
||||
|
||||
**Scope:**
|
||||
- [ ] Issue is a vertical slice (user-visible value)
|
||||
- [ ] Changes are localized to specific packages
|
||||
- [ ] No cross-cutting concerns hidden in implementation
|
||||
|
||||
**Design:**
|
||||
- [ ] Follows existing patterns in the codebase
|
||||
- [ ] New abstractions are justified
|
||||
- [ ] Interface changes are backward compatible (or breaking change is documented)
|
||||
|
||||
**Dependencies:**
|
||||
- [ ] New dependencies are minimal and justified
|
||||
- [ ] No new circular dependencies introduced
|
||||
- [ ] Integration points are clearly defined
|
||||
|
||||
**Testability:**
|
||||
- [ ] Acceptance criteria are testable
|
||||
- [ ] New code can be unit tested in isolation
|
||||
- [ ] Integration test requirements are clear
|
||||
|
||||
### PR Review Checklist
|
||||
|
||||
Use this when reviewing pull requests for architecture concerns.
|
||||
|
||||
**Structure:**
|
||||
- [ ] Changes respect existing package boundaries
|
||||
- [ ] New packages follow naming conventions
|
||||
- [ ] No new circular dependencies
|
||||
|
||||
**Interfaces:**
|
||||
- [ ] Interfaces are defined where used
|
||||
- [ ] Interfaces are minimal and focused
|
||||
- [ ] Breaking interface changes are justified
|
||||
|
||||
**Dependencies:**
|
||||
- [ ] Dependencies injected via constructors
|
||||
- [ ] No new global state
|
||||
- [ ] External dependencies properly abstracted
|
||||
|
||||
**Error Handling:**
|
||||
- [ ] Errors wrapped with context
|
||||
- [ ] Sentinel errors for expected conditions
|
||||
- [ ] Error types for rich error information
|
||||
|
||||
**Testing:**
|
||||
- [ ] New code has appropriate test coverage
|
||||
- [ ] Tests are clear and maintainable
|
||||
- [ ] Edge cases covered
|
||||
|
||||
## Anti-Patterns to Flag
|
||||
|
||||
### God Packages
|
||||
|
||||
**Problem:** Packages like `utils/`, `common/`, `helpers/` become dumping grounds.
|
||||
|
||||
**Symptoms:**
|
||||
- 20+ files in one package
|
||||
- Unrelated functions grouped together
|
||||
- Package imported by everything
|
||||
|
||||
**Fix:** Extract cohesive packages based on what they provide: `validation`, `httputil`, `timeutil`.
|
||||
|
||||
### Circular Dependencies
|
||||
|
||||
**Problem:** Package A imports B, and B imports A (directly or transitively).
|
||||
|
||||
**Symptoms:**
|
||||
- Import cycle compile errors
|
||||
- Difficulty understanding code flow
|
||||
- Changes cascade unexpectedly
|
||||
|
||||
**Fix:**
|
||||
- Extract shared types to a third package
|
||||
- Use interfaces to invert dependency
|
||||
- Merge packages if truly coupled
|
||||
|
||||
### Leaky Abstractions
|
||||
|
||||
**Problem:** Implementation details leak through abstraction boundaries.
|
||||
|
||||
**Symptoms:**
|
||||
- Database types in domain layer
|
||||
- HTTP types in service layer
|
||||
- Framework types in business logic
|
||||
|
||||
**Fix:** Define types at each layer, map between them explicitly.
|
||||
|
||||
### Anemic Domain Model
|
||||
|
||||
**Problem:** Domain objects are just data containers, logic is elsewhere.
|
||||
|
||||
**Symptoms:**
|
||||
- Domain types have only getters/setters
|
||||
- All logic in "service" classes
|
||||
- Domain types can be in invalid states
|
||||
|
||||
**Fix:** Put behavior with data. Domain types should enforce their own invariants.
|
||||
|
||||
### Shotgun Surgery
|
||||
|
||||
**Problem:** Small changes require editing many files across packages.
|
||||
|
||||
**Symptoms:**
|
||||
- Feature adds touch 10+ files
|
||||
- Similar changes in multiple places
|
||||
- Copy-paste between packages
|
||||
|
||||
**Fix:** Consolidate related code. If things change together, they belong together.
|
||||
|
||||
### Feature Envy
|
||||
|
||||
**Problem:** Code in one package is more interested in another package's data.
|
||||
|
||||
**Symptoms:**
|
||||
- Many calls to another package's methods
|
||||
- Pulling data just to compute something
|
||||
- Logic that belongs elsewhere
|
||||
|
||||
**Fix:** Move the code to where the data lives, or extract the behavior to a shared place.
|
||||
|
||||
### Premature Abstraction
|
||||
|
||||
**Problem:** Creating interfaces and abstractions before they're needed.
|
||||
|
||||
**Symptoms:**
|
||||
- Interfaces with single implementations
|
||||
- "Factory" and "Manager" classes everywhere
|
||||
- Configuration for things that never change
|
||||
|
||||
**Fix:** Write concrete code first. Extract abstractions when you have multiple implementations or need to break dependencies.
|
||||
|
||||
### Deep Hierarchy
|
||||
|
||||
**Problem:** Excessive layers of abstraction or inheritance.
|
||||
|
||||
**Symptoms:**
|
||||
- 5+ levels of embedding/composition
|
||||
- Hard to trace code flow
|
||||
- Changes require understanding many layers
|
||||
|
||||
**Fix:** Prefer composition over inheritance. Flatten hierarchies where possible.
|
||||
Reference in New Issue
Block a user