← Software design practices guides

How to define and enforce module and architecture boundaries with specs

How-To Software design practices Intermediate 1101002HOWTO-1101002

HOWTO-1101002Software design practicesIntermediate

This guide shows you how to define and enforce module and architecture boundaries with SpecDD in a spec-driven development workflow.

Architecture boundaries fail when they live only in diagrams, old design docs, or someone’s memory. A contributor makes a local change, reaches across a layer because it is convenient, and the architecture gets a little harder to reason about. The change may even pass tests while still weakening the design.

SpecDD helps by putting the boundary near the code it protects. A module spec can state the module’s responsibility, owned files, allowed collaborators, and forbidden access. Child specs inherit those constraints and add local behavior without silently loosening the architecture.

Short answer

Write boundary rules in the spec that owns the boundary. Use a module, package, repository, or root spec for broad architecture constraints. Use local specs for service, model, adapter, API, component, job, event, or policy behavior. Use Owns, Can modify, Depends on, Must not, and Forbids so implementation and review can check whether the change stayed inside the intended architecture.

When to use this guide

Use this guide when:

The design idea

Architecture boundaries are not only about folders. A folder can look organized while code inside it still imports the wrong layer, owns too much behavior, or bypasses a public interface. A useful boundary answers four questions:

SpecDD answers those questions in a form that is readable by humans and usable by agents during implementation.

Steps

1. Name the boundary

Use a subject name that describes the architectural unit:

Spec: Trips Module

or:

Spec: Billing Package

or:

Spec: Public API Layer

The name should help reviewers understand what boundary is being protected.

2. Place the boundary spec

Place broad boundary specs at the level that owns the rule:

src/trips/module.sdd
packages/billing/package.sdd
services/api/api-layer.sdd

Do not copy the same module rule into every child spec. Parent constraints are inherited. Child specs should add local behavior or narrow rules, not duplicate the architecture statement.

3. Define ownership

For a module:

Spec: Trips Module

Purpose:
  Provide the part of the app where people plan trips.

Owns:
  ./src/trips/*

For a package:

Spec: Billing Package

Purpose:
  Provide billing calculations and public billing service entry points.

Owns:
  ./packages/billing/*

Ownership describes what the spec governs. If a task needs narrower write authority, add Can modify in the local spec that owns the task.

4. State allowed dependencies

Use Depends on for important collaborators:

Depends on:
  TripStorage
  DestinationSearch

Do not list every import. List dependencies that matter for architecture, review, testing, or future maintenance.

5. Forbid boundary violations

Use Must not for forbidden behavior:

Must not:
  Purchase bookings or tickets.
  Manage destination search results.

Use Forbids for hard dependency or access boundaries:

Forbids:
  ../billing/*
  direct booking API access

Forbids is stronger than a task or dependency request. If a child task asks to use a forbidden dependency, the task is invalid until the owning boundary is reviewed and changed.

6. Let child specs narrow the boundary

Parent:

Spec: Trips Module

Must not:
  Purchase bookings or tickets.

Child:

Spec: Itinerary Behavior

Must:
  Reject itinerary items without a place name.

Must not:
  Change destination search behavior.

The child adds local behavior and local boundaries. It does not need to restate the booking rule, and it cannot weaken it.

7. Review changes against the boundary

During review, check:

This is where SpecDD becomes architecture enforcement without requiring every small pull request to reopen a broad architecture discussion.

Example module boundary

Spec: Trips Module

Purpose:
  Provide the part of the app where people plan trips.

Owns:
  ./src/trips/*

Must:
  Trips have a destination and date range.
  Places can be added to a trip itinerary.
  Itinerary items remain grouped by day.
  Changes are saved through trip storage.

Must not:
  Purchase bookings or tickets.
  Manage destination search results.

Forbids:
  ../booking/*
  direct booking API access

This spec does not describe every trips implementation detail. It defines the module’s architectural role and the boundaries that later local specs inherit.

Common mistakes

How to verify the result

The boundary is working when:

← Software design practices guides