Who Needs Architects? – When Tactics Do Not Add Up to Strategy

Death of Wladyslaw Szujski, 1914

Going into World War I, the French army had adopted a simple doctrine, attack. Attaque à outrance; attack to excess. Courage, particularly that inspired by the Poilu‘s dashing red trousers, would carry the day. Unfortunately for several million French soldiers, the plan was flawed.

It’s easy to see why this is the case in the age of machine guns and modern artillery, but it would likely have been just as ineffective without that complication. The bigger problem was that, even if the attacks had succeeded, each additional success would carry the seed of a greater defeat in the future. Each success would carry the victorious unit farther forward, making re-supply and communications that much more difficult. Each success would spread the victor that much thinner, making it more brittle to counter-attack.

Without focus, lots of little tactical successes can breed strategic defeat.

Or, as Richard Sage put it:

Lack of clarity should always be a warning sign.

It’s important to understand that while there is a relationship between goals, strategies, and tactics, there are differences as well. Seth Godin’s “Goals, strategy and tactics for change” captures both:

The Goal: Who are you trying to change? What observable actions will let you know you’ve succeeded?
The Strategy: What are the emotions you can amplify, the connections you can make that will cause someone to do something they’ve hesitated to do in the past (change)? The strategy isn’t the point, it’s the lever that helps you cause the change you seek.
The Tactics: What are the actions you take that cause the strategy to work? What are the events and interactions that, when taken together, comprise your strategy?

While he was defining the terms in the context of implementing change, the same definitions work well for software development. More than just a bag of tactics, a strategy is a set of tactics focused on attaining a goal. Just as a book is made up of words and sentences, it is also more than that. The intentional structuring of those words and sentences are what makes it a book; that is what gives it meaning. Likewise, systems are more than the sum of their parts.

A system exists via the structure and interaction of its component parts. A well designed system is one where those parts are intentionally structured so that their interaction achieves a goal effectively and efficiently. A failure to do so can impact the technical architecture of a system (taken from Tony DaSilva’s “Fragmented, Half-baked, Thoughts”):

System in Context by Tony DaSilva

While both System A and System B have been made to fit their context, is there any question as to which is more likely to have quality issues? Extensive refactoring, particularly at the architectural level, can become expensive. This leaves the owners of System B with a dilemma – how long can they tolerate the technical debt caused by the mismatch between the system and its context before the costs justify the work?

Failure to intentionally design can also manifest in the user experience. As Stephen Anderson tweeted:

As I noted in “Who Needs Architects? – Navigating the Fractals”, systems exist in contexts which exist in ecosystems. When designing for a particular level of abstraction, you must consider your context(s) and design your internal architecture accordingly. Likewise, you must consider the ecosystem in which the system will exist and design the external architecture accordingly. Avoiding intentional design (which I will note is neither BDUF nor over-engineering) might seem agile, but is in reality, a form of technical debt affecting quality, cohesion, and maintainability.

Tactical excellence focused on a goal is strategic excellence. Without the focus, however, tactical excellence is wasted.

“Design? Security and Privacy? YAGNI” on Iasa Global

Two of my favorite “bumper sticker philosophies” (i.e. short, pithy, and incredibly simplistic sayings) are “the simplest thing that could possibly work” and YAGNI. Avoiding unnecessary complexity and unneeded features are good ideas at first glance. The problem is determining what is unnecessary and unneeded. Just meeting the functional requirements is unlikely to be sufficient.

Read “Design? Security and Privacy? YAGNI” on the Iasa Global site for a post about how it’s important to have someone responsible for Quality of Service requirements in general and security in particular.

Form Follows Function on SPaMCAST 315

SPaMCAST logo

Have you ever wanted to put a voice with the words that you’re reading here? Now you have the chance.

Tom Cagley, who publishes one of my favorite blogs, Software Process and Measurement (SPaMCAST), has invited me to become a regular on his podcast. We decided to name the segment “Form Follows Function” (after all, what else would we call it?). We kick it off this month with a brief discussion of my post “Quick Fixes That Last a Lifetime”.

You can listen to the podcast here.

I first appeared on Tom’s “SPaMCAST 268 – Gene Hughson, Architecture, Management, Software Development” last December and I’m honored to be invited to be a regular part of his excellent podcast.

Quick Fixes That Last a Lifetime

Move Fast and Break Things on xkcd

“Move fast and break things.”

“Fail fast.”

“YAGNI.”

“Go with the simplest thing that can possibly work.”

I’ve written previously about my dislike for simplistic sound-bite slogans. Ideas that have real merit under the appropriate circumstances can be deadly when stripped of context and touted as universal truths. As Tom Graves noted in his recent post “Fail, to learn”, it’s not about failing, it’s about learning. We can’t laugh at cargo cultists building faux airports to lure the planes back while we latch on to naive formulas for success in complex undertakings without a clue as to how they’re supposed to work.

