Camper vans are like microservices architectures in a way: they’re really popular these days, largely because they seem like the ticket to greater agility. Living out of a camper van might feel like the ticket to personal liberation, just as adopting a microservices architecture can seem like the best way to build flexible, scalable apps.
Microservices do deliver those benefits – at least sometimes. But other times, attempts to migrate to a microservices architecture don't work out as planned. They can crash and burn, just like Millenials' efforts to live the van life.
Now, we can't offer much perspective on whether or not a camper van is right for you (although the Internet has plenty of thoughts). What we can offer is an overview of the pros and cons of microservices – with an emphasis on the potential cons, because they're easy to overlook when you're getting excited about the potential of moving your monolithic apps to a microservices architecture.
To be clear, we're not here to say that microservices are inherently bad, or that no one should use them. But we do think it's important to burst the hype bubble surrounding microservices. The reality of microservices architectures is that – although they truly have the potential to deliver lots of great benefits – many things can also go awry when you use microservices without the proper preparations.
What is a microservices architecture?
A microservices architecture is an application development strategy that breaks application functionality into a suite of discrete services. Each service handles a specific task. When put together, the services deliver all of the business capability that the app requires to serve its intended purpose.
Microservices vs. monolithic architecture
We just gave you a basic description of what a microservices architecture entails. But in some ways, the easiest approach to understanding microservices is to think in terms of what they are not.
Here, we're talking about monolithic architecture, which is the opposite of microservices architectures. A monolithic architecture is a type of application design that builds all of an application's functionality into a single unit. The exact definition of monolithic architecture is hard to pin down because there are many ways to design a monolithic app. For example, most monoliths run as a single process, but in some cases an app might start multiple processes at runtime. Monoliths also tend to have a single, unified codebase, but there's no law that says you can't separate their source code into multiple directories or files.
But setting aside subtle variations on the way a monolithic architecture can be implemented, it's fair to say that in most cases the key differences between microservices and monolithic apps boil down to:
- Tight vs loose coupling: In a monolith, all processes (if there is more than one) typically need to be running for the app to function at all, because the processes closely depend on each other – meaning they are "tightly coupled," to use a technical term. Microservices are "loosely coupled," which means each service instance can operate independently of the others (although some application functionality might not be available if some services are down).
- Scalability: The independence of the service instances in a microservices architecture from other services makes microservices apps easier to scale because individual services can be scaled up or down individually. With a monolith, you'd need to scale the entire application – a more complicated and risk-prone operation.
- Host environment: The tight coupling of monolithic architectures means that most monolithic apps have to run on a single host. (It may be possible to run replicas of the app on other hosts, but that's different from using a truly distributed hosting environment.) In contrast, microservices designs make it possible to distribute an app across a cluster of servers, each of which hosts one or more discrete microservices.
- Complexity: Because microservices apps contain more moving pieces, they require more effort to design, implement, deploy, and manage. Indeed, one of the biggest arguments in favor of monolithic architectures is that they are just simpler, and sometimes, the benefits of simplicity outweigh the benefits that microservices deliver.
Microservices vs. SOA
It's worth noting, by the way, that there is a middle ground between microservices and monoliths. It's called Service Oriented Architecture (SOA). SOA means breaking a monolithic app into a relatively small set of services – such as one to handle the frontend and one for the backend.
In the 2000s, SOA was a popular way of making monolithic apps a bit more flexible. But the consensus among developers and DevOps engineers today is that SOA tends to give you the worst of both worlds: You get added complexity, but without most of the scalability and efficiency benefits of a real microservices architecture. So, today, most teams opt either for microservices or for monoliths - SOA is no longer a widely used design strategy.
Key characteristics of a microservices architecture
Now that we've covered the basics of what microservices mean and what makes them different from monoliths, let's dive deeper into what a microservices architecture entails.
This is a bit tricky because, as Martin Fowler and James Lewis note in one of the most influential essays on microservices, there is "no precise definition" of a microservices architecture. In other words, there is no one right way to design a microservices app. Nor are there specific tools, programming languages or deployment technologies that you must strictly use to implement a microservices app. Instead, you should think of microservices as a high-level style or approach to app development more than a rigid recipe for designing applications.
That said, in practice, most microservices design patterns involve the following characteristics:
- Each microservice handles a discrete facet of application functionality: There are no hard-and-fast rules about how to divvy up functionality, but you might, for instance, devote one microservice to handling authentication, and another to interfacing with a database that your app needs to access.
- Codebases are broken into discrete units: Developers maintain the code for each microservice separately.
- Each microservice is deployed separately: Running each microservice in its own container is the most common way of achieving isolation between microservices during runtime, but it's not the only viable approach. You could also use serverless containers, or even just deploy each microservice as a separate, non-containerized process.
- Microservices can be redeployed and updated independently of each other: If one microservice stops running, the others will remain operational (although the functionality provided by the non-running microservice will become unavailable).
Microservices architecture examples: Common use cases
You can use a microservices architecture for almost any type of app or use case. But to provide a sense of what makes microservices useful in the real world, here's a look at scenarios where microservices can offer particular benefits.
Data processing workloads often require moving and analyzing large volumes of information. Microservices can help here by delegating these tasks to individual services, which can scale independently of others.
In other words, rather than having to scale your entire monolithic app in order to accommodate more data, you could scale up just the specific services that perform work like data ingestion, transformation, and analytics. You don't need to waste resources duplicating other parts of the app (like the frontend) that don't have to scale to support changing data processing workloads.
Applications that manage media content are similar to data processing apps in that certain parts of the apps may need to deal with large volumes of data, while others don't. The ability to manage different parts of the application separately is an advantage of microservices in this context.
For instance, consider a web app that allows users to upload video files, which need to be reformatted after being uploaded – a compute-intensive task. With a microservices architecture, you could devote more compute resources to the microservice responsible for video processing whenever a user uploads a video. Meanwhile, the rest of your app's resource utilization would not change. This is a more efficient approach to resource management than you could achieve using a monolith, where you'd have to allocate resources to the entire application.
Applications that need to support high rates of transactions are good candidates for a microservices architecture. Here again, the main reason why is that microservices make it possible to delegate application functionality to discrete pieces, and then manage each one independently.
That way, you can scale each microservice based on the number of transactions it is processing at a given point in time. You might have one microservice that handles order history lookup requests, for example, while another processes payments. Since the request rate for each type of transaction is likely to be independent of the other, the ability to scale each function separately is beneficial.
If you currently have a website that you’re considering modernizing or migrating, you might choose to convert it to a microservices architecture.
The exact way that you do that would depend on factors like how the site is currently designed and which programming language or framework it uses. But in general, a shift to microservices would likely entail breaking the website's functionality into discrete services. For instance, one service might serve text while another delivers images and a third serves videos.
The advantage of microservices in this case is that they would make the website architecture more granular. If you wanted to change the way you process text before serving it to users, for instance, you could update just the microservice responsible for text, without changing how you deliver other types of content.
The benefits of using a microservices architecture
At a high level, the benefits of adopting microservices are that they allow teams to break complex codebases and applications into smaller units, which are easier to manage and easier to deploy. And in the cloud, microservices can help to consume cloud resources more efficiently, as well as to improve the reliability of cloud apps.
But to go deeper, here's a more detailed list of the benefits of using a microservices architecture:
- Fast and easy deployment: Since microservices can typically be deployed independently of each other, it's faster and easier to deploy a microservices app – and to make continuous updates to it in order to implement new business capabilities – than it is to deploy and update a monolith. Being able to deploy microservices in containers, which helps to provide parity between dev and prod environments, also simplifies things from a software delivery perspective.
- Scalability: By a similar token, microservices make it possible to scale applications quickly and – if desired – granularly. You can quickly deploy additional instances of the microservices that your app needs to handle an uptick in requests, and you can shut the instances down when they're no longer needed to save money.
- Code maintainability: By breaking large codebases into smaller pieces, microservices make developers' lives easier. It's simpler to implement new features or track down and fix bugs when you can work within the code of just one microservice, as opposed to having to worry about an entire application's codebase.
- Fault tolerance: The fact that microservices usually operate independently of each other means that they have high fault tolerance. As we’ve explained above, if one microservice fails, the others keep working, increasing the reliability of your app.
- Experimentation: In some respects, microservices allow developers to experiment more while reducing risk. If you want to add a new feature to your app, you could deploy it as a separate microservice to test it out. If it causes issues, you could simply remove it, without having to redeploy the entire app.
These are the main reasons why software architects, developers, DevOps engineers, SREs, and everyone else who cares about fast, reliable applications are into microservices these days.
Challenges of a microservices architecture
We said it before, and we'll say it again: Although microservices can deliver a lot of benefits, they also have the potential to introduce a lot of problems – especially for teams that fail to plan ahead to mitigate those problems.
The main challenges you're likely to run into if you adopt a microservices architecture include:
- Interprocess communication: Although microservices can run independently, they need to talk to each other and share data. That requires a complex inter-process communication framework within the application – typically, one driven by APIs. Implementing interprocess communication increases developer effort. Interprocess communication also adds to the number of things that could go wrong with an app, and it can make it challenging to maintain data consistency across discrete microservices.
- Fatter technology stack: Microservices require more resources to run in the sense that a microservices app typically depends on an orchestrator, an API gateway or service mesh, and a cluster of servers. This means you end up with a "fatter" technology stack than you'd use for the typical monolith, which you can run on just a single server, without needing an orchestration layer or other services to help manage your distributed app.
- Testing and debugging: Getting to the root cause of performance issues tends to be tricky when you use a microservices architecture because it's not always readily obvious which microservice is causing a problem. For instance, an error in your app's login process may be triggered not by the microservice that handles authentication, but by some backend microservice that the authentication process depends on.
- Deployment complexity: Because each microservice needs to be deployed separately, microservices multiply the effort required to deploy applications. You'll typically need to set up a different CI/CD pipeline and release automation tooling for each microservice, which is a lot more effort than having to manage just one deployment for a monolithic app.
Microservices planning best practices
Given the many ways that microservices designs can be implemented, there are no hard and fast rules about how best to work with microservices. But a general guide includes the following tips:
- Organize your microservices architecture strategically: Think carefully about how many microservices you implement and which functionality each one addresses. Having too many microservices – or too much overlap between their functionality – can overwhelm you with complexity when it comes to tasks like microservices monitoring. On the other hand, having too few will deprive you of the benefits that microservices are supposed to deliver.
- Iterate and test: In addition to planning your microservices architecture carefully, experiment with different iterations to see how effective they are. You don't need to have written your entire codebase to run these tests - you can use techniques like mocking to evaluate how different architectural patterns impact microservice behavior.
- Choose the right data storage: You don't need to adopt any specific type of storage system (such as object storage or file system storage) to use microservices, but factors like how fast you can read and write from storage and how scalable the storage is will impact application performance. In addition, storage costs can vary depending on which type of storage you choose. Assess the different options and cost-benefit tradeoffs before committing to specific types of microservices storage.
- Choose the right hosting solution: Hosting environments for microservices apps can vary widely, too. Compare the benefits of drawbacks of different options, such as on-prem vs. cloud-based hosting, deploying microservices as containers vs. serverless functions, and deploying microservices on a single server vs. using a cluster of distributed nodes.
- Have a maintenance plan: Your microservices will probably need to evolve over time as your developers fix bugs and add new features. Have a plan for redeploying new versions of the microservices in a way that minimizes disruption to the overall application and users. For example, you might consider using a blue/green deployment strategy, which helps to switch seamlessly from one version of an application to another version that undergoes validation before becoming the "live" version.
How to implement a microservices architecture
Again, there are many ways to go about implementing a microservices architecture, and the steps you take will depend on factors like what your microservices use case is and the type of programming languages you are leveraging.
That said, the basic process for implementing microservices typically involves answering these questions:
- How many microservices do I need? The more microservices you implement, the harder it will be to deploy and manage them. It's wise to err on the side of fewer microservices, especially if you're new to the world of microservices design.
- How will I deploy my microservices? Again, containers are the most common way to host microservices. However, you may be able to achieve better cost and performance using serverless functions instead or another alternative to containers.
- How will I secure and manage my APIs? Since APIs are central to the way microservices apps function, ensuring that your APIs are secure and easy to manage is arguably even more important than doing the same for your application code.
- How will I handle observability? Exposing, collecting, and interpreting observability data can be especially tricky when you use a microservices architecture – so tricky, in fact, that the topic deserves its own section…
What to know about microservices observability
The main reason why observing microservices can be hard is that this “fatter technology stack” demands much more attention and a more complex integration for R&D teams. Moreover, it introduces the following issues:
- Root causes are not always obvious within distributed microservices environments.
- Each microservice typically generates its own logs and metrics, so there is more data to collect and analyze in order to observe application state.
That said, one way to simplify microservices observability is to take advantage of the fact that microservices are API-driven by observing API performance in addition to relying on metrics, logs, and traces from your microservices themselves. Because API transactions involve exchanges between microservices, tracing API calls is a great way not just to identify performance issues, but also to determine which microservices did what during a problematic transaction – and which ones are therefore likely to be the root cause of problems.
The fact that focusing on observing APIs frees you from having to instrument observability within each microservice is icing on the cake. You can collect most of the data you need to observe your app from API transactions, rather than having to run agents alongside each microservice or add code to each one to expose metrics and logs directly.
The eBPF approach to microservices architectures and observability
The bottom line: We can't promise that adopting a microservices architecture will be a pain-free experience. Tasks like having to manage multiple other services and deal with more complex technology stacks are real challenges. When it comes to observability, however, the potential pitfalls surrounding microservices can be conquered.
Thanks to tools like eBPF ,there are virtually no limits on the amount of information we can collect about microservices state and performance. eBPF opens up new ways to observe an API-centric architecture which means that you can observe any app – whether it's a monolith or a set of microservices – with zero instrumentation and without compromising on the depth or granularity of data; making those pesky aforementioned issues a walk in the park for you and your R&D team.