AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Clean Architecture

skill-kinhluan-skills-clean-architecture · by kinhluan

Apply Clean Architecture principles (Dependency Rule, layers, boundaries) to design maintainable, testable systems. Use when structuring code, defining component boundaries, or choosing between layered/hexagonal/onion architectures. Maps to C4 Model L3 Components and DDD Tactical patterns.

No reviews yet
0 installs
30 views
0.0% view→install

Install

$ agentstack add skill-kinhluan-skills-clean-architecture

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-kinhluan-skills-clean-architecture)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Clean Architecture? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Clean Architecture

Separate stable business rules from volatile technical details.

Clean Architecture, formalized by Robert C. Martin (Uncle Bob) in Clean Architecture: A Craftsman's Guide to Software Structure and Design (2017), is an evolution of Layered Architecture, Hexagonal Architecture (Alistair Cockburn, 2005), and Onion Architecture (Jeffrey Palermo). It provides a set of principles for organizing code so that business logic remains independent of frameworks, UI, and databases.

> "The goal of software architecture is to minimize the human resources required to build and maintain the required system." — Robert C. Martin


1. The Dependency Rule

Source code dependencies must always point inward, toward higher-level policies, never outward toward lower-level details.

┌─────────────────────────────────────────────────────────────┐
│                    OUTER LAYERS (Details)                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Frameworks & Drivers                                │   │
│  │  (React, FastAPI, gRPC, PostgreSQL, Redis, AWS)     │   │
│  │  ▲                                                  │   │
│  │  │ depends on                                       │   │
│  │  ▼                                                  │   │
│  │  Interface Adapters                                  │   │
│  │  (Controllers, Presenters, Gateways, Repositories)  │   │
│  │  ▲                                                  │   │
│  │  │ depends on                                       │   │
│  │  ▼                                                  │   │
│  │  Application Business Rules                          │   │
│  │  (Use Cases, Interactors, Application Services)     │   │
│  │  ▲                                                  │   │
│  │  │ depends on                                       │   │
│  │  ▼                                                  │   │
│  │  Enterprise Business Rules                           │   │
│  │  (Entities, Domain Objects, Value Objects)          │   │
│  └─────────────────────────────────────────────────────┘   │
│                    INNER LAYERS (Policies)                   │
└─────────────────────────────────────────────────────────────┘

Key implication: The inner circles (Entities, Use Cases) know nothing about the outer circles. They don't import framework libraries, don't know about the database, don't depend on the HTTP layer.


2. The Four Layers

Layer 1: Entities (Enterprise Business Rules)

What: Core business objects that encapsulate the most general and high-level rules.

Characteristics:

  • Independent of the application — could be shared across multiple apps in the same domain
  • Contain business logic that would exist even without this specific application
  • Plain objects, no framework dependencies
// entity/Order.ts — pure business logic, no framework imports
export class Order {
  private constructor(
    private readonly id: string,
    private items: OrderItem[],
    private status: OrderStatus
  ) {}

  static create(items: OrderItem[]): Order {
    return new Order(crypto.randomUUID(), items, OrderStatus.PENDING);
  }

  addItem(item: OrderItem): void {
    if (this.status !== OrderStatus.PENDING) {
      throw new DomainError('Cannot modify a submitted order');
    }
    this.items.push(item);
  }

  submit(): void {
    if (this.items.length === 0) {
      throw new DomainError('Cannot submit empty order');
    }
    this.status = OrderStatus.SUBMITTED;
  }

  getTotal(): Money {
    return this.items.reduce(
      (sum, item) => sum.add(item.price.multiply(item.quantity)),
      Money.zero()
    );
  }
}

Layer 2: Use Cases (Application Business Rules)

What: Application-specific business rules. Orchestrate the flow of data to and from entities.

Characteristics:

  • One Use Case = one user story / one JTBD outcome
  • Contains NO framework code
  • Depends only on Entities and interfaces (ports) defined in the same layer
// usecase/SubmitOrder.ts
import { Order } from '../entity/Order';
import { OrderRepository } from './port/OrderRepository';
import { PaymentGateway } from './port/PaymentGateway';
import { NotificationService } from './port/NotificationService';

export interface SubmitOrderInput {
  orderId: string;
  paymentMethod: PaymentMethod;
}

export interface SubmitOrderOutput {
  success: boolean;
  transactionId?: string;
  error?: string;
}

export class SubmitOrderUseCase {
  constructor(
    private orderRepo: OrderRepository,
    private paymentGateway: PaymentGateway,
    private notifier: NotificationService
  ) {}

  async execute(input: SubmitOrderInput): Promise {
    const order = await this.orderRepo.findById(input.orderId);
    if (!order) {
      return { success: false, error: 'Order not found' };
    }

    order.submit();

    const payment = await this.paymentGateway.charge(
      order.getTotal(),
      input.paymentMethod
    );

    if (!payment.success) {
      return { success: false, error: payment.error };
    }

    await this.orderRepo.save(order);
    await this.notifier.sendOrderConfirmation(order);

    return { success: true, transactionId: payment.transactionId };
  }
}

Layer 3: Interface Adapters

What: Convert data from the format most convenient for Use Cases and Entities, to the format most convenient for external agencies (frameworks, UI, database).

Characteristics:

  • Controllers handle HTTP requests
  • Presenters format responses
  • Repositories implement data access interfaces
  • Gateways wrap external APIs
// adapter/controller/OrderController.ts
import { SubmitOrderUseCase } from '../../usecase/SubmitOrder';

export class OrderController {
  constructor(private submitOrder: SubmitOrderUseCase) {}

  async handleSubmit(req: HttpRequest): Promise {
    const result = await this.submitOrder.execute({
      orderId: req.body.orderId,
      paymentMethod: req.body.paymentMethod
    });

    if (!result.success) {
      return { status: 400, body: { error: result.error } };
    }

    return { status: 200, body: { transactionId: result.transactionId } };
  }
}
// adapter/repository/PostgresOrderRepository.ts
import { OrderRepository } from '../../usecase/port/OrderRepository';

export class PostgresOrderRepository implements OrderRepository {
  constructor(private db: Knex) {}

  async findById(id: string): Promise {
    const row = await this.db('orders').where({ id }).first();
    return row ? this.toEntity(row) : null;
  }

  async save(order: Order): Promise {
    await this.db('orders')
      .insert(this.toRow(order))
      .onConflict('id')
      .merge();
  }

  private toEntity(row: OrderRow): Order { /* ... */ }
  private toRow(order: Order): OrderRow { /* ... */ }
}

Layer 4: Frameworks & Drivers

What: The outermost layer — frameworks, tools, and delivery mechanisms.

Characteristics:

  • React, Vue, Next.js (UI)
  • Fastify, Express, FastAPI (HTTP)
  • PostgreSQL, MongoDB, Redis (Database)
  • AWS, GCP, Azure (Infrastructure)
  • This layer contains only glue code — wiring dependencies together
// main.ts — composition root, only place where framework imports exist
import fastify from 'fastify';
import knex from 'knex';
import { OrderController } from './adapter/controller/OrderController';
import { SubmitOrderUseCase } from './usecase/SubmitOrder';
import { PostgresOrderRepository } from './adapter/repository/PostgresOrderRepository';
import { StripePaymentGateway } from './adapter/gateway/StripePaymentGateway';
import { EmailNotificationService } from './adapter/service/EmailNotificationService';

const app = fastify();
const db = knex({ client: 'pg', connection: process.env.DATABASE_URL });

// Wire dependencies — this is the ONLY place outer layers connect
const orderRepo = new PostgresOrderRepository(db);
const paymentGateway = new StripePaymentGateway(process.env.STRIPE_KEY);
const notifier = new EmailNotificationService();
const submitOrder = new SubmitOrderUseCase(orderRepo, paymentGateway, notifier);
const orderController = new OrderController(submitOrder);

app.post('/orders/:id/submit', orderController.handleSubmit.bind(orderController));

3. Ports and Adapters (Hexagonal Architecture)

Clean Architecture is conceptually equivalent to Hexagonal Architecture (Alistair Cockburn). The terminology differs:

| Clean Architecture | Hexagonal / Ports & Adapters | Meaning | |---|---|---| | Use Case | Application Service | Orchestrates business flow | | Interface Adapter | Adapter | Bridges between app and external world | | Port (implicit) | Port | Interface defined by the application, implemented by adapter | | Entity | Domain Model | Core business logic |

                    ┌─────────────────┐
                    │   HTTP Client   │
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │  HTTP Adapter   │ ←── Driver Adapter
                    │  (Controller)   │
                    └────────┬────────┘
                             │
        ┌────────────────────┼────────────────────┐
        │              ┌─────▼─────┐              │
        │              │           │              │
        │   ┌──────────┤  Port     ├──────────┐   │
        │   │          │ (interface)│          │   │
        │   │          └───────────┘          │   │
        │   │                                 │   │
        │   │    ┌───────────────────────┐    │   │
        │   │    │    APPLICATION        │    │   │
        │   │    │  ┌─────────────────┐  │    │   │
        │   │    │  │   Use Cases     │  │    │   │
        │   │    │  └─────────────────┘  │    │   │
        │   │    │  ┌─────────────────┐  │    │   │
        │   │    │  │    Entities     │  │    │   │
        │   │    │  └─────────────────┘  │    │   │
        │   │    └───────────────────────┘    │   │
        │   │                                 │   │
        │   │          ┌───────────┐          │   │
        │   │          │   Port    │          │   │
        │   │          │(interface)│          │   │
        │   │          └─────┬─────┘          │   │
        │   │                │                │   │
        │   └────────────────┼────────────────┘   │
        │                    │                    │
        │           ┌────────▼────────┐           │
        │           │  DB Adapter     │ ←── Driven Adapter
        │           │ (Repository)    │
        │           └────────┬────────┘           │
        │                    │                    │
        │           ┌────────▼────────┐           │
        │           │   PostgreSQL    │           │
        │           └─────────────────┘           │
        └─────────────────────────────────────────┘

