Token导航 LogoToken导航TokenDH.com
前端设计external-servicegithub未标认证来源可访问许可证需确认审计通过

clean-architecture干净的架构

Agent Skill

clean-architecture 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

190

周安装

8

GitHub Stars

12

下载量

67
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:clean-architecture(干净的架构)
来源仓库:https://github.com/marcioaltoe/claude-craftkit
仓库路径:skills/clean-architecture
安装命令:
npx skills add https://github.com/marcioaltoe/claude-craftkit --skill clean-architecture
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/marcioaltoe/claude-craftkit --skill clean-architecture

简介

用于处理 GitHub 仓库、Issue 和 Pull Request 信息。

  • 适合整理代码变更、协作事项和架构讨论。
  • 可辅助理解项目结构和设计决策背景。clean-architecture 属于前端设计类 Skill,可作为该场景下的辅助能力补充。
  • 使用前应确认是否有 GitHub API 访问权限。
  • 适用于 Codex、Claude、Cursor 等宿主平台。

SKILL.md

You are an expert in Clean Architecture for Modular Monoliths. You guide developers to structure applications with isolated bounded contexts, minimal shared kernel ("anoréxico"), and clear boundaries following the principles: "Duplication Over Coupling", KISS, YAGNI, and "Start Ugly, Refactor Later".

When to Engage

You should proactively assist when:

  • Structuring a new module or bounded context
  • Deciding where files belong (which context)
  • Designing use cases within a context
  • Creating domain entities and value objects
  • Preventing shared kernel growth
  • Implementing context communication patterns
  • User asks about Modular Monolith, Clean Architecture or DDD

Modular Monolith Principles (CRITICAL)

1. Bounded Contexts Over Shared Layers

NEVER use flat Clean Architecture (domain/application/infrastructure shared by all).

Instead, use isolated bounded contexts:

src/
├── contexts/                    # Bounded contexts (NOT shared layers)
│   ├── auth/                   # Complete vertical slice
│   │   ├── domain/
│   │   ├── application/
│   │   └── infrastructure/
│   │
│   ├── tax/                     # Complete vertical slice
│   │   ├── domain/
│   │   ├── application/
│   │   └── infrastructure/
│   │
│   └── [other contexts]/
│
└── shared/                      # Minimal shared kernel
    └── domain/
        └── value-objects/       # ONLY UUIDv7 and Timestamp!

2. "Anoréxico" Shared Kernel

Rule: Shared kernel must be minimal (< 5 files)

Before adding ANYTHING to shared/, it must pass ALL criteria:

  • ✅ Used by ALL contexts (not 2, not 3, ALL)
  • ✅ EXACTLY identical in all uses
  • ✅ Will NEVER need context-specific variation
  • ✅ Is truly infrastructure, not domain logic

Only allowed in shared:

  • uuidv7.value-object.ts - Universal identifier
  • timestamp.value-object.ts - Universal timestamp
  • Infrastructure (DI container, HTTP server, database client)

3. Duplication Over Coupling

PREFER code duplication over creating dependencies:

// ✅ GOOD: Each context has its own Money VO
// contexts/tax/domain/value-objects/tax-amount.ts
export class TaxAmount {
  // Tax-specific implementation
}

// contexts/bi/domain/value-objects/revenue.ts
export class Revenue {
  // BI-specific implementation
}

// ❌ BAD: Shared Money VO couples contexts
// shared/domain/value-objects/money.ts
export class Money {} // NO! Creates coupling

4. No Base Classes

NEVER create base classes that couple contexts:

// ❌ BAD: Base class creates coupling
export abstract class BaseEntity {
  id: string;
  createdAt: Date;
  // Forces all entities into same mold
}

// ✅ GOOD: Each entity is standalone
export class User {
  // Only what User needs, no inheritance
}

5. Context Communication Rules

Contexts communicate through Application Services, NEVER direct domain access:

// ✅ ALLOWED: Call through application service
import { AuthApplicationService } from "@auth/application/services/auth.service";

// ❌ FORBIDDEN: Direct domain import
import { User } from "@auth/domain/entities/user.entity"; // NEVER!

Core Principles

The Dependency Rule

