CtrlK
BlogDocsLog inGet started
Tessl Logo

clean-architecture

Apply Clean Architecture principles to define layer boundaries, identify dependency violations, and structure domain vs infrastructure code. Use when designing service boundaries, separating business logic from infrastructure, evaluating hexagonal/onion/ports-and-adapters architecture, structuring module layout, or resolving dependency inversion and circular dependency issues.

68

Quality

83%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

The risk profile of this skill

SKILL.md
Quality
Evals
Security

Clean Architecture

Strategic architecture principles for boundaries, dependencies, and layered system design.

When to Use

  • Designing service boundaries or module structure
  • Evaluating dependency direction and circular dependencies
  • Structuring new applications or refactoring monoliths
  • Assessing where to place entities, use cases, adapters, and frameworks
  • Deciding when to introduce architectural boundaries

When Not to Use

  • Class-level design decisions (use solid-principles)
  • Choosing structural patterns like Factory or Adapter (use design-patterns)
  • Testing strategy configuration (use testable-design)

Architecture Layers

Dependency Rule: Dependencies point inward only. Inner layers know nothing about outer layers.

Layers (inner → outer): EntitiesUse CasesInterface AdaptersFrameworks & Drivers

See references/dep-inward-only.md for layer definitions and examples.

Workflow

Step 1: Identify Decision Type

Output: Classify as entity, use case, adapter, or framework decision.

Ask:

  • Does this logic belong to core business rules? → Entity
  • Does it orchestrate business workflows? → Use Case
  • Does it translate between layers? → Adapter
  • Is it external infrastructure? → Framework

Step 2: Apply Dependency Direction Checks

Output: Dependency violations with corrective actions.

Checklist:

  • Dependencies point inward (no outer layers importing inner layers)
  • No circular dependencies between modules
  • Clear ownership of interfaces (defined by inner layers, implemented by outer layers)
  • Entities remain pure (no framework or infrastructure imports)

Example:

Violation: Entity imports ORM decorator from infrastructure.
Refactor: Define entity as plain object; map to ORM in repository (adapter layer).

Step 3: Design Use Cases

Output: Use case interface with input/output ports.

Template:

// Use Case (application layer)
interface CreateOrderUseCase {
  execute(input: CreateOrderInput): Promise<CreateOrderOutput>
}

// Input/Output ports (defined by use case)
interface CreateOrderInput {
  userId: string
  items: OrderItem[]
}

interface CreateOrderOutput {
  orderId: string
  status: string
}

Use cases:

  • Have one responsibility (single workflow)
  • Depend on abstractions (ports), not concrete implementations
  • Orchestrate entities and gateways
  • Contain no presentation logic (formatting, HTTP, UI)

Step 4: Define Boundaries with Adapters

Output: Adapter interfaces (ports) and implementations.

  • Controllers: HTTP requests → use case input
  • Presenters: Use case output → HTTP response
  • Gateways: Use case calls → database/API operations
  • Repositories: Entity persistence → database operations

Example:

// Port (defined by use case layer)
interface IOrderRepository {
  save(order: Order): Promise<void>
  findById(id: string): Promise<Order | null>
}

// Adapter (infrastructure layer)
class PostgresOrderRepository implements IOrderRepository {
  async save(order: Order): Promise<void> {
    // Map entity to ORM, persist
  }
}

Step 5: Place Framework Code at Edges

Output: Framework integrations isolated in outermost layer.

Keep frameworks (Express, NestJS, TypeORM, React) in the infrastructure/adapter layer:

  • DI container at application edge
  • ORM configurations in infrastructure
  • Web server in infrastructure
  • Domain remains framework-free

Example:

BAD: Entity uses @Entity decorator from TypeORM.
GOOD: Entity is plain TypeScript; repository maps to TypeORM in infrastructure.

Step 6: Document Boundary Decisions

Output: ADR with rationale, alternatives, and risks.

Template:

Decision: Extract authentication into separate bounded context.
Rationale: Auth has independent lifecycle and team ownership.
Alternatives: 
  - Keep in monolith (simpler, tightly coupled)
  - Partial boundary (YAGNI, easier to extract later)
Chosen: Full bounded context (clear ownership, independent deployment)
Risks: Network calls add latency; requires distributed transaction handling.

Anti-Patterns

NEVER allow circular dependencies

BAD: Module A imports B and B imports A.
GOOD: Extract shared contract/module and invert dependencies.

WHY: a cycle means neither module can be understood, tested, or deployed independently — changing one always risks breaking the other, and most build/bundler tools cannot even guarantee a deterministic load order across the cycle.

NEVER let entities depend on frameworks

BAD: Entity imports ORM decorators, framework types, or infrastructure.
GOOD: Entities are plain objects; adapters handle framework mapping.

WHY: once an entity carries an @Entity decorator or a framework base class, every business-rule test must boot that framework too, and swapping the ORM means rewriting the domain model instead of one adapter.

NEVER put business logic in controllers

BAD: Controller validates, calculates, and persists data.
GOOD: Controller calls use case; use case orchestrates business logic.

WHY: business logic trapped in a controller can only be exercised through an HTTP request — it MUST be re-implemented for a CLI, a queue consumer, or a test that wants to skip the transport layer entirely.

NEVER bypass interface contracts

BAD: Use case instantiates concrete PostgresRepository.
GOOD: Use case depends on IRepository interface; DI provides implementation.

WHY: a use case that names a concrete repository class can never be unit-tested without a real (or heavily mocked) database, and swapping storage engines means editing every use case instead of one adapter.

NEVER design boundaries for imagined future requirements

BAD: Add full hexagonal architecture "in case" of future DB migration.
GOOD: Solve current need; refactor when trigger appears (YAGNI).

WHY: a boundary drawn for a migration that never happens is pure ongoing tax — every future change must thread through an abstraction layer that protects against a scenario nobody triggered.

NEVER let a repository interface leak persistence details into the domain

BAD:

interface IOrderRepository {
  findByRawSql(query: string): Promise<OrderRow[]>
}

GOOD:

interface IOrderRepository {
  findById(id: string): Promise<Order | null>
}

WHY: a port that exposes SQL, ORM query builders, or database row shapes ensures the use case layer is coupled to the storage engine even though it depends only on an "abstraction" — the interface must be defined entirely in domain terms, never in persistence terms.

Quick Commands

# Find dependency direction violations
rg -n "import.*infrastructure.*from.*domain|import.*adapter.*from.*entity" src
# Find circular dependencies
nx graph
# Find framework leakage into domain
rg -n "@Entity|@Injectable|@Component" src/domain

References

Dependencies: inward-only · acyclic · data crossing boundaries · no framework imports

Components: screaming architecture · stable dependencies

Boundaries: cost awareness · defer decisions · service internal architecture

Entities: purity · rich not anemic · encapsulate invariants · value objects · no persistence awareness

Use Cases: isolation · explicit dependencies · orchestrates not implements · input/output ports · no presentation logic · transaction boundary

Adapters: gateway abstraction · thin controller · mapper translation · presenter formats

Frameworks: DI at edge · domain purity · ORM in infrastructure · web in infrastructure

  • Clean Architecture (Uncle Bob)
  • Hexagonal Architecture (Alistair Cockburn)
  • Screaming Architecture
Repository
pantheon-org/tekhne
Last updated
First committed

Also appears in

pantheon-ai/design-principles
Stale

last in sync Aug 28, 2026

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.