Files
skills/solid/references/architecture.md
Mathias d6a71e370e
Some checks failed
release / tag (push) Has been cancelled
chore: bootstrap skills library — 19 skills + installer + CI auto-tag
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]
2026-05-24 14:59:54 +02:00

8.5 KiB

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

// 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)
// 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:

# 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