#17 Don't Stand Still: Evolve Your Architecture
One of the basic things you need to understand is that architecture is not given once and for all. A change in your business model will affect what your architecture looks like.
Years ago, when I started my journey with software architecture, I thought it was possible to prepare a proper architecture once so that we would be ready for any change in the future.
As you can guess, I was very naive. It ended up with architectural monsters, both on the infrastructure and solution sides. After 2-3 such projects, I knew I had to change something because it could no longer be like this. I started digging into the topic.
I looked for mentors and read a lot of books (I will link the most helpful ones at the end of this post). Finally, I was ready to apply a completely different approach to what I used to do—the evolutionary approach. What does it mean?
Instead of adding all the things that you know:
Kubernetes
Kafka
Microservices
Cache
You focus only on the ones that you really need and that help you solve current problems in the current context.
The context is the king. Sometimes, you might hear that Kubernetes or Kafka are monsters that shouldn’t be used. Nothing could be further from the truth. In the proper context, they do a great job. On the other hand, why would you consider them in an application that serves 1,000 daily active users who generate several requests per second? Context, context, and one more time, context.
The environment in which you work will change in many areas. For example, your application might serve 1,000 users today, and it will serve 100,000 in a year. Today, you can build an application to sell bikes, but tomorrow, your company will acquire a competitor that sells motorbikes, and it will change the model entirely. Or there won't be your app or business anymore, and the same thing will happen to your shiny architecture.
That’s why it makes no sense to build the architecture while thinking about what will happen next year. Think about current needs. Forget about wishful thinking.
When I started to approach evolution seriously, I noticed it was not as simple as I thought. The problem turned out to be both catching the right moment for change and the reluctance of businesses to invest.
Why change it, after all it is working.
I addressed the first problem by defining four steps of the evolution. The latter is still unsolved, but I will let you know when I find a perfect solution.
Going back to the first. There are four steps:
You make the decision based on the state of your application—it might be in the simplicity, maintainability, growth, or complexity phase.
When dealing with simplicity, your usual goal is to deliver the application to the market for the first time. This works great when you build the MVP and have several weeks to deliver, or maybe you build a temporary application for an event in your city. At the same time, it does not mean that you build spaghetti code. I recommend modularizing it around behaviors and keeping it highly cohesive. This way, the code will resist changing conditions for longer and will be easier to maintain. This step has only one rule - make your architecture as simple as possible.
Still, at some point, it will become hard to maintain. That’s when you should focus on improving the application's maintainability. What does it mean? It depends on the context. Assuming you created a single project with production code in the previous step, it might mean extraction to separate projects because it will simplify handling dependencies, or it will be easier to maintain in case multiple teams work on the same codebase. Be aware of one thing: There is a high chance that when you focus on maintainability (architectural driver), you will increase complexity (another architectural driver). This is a known problem of architectural drivers - the moment you touch one, you automatically affect the others.
The next step is directly related to the growth of your application. When it gets a lot of traction, the natural consequence is that the user base will grow, resulting in much more traffic. At some point, your scaling strategy, e.g., scaling the entire modular monolith, will generate too much cost and take a lot of time. Or the performance will heavily decrease. That’s where you might start thinking about a separate deployment unit for some parts of your application. Maybe one of your modules generates most of the traffic, and extracting it to a separate microservice makes sense - this way, you will need to scale only this part, which can result in lower costs and faster scaling. The drawback? In the case of the modular monolith, the entire communication between modules could have stayed inside a single process. You will need an external component like a message broker to communicate between two deployment units (modular monolith + microservice), which run in separate processes. This will increase the complexity of your solution.
At some point, the complexity of the business logic in some modules might become so high that it is not easy to maintain it using the current solution. This might be the moment to replace existing patterns with ones that fit better. Maybe the domain model would be a fit for one of the modules? I can tell you right away that this will be the most challenging step because it focuses on behaviors, not technical solutions like the previous ones. At the same time, it allows you to get a double benefit - along with the reduction of complexity (no more ifology), the maintainability also increases.
Accepting that your architecture evolves is one of the first steps toward building resilient applications. The sooner you do it, the better. This way, you will avoid the problem of creating an overly complicated architecture from the beginning and simultaneously lower the entry threshold for new team members.
If you are interested in how this process can look in practice, based on the real-world application, I invite you to check the Evolutionary Architecture By Example repository. Last year, together with Kamil Bączek, we created it to help understand the evolutionary approach. It is developed in C#.
How do you approach the topic of software architecture in your applications? Do you follow the evolutionary way?
“context is king” simple yet often forgotten advice. Awesome newsletter issue🔥