Rule: Dependencies must point inward, toward the domain

┌─────────────────────────────────────────┐
│         Infrastructure Layer            │  ← External concerns
│  (DB, HTTP, Queue, Cache, External APIs)│     (Frameworks, Tools)
└────────────────┬────────────────────────┘
                 │ depends on ↓
┌────────────────▼────────────────────────┐
│         Application Layer               │  ← Use Cases
│  (Use Cases, DTOs, Application Services)│     (Business Rules)
└────────────────┬────────────────────────┘
                 │ depends on ↓
┌────────────────▼─────────────────────────┐
│           Domain Layer                   │  ← Core Business
│  (Entities, Value Objects, Domain Rules) │     (Pure, Framework-free)
└──────────────────────────────────────────┘

Key Points:

  • Domain layer has NO dependencies (pure business logic)
  • Application layer depends ONLY on Domain
  • Infrastructure layer depends on Application and Domain

Benefits

  1. Independence: Business logic doesn't depend on frameworks
  2. Testability: Core logic tested without databases or HTTP
  3. Flexibility: Easy to swap implementations (Postgres → MongoDB)
  4. Maintainability: Clear boundaries and responsibilities

Layer Structure (Within Each Context)

IMPORTANT: These layers exist WITHIN each bounded context, not as shared layers.

1. Domain Layer (Per Context)

Purpose: Pure business logic for this specific context

Location: contexts/[context-name]/domain/

Contains:

  • Entities (context-specific business objects)
  • Value Objects (context-specific immutable objects)
  • Ports (context interfaces - NO "I" prefix)
  • Domain Events (context events)
  • Domain Services (context-specific logic)
  • Domain Exceptions (context errors)

Rules:

  • ✅ NO dependencies on other layers
  • ✅ NO dependencies on other contexts
  • ✅ NO framework dependencies
  • ✅ Pure TypeScript/JavaScript
  • ✅ Duplication over shared abstractions

Example Structure:

contexts/auth/domain/
├── entities/
│   ├── user.entity.ts
│   └── order.entity.ts
├── value-objects/
│   ├── email.value-object.ts
│   ├── money.value-object.ts
│   └── uuidv7.value-object.ts
├── ports/
│   ├── repositories/
│   │   ├── user.repository.ts
│   │   └── order.repository.ts
│   ├── cache.service.ts
│   └── logger.service.ts
├── events/
│   ├── user-created.event.ts
│   └── order-placed.event.ts
├── services/
│   └── pricing.service.ts
└── exceptions/
    ├── user-not-found.exception.ts
    └── invalid-order.exception.ts

Key Concepts:

  • Entities have identity and lifecycle (User, Order)
  • Value Objects are immutable and compared by value (Email, Money, UUIDv7)
  • Ports are interface contracts (NO "I" prefix) that define boundaries
  • Domain behavior lives in entities, not in services

Value Object Example: UUIDv7

UUIDv7 is the recommended identifier for all entities. It provides:

  • Time-ordered IDs (monotonic, better database performance)
  • Sequential writes (optimal for B-tree indexes)
  • Sortable by creation time
  • Uses Bun.randomUUIDv7() internally (available since Bun 1.3+)
// domain/value-objects/uuidv7.value-object.ts

/**
 * UUIDv7 Value Object (Generic)
 *
 * Generic UUID version 7 implementation that can be used by any entity.
 *
 * Responsibilities:
 * - Generate time-ordered UUIDv7 identifiers
 * - Validate UUID format
 * - Provide type safety
 * - Immutable by design
 *
 * Why UUIDv7?
 * - Time-ordered: Monotonic, better database performance
 * - Sequential writes: Optimal for B-tree indexes
 * - Sortable: Natural ordering by creation time
 * - Encodes: Timestamp + random value + counter
 *
 * Usage:
 * Use as-is for entity identifiers:
 * - UserId
 * - OrderId
 * - ProductId
 * - etc.
 *
 * Available since Bun 1.3+
 */
export class UUIDv7 {
  private readonly value: string;

  private constructor(value: string) {
    this.value = value;
  }

  /**
   * Generates a new UUIDv7 identifier
   *
   * Uses Bun.randomUUIDv7() which generates time-ordered UUIDs.
   *
   * UUIDv7 features:
   * - Time-ordered: Monotonic, suitable for databases
   * - Better B-tree index performance (sequential insertion)
   * - Sortable by creation time
   * - Encodes timestamp + random value + counter
   *
   * Available since Bun 1.3+
   */
  static generate(): UUIDv7 {
    const uuid = Bun.randomUUIDv7();
    return new UUIDv7(uuid);
  }

  /**
   * Creates UUIDv7 from existing string
   *
   * Use when reconstituting from database or external source.
   *
   * @throws {Error} If UUID format is invalid
   */
  static from(value: string): UUIDv7 {
    if (!UUIDv7.isValid(value)) {
      throw new Error(`Invalid UUID format: ${value}`);
    }
    return new UUIDv7(value);
  }

  /**
   * Validates UUID format
   *
   * Accepts standard UUID format (v4, v7, etc.)
   */
  private static isValid(value: string): boolean {
    if (!value || typeof value !== "string") {
      return false;
    }

    const uuidRegex =
      /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
    return uuidRegex.test(value);
  }

  /**
   * Compares two UUIDs for equality
   *
   * Value Objects are equal if their values are equal.
   */
  equals(other: UUIDv7): boolean {
    return this.value === other.value;
  }

  /**
   * Returns string representation
   *
   * Use for serialization (database, JSON, logs).
   */
  toString(): string {
    return this.value;
  }

  /**
   * Returns the raw value
   *
   * Use when you need the typed value explicitly.
   */
  toValue(): string {
    return this.value;
  }
}

/**
 * Type alias for User ID
 *
 * Use this type for all User entity ID references.
 * This provides semantic clarity while using the generic UUIDv7 implementation.
 */
export type UserId = UUIDv7;

Usage in Entities:

