Clean Architecture skill for Codex

The dependency rule, minus the ceremony. Codex treats Clean Architecture as one property, business policy that does not depend on web, database or SDK details, and sizes the layers to the project instead of copying a four-ring template. It removes single-implementation interfaces and mapper chains that add no value, organises code feature-first, and adds an import-linter, dependency-cruiser or ArchUnit check so the rule is enforced in CI.

  • Architecture
  • $clean-architecture
  • 201 lines
  • 2 files
  • ~2,050 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- clean-architecture

Installs to ~/.agents/skills/clean-architecture/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $clean-architecture, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Applies Clean Architecture's dependency rule pragmatically - domain and use cases independent of frameworks, databases and UI, with layering enforced by import-linter, dependency-cruiser or ArchUnit - while sizing the number of layers, interfaces and mappers to the project instead of copying a template. Use when structuring a new service, untangling a codebase where controllers talk SQL, reviewing a layered design for over-engineering, or adding architecture tests that keep layers honest.

Use it when

  • Starting a service that will hold real business rules for years
  • Business logic is mixed into controllers, ORM models or framework hooks
  • A layered codebase feels slow to change because of interfaces and mappers everywhere
  • You want CI to block imports that break the layering

Use something else when

How to check the result

A throwaway framework import in a domain file makes the CI import check fail, and domain plus use case tests pass with no database or network available.

The mistake it prevents

Copying a full four-layer template with an interface per class and DTO mappers at every boundary onto a small CRUD service, tripling the cost of every change.

~/.agents/skills/clean-architecture/SKILL.md

--- name: clean-architecture description: Applies Clean Architecture's dependency rule pragmatically - domain and use cases independent of frameworks, databases and UI, with layering enforced… license: MIT ---

The goal is one property: business policy does not depend on delivery or storage details, so it can be tested and changed on its own. Done well, that property is enforced by a check in CI, and the codebase has no layer, interface or mapper that does not pay for itself. Four concentric rings drawn on a slide do not count.

The dependency rule

frameworks & drivers (web, DB, queues, SDKs)
   -> interface adapters (controllers, presenters, repositories, gateways)
       -> use cases (application services, orchestration, transactions)
           -> entities (domain rules, value objects)

Source code dependencies point only inward. Inner code never names an outer type, a framework annotation, an HTTP status or a table. Data crossing a boundary is in the form the inner layer defines.

Size the architecture to the project

ProjectLayersNotes
Script, prototype, < 2k linesNoneFunctions and modules; revisit if it survives
CRUD API, logic mostly validation2: handlers + data access, domain types sharedFramework-idiomatic (Rails, Django, Laravel conventions win)
Service with real business rules and 1-3 integrations3: domain, application, adaptersThe common sweet spot
Large, long-lived core domain, many integrations, several delivery channels4 rings, feature-slicedUse DDD for the domain ($domain-driven-design)

Default to three layers. Add the fourth only when you can name the use cases that would otherwise be duplicated across delivery channels.

Over-engineering smells (remove these)

SmellWhy it hurtsInstead
IUserService with one UserService, no test doubleTwo files per change, no seam usedConcrete class; extract an interface when a second implementation or a fake is needed
Request DTO -> command -> domain -> entity -> response DTO for a field renameFive mappers per fieldMap at the outer boundary only; let use cases accept simple input types
One use case class per CRUD verb on every tableHundreds of trivial classesUse cases only where there is orchestration or policy; plain repository calls otherwise
Domain objects that are bags of getters/setters with logic in “services”Layers without the benefitPut rules in entities/value objects
Generic Repository<T> with arbitrary filteringPersistence leaks inwardRepository methods named for domain needs
Separate projects/packages per layer in a 3k-line serviceBuild frictionFolders plus an import check
src/
  billing/
    domain/          # entities, value objects, domain errors - pure code
    application/     # use cases, ports (interfaces for repos/gateways), transactions
    adapters/        # http handlers, db repositories, external API clients
  shipping/
    ...
  main.py            # composition root: build adapters, inject into use cases

Feature-first keeps a change to billing inside one folder. Layer-first at the top level (controllers/, services/, repositories/) scatters every feature across the tree.

Workflow for an existing codebase

- [ ] 1. Read AGENTS.md; map the current layers and where business rules live
- [ ] 2. List dependency violations (inner code importing framework/DB/HTTP)
- [ ] 3. Agree the target layer count with the user (table above)
- [ ] 4. Add the import check in "report" mode and record the baseline violations
- [ ] 5. Fix violations feature by feature, behind characterisation tests
- [ ] 6. Turn the check to "fail" for fixed features; prevent new violations everywhere

Find violations quickly:

rg -n "^(from|import) (fastapi|flask|django|sqlalchemy|requests)" src/*/domain src/*/application
rg -n "from ['\"](express|pg|@prisma/client|axios)['\"]" src/*/domain src/*/application
rg -n "import (org\.springframework|jakarta\.persistence)" src/main/java/**/domain

Enforce the rule

Python: import-linter

# .importlinter
[importlinter]
root_package = app
include_external_packages = True

[importlinter:contract:billing-layers]
name = Billing layers point inward
type = layers
layers =
    app.billing.adapters
    app.billing.application
    app.billing.domain

[importlinter:contract:domain-is-pure]
name = Domain imports no frameworks
type = forbidden
source_modules =
    app.billing.domain
forbidden_modules =
    sqlalchemy
    fastapi
    requests

Run with lint-imports (install import-linter; needs network for the first install). include_external_packages = True is what lets a contract forbid third-party modules.

TypeScript: dependency-cruiser

// .dependency-cruiser.cjs
module.exports = {
  forbidden: [
    {
      name: "domain-is-pure",
      severity: "error",
      from: { path: "^src/[^/]+/domain/" },
      to: { path: "^src/[^/]+/(application|adapters)/|node_modules/(express|pg|@prisma|axios)" },
    },
    {
      name: "application-no-adapters",
      severity: "error",
      from: { path: "^src/[^/]+/application/" },
      to: { path: "^src/[^/]+/adapters/" },
    },
  ],
};

Run with npx depcruise src --config .dependency-cruiser.cjs.

Java/Kotlin: ArchUnit

@AnalyzeClasses(packages = "com.acme")
class ArchitectureTest {
  @ArchTest
  static final ArchRule domainIsPure = noClasses().that().resideInAPackage("..domain..")
      .should().dependOnClassesThat().resideInAnyPackage(
          "..application..", "..adapters..", "org.springframework..", "jakarta.persistence..");

  @ArchTest
  static final ArchRule applicationIgnoresAdapters = noClasses().that().resideInAPackage("..application..")
      .should().dependOnClassesThat().resideInAPackage("..adapters..");
}

For Go, keep the domain in its own package with no imports of adapter packages and check with go list -deps ./internal/billing/domain | rg "adapters|database/sql" in CI.

Boundaries and data

  • Use cases accept plain input (a struct, dataclass or primitives) and return a result type or raise domain errors. They never receive an HTTP request object or return a response object.
  • Transactions belong to the use case (or a unit-of-work port it calls), not to controllers and not to repositories individually.
  • Validation splits in two: shape validation (types, required fields) at the adapter with the framework’s tools; business validation (credit limit, state transitions) in the domain.
  • Errors: domain errors are domain types; adapters map them to HTTP status codes or exit codes.
  • Read-heavy screens may query the database directly through a query service in the adapter layer. Forcing reports through aggregates is a common performance trap.

Gotchas

  • Framework annotations on entities (@Entity, Pydantic models as domain objects, @Column) are a dependency on the outer ring. Accept this knowingly for simple models and record the trade-off; do not pretend the rule holds.
  • “Shared” or “common” packages imported by every layer become a back door. Keep shared code to true primitives (Money, Result) with no I/O.
  • Dependency injection containers are a wiring tool for the outer ring. If use cases resolve dependencies from a container, the container has become a dependency.
  • Splitting into many build modules early makes refactors slow. Start with folders and an import check; promote to modules when the team or build demands it.

Verification

  • The import check runs in CI and fails when you add import sqlalchemy (or equivalent) to a domain file. Prove it once with a throwaway change, then revert it.
  • Domain and use case tests run without a database or network.
  • Pick one recent feature: count the files touched for a one-field change. More than four or five layers of mapping is a sign to collapse layers.

Output contract

**Chosen shape:** 3 layers, feature-first (reason: <...>)
**Violations found:** <n> (baseline file: <path>), fixed: <n>
**Enforcement:** <tool>, config <path>, CI step <name>
**Simplifications made:** <interfaces/mappers removed and why>
**Deferred:** <features still violating, with plan>

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Clean Architecture"
  short_description: "The dependency rule, minus the ceremony"
  default_prompt: "$clean-architecture Our controllers run SQL directly - restructure this service into clean layers without going overboard"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem