← Software design practices guides

How to manage dependency direction with specs

How-To Software design practices Intermediate 1101019HOWTO-1101019

HOWTO-1101019Software design practicesIntermediate

This guide shows you how to manage dependency direction with SpecDD in a spec-driven development workflow.

Dependency direction is one of the most important parts of software design. It decides which parts of the system know about which other parts. When direction is clear, inner rules stay independent, adapters stay replaceable, UI stays focused, and packages can evolve without circular knowledge.

When direction is unclear, convenient imports win. A domain model imports storage. A UI component imports an adapter. A low-level package imports an application service. A child module reaches into a sibling’s internals. SpecDD lets you put the direction rule where it belongs and enforce it during every change.

Short answer

Write dependency direction in the spec that owns the architecture boundary. Use Depends on for allowed collaborators, Forbids for reverse imports, blocked paths, modules, tools, libraries, or access patterns, and Must not for behavior that would invert the design. Use local specs to depend on stable contracts, not internals. Review dependency changes as architecture changes.

When to use this guide

Use this guide when:

The design idea

Dependencies are not neutral. Every dependency creates knowledge. If high-level policy depends on low-level details, the policy becomes harder to reuse and test. If sibling modules depend on each other’s internals, local changes become cross-module changes. If a package imports its consumer, reuse becomes fragile.

SpecDD helps because it separates three ideas:

Together, those sections make direction reviewable.

Steps

1. Choose the dependency boundary

Place dependency direction at the owner of the rule:

Do not put project-wide import conventions in a local spec. Project-wide conventions belong in .specdd/bootstrap.project.md.

2. State allowed direction

Use Depends on for important allowed collaborators:

Spec: Itinerary Service

Depends on:
  ItineraryValidation
  TripStorage

For a broader boundary:

Spec: Trips Module Architecture

Depends on:
  UI depends on services
  services depend on domain
  services depend on adapters

Keep the list focused on design-significant relationships.

3. Forbid reverse dependencies

Use Forbids:

Forbids:
  domain importing ./adapters/*
  domain importing ./ui/*
  adapters importing ./ui/*
  packages/core importing packages/app/*

Forbids is the right section for dependency direction rules that block imports, paths, libraries, tools, or access patterns.

4. Use behavior boundaries too

Dependency direction is not only imports. A layer can preserve imports and still take on the wrong responsibility.

Use Must not:

Must not:
  UI components decide trip edit access rules.
  Storage adapters decide itinerary visibility.
  Domain models perform network calls.

These rules stop responsibility inversion.

5. Depend on contracts, not internals

Good:

Depends on:
  TripStorage

Weak:

Depends on:
  ./adapters/trip-storage/local-storage-json-v3-internals.js

Use the stable contract whenever possible. If a caller must depend on an internal detail, treat that as a design decision and write it explicitly.

6. Keep tasks within dependency direction

Invalid task:

Tasks:
  [ ] Import TripStorage directly from the Itinerary Form.

If the component spec forbids adapter imports, this task is blocked. The task should be rewritten:

Tasks:
  [ ] Submit itinerary item input through itinerary behavior.

Tasks do not override Forbids or Must not.

7. Review dependency changes

When a change adds or removes a dependency, ask:

If the dependency direction needs to change, update the owning spec first or in the same reviewed change.

Example package direction

Spec: Trip Packages

Purpose:
  Keep reusable trip domain packages independent from application and UI packages.

Structure:
  ./packages/trip-domain/: reusable trip domain rules
  ./packages/trip-app/: application services and adapters
  ./packages/trip-ui/: UI components

Must:
  trip-app depends on trip-domain.
  trip-ui depends on trip-app public contracts.
  trip-domain remains independent of application and UI packages.

Forbids:
  ./packages/trip-domain/* importing ./packages/trip-app/*
  ./packages/trip-domain/* importing ./packages/trip-ui/*
  ./packages/trip-app/* importing ./packages/trip-ui/*

Must not:
  UI packages decide trip domain invariants.
  Domain packages perform network or storage access.

This spec defines dependency direction at the package boundary. Local specs can then add narrower rules without contradicting it.

Common mistakes

How to verify the result

Dependency direction is managed well when:

← Software design practices guides