Structure an Effect codebase by domain

A domain is an area of application work. A service is a named interface for that work. A Layer describes how to build a service and supply its dependencies.

Group files by domain: src/coding/edit.ts puts an edit operation with the coding work it belongs to. Keep one service and its Layer in each file. Capture dependencies when building the service, so callers need only that service.

An entrypoint is where the application starts. Its composition root chooses and supplies implementations. Keep platform adapters behind service interfaces. An adapter implements an interface for a platform or provider.

Justin Bennett asked about folders and service organization. Sam Goodwin’s answer:

src/{domain} instead of src/{mechanism}. E.g. src/coding/edit.ts instead of src/tools/edit.ts. “tools” is the mechanism, “coding” is the domain.

The shape in one tree

A small app can use this layout:

src/
├── files/
│   ├── file-inspector.ts    Service, dependency capture, Layer
│   ├── stats.ts             Schema and expected errors
│   └── inspect-machine.ts   Optional finite lifecycle
├── platform/
│   └── files-node.ts        Platform adapter Layer
└── main.ts                  Composition root and runtime entrypoint
test/
└── files/
    └── file-inspector.test.ts

Domain folders own behavior. platform supplies implementations. rat-stack separates these jobs into packages; a small app can keep them in one package.

The rules that hold it together

Capture dependencies when constructing a service

Keep the service interface and its Layer together. FileInspector captures FileSystem during construction. Its methods have no filesystem requirement.

packages/core/src/file-inspector.ts · 54d35603
export class FileInspector extends Context.Service<
  FileInspector,
  {
    readonly inspect: (
      path: string
    ) => Effect.Effect<FileStats, FileStatsError>;
  }
