(originally published July 9, 2013)
SRP and my Head
Many years ago, whilst working at an Investment Bank with a number of ThoughtWorks colleagues, we were thinking about how big objects should be. Obviously as developers we were thinking in terms of the Single Responsibility Principle, Open-Closed Principle and Separation of Concerns.
I came up with a simple heuristic. Simply stated, an object should be no bigger than the size of my head when pressed up against the monitor – basically a screenful of code. Now, I have a fairly big head (but thats ok, cos there ain’t much inside it) so your mileage may vary.
The reason I bring this up is because I’ve been speaking at a few conferences recently on the topic of Micro Services, and the following question is often asked:
“How big should an application be?”Many folks over the years…
I normally have three answers ready. The first is obvious: It Depends. The second is less useful ~438 square metres. The third, and hopefully most useful, is: no bigger than my head.
Although not physically. The physical size thing is hyperbole after all. I am a consultant and so pushing my head up against monitors in an effort to decide whether an object should be refactored is generally looked on as suspect behaviour when visiting a client; no, the metaphor is dual purpose.
The second and more important interpretation for me is that at the level of abstraction of an object, it’s responsibility should be no bigger than my intellect, memory, my head can comprehend. For me that’s the really interesting idea. I should be able to comprehend the whole of the object easily and understand its responsibility as a whole.
How does this relate to SOA or micro-services? Micro-services should also fit in my head.
Turtles all the way down
Dan North introduced me to the ideas of chunking up and down levels of abstraction, a technique from NLP, on the same project from whence this idea came. From his blog:
“Neuro-linguistic programming (NLP) describes a technique called chunking, that’s useful for either solving problems or creating options. For any statement, you can chunk up by asking “Why..?” or “What for..?” questions and chunk down by asking “How..?” questions. The further you chunk up, the broader your perspective becomes, and the further you chunk down, the more detailed.”Daniel Terhorst-North
Chunking up levels of abstraction from an individual object modelling a concept in your domain to a business capability defining a bounded-context via micro-services, I should be able to easily comprehend the responsibility of each level of abstraction in it’s entirely. Basically, it’s turtles all the way down (until you hit the elephants).
- The domain of the individual object; where you are concerned with methods (or functions if you are a hipster Clojure programmer) and fields. An object should follow the SRP – be as big as my head.
- The domain of groups of objects; namespaces or packages. At this level of abstraction we are concerned about whether we can understand what domain the set of objects are a part of. As an aside, I often see objects grouped by technical concern at this level of abstraction; com.sheep.Model or com.cheese.Controllers for example.
Organising your codebase in this way may make it easier to find a Controller if you are poking about in your package explorer but it doesn’t help you to understand what the namespace does. Better to group objects by vertical slice; by business domain, over technical archetype. This has the added benefit of not introducing un-necessary coupling between namespaces, everything in – com.cheese.Controllers using everything in com.sheep.Model. If you package by domain then you avoid this spaghettification. Besides, if the only reason to do it is to find it in the package explorer, may I insist respectfully suggest you learn what ctrl-n does in intelliJ or ReSharper?
- The domain of individual applications; micro-services. Each individual application should, again, be easily understandable.
When I talk about micro-services this is generally what I mean. Individual applications that are choreographed to perform a single task within a single distributed bounded context around a specific business capability.
These splits are most often made for cross-functional reasons, for example the ability to scale independently from one another to provide additional degrees of freedom in your system.
An example I often use is that of Customer Management. You may have a queue, queue processor and domain service deployed as separate applications. This allows you to scale processors up or down depending on throughput at any given point in time – a technique becoming much more viable in our world of on-demand provisioning, auto-scaling and Phoenix Servers.
Another example is splitting along read / write concerns. These usually have different cross-functional characteristics too (and in the CQRS world different domains) – here a split allows you to scale read and write independently. The point, though, that each application within a business capability should have a comprehensible single purpose. The Single Source Of Truth for Customers, Processing Queue Entries, Storing Requests, Projecting Events. Writing transactions.
- The domain of multiple business capabilities. This is the domain traditionally addressed by SOA implementations.
It may be tautalogical to suggest to those who have read this far that business capabilities should have single responsibilities.
I will leave aside the fact that most organisations’ internal architectures have more in common with stovepipe enterprises than quote modern architectures unquote and instead consider the platonic world of SOA.
Each business capability, should again be no bigger than my head when you chunk up to this level. And these capabilities should be business-meaningful. Many organisations end up arranged into teams according to specific technologies – the BPM team, the DBA’s, the “services” people. The problem with this – teams organised around technology types and layers is that you end up with an enterprise architecture that mirrors this. Turns out Melvyn Conway, of Conway’s Law fame was pretty smart – if you organise around technology or layer boundaries you will end up with an architecture that mirrors the team structure.
This is almost exactly opposite, in my view, the desirable topology of a service oriented architecture. We are talking about business services here, very definitely not technology services. One should be able to talk to our colleagues across the yawning crevasse of doom about business capabilities and the business processes they choreograph without them getting the glazed expression all to common to those on exposure to the sentence “yes well, the message from the ESB wasn’t transformed properly by the XSLT so we had to dump it down shatner’s bassoon”.
Chunking up the level of abstraction to Business capabilities which implement business processes, the capabilities should fit in my head.
So, my conjecture, and for me, a core principle to apply to the size of an application is:
Can you understand everything it does, does it fit in your head? If you can’t – and that is usually the case when something is doing more than one thing – you might want to think about splitting it up.