What is a microservices architecture?
Let's say that you are building a keywords SEO application. The client wants to search for the highest-ranking keywords that related to a specific root query. So, your application would basically do the following:
- Receive the query from the client through a web interface (like a web page).
- Contacts other backend services, be them internal (like your own database) or external (like a third-party service, Google Keyword tool…etc.).
- Stores the result of the client's query in a backend database.
- Presents the output to the client through the web interface.
Although very basic, this example looks a lot like modern web applications.
So, let's see how such an application can be built:
The above architecture belongs to what it is called “monolithic”. It simply means that everything is tightly coupled to each other.
And what's wrong with the monolithic approach?
Having all your components closely related makes it hard to change one without inherently affecting the other. So, let's list the drawbacks here:
- Difficult to make changes: for example, what if you wanted to switch to Ruby instead of PHP? Or even what if you wanted to use NodeJS instead of Apache? this simply means breaking the application code, spending many hours coding, testing, and debugging. Accordingly, changes are highly discouraged.
- Hard to evolve: whenever you wanted to add new features to the application, you are risking breaking other parts. For example, you may want to introduce a service that grabs data from Google's keyword tool. You just learned about a fantastic library called requests that can make establishing web requests a breeze. But there is only one problem: it's written in Python. You will have to search for a similar one in PHP or write your own.
- Single point of failure: if you have a problem either in your PHP code or something went wrong with Apache, the only page that your visitors may see is the nasty 500 Internal Server Error.
Breaking down the monolith
The modern microservices approach is to break down the above application into several loosely-coupled components on physically different servers. This can be depicted as follows:
You can add or remove whatever components from your architecture according to your needs. Notice that I intentionally removed the vendor of each component; so, now the web interface does not have to be Apache (and only Apache). Similarly, the database. Now, let's explain what each component of those do:
- The web interface: a mere web server. It does nothing more than receiving HTTP requests and responding with static files (CSS/JS/HTML) and images. If the request is intended for the application to process it (like ending with .php), the component routes the request to the application server. This is commonly referred to as a reverse proxy. You can continue using Apache, or you can use a more robust application for such a scenario like Nginx.
- The application part is what's responsible for the business logic. It receives the request, validates it, processes it, and it may contact other backend components for further processing. Eventually, it returns the response to the web interface.
- The Python component may or may not be called by the PHP component depending on whether or not the case needs to contact a third-party tool. Both the PHP and the Python components may contact the DB through the data access layer to exchange data.
- The data access layer is what allows you to decouple the database from the rest of the application. You can choose whatever programming language that fits. It will receive HTTP requests, converts them to database queries, executes them, and returns the result to the calling client.
- Because we have a data access layer, we can choose the appropriate database engine (MySQL, MongoDB, or even Oracle).
All the above components exchange data in a RESTful manner.
The REST standard
REST (Representational State Transfer) is like an agreement by which different APIs can communicate with each other. Since we highly abstracted our architecture, a given component send a request to another one might not (and should not) be aware of how the other component would fulfil this request. But it must be aware of how to ask for data. This is what REST comes into play.
The REST standard uses HTTP verbs to define requests. So, in our example, if the client is requesting a list of keywords related to a root keyword bodybuilding, the URL should be something like this:
http://www.example.com/root/bodybuilding/ and the HTTP method would be
GET verb is used because you are fetching information from the resource.
The PHP component might send another
GET request to the Python component. But whenever the PHP or the Python servers want to create a record in the database, they'll use a
POST request. If the intention is to update an existing record rather than creating a new one, the appropriate verb would be
PUT. In all cases, JSON is used as a data-serialization language for exchanging data between the servers.
Pros and cons of microservices
No technology or architecture proves to be a silver bullet to all problem and pain points. Tradeoffs must be made when using one method or another. That being said, let's see the advantages of a microservices architecture:
- You are free to make changes to any component without risking breaking other parts of the application. So, you can replace Apache with Nginx, PHP with NodeJS, and Python with Go or Ruby. As long as each component complies with the REST standard and responds correctly to different HTTP requests, there should be no problem.
- If one component is down, the other shouldn't be. This means that, PHP suffered an exception for example, your web interface can display a nice “please try again later” message in the page sections that requires data processing. At least your application will show a welcome screen. This be a real client-saver.
- You can always add more components as you wish. More and more features can be easily brought to or removed from the application by just orchestrating the components.
- It's easier to debug an error that you know where it happened rather than scrolling through tens of thousands of lines of code. If the Python part is not responding correctly, you can isolate this part, debug it, solve the issue and get it back up. All this can be done with the application still running and serving visitors. You don't have to stop and start Apache because of a code change.
- Using the right tool for the job, if done correctly, can dramatically boost the application performance. For example, if you want to introduce a chat service where the visitor can talk to one of your representatives, you can create a NodeJS component just for this service. NodeJS is known to be way better than other web servers when it comes to real-time communication. Implement the same feature in Apache or IIS will be not only tedious, but also inefficient.
Some of the drawbacks of a microservices architecture include
- It must be carefully designed. Having more than one component means more and more moving parts. Questions of network latency, request lifecycle, and bottlenecks must be carefully addressed. Otherwise, it will be bringing you less value than a monolithic counterpart.
- It might take considerably longer time to build. Imagine a PHP page that will send an SQL statement to a MySQL database and the same page having to establish an HTTP request to another broker service that must be built first.
- More code means more testing efforts and more scenarios to examine.
- A microservices architecture is tempting to add more and more features and components. This introduces more complexity on its own.
What about the costs? Simply, use Docker!
The most important drawback here is cost! Instead of purchasing one or two servers for my monolithic application stack, I will have to but 2x or 3x that number to implement a microservices-driven one. This is where Docker and its related orchestration tools like Docker Swarm and Kubernetes come into play.
In a previous post, we briefly talked about Docker, what it is and how it can address your infrastructure needs. But, how can it help reducing costs in such an architecture?
Adding each of the above components in its own container will ensure that you use the available resources to the most efficient way. Containers are designed so that they can be lightweight, small, fast, and scalable.
In the above scenario, you can just have four physical (or virtual) servers and add the containers in the following diagram:
As you can see, using Docker, you can host multiple containers on the same physical server. An orchestrator service like Docker Swarm or Kubernetes, can control the clusters for you.
An orchestrator service acts a load balancer among the containers. If a request arrives, it routes it back to the next available container. If that container is down for some reason, it is removed from the cluster, and a new one is spawned. It can go as far as creating new containers as the load increases.
Combined with microservices, an orchestrator and Docker, you can create an easily-scalable application that be easily extended to your needs. In future articles, I may demonstrate a real-world web application that makes use of Docker and the microservices architecture.