A 6-point plan for implementing a scalable microservices architecture
In a previous post, I outlined the many benefits that a microservices architecture brings, if implemented correctly. However, one of the first steps along the path to a microservices architecture is determining if your organization really needs microservices. If your organization has made this determination and is ready to dive in, we find that companies that are successful have a specific, actionable implementation plan. Following is an approach that we find works for many of our clients.
Before delving into the details, I want to emphasize that you don't have to choose the full-blown options outlined in this post. Rather, your organization can abstract your actual implementations and leverage a microservices architecture by defining interfaces for some of your "infrastructure" services. This would allow for you to choose appropriate implementations and decouple you from cloud services as well.
Following are the parts we suggest you consider to ensure that you have a healthy path to a microservices architecture.
1. Serve a business purpose
Suppose that you have a single functionality service. Your team should be able to handle this just as easily as a self-sufficient product that should have:
- Automated deployment (this is good even for monolith applications)
- Exposure to other systems (accessible endpoint)
- Provisioned storage (if any)
- Scalability and load balance (depending on resiliency requirements); just double-up the infrastructure and load balance service
If the service is designed well (stateless) this should be pretty standard and not different from a typical application.
However, let's put this into perspective. Assume that you will have 100 microservices, which changes the above requirements. To manage a large number of services, you would need to automate all of the above requirements. This should already be part of your current development (automated provisioning, deployments, etc.) anyway. However, this is worth noting since it will become an obstacle when you try to deploy hundreds of services. Sorting out this step in advance will actually make your current delivery process better, even if you decide against a microservices architecture.
At the conclusion of this step, you end up with something similar to the following:
2. Protect your stuff
Once you have your single service, you will immediately notice that it needs some kind of protection. This could be built using middleware and work pretty well "out of the box." However, when you scale up a hundred times, you will probably want a uniform way to secure your services. For REST API services, for example, you would typically want some kind of SSO. This would require some means of authentication. If your organization already has some kind of SSO that you could leverage, you can simply integrate with that system. If your organization does not have some type of SSO, then you'll need an authentication service to handle your authentication requests. At this point, it's probably safer to go with an open source, tested software. A commercial software, custom building proper (secure) authentication service is expensive, and you need competence.
If you do not yet know what's going to be behind the scene, you may want to abstract your authentication via microservices as well, so that you can easily interchange the underlying product. This also would allow you to easily integrate security in the early stages before a final decision is made.
After you choose an authentication service, it makes sense to have only a single service throughout the entire organization. That quickly becomes a project in its own right!
3. See no evil, hear no evil
As soon as one of your microservices is deployed, you immediately need to start monitoring it. At this point, you should already be using some libraries (such as log4j, log4net, etc.) that support proper logging and has various adapters for your convenience. As soon as you scale this to hundreds of microservices, however, you will encounter difficulties unless you have proper log inspection tools, such as cloud-based services loggly or splunk. Your application support team will not appreciate having to dig through mountains of data to find issues (not to mention alerting). I've seen instances where application support had 15,000 issues in their log! Guess if they actually had time to go through all of them.
To avoid this chaos, you should create a uniform way to log. This could be done either by using some libraries in your microservices (most respectable cloud providers have SDKs for myriad languages) or by creating an abstracted logging microservice that would have proper implementation later.
As soon as you define your interfaces, it becomes reasonable to use that service for the entire organization. This again becomes a project in itself!
The same approach should be used for performance monitoring. It's fairly easy to profile a single service and run all the performance/load tests. However, as soon as you scale, it becomes impossible to cover all scenarios. You would need to have a discussion with your application and system support teams regarding how you want to manage scalability issues. Again, you can choose to abstract out performance monitoring calls into a microservice and decide on a backing tools later. Yet another project in itself!
You most likely see a pattern emerging that all cross-cutting services can become a huge headache for a small project team. All of those infrastructure (supporting) services are products in their own right. To make them usable by the entire organization involves numerous things. Unless these cross-cutting services are readily available for consumption and a fair share of enterprise thought went into them, a project team will be unable to come up with good solutions at the same time as they are trying to deliver business functionality. What I see typically happen are hacks being created with the intent that they will be replaced with proper solutions in the future. However, during this time, new organization-wide initiatives crop up, resulting in numerous support service hacks, which are usually used by a single product (although the support effort never goes away while the product is active).
4. Find your stuff
When creating products using microservices, you will quickly encounter that dependencies between them needs to be managed somehow. In a monolith configuration, files are describing all the "connection" strings. Looking at one service this sounds like a good approach. However, as soon as you start scaling and adding unsymmetrical scaling (some services are deployed to more nodes than others) you'll notice that configuration files are not feasible. Here comes service registry. There are multiple open source tools that can solve these issues (e.g., Consul, Etcd, ZooKeeper). Until you have a tool or process in place, you'll have to improvise. The best suggestion under this scenario is to abstract the service registry as well. As I mentioned previously, abstraction could be done by creating a microservice, or, on a component level, by creating a library that could be used in your services. One additional benefit that you get is that it also could act as your service version tracing repository. It is easy to build additional functionality that would flag services that are not requested anymore. This would create a list of services that need to be decommissioned. The same could be used to create dependency graphs.