Over-Architected? Maybe, Maybe Not.

An oft-heard criticism of way-to-many software solutions is that it's over-architected, implying that the design, abstractions, implementation, deployment, whatever is unnecessarily complex, difficult to understand, unmaintainable, unnecessary, wrong. Criticisms often thrown into the ether without context or supporting narrative, criticisms that often stick.

So what's gained by labeling a solution as over-architected?

Often it's the ultimate get-out-of-jail card for software engineers, deflecting blame for larger-than-expected efforts and timelines without objective analysis or even understanding of the current implementation. Unfortunately, senior leadership often blindly accepts the explanation with a sympathetic Ah, over-architected, I'm so sorry shrug, and especially effective when the original engineers aren't available to fill in the blanks on requirements, decisions, implementation, etc.

Yes, the original architecture implemented may be orthogonal to your preferred architecture - the tech stack, the business assumptions, abstractions, etc - but are you in a position to unequivocally say it's over-architected? Have your company's business goals or technical direction changed since the original implementation? What were the non-functional requirements that needed to be considered? Anything interesting in commit messages?

Most importantly, do your concerns define over-architected or is something less sinister going on. Hmmmm, I'm not sure!

Software Engineers Engineer Software

Shakespeare and the Internet
To maintain, or to not maintain, that is the question:
Whether 'tis nobler in the mind to suffer
The slings and arrows of outrageous implementation,
Or to take arms against a sea of troubles
And to start anew with a clean slate.
- Adapted (embarrassingly) from Shakespeare's Hamlet

Hands up if prefer maintaining existing than writing new code. Keep your hands up if that includes maintaining someone else's code. Ah, thought so.

While Hamlet's original soliloquy discusses his potential suicide, my (poor) adaptation references software development: software engineers often go out of their way to take steps leading to unfettered creation of new code over long-term responsibility of someone else's code.

And when given maintenance work - in whatever form it may take - engineers look for angles that allow them to revamp, restructure, rewrite, and in general make the work more interesting. Correct decision if the blame shows excessive churn, the cyclomatic complexity is obnoxiously high, or the code has truly become unmaintainable. Granted, product owners and scrum masters won't be pleased, but at some point the sins of the past can no longer be ignored.

Publicly calling the existing code over-architected and you may get your wish. However, occasionally someone requests a deeper explanation which calls into question the reasons, dubious because the engineer:

  • doesn't understand the current implementation, nor makes an effort to learn;
  • doesn't understand business or technical requirements, or other underlying assumptions;
  • doesn't agree with design patterns or code structure used;
  • doesn't like the code style or formatting, or even the programming language itself.

And, to no one's surprise, the rewrite becomes larger than envisioned because, ironically, s/he has to finally understand the existing solution to reimplement it: despite claims you're not paving the cowpath, inevitably you may need to, which isn't apparent without overall comprehension. Is your organization really prepared to go API First, microservices, NoSQL, or whatever else is being added to the resume.

The time lines increase, leadership frustrations grow, and other essential work is delayed and deferred, all because the engineer insisted it was necessary. We've all seen it, many of us has been the instigator, and it's not usually a pretty story.

And the question remains: What it truly over-architected?

Problematic Architectures

Architectures applied within software engineering disciplines are labeled a myriad of ways, e.g., enterprise, system, application, software, cloud, integration. To add to the confusion, organizations often define the architecture disciplines to suit their internal needs, making it difficult to clearly define each discipline's responsibilities and boundaries. It's definitely not an apples-to-apples comparison.

That said, defining problematic architectures is possible if talk generically and don't try and define specifics within an architectural discipline. I see three basic types of potentially problematic architectures.

Differently-Architected

Differently-architected solutions are those solutions where opinions differ on how the non-functional requirements are addressed within the solution. Should a solution intended for the cloud be cloud-native or cloud-agnostic? Is stability and reliability more or less important than throughput and performance? Are supporting resources selected based on cost, capabilities, or both?

Any concerns or complaints you have with the underlying architecture must be balanced against the non-functional requirements identified as important for a successful solution. It may be that you disagree with the non-functional requirements, therefore your concerns or complaints may only be relevant when non-functional requirements are re-prioritized. The solution is valid if the non-functional requirements are met, regardless of your feelings.

Even when you are in complete agreements about the non-functional requirements, it's guaranteed that different engineers will created different solutions: synchronous vs. asynchronous, object-oriented inheritance vs. composition, functional or CRUID-based API endpoints SQL vs. NoSQL, API First vs. MVC. It's subjective, because each solution is essentially correct, it's just that your approach is different than mine.

