← Software design practices guides

How to keep business rules in the domain layer

How-To Software design practices Intermediate 1101016HOWTO-1101016

HOWTO-1101016Software design practicesIntermediate

This guide shows you how to keep business rules in the domain layer with SpecDD in a spec-driven development workflow.

Business rules are durable decisions about what the product or domain allows. Examples include whether a trip date is valid, who can edit an itinerary, when a booking is allowed, how a discount is calculated, or which state transitions are legal.

These rules often leak into UI components, controllers, API handlers, jobs, adapters, and migrations because those are the places where inputs arrive. SpecDD helps by giving each rule an owner and making other layers delegate to that owner instead of copying the rule.

Short answer

Put business rules in the spec that owns the domain concept: usually a model, policy, domain service, or module spec. Use Must and Scenario to define the rule, Must not and Forbids to keep the rule out of UI, controllers, jobs, and adapters, and Depends on or References so other layers can use the rule without owning it.

When to use this guide

Use this guide when:

The design idea

A business rule should have one durable owner. Other layers can present the rule, call the rule, persist the result, or translate errors, but they should not become independent rule owners.

This matters because business rules change. If the rule is copied across UI, API, job, and adapter code, every change becomes a hunt. Worse, one copy may stay stale while tests still pass for another path.

SpecDD makes the rule owner explicit.

Steps

1. Identify the business rule

Write the rule in plain behavior language:

Only trip owners and collaborators with edit access can change itinerary items.

Then ask:

2. Choose the domain owner

Good owners include:

For edit access, a policy spec is a good fit:

Spec: Trip Edit Access

3. Write the domain spec

Spec: Trip Edit Access

Purpose:
  Decide whether a person may edit a trip itinerary.

Owns:
  ./trip-edit-access.policy.js
  ./trip-edit-access.policy.test.js

Accepts:
  person
  trip
  requested edit action

Returns:
  allow or deny

Must:
  Allow the trip owner to edit itinerary items.
  Allow collaborators with edit access to edit itinerary items.
  Deny collaborators with view-only access.

Scenario: view-only collaborator
  Given a collaborator has view-only access
  When the collaborator edits an itinerary item
  Then the edit is denied

This spec owns the rule.

4. Forbid rule leakage

In UI, API, job, or adapter specs, block duplicated decisions:

Must not:
  Decide trip edit access rules.
  Duplicate Trip Edit Access policy logic.

For hard layer boundaries:

Forbids:
  UI importing policy internals
  adapters importing UI authorization helpers

The other layer may call the stable policy contract. It must not reimplement the rule.

5. Reference the domain rule from other layers

API spec:

Spec: Update Itinerary Item API

Depends on:
  TripEditAccess

Must:
  Reject itinerary updates when TripEditAccess denies the requested edit.

Must not:
  Decide edit access rules directly.

UI component spec:

Spec: Itinerary Editor

Must:
  Show denied-access feedback returned by itinerary behavior.

Must not:
  Decide whether the person may edit the trip.

This keeps behavior consistent across entry points.

6. Review tests and changes

Tests should prove the rule at the owner. Other layers can test that they call or respond to the rule, but they should not duplicate every rule case unless that layer owns user-visible behavior around the result.

During review, ask:

Common rule owners

Use a model spec for:

Use a policy spec for:

Use a service spec for:

Use an adapter spec for:

Adapters should not own domain decisions unless the external system is itself the domain authority.

Common mistakes

How to verify the result

Business rules are staying in the domain layer when:

← Software design practices guides