19 September 2019
Design patterns in microservices for CTOs: API Gateway, Backend for Frontend and more
Microservices architecture is one of the hottest trends in software development. As the CTO, you need to know when to use them. But you also need a deeper knowledge of the topic to really get a hold of your project. By learning more about design patterns in microservices, you will learn exactly how microservices work and how developers can make all the services more efficient, scalable, and secure. Meet the most popular microservices design patterns.
In the previous article on microservices, we introduced the basics of this popular software architecture. With that knowledge, you know what kind of projects microservices architecture is best suited for and what projects call for monolithic architecture. But it doesn’t mean that the process of choosing the best architecture for your project is over. In fact, once you decide to go for microservices, there will be even more decision to make. That’s why you should learn about design patterns for microservices.
In this article, we’re going to introduce the issue of design patterns in microservices, go over the most important patterns and explain why choosing the right one is so important from the business perspective.
What are microservices design patterns?
As you already know, a microservice is a largely independent application component tasked with a specific function in a system. It may have a single service instance or many instances. Multiple microservices, each taking care of another function of an app, combined with clients (e.g. the frontend of your web and mobile apps) and other (optional) intermediary layers that span multiple services make a microservices-based architecture. This makes microservices a great choice for Domain-driven design (DDD).
This type of setup has many advantages, such as the ability to write any service in a different technology and deploy them and other services independently as well as performance boost and more. But it also comes with quite a few challenges, including complex administration, data consistency and configuration issues and more.
Microservices design patterns exist to address such common challenges in microservices and provide proven solutions to make your architecture more efficient and the entire administration process – less costly and troublesome. As a result, learning about microservices design patterns is a great way to understand microservices better – more high-level than actual coding, yet specific enough to figure out the inner workings of microservices.
Why should you learn about design patterns in microservice architecture?
Choosing the right design patterns in microservice architecture can literally make or break your microservices-based project. They are the best proof that microservice architecture alone is not a cure-all, and to really benefit from them, you need to do them right.
If you DON’t care about microservices design patterns:
- your app may perform badly (due to unnecessary calls and inefficient use of resources),
- the whole system will be unstable (e.g. connecting and integration issues),
- it may face scalability issues (adding more services can cause difficult-to-maintain dependencies, which may even turn it into a de facto monolith),
- it may compromise security by exposing the endpoints of your microservices to the public and by other means.
- you might have a lot more maintenance and debugging work to do than you could with better preparation.
Interested in developing microservices? 🤔
Make sure to check out our State of Microservices 2020 report – based on opinions of 650+ microservice experts!
Microservice architecture – types of microservices design patterns
Design patterns in microservices exist in pretty much every aspect of the microservice architecture. Some of the most important ones could be divided into areas such as:
It concerns the methods of communication between microservices and client apps (the frontend layer).
These design patterns constitute various ways microservices can communicate between each other.
A variety of security-related concerns in microservice architecture, such as the organization of the security layer, authorization and level of access to particular microservices for different types of users, etc.
Ensuring that all microservices are ready to address the needs of the system (regardless of how intense the traffic is), ensuring the lowest possible downtime. This includes activities such as making sure microservices can restart on another machine, or whether enough machines are available, microservices being able to report their current states on their own, health checks and more.
Service discovery in microservice architecture
It refers to the methods microservices use to find each other and make their locations known.
Setting parameters and monitoring the performance of the entire microservice architecture to continuously optimize it as you go.
In the subsequent parts of the article, we’ll focus primarily on the first type, going over the three most popular communication patterns – the direct pattern, API Gateway and Backend For Frontend (BFF). They provide an excellent opportunity to understand how microservices-based architecture actually works and the implications a developer’s choices have on its performance.
The direct design pattern
This is the most basic setup for a microservices-based architecture. In this design pattern, a client app makes requests directly to microservices, as shown in the picture below. Each microservice has a public endpoint (URL) the client can communicate with.
This design pattern is very easy to set up and quite sufficient for relatively small apps, but causes quite a few challenges that become more and more apparent and troublesome as your application grows in size and complexity:
- Performance issues
Even a single page of your app may need multiple calls for different microservices, which may lead to large latency and performance issues.
- Scalability issues
Because the client app directly references microservices, just about any change to the microservices can cause the app to break. This makes maintenance difficult.
- Security issues
Without an intermediary layer, the endpoints of microservices are exposed. This isn’t necessarily making an app inherently insecure, but it definitely makes it more difficult to take care of security.
- Complexity issues
What’s more, security and other cross-service tasks need to be included in each public microservice. If an extra layer existed, they could be included there, making all microservices simpler.
Since microservices are usually recommended for complex apps, there must be more scalable patterns with greater business capability and the ability to easily support multiple services.
API Gateway design pattern
There sure are! The API Gateway takes the business capability up a level. As described in the picture below, it provides an extra layer, a single entry point between a group of microservices and the frontend layer. This design pattern addresses all the concerns we just mentioned, by hiding the endpoint of microservices from the public, abstracting references to microservices from the client and reducing latency by aggregating multiple calls.
However, this microservice design pattern is still not safe from scalability issues. It’s more than sufficient when the architecture revolves around one client. But if there are multiple client apps, the API Gateway design pattern may eventually get bloated, having absorbed all the varying requirements from different client apps. Eventually, it may practically become a monolithic app and face a lot of the same issues experienced with the direct pattern.
Therefore, if you plan for your microservices-based system to have multiple clients or distinct business domains, you should rather consider using the Backend for Frontend design pattern from the start.
Backend for Frontend design pattern
BFF is essentially a variant of the API Gateway pattern. It also provides an additional layer between microservices and clients. But rather than a single point of entry, it introduces multiple gateways for each client.
With BFF, you can add an API tailored to the needs of each client, removing a lot of the bloat caused by keeping it all in one place. The result pattern can be seen in the picture below.
It’s worth it to mention that this particular pattern may still be extended for particularly complex apps. Different gateways can also be created for particular business domains. This model is flexible enough to respond to just about any type of microservices-based situation. It’s perfect for continuous delivery of microservice architecture on a really large scale and provides incredible business capabilities.
Does it mean that each microservices-based architecture should use the BFF pattern? Not necessarily. The more complex design, the more setup and configuration it requires. Not every app may require that. But if you want to create an ecosystem of apps, or plan to extend it in the future, you may choose a more complex communication pattern for the sake of future scalability.
If you want to learn more about BFF, make sure to read our Backend for Frontend case study – it’s a story of an app ecosystem reinvented using the pattern. It shows its business capability and how microservice architecture can go a long way to improve data processing efficiency and user experience.
Other noteworthy design patterns in microservices
As I have mentioned before, design patterns exist in all aspects of microservices. Developers are often forced to make a choice between one microservice design pattern and another, taking different factors into consideration. In some other situations, microservices design patterns can be combined or used alongside each other.
For internal communication, some of the most popular patterns include REST, gRPC, Message Broker, or the Remote Procedure Invocation. When it comes to security, the Access Control List (ACL) can be used for each of the multiple services or each gateway, or as an independent microservice (or not used at all). When it comes to availability, we can use load balancing based on DNS or hardware. The service discovery can be performed on the client-side or server-side. As for configuration, it can be externalized (each of the multiple services has one on its own) or centralized (all the configuration in one place). The list goes on and include event sourcing pattern, proxy service or circuit breaker pattern among others.
See also: Practical introduction to gRPC
Design patterns in microservices webinar – Q & A
Want even more insights on design patterns in microservices?
Around the time of wirting the article, we also organized a practical webinar on the same subject. While Adrian Zmenda was conducting the lecture, I (Adam) tried to quickly answer all questions in the chat window. Since the participants’ questions were so on point, we decided to publish the entire Q&A here – I’m sure that lots of developers and CTOs will find all this microservices-related knowledge useful too. Hey ho, let’s go!
1. Will you share the webinar presentation?
If you’d rather just read the presentation itself, it’s available below:
2. How to handle a situation when you have REST API Gateway or BFF and you want to do something async? I want to send 10 emails and return success or error information to the user when the process is completed. Maybe I should use WebSocket/messaging communication instead of rest Gateway/BFF?
Backend for Frontend/Gateway doesn’t need to be a REST API. We have a project where outside (SPA) communication is done by WebSockets and inner one using REST and GRPC. So yeah, that’s definitely an option here.
3. If the API Gateway does the authentication then should individual microservice still need to do authentication?
You don’t need to authenticate inner services, only those that can be called directly without a gateway. Of course, you still need a token because it might carry some useful information, like user ID, user roles etc.
4. In order to talk to services, we have our classes. Is it a good idea to have a shared library of these classes between different multiple microservices, or rather keep that separate for each microservice?
First of all, duplication is not a bad thing. For example, I wouldn’t create shared lib for “clients” to each service, but rather build them on runtime using some contract – per service. On the other hand, things like loggers, tracers and other standardized things are good to be put into a shared lib.
5. How do you manage to log for the full end-to-end request?
It’s much better to use tracing instead, and pass some REQID field for easier tracing.
6. If configs are external, what happens if there is a network error when fetching the config? How often do you update configs and how?
If this is at the start of service, you can try to retry a few times and then restart the service. However, if the service is already fully working, then I would assume you have some “working” config copy (cache). Honestly, we don’t use “config service” most of the time. We’d rather pass config into the service and restart/update it if necessary, rather than have another possible point of failure.
7. For the container orchestration, should I go with basic orchestration tool like Docker Swarm first, rather than using complex orchestration tool like Kubernetes?
If you have previous experience with Kubernetes (and micro/SOA infrastructure) then I would stick to it. Otherwise, I would use a less complex solution – at least during the first phase.
8. I’ve noticed that you prefer monorepo. How do you guys resolve the case when A and B features merged/deployed to dev but then you need only feature A go to master/prod?
It all depends, but one of the answers is – feature flags. Everything can be merged go to prod, but by default, it’s switched off. Our approach is that everything on dev goes to prod. We may keep things on feature branches, but we’d rather keep it in code and use feature flags.
9. Should I use separate DB per service?
It all depends. We worked with both approaches and they both have their pros and cons. You can have a single DB and separate tables – it makes it a little bit easier to handle (since you can access data from other services directly) or single DB per service (a different DB type per service is much more flexible).
10. Is it a bad practice to have HTTP(s) protocol between services that are closed from the client and used only BE other services? It’s kinda slow in comparison to other protocols…
It’s not a bad practice, but personally I would rather use some messaging/binary protocols.
11. Is Domain-Driven Design compatible with microservices?
It’s quite common to convert bounded context into separate microservices. However, it’s not like you have to use DDD with microservices. Some of them might be a simple CRUD, and DDD wouldn’t be useful there.
12. How do you know that request is valid without the authentication? You will add a token to the request but how microservice decide that it is a valid token.
Most of the time, we have a separate security service that has information about the private key for tokens. Every time we send a request from the outside (SPA) we pass a token and in API Gateway we make a single call to security service to check if the token is valid. That way we only need to check it once, and then you’re allowed to go inside.
13. Which tool would you use for centralized logging, provided you can have service written in PHP, NodeJS or Python?
We require logs to be passed to standard output so all of them can be passed to a single source of logging. Then we can use some visualization tools like Kibana/Grafana/Greylog to check them. The only important thing is a common format for logs.
14. How do you handle a service that needs to be used in several places? For example, a service that has users’ information and you need that data in several other microservices.
One approach is to use messages as a way to pass that information and keep a local copy. Every time it changes, you just push an event and subscribe for that event in specific services. Other option might be to use a single DB and access to tables from other services. It’s risky, but we’ve seen such communication working pretty well too.
15. How to avoid the risk of the message broker becoming the single point of failure?
Scale it and replicate it. Most of the message brokers support this.
16. How to minimize operation latency?
There are a couple of options:
- reduce the number of hops by adding a local cache for common requests. Instead of asking for “user details” you can have a local “slim” version of user details, using Kafka as a data source or some kind of a stream.
- use a faster protocol – binary instead of HTTP.
17. Should API gateway aggregate data from all services without having services to communicate with each other and without services knowing about each other?
We need to separate outside and inside communication. Outside is when gateways are useful – so from SPA to our system. As far as inside is concerned, we may use events (Kafka, Rabbit etc.) or direct communication (REST, RPC).
18. Can you mention any recommended packages/approaches to code sharing across NodeJS microservices? Would having a monorepo and/or submodules be the way to go or separate packages referenced as dependencies?
In TSH, we are fans of monorepo, so I obviously recommend it. But it’s not the only option.
19. Let’s say we have a message broker that should notify three services. If the message comes in, how to keep it available exactly for three services (so it won’t be picked by only one service ) and then get rid of it.
We would introduce a special piece of code called saga, responsible for making sure that all of those services got their message.
20. Serverless (e.g. AWS lambda) vs. Microservices?
We’re fans of both. It all depends on what problem you’re facing – you need some “state” or is it just a fully stateless API.
21. If service is set up in a way that it is going to be restarted, in case of fail should I still need a health-check?
Yes, because you want to know when it happens, how often and then find what was the cause. You can think of it as a kind of “alert”.
22. Do you have any strategies for when service should be separated from your mono app? How to migrate data from databases? Fix routing?
This is why having a gateway in the first place is important. If you provide a layer before the actual app, you’re free to proxy requests for specific routes to a specific app (some goes to the monolith and the rest to your new microservice). In the case of data, it all comes down to a simple question – do need to separate databases? Splitting tables is always a complex task, and unfortunately, there is no ready-to-go method for it.
23. Do you use some tool in a monorepo for speeding up a build? I use Lerna and if we modularize the code we can run parallel. Could there be a way to have multiple services and builds that call separate builds?
We use Lerna or tools like Plop to build our custom CLI for building services. If you use Jenkins for the build, it’s absolutely possible. You might have several jobs that work only on specific services.
24. Why versioning is better in monorepo? Since services are independently deployable we should have separate version numbers increased in a different time. Could you explain?
I wouldn’t say it’s better, but it is easier to keep all services in sync because you can add new functionality and update other services at once in a single PR. In the case of separate repositories, you would need to create multiple PRs, wait for specific services to be merged, deployed etc.
25. AWS Lambda: When to use a separate function and when to have a routing inside a function to serve several endpoints?
There is no simple answer. Separate functions are easier to debug but make the system more complex. On the other hand, having the routing inside function requires more code, makes debugging a little bit easier but your architecture will be simpler. Another downside is a fact that you’re in fact building the monolithic app and may hit deployment limit.
26. Let’s say your communication is based on message broker and one of the services is down while there are plenty of messages awaiting for this service availability. What should happen to those messages in case we timeout the request for the user? Other services may not even know what channel should be purged.
A solution that pops into my mind is to have a separate channel for specific types of messages. It will allow you to specify TTL/retention time for that information, so when it hits timeout it will be removed automatically.
27. If the container orchestration tool provides the service discovery feature then do you still need to use third party service discovery tool like Consul?
Probably not. You should be fine, as long as you don’t hardcode addresses to services.
28. How can a centralized ACL service know about the specific permissions that need to be enforced in another service that is dictated by that service’s specific business model? Isn’t that mixing the responsibilities of the services?
The business model has access to security service, so you still are able to check if you can perform a specific business operation in a given service. Security service knows how to validate token and has access to ACL table, while other services might have a local copy of it (synchronised every time it changes in a security service) or call security method to check access for a specific resource directly. It removes a need for duplication of ACL logic in many places.
29. So the API Gateway is responsible for checking whether a token is valid or not. How other microservices knows that request comes from API Gateway and is not a direct request to the REST API of the microservice?
They don’t need to know. All of our services are private and the only way to communicate with them is the gateway. So it doesn’t matter if it’s a call from a gateway or other service.
30. What do you think about the auth mechanism?
We are big fans of token-based authentication. On the other hand, when it comes to authorisation, we like to use attribute-based access control or role-based access control.
31. Should logging (events, debug data) be a separate service? What tools to use gather analyse log files?
Mostly, we take everything from standard output, then it’s taken from container and passed to elasticsearch DB. From the developer’s point of view, the only thing we need to take care of is the same logs format between different languages. Most of the time we use Grafana to visualize logs, but before that, we used to work with Kibana/Graylog.
32. When it is okay to have inter-service communications and when all services should talk to each other through an entry point?
As long as your services are in the same private network, there is no need to communicate through the entry point. If you have multiple apps (set of microservices) hosted in separate networks, then the communication should go through gateways.
Phew, that’s it! Thanks to you all for constructive questions about microservices design patterns. 🙌 I hope that my advice was able to help you a little. If you missed our previous webinars but you love what we’re doing here, follow us on social media to make sure you’ll attend the next one.
Interested in developing microservices? 🤔 Make sure to check out our State of Microservices 2020 report – based on opinions of 650+ microservice experts!
Microservice design pattern – summary
As microservices become more and more popular, new design patterns emerge to help solve various development challenges, make the architecture even more efficient and improve its business capabilities, including continuous delivery through DevOps. Microservices can give you many scalability benefits as a result of employing the single responsibility principle, often with a separate database per service. Each service can span multiple service instances.
It’s worth it to know as many of them as you can, but it all comes down to choosing the right ones for your particular software ecosystem. When it comes to that – trust your developers, but make sure that you are aware of their choices and the implications they have on your software. Otherwhise, you may end up with something that is not fundamentally better than a monolithic architecture.
Consider all your options to find the best architecture for your project. Some apps may work great as monolithic applications. Other can use some microservice design pattern to achieve optimal business capabilities and for the business logic to be developed without delays.
It’s always a good idea to learn even more about various design patterns such as vine pattern, branch pattern, event driven architecture, decomposition patterns, centralized logging service circuit breaker design pattern, service discovery pattern, chained microservice design pattern, strangler pattern, saga pattern or aggregator design pattern.
As you can see, there are many considerations when it comes to choosing the most suitable backend services. The answer is not cut and dry and requires expert opinion. It’s a long-term commitment, so do not underestimate it!