Some checks failed
release / tag (push) Has been cancelled
Phase 1 of mathias/skills extraction (infra#62 Track D — homelab next-step plan addendum). Imports ~/dev/.skills/ verbatim (19 skill dirs + SKILLS_INDEX.md) and adds the installation surface: - Taskfile.yml — install / update / list / release / check targets - install.sh — bootstrap installer for hosts without Task. Idempotent symlink wirer; default checkout at ~/.local/share/skills/ on every host; SKILLS_REF env var pins a tag (default: main). - .gitea/workflows/release.yml — auto-tag every push to main by Bump-Type footer (major/minor/patch, default patch). Skipped when commit contains [skip-release]. - README — usage, versioning, contribution flow, secret-hygiene rule. Phase 1 wires Claude Code only (~/.claude/skills/<name> global + <repo>/.claude/skills/<name> per-repo). Phase 2 adds Crush, opencode, antigravity, and gitea-resident agents (cobalt-dingo, agentsquad) once their skill conventions are researched. Public repo, markdown-only — no secrets, no client names. Verified via pre-push grep before initial push. [skip-release]
319 lines
8.5 KiB
Markdown
319 lines
8.5 KiB
Markdown
# Software Architecture
|
|
|
|
## The Goal of Architecture
|
|
|
|
Enable the development team to:
|
|
1. **Add** features with minimal friction
|
|
2. **Change** existing features safely
|
|
3. **Remove** features cleanly
|
|
4. **Test** features in isolation
|
|
5. **Deploy** independently when possible
|
|
|
|
## Architectural Principles
|
|
|
|
### 1. Vertical Boundaries (Features/Slices)
|
|
|
|
Organize by **feature**, not by technical layer.
|
|
|
|
```
|
|
BAD: Layer-first
|
|
src/
|
|
controllers/
|
|
UserController.ts
|
|
OrderController.ts
|
|
services/
|
|
UserService.ts
|
|
OrderService.ts
|
|
repositories/
|
|
UserRepository.ts
|
|
OrderRepository.ts
|
|
|
|
GOOD: Feature-first
|
|
src/
|
|
users/
|
|
UserController.ts
|
|
UserService.ts
|
|
UserRepository.ts
|
|
orders/
|
|
OrderController.ts
|
|
OrderService.ts
|
|
OrderRepository.ts
|
|
```
|
|
|
|
**Why:** Changes to "users" feature stay in `users/`. High cohesion within features.
|
|
|
|
### 2. Horizontal Boundaries (Layers)
|
|
|
|
Separate concerns into layers with clear dependencies.
|
|
|
|
```
|
|
┌──────────────────────────────────────┐
|
|
│ Presentation │ UI, Controllers, CLI
|
|
├──────────────────────────────────────┤
|
|
│ Application │ Use Cases, Orchestration
|
|
├──────────────────────────────────────┤
|
|
│ Domain │ Business Logic, Entities
|
|
├──────────────────────────────────────┤
|
|
│ Infrastructure │ Database, APIs, External
|
|
└──────────────────────────────────────┘
|
|
```
|
|
|
|
### 3. The Dependency Rule
|
|
|
|
**Dependencies point INWARD.**
|
|
|
|
```
|
|
Infrastructure → Application → Domain
|
|
↓ ↓ ↓
|
|
(outer) (middle) (inner)
|
|
```
|
|
|
|
- Inner layers know NOTHING about outer layers
|
|
- Domain has zero dependencies on infrastructure
|
|
- Use interfaces to invert dependencies
|
|
|
|
```typescript
|
|
// Domain defines the interface (inner)
|
|
interface UserRepository {
|
|
save(user: User): Promise<void>;
|
|
findById(id: UserId): Promise<User | null>;
|
|
}
|
|
|
|
// Infrastructure implements it (outer)
|
|
class PostgresUserRepository implements UserRepository {
|
|
save(user: User): Promise<void> {
|
|
// SQL here
|
|
}
|
|
}
|
|
|
|
// Domain service uses the interface
|
|
class UserService {
|
|
constructor(private repo: UserRepository) {} // Depends on abstraction
|
|
}
|
|
```
|
|
|
|
### 4. Contracts
|
|
|
|
Interfaces define boundaries between components.
|
|
|
|
```typescript
|
|
// The contract
|
|
interface PaymentGateway {
|
|
charge(amount: Money, card: CardDetails): Promise<ChargeResult>;
|
|
refund(chargeId: string): Promise<RefundResult>;
|
|
}
|
|
|
|
// Multiple implementations possible
|
|
class StripeGateway implements PaymentGateway { }
|
|
class PayPalGateway implements PaymentGateway { }
|
|
class MockGateway implements PaymentGateway { } // For tests
|
|
```
|
|
|
|
### 5. Cross-Cutting Concerns
|
|
|
|
Concerns that span multiple features: logging, auth, validation, error handling.
|
|
|
|
**Options:**
|
|
- Middleware/interceptors
|
|
- Decorators
|
|
- Aspect-oriented approaches
|
|
- Base classes (use sparingly)
|
|
|
|
```typescript
|
|
// Middleware approach
|
|
class LoggingMiddleware {
|
|
handle(request: Request, next: Handler): Response {
|
|
console.log(`Request: ${request.path}`);
|
|
const response = next(request);
|
|
console.log(`Response: ${response.status}`);
|
|
return response;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. Conway's Law
|
|
|
|
> "Organizations design systems that mirror their communication structure."
|
|
|
|
**Implication:** Team structure affects architecture. Align both intentionally.
|
|
|
|
---
|
|
|
|
## Common Architectural Styles
|
|
|
|
### Layered Architecture
|
|
|
|
Traditional layers: Presentation → Business → Persistence
|
|
|
|
**Pros:** Simple, well-understood
|
|
**Cons:** Can become a "big ball of mud" without discipline
|
|
|
|
### Hexagonal Architecture (Ports & Adapters)
|
|
|
|
Domain at center, adapters around the edges.
|
|
|
|
```
|
|
┌─────────────────────┐
|
|
│ HTTP Adapter │
|
|
└─────────┬───────────┘
|
|
│
|
|
┌─────────────────▼─────────────────┐
|
|
│ DOMAIN │
|
|
│ ┌─────────────────────────┐ │
|
|
│ │ Business Logic │ │
|
|
│ │ Use Cases │ │
|
|
│ └─────────────────────────┘ │
|
|
└─────────────────┬─────────────────┘
|
|
│
|
|
┌─────────▼───────────┐
|
|
│ Database Adapter │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
**Ports:** Interfaces defined by the domain
|
|
**Adapters:** Implementations that connect to the outside world
|
|
|
|
### Clean Architecture
|
|
|
|
Similar to Hexagonal, with explicit layers:
|
|
|
|
1. **Entities** - Enterprise business rules
|
|
2. **Use Cases** - Application business rules
|
|
3. **Interface Adapters** - Controllers, Presenters, Gateways
|
|
4. **Frameworks & Drivers** - Web, DB, External interfaces
|
|
|
|
---
|
|
|
|
## Feature-Driven Structure (Frontend)
|
|
|
|
```
|
|
src/
|
|
features/
|
|
auth/
|
|
components/
|
|
LoginForm.tsx
|
|
SignupForm.tsx
|
|
hooks/
|
|
useAuth.ts
|
|
services/
|
|
authService.ts
|
|
types/
|
|
auth.types.ts
|
|
index.ts # Public API
|
|
checkout/
|
|
components/
|
|
hooks/
|
|
services/
|
|
types/
|
|
index.ts
|
|
shared/
|
|
components/ # Truly shared UI
|
|
hooks/ # Truly shared hooks
|
|
utils/ # Truly shared utilities
|
|
```
|
|
|
|
---
|
|
|
|
## Feature-Driven Structure (Backend)
|
|
|
|
```
|
|
src/
|
|
modules/
|
|
users/
|
|
domain/
|
|
User.ts
|
|
UserRepository.ts # Interface
|
|
application/
|
|
CreateUser.ts # Use case
|
|
GetUser.ts # Use case
|
|
infrastructure/
|
|
PostgresUserRepo.ts
|
|
presentation/
|
|
UserController.ts
|
|
UserDTO.ts
|
|
orders/
|
|
domain/
|
|
application/
|
|
infrastructure/
|
|
presentation/
|
|
shared/
|
|
domain/ # Shared value objects
|
|
infrastructure/ # Shared infra utilities
|
|
```
|
|
|
|
---
|
|
|
|
## The Walking Skeleton
|
|
|
|
Start with a minimal end-to-end slice:
|
|
|
|
1. **Thinnest possible feature** that touches all layers
|
|
2. **Deployable** from day one
|
|
3. **Proves the architecture** works
|
|
|
|
Example walking skeleton for e-commerce:
|
|
- User can view ONE product (hardcoded)
|
|
- User can add it to cart
|
|
- User can "checkout" (just logs)
|
|
|
|
From there, flesh out each feature fully.
|
|
|
|
---
|
|
|
|
## Testing Architecture
|
|
|
|
```
|
|
┌────────────────────────────────────────────┐
|
|
│ E2E / Acceptance Tests │ Few, slow, high confidence
|
|
├────────────────────────────────────────────┤
|
|
│ Integration Tests │ Some, medium speed
|
|
├────────────────────────────────────────────┤
|
|
│ Unit Tests │ Many, fast, isolated
|
|
└────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Test by layer:**
|
|
- **Domain:** Unit tests (most tests here)
|
|
- **Application:** Integration tests with mocked infra
|
|
- **Infrastructure:** Integration tests with real dependencies
|
|
- **E2E:** Critical paths only
|
|
|
|
---
|
|
|
|
## Architecture Decision Records (ADRs)
|
|
|
|
Document significant decisions:
|
|
|
|
```markdown
|
|
# ADR 001: Use PostgreSQL for persistence
|
|
|
|
## Status
|
|
Accepted
|
|
|
|
## Context
|
|
We need a database. Options: PostgreSQL, MongoDB, MySQL
|
|
|
|
## Decision
|
|
PostgreSQL for:
|
|
- ACID compliance
|
|
- Team familiarity
|
|
- JSON support for flexibility
|
|
|
|
## Consequences
|
|
- Need PostgreSQL expertise
|
|
- Schema migrations required
|
|
- Excellent query capabilities
|
|
```
|
|
|
|
---
|
|
|
|
## Red Flags in Architecture
|
|
|
|
- **Circular dependencies** between modules
|
|
- **Domain depending on infrastructure**
|
|
- **Framework code in business logic**
|
|
- **No clear boundaries** between features
|
|
- **Shared mutable state** across modules
|
|
- **"Util" or "Common" packages** that grow forever
|
|
- **Database schema driving domain model**
|