Terminus Architecture
In my experience most software architectures in projects I see are too technical. They don’t focus on the most important thing: results and behavior.
Robert C. Martin coined they term „screaming architecture“:
Your architectures should tell readers about the system, not about the frameworks you used in your system.
I very much agree with that. But how exactly can this be accomplished?
A first step in the right direction to me is a Sleepy Hollow Architecture. It separates two fundamentally different parts of any software system: the head from the the body, the frontend from the backend.
A software might be about how to enter and/or visualize data. Or it might be about what’s behind such a facade (e.g. calculations or integration of resources). That likely results in frontend and backend being of considerable different size. Or it even might lead to using different programming platforms. This is already pretty obvious, even inevitable in necessarily distributed systems. But the point of the Sleepy Hollow Architecture is more fundamental. It suggests to make a share-nothing message-oriented separation of frontend and backend a principle. Start with it as a default like you start a building with the separation of foundation/basement and what’s built on top of that.
Without a clear separation the architecture will have a hard time to „scream“ what the software system is about, where its focus is. Strategic investments into pleasing the user with more usability or functionality will be harder to make without this fundamental structure.
But isn’t that what the Layered Architecture or MVC is about? Yes, true, but they are trying to do more, they are more complicated, they are less fundamental in my view. I used to think along their lines but have come to the conclusion that software architecture should start out differently. The first evolutionary phase to me is the Sleepy Hollow Architecture. That’s just the beginning, though!
More structure for the body
The purpose of software is to produce results. Or to phrase it a bit more technically: software transforms input into output – which is perceived as behavior. You want to send it some data and receive some as a result. That’s true for a desktop calculator, an online banking app, or a first-person shooter game. For this „definition“ it’s not important what kind of data you send or receive (with regard to size or structure of frequency) or how you send it (e.g. by keyboard, mouse, gesture, voice) or receive it (e.g. see, hear, feel it).
The Sleepy Hollow Architecture acknowledges that by making clear who’s truly creating behavior: the body. The body is the workhorse, the message handler. The frontend is „just“ a mediator between user and backend. The frontend’s primary purpose is to allow the user to „construct“ input data structures and send them to the backend as a request. But it also presents the user the backend’s response (which might be only part of the result created by the backend).
However, as much as the Sleepy Hollow Architecture’s share-nothing message-oriented relationship between frontend and backend makes it clear that software is about data structure transformation, it falls short of differentiating how this is done. To the Sleepy Hollow Architecture the backend is a monolith, a lump, a black box. And that’s fine for a start.
In the end, though, as a programmer I’d like more guidance as to how to structure the backend now that I know how important it is. Is this the time to apply „the rest of the“ Layered Architecture? Or what about a Hexagonal or Clean Architecture?
I’ve come to the conclusion that those architectures don’t really focus on what software is about. Where buildings are about rooms, i.e. providing bounded three-dimensional spaces, software is about processing messages.
If a school’s or library’s architecture is „screaming“ its purpose by bounding space in a particular way; you can look at a blueprint and pretty much at a glance see what the depicted building is about; the layout of the rooms is telling. Then why not also split a backend into its major building blocks?
The buildings blocks of buildings are rooms; the building blocks of backends are message handlers. Hence the „natural“ basic structure of the backend is an array of message handlers.
The frontend is the one source of the input messages and the destination of the output messages. But behind this facade there are individual message handlers for each request. To me that’s looking like a terminus, a railway station where trains don’t pass through, but stop and turn. (The term was inspired by the two train stations I passed through today in Paris: Gare de l’Est and Gare Montparnasse.)
(terminus Frankfurt/Main, image © Thomas Wolf, www.foto-tw.de, CC BY-SA 3.0 DE)
(Paris Gare de l'Est, image © Google)
All the tracks run in/out in parallel. The station’s building is the one source/destination of the people who leave/arrive on different trains. The people are like the messages getting processed and produced. The trains running along the tracks are the handlers.
The Terminus Architecture can be depicted like above with the head being on one side of all the message handlers. Or it can be split into input editor and output projector sitting on opposite sides of the „tracks“. That way the message processing flow is more in focus:
As shown, message handlers can access resources or even share resources. Whatever they like. The point of the Terminus Architecture is that the primary architectural building blocks are message handlers at all as opposed to horizontal concerns like „business logic“.
On top of the Terminus Architecture you might want to further structure message handlers. No, you should further structure them! But that’s of no concern to the Terminus Architecture. Its message is just „Think about message transformation first!“
Manifesting Agility in code
Why slice the body of a software into message handlers instead of layers?
Firstly because software is about message handling. The Terminus Architecture thus creates basic congruency between purpose and form. To me that’s another essential step towards a screaming architecture.
Secondly because this furthers incremental development. Each message handler is an increment, sometimes a smaller one, sometimes a larger one. Adding another message handler increases the value of a software tangibly because it’s a cross-cut from message reception to any resource access.
MVC, Layered Architecture, Hexagonal Architecture, Clean Architecture all are not incremental. I would call them horizontal architectures as opposed to the Terminus Architecture, which is vertical.
The Terminus approach is orthogonal to the traditional software architectural patterns. And for a very good reason: Agility!
If Agility is about progressing incrementally through requirements then to me it seems consistent to structure code accordingly. Doing Scrum on a Layered Architecture hence is a contradiction in my view. No small wonder that even projects run in an agile manner often cannot reap all the benefits. Among many detrimental factors they can be hindered by an architecture not matching a basic tenet: incrementally increasing value.
Today’s trend towards serverless software systems is supporting the Terminus Architecture well with platforms like AWS Lambda or Azure Functions. My intention is, however, to bring this kind of thinking to (almost) all application. No application is too small to not benefit from a Terminus Architecture; because today’s small applications are tomorrow’s large(r) applications.
With a Terminus Architecture it’s easy to add another and another message to be handled. It’s also easy to clearly separate work on different features located in different message handlers.
An example application
To show you how a Terminus Architecture might look in code I refactored the sample application from the Sleepy Hollow Architecture article. The backend of the stock portfolio manager got split up into several message handlers.
This manifests itself first in the contract between frontend and backend:
There is no single interface for all message handling anymore. Messages and their message handler abstractions are now co-located. All that belongs together is not sitting in one place next to each other in a folder.
There are a couple of generic basic message handler interfaces:
But most importantly each message handler now has its own interface; here’s an example:
Already at this point I find the Terminus Architecture very focussing. I don’t get overwhelmed by so many things a backend needs to do. And I don’t need to ponder the question of whether all the backend services should be represented by one interface or multiple. There simply is no doubt: every single service, i.e. an offer to handle a certain message, gets its own interface. Always.
But it does not stop there! Look at the backend implementation:
All messages are represented by their message handler implementations. There is a 1:1 mapping between user interactions with the frontend (user interface), messages, and message handlers.
All requirements can be traced back to one or more interactions. And now they can be further traced to their handlers. The design time file structure as well as the runtime object structure mirror the basic requirements.
I call that a behavior-oriented (or results-oriented) approach as opposed to the technical approach of the Layered Architecture etc. (or worse a „Spring Boot Architecture“).
But wait, there is more!
Just take a quick look at one of the message handlers:
How small is that, right? Yes, very, very focused. I’d say that’s right along the SRP. And it’s the ISP at its best. And DIP on top of that. And also OCP because for the next message to handle you don’t need to change a message handler but just stuff (extend the codebase). That should appeal to you if you’re a friend of the SOLID principles.
In case you should feel reminded of Functional Programming let me say: Why not? Mainstream object-orientation has done enough harm to codebases over the past 25 years. It’s time for a pivot.
Although I’m a friend of Functional Programming I don’t see a stark need to switch from C# to F# or from Java to Clojure tomorrow, though. For now it’s enough to maybe use some OO features in a bit different manner.
And finally here’s how to integrate frontend and backend in a Terminus Architecture:
Of course now there are a number of message handlers not just one which need to get instantiated. They then could all get injected into the frontend in a monolithic application. But I chose a different approach.
This time I don’t inject the message handlers but attach them as event handlers to the frontend. That way the frontend is even more focused on frontend stuff, I think. It’s functionally independent of the message handlers.
Summary
I’ve come to like the Sleepy Hollow + Terminus Architecture combination. It takes off a lot of mental load when approaching a new problem. I feel so much more focused. And the result is more congruent with the view of the customer who does not care about technical details but getting requests processed by the software.
It might seem the Terminus Architecture is adding some textual overhead: more interfaces, more classes, more integration code. Yes, maybe. But I find it worth it. More text, but less mental burden.
And any shared data between message handlers now is more explicit. That helps me to see dependencies more clearly and reason about the code.
A Terminus Architecture to me is a first step towards a screaming architecture because on a high level of abstraction it structurally represents what’s most important in a software: the processes (or workflows or maybe use cases; whatever you want to call message handling). And not only in some large enterprise system but in a manageable way for every application. A good foundation for evolutionary growth.
Try it for yourself!