Hello, World can be implemented thousands of different ways, each equally right or wrong, so different engineers and architects are going to approach each problem differently. Is that wrong? No. Is that over-architected? If the non-functional requirements are clearly identified and implemented, probably not. But you still likely disagree with what I designed and implemented.

Mis-Architected

Identifying a mis-architected solution usually requires deconstructing a flawed implementation from conception to deployment: poorly-defined or undefined requirements, poor code quality, poor project execution, unrealistic timelines, and an unhelpful architecture. The problems - and blame - are usually multi-faceted.

Setting those complexities aside, there are common characteristics of a mis-architected solution:

  • Non-functional requirements aren't addressed, assuming any are identified;
  • The technical skills and background necessary for successful implementation don't exist within the organization;
  • The architecture requires building components outside the organization core competency, especially when existing components exist;
  • The deployed solution is unstable requires regular attention to avoid outages;
  • Maintenance and extension relies on individuals whom are deemed irreplaceable.

You likely recognize one of those if you've ever participated in a train wreck project. You might also realize that what you thought was mis-architected was actually non-architected. Most importantly, these projects and there resultant solutions are often unsalvageable, and everything involved with it was a waste of time.

Over-Architected

Is any solution over-architected? Probably. Like pornography, it's difficult to define but you'll know it when you see it. Is this light-switch over kill? Perhaps for most of us, but the Rube Golberg's and Makers in this world would beg to differ.

This following experience may represent an over-architected; it may also just be mis-architected, definitely differently-architected.

Problem Statement

In my days as an independent software consultant, I was contracted as a backfill for a just-departed employee. The ex-employee had architected, designed and (mostly) implemented a custom framework for building the company's first true web application: s/he left sample implementations and little (no?) documentation (design, usage, etc), and remaining staff had to try and pick up the pieces.

[For the Millennials reading this, remember that early days of web development had no real standards or best practices, Open Source was in its infancy, and it was the wild west as everyone searched for answers. Underpowered users' computers struggled with simple browser-side scripting (pre-DOM), and each browser vendor browser liberally interpreted the HTML standard. It's when Internet Explorer started its reign of terror (and yet somehow still exists today). So completely different than today.]

My assignment: own the framework, learn its secrets, define a roadmap for developing the app.

Understand and Internalize

Conceptually simple from 100K feet/30.5K meters: server-side generation of HTML to create, orchestrated by a Java Servlet app (the framework) that handles requests, responses, navigation, etc. At 50K feet/15K meters, it became less simple. At 25K feet/7600 meters it became downright scary.

The concerns increased the more I dug. Pages were tightly coupled and seemingly small changes quickly cascaded to neighboring pages. Pages directly generated HTML, making it difficult to ensure consistency across the app. Changing default framework behavior relied on finding the corresponding hooks, which were often numerous and confusing. Orchestration worked in the original servlet engine (Sun Java Web Server) but wouldn't if changed (starting to look at Tomcat for performance). Local development required regular syncing of other engineers' code, made difficult with the version control of the day (CVS, Subversion, Visual Source Safe, etc.).

I've forgotten other horrors concerns, but the barriers were daunting and the chance of success dimming. Hard decisions needed to be made.

Decision Time

My conclusion: the framework was fit-for-purpose (barely) but not fit-for-development, any attempt to continue would overwhelm the remaining engineers and likely never be completed.

The manager, to her credit, agreed and decided to cut her losses. Long story short: a simpler, more focused framework was designed, and within three weeks engineers could start app development on a working model, successfully demoing our progress after two months Ultimately, very successful, and used for multiple apps before being retired.

The Verdict

Reasons to consider the original framework over-architected:

  • Missing non-functional requirements: no guardrails to reign in design and implementation;
  • Ego-driven design: attempt to show how smart you are by including everything but the kitchen sink;
  • Overly-complex: difficult to implement, maintain, and support;
  • Un-differentiator: architecture functionality is not competitive differentiator and substantially extended time-to-market.

You could argue mis-architected and perhaps differently-architected, but that's semantics. Regardless, we did our due diligence before changing tact, objectively justifying instead of just saying over-architected.

Final Thoughts

What passes as architecture for a software solution varies widely, based on any number of external factors: business vs. software shop, experience and skillset of technical staff, technologies available, organizational maturity, blah blah blah. It's expected.

Too often, an implementation is quickly dismissed without understanding the context in which the work was done. Legitimate reasons may justify a partial or complete re-implementation, but may also be self-serving for engineers. As responsible software engineers, we should justify work efforts that most benefit the organization, not software for software's sake. This does not imply nothing but features and technology be damned, but does imply that we should be able to justify technical work proposed. Investigate, document, narrate, justify.

And don't just say It's over-architected!

Image Credits