The preachers of the blogosphere have penned fiery diatribes against the evils of monolithic applications, and these fire and brimstone sermons resonate with developers because we have smelled the sulfurous tech-debt of those necrotic repositories. Yes, we prayed for microservices to flood the codebase and wash our editors clean. Microservices as the savior, microservices as the promised land!
And it came to pass that we packed our business rules into an ark and when the waters subsided, we loosed them to repopulate our software world. Amen.
And yet, is our business or its coding practices fundamentally different after this voyage?
Chances are, they are not.
The same flawed individuals and reporting hierarchies came out of the change as went into it: giraffes did not magically become unicorns any more than team members magically became masterminds simply from having migrated concerns to a new architectural paradigm. Unless the migration to microservices was accompanied by a higher mandate that specifically targeted the team structure and its modus operandi, then Newton’s first law of “business as usual” will rule the day: each organization will find itself more or less in the same place as before, albeit without the excess baggage that would have been culled in any migration, no matter its destination or purpose.
And so we arrive at the central truth of the matter: architectural changes are not organizational changes, although the two are often conflated. To paraphrase Conway’s Law, the efficacy of the code produced by an organization will be mirrored by the efficacy of the organization’s communication. Pick any architecture you want, but in practice, badly structured organizations will produce bad code.
What if your organization doesn’t enforce that code must be tested? Well, then you’ve likely got smelly code on your hands and need help from an oversized QA department.
It doesn’t require containerized development environments? Then your code is at risk of being maintainable only by its original authors because only they can make it work.
No seed data? No meaningful end-to-end tests. The list goes on.
Switching to microservices does not fix your team, but it may help reveal its flaws.
It isn’t good for click-bait headlines, but the subtle truth is that most development teams need to address shortcomings in their organizational structure more than they need to address their architectural paradigms. For every team that has truly been pushed to the limits of their code’s underlying architecture, there must be scores who suffer more from their own ineptitude and lack of planning. For them and their managers, the architecture is merely a convenient scapegoat.
Organizations must concede that their applications could withstand greater load, offer increased transparency, and allow faster feature iteration if only each team member possessed the same mastery, high standards, and attention to detail. If only management would allow their teams to fix what they know to be fundamentally broken. A monolithic application written in an outdated framework can still be performant, clean, and elegant if entrusted to a thoughtful proprietor. If a codebase is bloated or snarled, then that is more a testament to the skills absent in its contributors and the lack of the proper technical standards. Sure, you’ve got problematic code, but it didn’t get that way on its own: your organization made it that way.
Once we acknowledge the team’s organizational structure as the critical factor in determining the chances for success in a development project, we can identify the most important qualities that must be brought to bear by the leadership of that organization.
This is the one to rule them all: all the other principles are bound to it. No amount of architecture or personnel changes will remedy problems with the code unless the proper lines of communication are established via specs and standards.
The single most important commitment an organization can make is to write clear and detailed technical specs prior to any development. “Effective product specs are a critical part of building great software,” writes Gaurav Oberoi in his wise article about writing tech specs. “They force critical thinking up front, scale communication, and raise accountability — all leading to higher quality, lower schedule risk, and less wasted time.”
An analogy from the food industry comes to mind: you might be turning a good profit with tasty burgers and fries at “The Airdrome“, but it wasn’t until Ray Kroc came along and wrote a detailed handbook on how to measure and reproduce every aspect of the restaurant that you have yourself a McDonald’s franchise. The Airdrome made a good meal, but only McDonald’s could scale.
The most likely follow-up questions are who will write the tests and how should they be structured? Unfortunately, there isn’t a clear answer for this; Gaurav’s article includes some thoughtful examples that are useful as templates. You may find inspiration in the descriptions of the Software Requirements Specification (SRS), but at the end of the day, management must find a solution that works for the developers and stakeholders. If this mandate is not handed down on stone tablets from management on high, the chances for it to be adopted are virtually nil: developers will resist the extra work and accountability because there is no universal requirement for it. It can be a difficult transition but rest assured, once you have converted your organization to run on technical specifications, the rest is just minutiae.
Tests should go hand in hand with your specs: they are the real validation that the code implemented the spec. You will find that unit tests become largely secondary to integration tests. The only imperative is that your application fulfills the obligations defined in its specification. Gherkin or some other language of Behavior Driven Development (BDD) may be a useful counterpart to the specs.
Having a defined spec and a procedure for testing goes a long way in granting your software an aura of integrity.
Organizations always struggle with constraints, so there will always be attempts to reduce scope, simplify, or speed things up. A good manager must recognize with unfailing clarity the difference between a legitimate compromise and the corruption of standards. A feature may come or go, but the code standards must remain sacrosanct. Standards are the only thing that can protect a codebase from subpar contributions made by harried developers up against a deadline. Many organizations somehow cannot make the time to implement a solution properly, yet somehow they find the time to endlessly patch up the errors generated by bad implementations. The standards must be the safe port in that storm.
Once an organization has properly defined standards and has a competent team, microservices have the potential to offer some appealing benefits.
Smaller concerns: a true microservice concerns itself with only a few bits of functionality. It may be as small as a single AWS Lambda function or a set of related functions for accessing a resource type, but a true microservice is tightly scoped. Ideally, a microservice is so compact that one could conceivably rewrite it in its entirety in a day.
Simpler outsourcing: due to such a narrow purview, a microservice is much easier to outsource and it makes onboarding team members simpler. There’s no need to share all the keys to the kingdom, one must only make the service fulfill its responsibilities by passing its integration tests. Outsourcing without specs and tests is a dangerous habit best avoided.
Simpler testing: tests become primarily focused on integration because the only thing that really matters is that the microservice fulfills its role in the larger ecosystem.
Beyond the restating the obvious that microservices are not a fix for longstanding organizational problems, there are further clarifications on how microservices may not provide solutions for certain problems.
Impossible end-to-end testing: depending on the technologies used, it may become practically impossible to spin up a full environment representing the entirety of your business. A monolithic application might have run happily on your local machine, but trying to spin up the numerous containers required to run your entire environment can be nearly impossible without the help from DevOps and dedicated resources. Either you have to develop the microservices one at a time and run any end-to-end tests against a beefier environment, or you have to choose a technology like Seneca or Elixir which can run multiple services in a streamlined way with a parsimony of requirements.
Microservices are cool, but team structure is paramount. No architectural paradigm can make up for shortcomings in your organization’s communication. Microservices may make deployments and testing easier, and your team can embrace the possibilities they offer as a way to structure code across multiple repositories or modules and how to separate concerns according to specific use cases. Happy organizing!
Install LogRocket via npm or script tag.
LogRocket.init() must be called client-side, not
Angular’s new `defer` feature, introduced in Angular 17, can help us optimize the delivery of our apps to end users.
ElectricSQL is a cool piece of software with immense potential. It gives developers the ability to build a true local-first application.
Leptos is an amazing Rust web frontend framework that makes it easier to build scalable, performant apps with beautiful, declarative UIs.