>()("@rat-stack/core/FileInspector", {
  make: Effect.gen(function* makeFileInspector() {
    const fileSystem = yield* FileSystem.FileSystem;

}) {
  static readonly layer = Layer.effect(this, this.make);
}

What to notice: Construction needs the dependency; callers need only FileInspector. See service construction16 for the full pattern.

Provide Layers at the entrypoint

The composition root chooses implementations and supplies the application’s services and their dependencies. Give each entrypoint its own root. Our CLI assembles dependencies before running the program.

apps/cli/src/cli.ts · 54d35603
  Effect.provide(
    Layer.mergeAll(
      Layer.provideMerge(FileInspector.layer, NodeServices.layer),
      remoteJoinInterestLayer,
      Approval.denyAll
    )
  )
);

NodeRuntime.runMain(program);

Effect.provide resolves requirements before NodeRuntime.runMain runs the final Effect. See Layer composition13.

Keep ports inside and adapters outside

Domain code uses a service interface that names the application’s job. Core owns SubscriberIntake; packages/subscriber-delivery implements it. Apps provide that implementation. See hexagonal architecture19.

House style versus Effect idiom

Take the Effect idiomrat-stack adds
Services and composable Layersmake and static layer in the same file
Schema-backed boundariesCapability contracts and derived interface projections
Replaceable implementationsCartridges with an add/remove test
Typed failures and scoped effectsXState lifecycles through @xstate/effect
Effect-aware testsProperty/model testing and a manual-runtime ban
Explicit requirementsA type-aware lint fence
No prescribed comment policyNo prose comments in code

Where disagreement lives

Sam favors domain folders. Effect leaves folder layout to the application. Sam also calls one API route per file potentially controversial. rat-stack keeps one capability per file and derives routes.

A monorepo is one repository containing several packages. Choose monorepo versus single-package layout separately from the service graph.

New to Effect? Start with Effect basics17.

Sources

  1. Folder layout and service organization. Justin Bennett. Asks about both concerns and gives src/platform as an example. Accessed 2026-10-06.
  2. One service and Layer per file. Sam Goodwin. Recommends domain folders, colocated services and Layers, and entrypoint-level provision. Accessed 2026-10-06.
  3. Effect documentation. Effect. Starting point for maintained concepts and versioned documentation. Accessed 2026-10-05.
  4. Effect contributors. 2026. Context.Service. Effect 4.0.0.. Effect-TS. Exact-tag service guidance colocates a service interface and its Layer. Accessed 2026-10-06.
  5. Services and Layers. Kit Langton / Effect Solutions. Service contracts, implementations, and explicit dependency composition. Accessed 2026-10-05.
  6. Data Modeling. Kit Langton / Effect Solutions. Schema as runtime validation and a shared data model. Accessed 2026-10-05.
  7. Error Handling. Kit Langton / Effect Solutions. Expected failures and defects; API spellings need checking against the installed version. Accessed 2026-10-05.
  8. Testing. Kit Langton / Effect Solutions. Effect tests, scoped resources, and test services. Accessed 2026-10-05.
  9. Services and Layers: test isolation. Effect Solutions. Fresh test Layers by default; shared Layers for expensive suite resources. Accessed 2026-10-05.
  10. Project Structure draft. Kit Langton / Effect Solutions. Unfinished folder and package guidance; does not establish a community layout rule. Accessed 2026-10-05.
  11. Incremental Adoption. Kit Langton / Effect Solutions. Runners bridge existing imperative code at integration boundaries. Accessed 2026-10-05.
  12. rat-stack source snapshot. rat-stack. Services, contracts, adapters, composition roots, lifecycle machines, tests, and repository policy. Accessed 2026-10-06.

Last changed 2026-10-07 in f1a00f2. Source on GitHub. Change log.

Linked from

  1. A service captures its construction dependencies. Build a service with its dependencies, then expose methods that callers can use without rebuilding the graph.

    See also: domain structure, Layer composition, the Layer constructor pattern, and testing through services.¶

  2. add-a-capability. Learn how one contract and its handler become a command, HTTP route, MCP tool, browser RPC, and sandbox call.

    Add the implementation to the capabilities tuple consumed by its composition root.

  3. add-a-store. Add persistence through a service named for an application job, with validation, vendor Layers, migrations, bounded reads, and shared backend tests.

    Provide the chosen Layer at the app’s composition root.

  4. Agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
    Link context

    Structure an Effect codebase by domain — Start with folders for application work, services and Layers in the same file, and startup code that supplies implementations.

  5. Agent rules for rat-stack (AGENTS.md). What you may change, which commands to run, and which changes need approval.
  6. Build and run rat-stack (README.md). What is in the repo, how the example works, and how to run it.
  7. Change log. What changed in the files served here, newest first.

    Structure an Effect codebase by domain — edited: feat(learn): teach cards 63 to 82 with plain lines, diagrams and snippets

  8. Effect 4 study: September 2026. Dated Effect 4 source studies from September 2026; current versions live in pins.md.

    ts is the one composition root: Layer.provideMerge(FileInspector.layer, NodeServices.layer).

  9. Effect keeps the surfaces on one contract. Schema owns values, projections build interfaces, and Layers supply implementations.

    See also: Structure an Effect codebase by domain.¶

  10. Full agent guide. An Effect stack so pure (aspirational) Kit Langton will blush.
  11. Layer composition makes dependencies explicit. Supply a service's dependencies, choose which services remain visible, and reuse Layer identity within a build.

    In rat-stack, each entrypoint, where the application starts, chooses and supplies implementations in its composition root.¶

  12. Lint and type escape ledger (debt.md). A source-linked count of repo-owned lint and type escapes.
  13. Rat Stack lore | rat-stack. Short, source-grounded notes on the ideas and decisions behind rat-stack.
    Link context

    Structure an Effect codebase by domain — Start with folders for application work, services and Layers in the same file, and startup code that supplies implementations.

  14. Run Effect at the integration boundary. Build the runtime where a calling framework enters, reuse its services, and dispose it when that integration ends.

    The CLI’s composition root supplies requirements before running its program.¶

  15. Schema projections: 2026-09-18 history. Historical Effect rc.115 proposal and implementation receipts from 2026-09-18; use the one-capability-every-surface lore page for the current pattern.

    The handler’s dependencies flow through unchanged, so the composition root stays one Layer as it is today in apps/cli/src/cli.ts.

  16. Source change history (log.md). The generated change log as Markdown.

    Structure an Effect codebase by domain — edited: feat(learn): teach cards 63 to 82 with plain lines, diagrams and snippets

Unlinked mentions (1)
  • Agent rules for rat-stack (AGENTS.md) → devtools(capabilities, { runAs }) records each call’s identity and lets rat_call take as; the composition root passes runAsPerson from @rat-stack/auth/devtools, so devtools never imports auth.