Key rule: The application defines ports (interfaces). Adapters implement them. The application never knows which adapter is plugged in.


4. Boundaries and Crossing Them

The Boundary Crossing Rule

Data structures crossing boundaries must be simple, serializable DTOs — never framework-specific types.

// ❌ BAD: Framework type leaks into Use Case
class SubmitOrderUseCase {
  async execute(req: FastifyRequest): Promise { // ❌
    // ...
  }
}

// ✅ GOOD: Plain DTO in, plain DTO out
interface SubmitOrderInput {
  orderId: string;
  paymentMethod: PaymentMethod;
}

interface SubmitOrderOutput {
  success: boolean;
  transactionId?: string;
}

class SubmitOrderUseCase {
  async execute(input: SubmitOrderInput): Promise {
    // ... pure business logic
  }
}

Boundary Types

| Boundary | What it separates | Example | |---|---|---| | Use Case → Entity | App logic ↔ Domain logic | Use Case calls order.submit() | | Adapter → Use Case | Framework ↔ App logic | Controller calls Use Case | | Adapter → External | App ↔ Database/API | Repository calls PostgreSQL | | Process Boundary | Different deployable units | gRPC/HTTP between services |


5. Package by Component (Simon Brown)

Simon Brown, creator of C4 Model, contributed "The Missing Chapter" to Uncle Bob's Clean Architecture book. His key insight:

> Organize code by component, not by layer.

Package by Layer (Anti-pattern)

src/
  controllers/     ← All controllers
  services/        ← All services
  repositories/    ← All repositories
  models/          ← All entities

Problem: Business logic scattered across layers. High coupling. Hard to find related code.

Package by Feature (Better)

src/
  order/
    controller.ts
    service.ts
    repository.ts
    model.ts
  user/
    controller.ts
    service.ts
    repository.ts
    model.ts

Problem: Still no encapsulation — user/service.ts can import order/model.ts directly.

Package by Component (Recommended)

src/
  order/
    index.ts              ← Public API: OrderService, Order, OrderRepository
    internal/
      OrderEntity.ts      ← Entities (private)
      SubmitOrder.ts      ← Use Case (private)
      OrderController.ts  ← Adapter (private)
      PostgresOrderRepo.ts ← Adapter (private)
  user/
    index.ts              ← Public API: UserService, User, UserRepository
    internal/
      ...

Benefits:

  • Each component is self-contained
  • Public API surface is small and intentional
  • Internal structure can change without affecting other components
  • Maps directly to C4 L3 Components

6. Clean Architecture + C4 Model Integration

| C4 Level | Clean Architecture Layer | What to Draw | |---|---|---| | L1 System Context | System boundary | External actors, other systems | | L2 Containers | Deployable units | Web app, API service, database, message queue | | L3 Components | Package by Component | Order component, User component, Payment component | | L4 Code | Classes inside component | Entities, Use Cases, Repositories, Controllers |

Workflow:

1. C4 L1 → Define system scope and external dependencies
2. C4 L2 → Identify containers (deployable units)
3. Clean Architecture → Design component boundaries within each container
4. C4 L3 → Draw components as self-contained packages
5. Clean Architecture → Implement: Entities → Use Cases → Adapters
6. C4 L4 → Document complex Entities with UML class diagrams

7. Clean Architecture + DDD Integration

| Clean Architecture | DDD Tactical | Relationship | |---|---|---| | Entity | Aggregate Root / Entity | Same concept — encapsulates business rules | | Value Object | Value Object | Identical — immutable, compared by value | | Use Case | Application Service | Orchestrates domain operations | | Repository (port) | Repository | Abstracts persistence | | Domain Service | Domain Service | Cross-aggregate business logic | | Interface Adapter | Infrastructure Layer | Implements ports with concrete technology |

Key difference:

  • DDD focuses on discovering the domain model (Event Storming, Ubiquitous Language)
  • Clean Architecture focuses on structuring the code to protect that model from technical details

Best practice: Use DDD to discover the model, then use Clean Architecture to implement it.


8. Testing Strategy

Clean Architecture enables a testing pyramid without integration tests for every case:

         ┌─────────┐
         │   E2E   │  ← 1-2 tests: happy path through HTTP + DB
         │  (slow) │
         └────┬────┘
              │
         ┌────▼────┐
         │ Adapter │  ← Mock the port, test adapter logic
         │  Tests  │
         └────┬────┘
              │
         ┌────▼────┐
         │  Use    │  ← Mock repositories, test business flow
         │  Case   │
         │  Tests  │
         └────┬────┘
              │
         ┌────▼────┐
         │ Entity  │  ←

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [kinhluan](https://github.com/kinhluan)
- **Source:** [kinhluan/skills](https://github.com/kinhluan/skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.