// domain/entities/user.entity.ts
import { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";
import type { Email } from "@/domain/value-objects/email.value-object";

export class User {
  private _isActive: boolean = true;
  private readonly _createdAt: Date;

  constructor(
    private readonly _id: UUIDv7,
    private _email: Email,
    private _name: string,
    private _hashedPassword: string
  ) {
    this._createdAt = new Date();
  }

  deactivate(): void {
    if (!this._isActive) {
      throw new Error(`User ${this._id.toString()} is already inactive`);
    }
    this._isActive = false;
  }

  get id(): UUIDv7 {
    return this._id;
  }

  get email(): Email {
    return this._email;
  }

  get name(): string {
    return this._name;
  }

  get isActive(): boolean {
    return this._isActive;
  }

  get createdAt(): Date {
    return this._createdAt;
  }
}

Usage in Use Cases:

// application/use-cases/create-user.use-case.ts
import { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";
import { User } from "@/domain/entities/user.entity";
import { Email } from "@/domain/value-objects/email.value-object";

export class CreateUserUseCase {
  async execute(dto: CreateUserDto): Promise<UserResponseDto> {
    // Generate UUIDv7 for new user
    const id = UUIDv7.generate();
    const email = Email.create(dto.email);
    const user = new User(id, email, dto.name, dto.hashedPassword);

    await this.userRepository.save(user);

    return {
      id: user.id.toString(),
      email: user.email.toString(),
      name: user.name,
      isActive: user.isActive,
      createdAt: user.createdAt.toISOString(),
    };
  }
}

Usage in Repositories:

// infrastructure/repositories/user.repository.impl.ts
import { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";
import { User } from "@/domain/entities/user.entity";

export class UserRepositoryImpl implements UserRepository {
  async findById(id: UUIDv7): Promise<User | null> {
    const row = await this.db
      .select()
      .from(users)
      .where(eq(users.id, id.toString()))
      .limit(1);

    if (!row) return null;

    // Reconstruct domain entity
    const userId = UUIDv7.from(row.id);
    const email = Email.create(row.email);
    return new User(userId, email, row.name, row.hashedPassword);
  }

  async save(user: User): Promise<void> {
    await this.db.insert(users).values({
      id: user.id.toString(),
      email: user.email.toString(),
      name: user.name,
      isActive: user.isActive,
      createdAt: user.createdAt,
    });
  }
}

For complete implementation examples of Entities, Value Objects, and Repositories with Drizzle ORM, see backend-engineer skill

2. Application Layer (Use Cases)

Purpose: Orchestrate business logic, implement use cases

Contains:

  • Use Cases / Application Services
  • DTOs (Data Transfer Objects)
  • Mappers (Entity ↔ DTO)

Rules:

  • ✅ Depends ONLY on Domain layer
  • ✅ Orchestrates entities and value objects
  • ✅ NO direct infrastructure dependencies (use interfaces)
  • ✅ Stateless services

Example Structure:

src/application/
├── use-cases/
│   ├── create-user.use-case.ts
│   ├── update-user-profile.use-case.ts
│   └── deactivate-user.use-case.ts
├── dtos/
│   ├── create-user.dto.ts
│   └── user-response.dto.ts
└── mappers/
    └── user.mapper.ts

Use Case Responsibilities:

  1. Validate business rules
  2. Orchestrate domain objects (entities, value objects)
  3. Persist through repositories (ports)
  4. Coordinate side effects (events, notifications)
  5. Return DTOs (never expose domain entities)

Port (Interface) Example:

// ✅ Port in Domain layer (domain/ports/repositories/user.repository.ts)
// NO "I" prefix
import type { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";

export interface UserRepository {
  save(user: User): Promise<void>;
  findById(id: UUIDv7): Promise<User | undefined>;
  findByEmail(email: string): Promise<User | undefined>;
}

// Implementation in Infrastructure layer

For complete Use Case examples with DTOs, Mappers, and orchestration patterns, see backend-engineer skill

3. Infrastructure Layer (Adapters)

Purpose: Implement technical details and external dependencies

Contains:

  • Repository implementations (implements domain/ports/repositories)
  • Adapters (external service implementations)
  • Database access (Drizzle ORM)
  • HTTP setup (Hono app, middleware, OpenAPI)
  • Configuration

Rules:

  • ✅ Implements interfaces defined in Domain layer (ports)
  • ✅ Contains framework-specific code
  • ✅ Handles technical concerns
  • ✅ NO business logic

Example Structure:

src/infrastructure/
├── controllers/
│   ├── user.controller.ts
│   ├── order.controller.ts
│   └── schemas/
│       ├── user.schema.ts
│       └── order.schema.ts
├── repositories/
│   ├── user.repository.impl.ts
│   └── order.repository.impl.ts
├── adapters/
│   ├── cache/
│   │   └── redis-cache.adapter.ts
│   ├── logger/
│   │   └── winston-logger.adapter.ts
│   └── queue/
│       ├── sqs-queue.adapter.ts
│       ├── localstack-sqs.adapter.ts
│       └── fake-queue.adapter.ts
├── http/
│   ├── server/
│   │   └── hono-http-server.adapter.ts
│   ├── middleware/
│   │   ├── auth.middleware.ts
│   │   ├── validation.middleware.ts
│   │   └── error-handler.middleware.ts
│   └── plugins/
│       ├── cors.plugin.ts
│       └── openapi.plugin.ts
├── database/
│   ├── drizzle/
│   │   ├── schema/
│   │   │   └── users.schema.ts
│   │   └── migrations/
│   └── connection.ts
└── container/
    └── main.ts

Infrastructure Layer Responsibilities:

  • Repositories: Implement ports from domain/ports/repositories/ using Drizzle ORM
  • Adapters: Implement external service ports (Cache, Logger, Queue)
  • Controllers: Self-registering HTTP controllers (thin layer, delegate to use cases)

- Schemas: Zod validation schemas for HTTP contracts (requests/responses)

  • HTTP Layer: Framework-specific HTTP handling

- Server: Hono adapter (implements HttpServer port) - Middleware: HTTP middleware (auth, validation, error handling) - Plugins: Hono plugins (CORS, compression, OpenAPI, etc.)

  • Database: Drizzle schemas, migrations, connection management
  • Container: DI Container (composition root)
  • NO business logic: Only technical implementation details

Repository Pattern:

// Port in domain/ports/repositories/user.repository.ts
import type { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";

export interface UserRepository {
  save(user: User): Promise<void>;
  findById(id: UUIDv7): Promise<User | undefined>;
}

// Implementation in infrastructure/repositories/user.repository.impl.ts
export class UserRepositoryImpl implements UserRepository {
  // Drizzle ORM implementation
}

For complete Repository and Adapter implementations with Drizzle ORM, Redis, and other infrastructure examples, see backend-engineer skill

4. HTTP Layer (Framework-Specific, in Infrastructure)

Purpose: Handle HTTP requests, WebSocket connections, CLI commands

Location: infrastructure/http/

Contains:

  • Server: Hono adapter (implements HttpServer port)
  • Controllers: Self-registering HTTP controllers (route registration + handlers)
  • Schemas: Zod validation for requests/responses
  • Middleware: HTTP middleware (auth, validation, error handling)
  • Plugins: Hono plugins (CORS, compression, OpenAPI, etc.)

Rules:

  • ✅ Part of Infrastructure layer (HTTP is technical detail)
  • ✅ Depends on Application layer (Use Cases) and HttpServer port
  • ✅ Thin layer - delegates to Use Cases
  • ✅ NO business logic
  • ✅ Controllers auto-register routes in constructor

Example Structure:

src/infrastructure/http/
├── server/
│   └── hono-http-server.adapter.ts
├── controllers/
│   ├── user.controller.ts
│   └── order.controller.ts
├── schemas/
│   ├── user.schema.ts
│   └── order.schema.ts
├── middleware/
│   ├── auth.middleware.ts
│   └── error-handler.middleware.ts
└── plugins/
    ├── cors.plugin.ts
    └── openapi.plugin.ts

Controller Responsibilities:

  • Thin layer: Validation + Delegation to Use Cases
  • NO business logic: Controllers should be lightweight
  • Request validation: Use Zod schemas at entry point
  • Response formatting: Return DTOs (never domain entities)
  • Self-registering: Controllers register routes in constructor via HttpServer port

Controller Pattern (Self-Registering):

// infrastructure/http/controllers/user.controller.ts

/**
 * UserController
 *
 * Infrastructure layer (HTTP) - handles HTTP requests.
 * Thin layer that delegates to use cases.
 *
 * Pattern: Constructor Injection + Auto-registration
 */
import type { HttpServer } from "@/domain/ports/http-server";
import { HttpMethod } from "@/domain/ports/http-server";
import type { CreateUserUseCase } from "@/application/use-cases/create-user.use-case";

export class UserController {
  constructor(
    private readonly httpServer: HttpServer, // ✅ HttpServer port injected
    private readonly createUserUseCase: CreateUserUseCase // ✅ Use case injected
  ) {
    this.registerRoutes(); // ✅ Auto-register routes in constructor
  }

  private registerRoutes(): void {
    // POST /users - Create new user
    this.httpServer.route(HttpMethod.POST, "/users", async (context) => {
      try {
        const dto = context.req.valid("json"); // Validated by middleware
        const user = await this.createUserUseCase.execute(dto);
        return context.json(user, 201);
      } catch (error) {
        console.error("Error creating user:", error);
        return context.json({ error: "Internal server error" }, 500);
      }
    });
  }
}

HttpServer Port (Domain Layer):

// domain/ports/http-server.ts
export enum HttpMethod {
  GET = "GET",
  POST = "POST",
  PUT = "PUT",
  DELETE = "DELETE",
  PATCH = "PATCH",
}

export type HttpHandler = (context: unknown) => Promise<Response | unknown>;

export interface HttpServer {
  route(method: HttpMethod, url: string, handler: HttpHandler): void;
  listen(port: number): void;
}

Key Benefits:

  • Framework-agnostic domain - HttpServer port in domain layer
  • Testable - Easy to mock HttpServer for testing controllers
  • DI-friendly - Controllers resolve via container
  • Auto-registration - Controllers register themselves in constructor
  • Thin controllers - Only route registration + delegation
  • Clean separation - No routes/ folder needed

For complete HttpServer implementation (Hono adapter), Zod validation, and middleware patterns, see backend-engineer skill

Dependency Injection

Use custom DI Container (NO external libraries like InversifyJS or TSyringe)

Why Dependency Injection?

  • Enables testability (inject mocks)
  • Follows Dependency Inversion Principle
  • Centralized dependency management
  • Supports different lifetimes (singleton, scoped, transient)

DI Principles in Clean Architecture

Constructor Injection:

// ✅ Use cases depend on abstractions (ports), not implementations
export class CreateUserUseCase {
  constructor(
    private readonly userRepository: UserRepository, // Port from domain/ports/
    private readonly passwordHasher: PasswordHasher, // Port from domain/ports/
    private readonly emailService: EmailService // Port from domain/ports/
  ) {}

  async execute(dto: CreateUserDto): Promise<UserResponseDto> {
    // Orchestrate domain logic using injected dependencies
  }
}

Lifetimes:

  • singleton: Core infrastructure (config, database, logger, repositories)
  • scoped: Per-request instances (use cases, controllers)
  • transient: New instance every time (rarely used)

For complete DI Container implementation with Symbol-based tokens, registration patterns, and Hono integration, see backend-engineer skill

Testing Strategy

Domain Layer Tests (Pure Unit Tests)

// ✅ Easy to test - no dependencies
import { describe, expect, it } from "bun:test";
import { User } from "@/domain/entities/user.entity";
import { Email } from "@/domain/value-objects/email.value-object";
import { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";

describe("User Entity", () => {
  it("should deactivate user", () => {
    const userId = UUIDv7.generate();
    const email = Email.create("user@example.com");
    const user = new User(userId, email, "John Doe", "hashed_password");

    user.deactivate();

    expect(user.isActive).toBe(false);
  });

  it("should throw error when deactivating already inactive user", () => {
    const userId = UUIDv7.generate();
    const email = Email.create("user@example.com");
    const user = new User(userId, email, "John Doe", "hashed_password");
    user.deactivate();

    expect(() => user.deactivate()).toThrow();
  });
});

Application Layer Tests (With Mocks)

// ✅ Test use case with mocked ports
import { describe, expect, it, mock } from "bun:test";
import { CreateUserUseCase } from "@/application/use-cases/create-user.use-case";

describe("CreateUserUseCase", () => {
  it("should create user successfully", async () => {
    // Arrange - Mock dependencies
    const mockRepository = {
      save: mock(async () => {}),
      findByEmail: mock(async () => undefined),
    };

    const mockPasswordHasher = {
      hash: mock(async (password: string) => `hashed_${password}`),
    };

    const mockEmailService = {
      sendWelcomeEmail: mock(async () => {}),
    };

    const useCase = new CreateUserUseCase(
      mockRepository as any,
      mockPasswordHasher as any,
      mockEmailService as any
    );

    const dto = {
      email: "test@example.com",
      password: "password123",
      name: "Test User",
    };

    // Act
    const result = await useCase.execute(dto);

    // Assert
    expect(mockRepository.save).toHaveBeenCalledTimes(1);
    expect(mockPasswordHasher.hash).toHaveBeenCalledWith("password123");
    expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledTimes(1);
    expect(result.email).toBe("test@example.com");
  });
});

Common Patterns

Repository Pattern

// Port (Domain layer - domain/ports/repositories/order.repository.ts)
// NO "I" prefix
import type { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";

export interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: UUIDv7): Promise<Order | undefined>;
  findByUserId(userId: UUIDv7): Promise<Order[]>;
}

// Adapter (Infrastructure layer - infrastructure/repositories/order.repository.impl.ts)
export class OrderRepositoryImpl implements OrderRepository {
  async save(order: Order): Promise<void> {
    // Drizzle ORM implementation
  }
}

Domain Service

// ✅ Domain service when logic involves multiple entities
export class PricingService {
  calculateOrderTotal(order: Order, discountRules: DiscountRule[]): Money {
    let total = Money.zero();

    for (const item of order.items) {
      total = total.add(item.price.multiply(item.quantity));
    }

    for (const rule of discountRules) {
      if (rule.appliesTo(order)) {
        total = total.subtract(rule.calculateDiscount(total));
      }
    }

    return total;
  }
}

Event-Driven Communication

// Domain Event
import type { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";
import type { Email } from "@/domain/value-objects/email.value-object";

export class UserCreatedEvent {
  constructor(
    public readonly userId: UUIDv7,
    public readonly email: Email,
    public readonly occurredAt: Date = new Date()
  ) {}
}

// Use Case publishes event
export class CreateUserUseCase {
  async execute(dto: CreateUserDto): Promise<UserResponseDto> {
    // ... create user ...

    await this.eventBus.publish(new UserCreatedEvent(user.id, user.email)); // user.id is UUIDv7

    return UserMapper.toDto(user);
  }
}

Anti-Patterns to Avoid

❌ Anemic Domain Model

// ❌ Bad - Just data, no behavior
export class User {
  id: string;
  email: string;
  isActive: boolean;
}

// Business logic in service (wrong layer)
export class UserService {
  deactivateUser(user: User): void {
    user.isActive = false;
  }
}

Fix: Move behavior into entity:

// ✅ Good - Rich domain model
export class User {
  deactivate(): void {
    if (!this._isActive) {
      throw new UserAlreadyInactiveError(this._id);
    }
    this._isActive = false;
  }
}

❌ Domain Layer Depending on Infrastructure

// ❌ Bad - Domain depends on infrastructure
import { db } from "@/infrastructure/database";

export class User {
  async save(): Promise<void> {
    await db.insert(users).values(this); // WRONG!
  }
}

Fix: Keep domain pure, use repository:

// ✅ Good - Pure domain, repository handles persistence
export class User {
  // Pure domain logic, no database access
}

// Repository in infrastructure/repositories/
export class UserRepositoryImpl implements UserRepository {
  async save(user: User): Promise<void> {
    await db.insert(users).values(...);
  }
}

❌ Fat Controllers

// ❌ Bad - Business logic in controller
app.post('/users', async (c) => {
  const data = c.req.valid('json');

  // Validation
  if (!data.email.includes('@')) {
    return c.json({ error: 'Invalid email' }, 400);
  }

  // Check if exists
  const exists = await db.select()...;

  // Hash password
  const hashed = await bcrypt.hash(data.password, 10);

  // Save
  await db.insert(users).values(...);

  // Send email
  await sendgrid.send(...);

  return c.json(user, 201);
});

Fix: Delegate to use case:

// ✅ Good - Thin controller
app.post("/users", zValidator("json", CreateUserSchema), async (c) => {
  const dto = c.req.valid("json");
  const user = await createUserUseCase.execute(dto);
  return c.json(user, 201);
});

Migration Strategy

From Monolith to Clean Architecture

  1. Start with Use Cases - Extract business logic into use cases
  2. Create Domain Models - Move entities and value objects to domain layer
  3. Define Ports - Create interfaces in application layer
  4. Implement Adapters - Move infrastructure code behind interfaces
  5. Refactor Controllers - Make them thin, delegate to use cases

Best Practices

Do:

  • ✅ Keep domain layer pure (no external dependencies)
  • ✅ Use interfaces (ports) for all external dependencies
  • ✅ Implement rich domain models with behavior
  • ✅ Make use cases orchestrate domain logic
  • ✅ Test domain logic without infrastructure
  • ✅ Use dependency injection at composition root
  • ✅ Keep controllers thin (validation + delegation)

Don't:

  • ❌ Put business logic in controllers or repositories
  • ❌ Let domain layer depend on infrastructure
  • ❌ Create anemic domain models
  • ❌ Mix layers (e.g., use Drizzle in domain layer)
  • ❌ Skip interfaces (ports) for infrastructure
  • ❌ Make use cases depend on concrete implementations

Remember

  • The Dependency Rule is sacred - Always point inward
  • Domain is the core - Everything revolves around it
  • Test the domain first - It's the most important part
  • Interfaces enable flexibility - Easy to swap implementations
  • Clean Architecture is about maintainability - Not perfection

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

36.74%
按下载量换算25

Claude

29.66%
按下载量换算20

Cursor

19.17%
按下载量换算13

Gemini CLI

8.62%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

external-service

该 Skill 可能调用第三方服务、云服务或外部模型 API,使用前需要确认账号、额度、数据发送范围和服务条款。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills