Layers Are the Symptom of Methods

sagenschneider1 pts0 comments

Inversion of Coupling Control: Layers are the symptom of Methods

Friday, 10 July 2026

Layers are the symptom of Methods

Ask someone who does not write software to describe what happens when they edit an article and press save. You will get something like:

find the article<br>change the title

tell me it worked

Steps each finishing before the next begins.

Now open the code that does it:

A controller receives a request DTO, validates it, and calls down<br>A service asks a repository for the article, mutates it, saves it, and returns it<br>The original controller then maps the entity to a response DTO and returns that

The steps are still in there. But they happen going down and up the layers. Nothing about the file layout tells you the order things happen in. The person who described the feature would not recognise the code that implements it.

The usual explanation is that real systems are more complicated than the business description and layers are how we manage the complication. I have stopped believing that. The business steps were a good description. What the technology lost was the shape and the shape was taken from us by the method call.<br>Here is the same feature as a function orchestration:

load:<br>class: com.example.LoadArticle<br>next: apply

apply:<br>class: com.example.ApplyArticle<br>next: respond

respond:<br>class: com.example.RespondWithArticle

Find the article. Change the title. Tell me it worked. Steps in the order they happen, each a small class you can read on its own. That is the whole of it. There is no controller resuming, no service in the middle, and nothing happening on the way back up.

The interesting part is not that it matches the business steps. It is what stops being necessary once the method round trip is gone and particularly that layers stop being necessary. The transactional proxy stops being necessary, as the transaction can span the steps rather than a method call. Furthermore, aspects stop being necessary. What replaces them can start further functions rather than "magically" injecting functionality between method calls. Unchecked exceptions stop being necessary, because nothing has to climb a call hierarchy to be handled. Each of these I see was a workaround for a constraint that the method call imposed. And with function orchestration, each of them dissolves (see Inversion of Coupling Control for a more in depth discussion of coupling by the method).

The rest of this post discusses functional orchestration in more detail.

The layered sandwich is the shape of a method return

A method call is a round trip. The caller prepares, calls the method and then does further processing with the return. Effectively a sandwich around the method call. Stack enough sandwiches and you have a layered architecture.<br>We were taught to call this separation of concerns (presentation, service, repository). But which way round is it? Did we choose the principle and the method call happened to serve it? Or did we work around the method call and call it a principle after the fact?

What the business describes is a pipeline

Function orchestration removes the round trip. Functions run once and hand off forward. There is no way back up. The sandwich disappears and with it the reason for the layers.

In OfficeFloor a function has one of the following roles:

producer obtains an entity and sets it in scope<br>action changes an entity<br>responder turns an entity into a response<br>Every endpoint is a chain of producers then actions that ends in a responder.

Find the article (producer). Change the title (action). Tell me it worked (responder).<br>The non-technical description was already a pipeline. It always was. We just did not have the first class architecture capabilities to represent it. Well, until function orchestration.

The content I showed at the top was simplified. Here is the real update endpoint with the validation and the transaction that I left out:

validate:<br>class: com.example.ValidateArticle<br>next: load

load:<br>class: com.example.LoadArticle<br>govern: [ transaction ]<br>next: apply

apply:<br>class: com.example.ApplyArticle<br>govern: [ transaction ]<br>next: respond

respond:<br>class: com.example.RespondWithArticle

This is a sequence of steps. It reads top to bottom in the order it happens. It requires no layers.

Note what happened to the transaction. It is not an aspect wrapping a method call. It is govern, declared on each function spanning the transaction. This deserves more than a note and I will come back to it.

No longer is the response handled as part of the method sandwich. RespondWithArticle is a function like any other. It is not a controller resuming after its service bean returns. That distinction sounds pedantic until you notice it frees the pipeline from the synchronous nature of the method call.

Variables give a function explicit inputs and outputs

If functions do not call each other, and nothing returns a value up a stack, how does the entity get from LoadArticle to ApplyArticle?

Through a...

method call class layers function steps

Related Articles