Software architecture plays an important role in software development. Monolithic architecture is the default option for the majority of systems but comes with certain disadvantages. You can find more about that in our article Stories behind the Monolith. Those limitations are nothing but motivation for designing and building different and improved software architecture.
Microservices
In short, microservices are small autonomous services that work together. Most developers are already familiar with distributed systems so the general idea behind microservices is definitely not something new to them. The timing for microservices is perfect considering the need for continuous integration, automation of infrastructure, domain-driven design, cross-functional teams and similar. The previous sentence may sound like a bunch of buzzwords but those ideas are definitely affecting software architecture. There are plenty of guidelines about designing microservices but the industry is still developing best practices and how to work with them.
Micro
Every service must be small. There is no exact measure for the size of the service so defining small in a given context is not easy. First, let’s try to understand what makes a service too small. Having a service just to establish a connection with the database to create, read, update and delete records is not good practice. Services must be designed around a business domain and should have specific responsibilities. For instance, the auth service should be used for authentication of the users. It’s not just a wrapper around the database that stores accounts, the service has logic for registering new users and signing in existing users. Someone might have an idea to add the functionality for uploading a profile picture, then someone else might want to add the functionality for updating the whole profile and so on. As a result, our service can grow too large. We should keep the auth service focused on authentication. Every service should do one thing, and do it well. The service should be small enough that you don’t want it to be smaller. It’s up to the development team to find their definition of small.
Autonomous
It’s absolutely necessary that services are mutually independent. We must be able to change the service and deploy it without a need for changing any other service or consumer in our system. Consumers should be able to communicate with the service through an exposed API. We need to model our services properly as well as take care of appropriate decoupling. Always have in mind what to expose and what to hide in the service. Connecting a few different services to the same database is a common problem. You can decide to change the schema while working on one of the services and unintentionally break another service.
In other words, your service is not autonomous. There are plenty of companies with numerous development teams where each of them owns one or more services. The development teams have separate workstreams while all services are part of the same platform. It would be very inefficient if one team blocks the other one just because of their coupled services. Every service must be autonomous so we can take advantage of everything that microservices have to offer.
Microservices team ownership
Testing
Tests make our software more reliable. The main purpose of tests is to verify the functionality of the software. Ideally, we want to fix bugs before the users find them. Also, tests can help us understand the implementation of the features of our software. You’ve probably found yourself in a situation where the developer’s intention was not obvious from the code. In those situations, it would be nice to have a few tests with a proper description of the expected behavior of the code.
The problem occurs when developers have to wait too long to run the tests while developing. There are different types of tests and testing strategies so it’s important to find something that works for your team. You’ve probably heard about the Test Pyramid, a metaphor that tells us how many tests of each type we should have.
A simplified idea of the Test Pyramid is that we should have more unit tests than integration tests, and more integration tests than end-to-end tests. Unit tests are usually executed much faster than the other two types so we can use them as short feedback loops. Writing unit tests should be a part of the task when we code. It doesn’t take too much time to implement them and they can provide a lot of long-term value.
Developers are more confident to change tested code because tests can tell them if something went wrong. It’s unnecessary to continuously run unit, integration, end-to-end and other types of tests while developing. Instead, we can run unit tests to verify our code on a local machine and have all other tests as a part of the deployment pipeline. We shouldn’t write tests just to cover lines of code, tests should be well structured so they can help us to verify the functionality of the software.
Microservices provide plenty of opportunities to improve our software and the ways we develop it. We should design our software properly from the beginning so we don’t have to spend too much time fixing problems later. Developers like to take advantage of continuous integration and continuous development. However, we can’t have a CI/CD pipeline before we have appropriate tests. We want every team to develop fast without blocking other teams, but they can’t do it before their services are autonomous. It’s important to start with small steps. Create a good base for your project by following the basic principles and then incrementally introduce what your teams need on top of that.