A team of developers have been given a new greenfield project to deliver. The powers that be told them they have full control over the technical direction of the project. The team was excited to move on from their legacy project, it was a great opportunity for them to apply the lessons from the old onto the new. They were going to do things properly this time.
One of the developers closely follows the Uber Engineering Blog and there she read Uber uses Golang to hit stellar RPS (requests per second) numbers. She proposed that the team writes this project in Golang so they can leverage that same performance.
The second developer suggested they use React for the frontend and build an SPA (single page application), because that's what everyone's doing these days and it's the de facto standard.
The last developer found great success with serverless microservices at his previous company. He recommended they leverage the near infinite scalability of the serverless platform for their new project.
At face value, the plan seemed solid - Golang on the backend, a React SPA on the frontend and it's all going to be served by a serverless microservice infrastructure. It was going to be performant, infinitely scalable and decoupled.
Executing the plan proved more difficult than they anticipated. It turns out they forgot to take a few things into account.
The team didn't have any real experience with Golang as they were all primarily PHP developers. Building a React SPA was proving to be a time sink as they now had an additional problem of communicating with APIs. By the time they had 12 microservices, the complexity of their system was causing more issues than it was solving. To add insult to injury, they were only asked to build a very basic inventory management system for internal use.
Somewhere in the excitement and the prospect of doing it like the big companies the team forgot where they were, what worked for them and what they were building. What they thought they had was a solid architectural foundation to build upon, but what they ended up with was an unarchitecture.
I actually borrowed the idea for the word from this blog and think it encompasses what I'm trying to convey here. To me, unarchitecture is ill-fitting software architecture, which is often born of best intentions.
If good software architecture makes a codebase easy and enjoyable to work with, then unarchitecture is the antithesis of that. An unarchitected codebase is a source of endless friction and misery for developers, it makes new features tiresome to develop and fixing bugs a soul-crushing exercise.
The story in the introduction has a series of questionable decisions that led to unarchitecting the codebase, but in reality all it takes is just one decision to create a pocket of friction in any codebase. For example, you could just over-engineer the CI pipeline and make nobody on your team want to touch it with a 10 foot pole.
I've found that unarchitecture often comes from a good place. We often tend to apply what we already know as working without really weighing up the pros and cons (everything has cons) of the approaches within our current context. What worked for a company with a team of 20 might not necessarily work for a team of 3. This applies across the board to software packages, languages, libraries, frameworks and patterns.
I like to think it's akin to wearing your parents shoes when you're 7. You've seen your parents walk in them and you really want to be like the grown ups, but once you try them on you can barely walk in them, much less run.
I think there are a few ways we can try and prevent ourselves from unarchitecting our applications:
- We should aim to try and keep things as simple as possible for as long as possible. Introduce complexity only when we really need it.
- Put our ego aside and question whether the pattern or technology we're applying is right for us, within our current context. Some questions that I like to ask myself are:
- Will my team realistically be able to maintain this? Do we have the skills required currently, can we learn or grow the team to accommodate for what we're trying to achieve?
- Can it stifle future growth? If we're going for simplicity, will it be hard to evolve if we need to?
- If our decision turns out being a mistake, how hard will it be to take a step back and go on a different path?
- Is this something that will bring us value? How? Maybe our efforts are better focused elsewhere?
- I'm a big believer in Conway's Law and think we should take into account the structure of the organisation when we're architecting our applications. Our architecture should either complement the structure or drive change in the direction that we want to go.
In a world where microservices, serverless, Leviathan-esque container orchestration tools and decoupled frontends are all the rage, it's easy to lose sight of where we are and what we're trying to achieve and give in to hype driven development instead.
I think, in general, we should just strive to approach software architecture with more pragmatism and not give in to dogma. After all, there's no such thing as a panacea.