The concepts of emergent design and emergent architecture are cases in point. Some people contend that if you do the simplest thing that could possibly work, “The architecture follows immediately from that: the architecture is just the accumulation of these small steps”. It is trivially true that an architecture will emerge under those circumstances. What is unclear (and unexplained) is how a coherent architecture is supposed to emerge without any consideration for the higher levels of scope. Perhaps the intent is to replicate Darwinian evolution. If so, that would seem to ignore the fact that Darwinian evolution occurs over very long time periods and leaves a multitude of bodies in its wake. While the species (at least those that survive) ultimately benefit, individuals may find the process harsh. If the fittest (most adaptable, actually) survive, that leaves a bleaker future for those that are less so. Tipping the scales by designing for more than the moment seems prudent.

Distributed systems find it even more difficult to evolve. Within the boundary of a single application, moving fast and breaking things may not be fatal (systems dealing with health, safety, or finance are likely to be less tolerant than social networks and games). With enough agility, unfavorable mutations within an application can be responded to and remediated relatively quickly. Ill-considered design decisions that cross system boundaries can become permanent problems when cost and complexity outweigh the benefits of fixing them. There is a great deal of speculation that the naming of Windows 10 was driven by the number of potential issues that would be created by naming it Windows 9. Allegedly, Microsoft based its decision on not triggering issues caused by short-sighted decisions on the part of developers external to Microsoft. As John Cook noted:

Many think this is stupid. They say that Microsoft should call the next version Windows 9, and if somebody’s dumb code breaks, it’s their own fault.

People who think that way aren’t billionaires. Microsoft got where it is, in part, because they have enough business savvy to take responsibility for problems that are not their fault but that would be perceived as being their fault.

It is naive, particularly with distributed applications, to act as if there are no constraints. Refactoring is not free, and consumers of published interfaces create inertia. While it would be both expensive and ultimately futile to design for every circumstance, no matter how improbable, it is foolish to ignore foreseeable issues and allow a weakness to become a “standard”. There is a wide variance between over-engineering/gold-plating (e.g. planting land mines in my front yard just in case I get attacked by terrorists) and slavish adherence to a slogan (e.g. waiting to install locks on my front door until I’ve had something stolen because YAGNI).

I can move fast and break things by wearing a blindfold while driving, but that’s not going to get me anywhere, will it?

Microservices Under the Microscope

Microscope

Buzz and backlash seems to describe the technology circle of life. Something (language, process, platform, etc.; it doesn’t seem to matter) gets noticed, interest increases, then the reaction sets in. This was seen with Service-Oriented Architecture (SOA) in the early 2000s. More was promised than could ever be realistically attained and eventually the hype collapsed under its own weight (with a little help from the economic downturn). While the term SOA has died out, services, being useful, have remained.

2014 appears to be the year of microservices. While neither the term nor the architectural style itself are new, James Lewis and Martin Fowler’s post from earlier this year appears to have significantly raised the level of interest in it. In response to the enthusiasm, others have pointed out that the microservices architectural style, like any technique, involves trade offs. Michael Feathers pointed out in “Microservices Until Macro Complexity”:

If services are often bigger than classes in OO, then the next thing to look for is a level above microservices that provides a more abstract view of an architecture. People are struggling with that right now and it was foreseeable. Along with that concern, we have the general issue of dealing with asynchrony and communication patterns between services. I strongly believe that there is a law of conservation of complexity in software. When we break up big things into small pieces we invariably push the complexity to their interaction.

Robert, “Uncle Bob”, Martin has recently been a prominent voice questioning the silver bullet status of microservices. In “Microservices and Jars”, he pointed out that applications can achieve separation of concerns via componentization (using jars/Gems/DLLs depending on the platform) without incurring the overhead of over-the-wire communication. According to Uncle Bob, by using a plugin scheme, components can be as independently deployable as a microservice.

Giorgio Sironi responded with the post “Microservices are not Jars”. In it, Giorgio pointed out independent deployment is only part of the equation, independent scalability is possible via microservices but not via plugins. Giorgio questioned the safety of swapping out libraries, but I can vouch for the fact that plugins can be hot-swapped at runtime. One important point made was in regard to this quote from Uncle Bob’s post:

If I want two jars to get into a rapid chat with each other, I can. But I don’t dare do that with a MS because the communication time will kill me.

Sironi’s response:

Of course, chatty fine-grained interfaces are not a microservices trait. I prefer accept a Command, emit Events as an integration style. After all, microservices can become dangerous if integrated with purely synchronous calls so the kind of interfaces they expose to each other is necessarily different from the one of objects that work in the same process. This is a property of every distributed system, as we know from 1996.

Remember this for later.

Uncle Bob’s follow-up post, “Clean Micro-service Architecture”, concentrated on scalability. It made the point that microservices are not the only method for scaling an application (true); and stated that “the deployment model is a detail” and “details are never part of an architecture” (not true, at least in my opinion and that of others):

While Uncle Bob may consider the idea of designing for distribution to be “BDUF Baloney”, that’s wrong. That’s not only wrong, but he knows it’s wrong – see his quote above re: “a rapid chat”. In the paper that’s referenced in the Sironi quote above, Waldo et al. put it this way:

We argue that objects that interact in a distributed system need to be dealt with in ways that are intrinsically different from objects that interact in a single address space. These differences are required because distributed systems require that the programmer be aware of latency, have a different model of memory access, and take into account issues of concurrency and partial failure.

You can design a system with components that can run in the same process, across multiple processes, and across multiple machines. To do so, however, you must design them as if they were going to be distributed from the start. If you begin chatty, you will find yourself jumping through hoops to adapt to a coarse-grained interface later. If you start with the assumption of synchronous and/or reliable communications, you may well find a lot of obstacles when you need to change to a model that lacks one or both of those qualities. I’ve seen systems that work reasonably well on a single machine (excluding the database server) fall over when someone attempts to load balance them because of a failure to take scaling into account. Things like invalidating and refreshing caches as well as event publication become much more complex starting with node number two if a “simplest thing that can work” approach is taken.

Distributed applications in general and microservice architectures in particular are not universal solutions. There are costs as well as benefits to every architectural style and sometimes having everything in-process is the right answer for a given point in time. On the other hand, you can’t expect to scale easily if you haven’t taken scalability into consideration previously.

“Design By Committee” on Iasa Global Blog

Who's driving?

Can a team of experienced, empowered developers successfully design the architecture of a product? Sure.

Can a team of experienced, empowered developers successfully design the architecture of a product without a unified vision of how the architecture should be structured to accomplish its purpose? Probably not.

Can a team of experienced, empowered developers successfully design the architecture of a product while staying within the bounds of their role as developers? Absolutely not.

See the full post on the Iasa Global Blog (a re-post, originally published here).

Accidental Architecture

Hillside Slum

I’m not sure if it’s ironic or fitting that my very first post on Form Follows Function, “Like it or not, you have an architecture (in fact, you may have several)”, dealt with the concept of accidental architecture. A blog dedicated to software and solution architecture starts off by discussing the fact that architecture exists even in the absence of intentional design? It is, however, a theme that seems to recur.

The latest recurrence was a Twitter exchange with Ruth Malan, in which she stated:

Design is the act and the outcome. We design a system. The system has a design.

This prompted Arnon Rotem-Gal-Oz to observe that architecture need not be intentional and “…even areas you neglect well [sic] have design and then you’d have to deal with its implications”. To this I added “accidental architecture is still architecture – whether it’s good architecture or not is another thing”.

Ruth closed with a reference to a passage by Grady Booch:

Every software-intensive system has an architecture. In some cases that architecture is intentional, while in others it is accidental. Most of the time it is both, born of the consequences of a myriad of design decisions made by its architects and its developers over the lifetime of a system, from its inception through its evolution.

The idea that an architecture can “emerge” out of skillful construction rather than as a result of purposeful design, is trivially true. The “Big Ball of Mud”, an ad hoc arrangement of code that grows organically, remains a popular design pattern (yes, it’s a pattern rather than an anti-pattern – see the Introduction of “Big Ball of Mud” for an explanation of why). What remains in question is how effective is an architecture that largely or even entirely “emerges”.

Even the current architectural style of the day, microservices, can fall prey to the Big Ball of Mud syndrome. A plethora of small service applications developed without a unifying vision of how they will make up a coherent whole can easily turn muddy (if not already born muddy). The tagline of Simon Brown’s “Distributed big balls of mud” sums it up: “If you can’t build a monolith, what makes you think microservices are the answer?”.

Someone building a house using this theory might purchase the finest of building materials and fixtures. They might construct and finish each room with the greatest of care. If, however, the bathroom is built opening into the dining room and kitchen, some might question the design. Software, solution, and even enterprise IT architectures exist as systems of systems. The execution of a system’s components is extremely important, but you cannot ignore the context of the larger ecosystem in which those components will exist.

Too much design up front, architects attempting to make decisions below the level of granularity for which they have sufficient information, is obviously wrong. It’s like attempting to drive while blindfolded using only a GPS. By the same token, jumping in the car and driving without any idea of a destination beyond what’s at the end of your hood is unlikely to be successful either. Finding a workable balance between the two seems to be the optimal solution.

[Shanty Town Image by Otsogey via Wikimedia Commons.]