DDD Structure And Flow

SkillDev tools

Place and organize Ledger Wallet code in the DDD monorepo. Use when creating, moving, or reviewing code under apps, features, domain, shared, or support; deciding which layer owns a concern; structuring packages and flow steps; or checking package names, dependency boundaries, platform variants, and legacy imports.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the DDD Structure And Flow skill

What this skill tells your AI

The instructions your AI receives, as published by ledgerhq/ledger-live in .agents/skills/ddd-structure-flow/SKILL.md and read by ahel’s review.

Use the lowest layer that can own the concern without depending on a higher layer.

Read the canonical architecture guide and the package creation checklist before creating a package.

Upstream source: Structure & Flow

Choose The Owner

ConcernLocationOwnsDoes not own
Platform entry pointapps/<app>Screens, global routing, store composition, analytics, observability, app glueReusable feature internals
User-visible capabilityfeatures/flow/<feature>Business-aware UI, user journeys, local state, flow routingApp-specific screen composition
Capability shared across flowsfeatures/platform/<feature>Hooks, selectors, NFR rules, React glue, and components shared by several flows or use casesSingle-flow internals and fully generic components
Business objectdomain/entity/<entity>Runtime schema, inferred type, defaults, mocks, selectors, sliceNetwork calls and feature state
Network/data accessdomain/api/<name>API contracts, calls, transformations, RTK Query, thunksUI and app composition
Business-agnostic primitive or componentshared/<name>Generic schemas, utilities, Redux primitives, and components without domain or feature knowledgeDomain or app knowledge
Development-only toolingsupport/<name>Shared test, TypeScript, lint, and format configurationRuntime code

Use these distinctions:

  • Keep feature-scoped state in its features/flow package; do not promote it to an entity.
  • Keep an element in features/flow when it belongs to one flow or use case.
  • Move a feature-level element to features/platform when several flows or use cases need it, especially when it connects them to the domain.
  • Keep fully generic, business-agnostic elements in shared or the existing design-system package.
  • Keep app screens in each app. Let them compose exported flow entry points.
  • Keep .web and .native variants beside each other inside the owning feature.

Organize The Package

Every unit is a private package with package.json, src/, explicit exports, and no cross-package relative imports.

features/flow/<feature>/src/
├── components/                # shared by several steps
├── hooks/
├── router/                    # flow-local routing only
├── state/                     # feature-scoped state
├── steps/<StepName>/
│   ├── components/            # used only by this step
│   ├── viewModel.ts           # state and orchestration
│   ├── view.ts                # rendering and callbacks
│   ├── view.test.ts
│   └── index.ts
├── utils/
└── index.ts                   # minimal public API

domain/entity/<entity>/src/
├── schema.ts
├── schema.mock.ts
├── selectors.ts
├── slice.ts
└── index.ts

domain/api/<name>/src/
├── api.ts
└── index.ts

Colocate tests with the files they cover. Add folders only when they group files that change together.

Every index.ts above is a barrel: only export * from "./x" lines, with private code kept in an internals location. See package-public-api for the rules and the lint:structure check that enforces them.

Respect Boundaries

Dependencies flow downward:

apps → features/flow → features/platform → domain → shared

More precisely:

SourceMay depend on
sharedshared
domain/entitydomain/entity, shared
domain/apidomain/api, domain/entity, shared
features/platformfeatures/platform, domain, shared
features/flowfeatures/flow, features/platform, domain, shared
appsAny new-architecture layer
supportDevelopment tooling only; consume it through devDependencies
  • Never import internal legacy libs/* packages from shared, domain, or features.
  • Let legacy code consume new architecture only as migration glue.
  • Inject private new-architecture packages into published legacy packages at the app composition root.
  • Import another package through its npm name, never through a relative path.
  • Let Nx infer tags from paths; do not add manual tags unless local tooling requires them.

Name Packages

LocationPackage name
shared/<name>@shared/<name>
domain/entity/<name>@domain/entity-<name>
domain/api/<name>@domain/api-<name>
features/platform/<name>@features/platform-<name>
features/flow/<name>@features/flow-<name>
support/<name>@support/<name>

Review

  • Confirm the concern sits in the lowest valid layer.
  • Confirm folders express ownership and colocation, not arbitrary categories.
  • Confirm apps only compose reusable flows and platform concerns.
  • Confirm feature, entity, API, and development-only state have distinct owners.
  • Confirm imports follow the dependency table and package public APIs.
  • Confirm every index.* is a pure barrel and no package re-exports another — see package-public-api.

Signals

GitHub stars
618
Forks
490
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
ddd-structure-flow
Source
github.com/ledgerhq/ledger-live