skip to content

Which import direction between an automation suite's cases, business actions and protocol adapters keeps the layering intact?

level: middleimportance: must knowfreq 62%

answer

  1. Dependencies run in one direction only
  2. Three layers: intent, flows, transport
  3. Cases import actions, actions import adapters
  4. No module imports the layer above it
  5. Only the lowest layer knows the transport

basics

~20 s

Imports point one way: cases import business actions, actions import protocol adapters, and nothing imports back upward. An adapter that knows about a case, or a case that calls an adapter directly, has broken the layering.

solid answer

~40 s

Three layers, one direction. **Cases** state the claim in domain words and own the verdict. **Business actions** compose reusable flows — register a customer, place an order — and know nothing about how the product is reached. **Protocol adapters** are the only code that touches a transport: driving the application's screens, calling its service interface, seeding its data store. Cases import actions, actions import adapters, nothing imports upward. That buys two things: an adapter can be replaced without touching a case, and the layer a failure comes from tells you what kind of failure it is. When a case reaches past the actions into an adapter, it is pinned to that transport, and the next adapter change stops being an edit in one module and becomes a search through the case files.

code

pseudocode · 17 lines
pseudocode
module cases:
    may import -> actions
    may NOT import -> adapters

module actions:
    may import -> adapters
    may NOT import -> cases

module adapters:
    may import -> nothing above it

check_imports(suite):
    for each import in suite:
        if layer_of(import.target) is above layer_of(import.source):
            fail(import.file + " imports upward")
        if layer_of(import.source) skips a layer to reach import.target:
            fail(import.file + " skips the layer below it")

go deeper

for a junior

Be ready to name the three layers — cases, business actions, protocol adapters — and say which may import which. Recall that only the lowest layer touches the transport and that a case never calls it directly.

for a middle

Explain the mechanics: why an adapter must not import an action or a case, how the one-way rule is what makes an adapter replaceable, and what a check would have to walk to catch a violation automatically.

for a senior

Show you have paid for a broken direction on a real suite: transport calls scattered through cases, an adapter change that turned into a mass edit, and how you pulled it back without freezing the suite for a week.

for a principal

Own the argument for enforcing the direction mechanically rather than by convention, and the opposite case: when a small suite over a single transport does not earn three modules at all, and what signal tells you it now does.

## The three layers and what each one owns A suite that has grown past a few dozen cases settles into three kinds of code, whatever the stack calls them. - **Cases** state one claim about the product in domain words and own the verdict. - **Business actions** are the reusable flows — register a customer, place an order, cancel a subscription. They take and return domain values and say nothing about how the product is reached. - **Protocol adapters** are the only code that touches a transport: driving the application's screens, calling its service interface, seeding or reading its data store. | Layer | Owns | Speaks in | May import | | --- | --- | --- | --- | | Cases | The claim and its verdict | Product behaviour | Business actions | | Business actions | Reusable flows and their own guards | Domain nouns and verbs | Protocol adapters | | Protocol adapters | Reaching the product over one transport | Requests, screens, records, handles | Nothing above them | The rule is one sentence: **imports point downward only**. Cases import actions, actions import adapters, nothing imports upward, and a layer does not skip the layer directly below it. ## Why the direction is the valuable part Sorting code into three folders costs nothing and buys nothing. The one-way dependency is what buys something, and it buys two specific things. **Replaceability.** If the transport vocabulary lives only in adapters, changing how the product is reached is an edit inside one module. The same registration exercised through the screens and through the service interface becomes two adapters behind one action, not two copies of the suite. **Attribution.** With the direction held, the layer a failure comes from tells you its kind. A failure in a case is a claim about behaviour that did not hold. A failure inside an action is a flow or an arrangement that broke. A failure inside an adapter is a transport, deployment or credential problem. Blur the direction and every failure could be any of the three, and triage becomes reading code rather than reading a report. ## What a case that reaches straight for an adapter costs The shortcut always looks cheap: one line instead of a new action, and the case turns green today. The bill arrives later. 1. **The case is pinned to one transport.** It can no longer be run against a different way of reaching the product, because the way is written into it. 2. **The adapter grows a second audience.** It now has to keep both actions and cases happy, so its surface widens and stops being free to change. 3. **A change becomes a search.** Replacing the adapter is no longer an edit in one module; it is a hunt through case files for everyone who reached past the boundary. 4. **The failure loses its kind.** A transport error now surfaces from a case, where the reader expects a statement about behaviour. 5. **It spreads.** The next author copies the nearest working case, and one shortcut becomes the local convention within a release or two. ## What a boundary can actually enforce Two families of suite differ here, and the difference decides what a module boundary can hold. Where the suite is compiled together with the product's own code, the boundary can lean on the same visibility machinery that guards the product — but a case can also reach into the product's internal types and skip the adapter layer entirely, so the rule must forbid that too, not merely order the suite's own modules. Where the suite is a separate deployable that speaks only the product's wire protocols, no internal is reachable at all, but the adapter layer is thicker and the risk moves upward: transport vocabulary leaks into the actions' signatures because it is the only vocabulary available. The same one-way rule protects different things in the two families. ## Keeping the rule alive - **State it in one line** where new contributors read it. A rule that needs a diagram is a rule nobody applies under deadline. - **Check it mechanically.** A step that walks the suite's import graph and fails on an upward or skipping import beats review discipline, because the rule is broken by people who never knew it. - **Watch new transports.** Every new way of reaching the product is where the pressure appears first: the adapter does not exist yet, so the case writes the call itself. - **Give exceptions a name.** Something that genuinely does not fit the three layers — a probe that checks the deployment is up before any case runs — gets its own named module and its own rule, rather than living as an untracked exception inside a case. ## When three modules are too many A dozen cases over one transport do not need three modules; the layering is overhead against a codebase two people hold in their heads. The signal to split is not a case count but a second audience: a second transport, a second team, or the first time a flow is copied because reusing it would have meant importing a case. Introduce the direction then, and introduce it as a checked rule rather than a convention, which only survives while the people who agreed to it are the only authors.

  • How do you stop the one-way rule eroding once dozens of people are adding cases?
    Make it mechanical. A check that walks the import graph and fails the run on an upward or skipping import catches the authors who never knew the rule existed, which is who breaks it. Keep the rule short enough to state in one sentence, and review new adapters closely — a new transport is where the first shortcut always appears.
  • A case genuinely needs a transport-level value, such as a correlation identifier, to assert on. What do you do?
    Return it from the business action as a domain value rather than letting the case import the adapter. The action already made the call, so it can hand back a small result object carrying the identifier. The boundary stays intact and the case still gets what it needs to make its claim.
  • Does the rule forbid a case importing a data builder or a comparison helper?
    No, if those live in their own module with no upward imports of their own. The rule constrains the direction between layers, not the existence of shared leaf modules. The danger is a helper module that itself imports adapters and actions, because importing it then smuggles the whole graph into the case.

It is a kitchen: the menu describes dishes, the recipes compose steps, and the appliances do the work. The oven never reads the menu.

saying these in an interview costs you the question

  • Says any module may import any other as long as it compiles
  • Puts transport calls straight in cases to save writing an action
  • Treats layering as a folder naming convention with no check
  • Lets an adapter import a case to reuse its data setup
  • Believes review alone keeps the direction intact at scale