Microservices are all the rage, and with good reason. Agile software development methodologies have raised the bar in making teams more productive and getting code out faster. But software architectures haven’t necessarily kept up. Many systems are defined with complex, logically intertwined operations. But even most with more thoughtfully defined operations are still designed as a single code base operating on a single technology stack, running in a common memory space. Microservice architectures have the potential to accelerate and improve software development, but it is important to understand the tradeoffs that they make1.
Microservices are application subcomponents (think ingredients, or parts in a mechanical system) which can be developed and deployed separately. Each microservice focuses on one specific function. For example, a prepaid card system may have separate microservices handling account access through the authorization framework OAuth 2.0 and transaction verification, e.g. based on the geolocation of the transaction or a spending rule engine.
Microservices hold enormous potential for changing the enterprise application ground rules. Decomposing an application into independent services grants developers the freedom to operate relatively independently, with minimal worry about interfering with each other, since there is less of a concern that architectural decisions made for one area of the project will negatively impact other areas. This is in part because microservices allow your team to use different technology stacks for different aspects of the product.
This is not just a difference in quality but in quantity as well. Certain microservices can expand to consume large amounts of resources without affecting others, which can remain small.
Since the services are independently deployable, it is not necessary to integrate the various product components before pushing them out to the customer. Upgrades can be more routine and less scary.
Microservices are closely related to application programming interfaces (APIs), in that APIs can be used to define particular microservices. In allowing implementations of various microservices to differ, these architectures may benefit from virtual machines, or from the newer container technology. The profession of dev ops aims to make it easier for software engineers to take control of how their code is deployed to, and instrumented in, a production system; projects that use microservices will benefit more strongly from such work.
But do microservices really affect the bottom line? They certainly can. A microservice architecture can increase revenues because it enjoys greater uptimes. Why greater uptimes? Because when you have all your software components decoupled you don’t have a single point of failure that can take your whole application down. If something fails, it will only be a single component and you can switch it out quickly with another that provides the same service. Conversely, developers can modify any single microservice and as long as they conform to clear APIs2, put the remainder of the system at minimal risk. Microservices thus have the potential to get new ideas out to market faster.
So, what needs to go into having our next project use a microservice architecture? There are two sides to consider:
- Building individual microservices
- Installing a supporting infrastructure that will allow microservices to play nicely with each other
Let’s take them one at a time.
Building Individual Microservices
What’s involved in building an individual microservice? Here are four challenges that your team will need to confront before the new behavior can take its place in your system’s architecture:
1. Defining what the service should do, and what is best left to other services.
Because of the entangled concerns, it is difficult for a developer of a monolithic application to anticipate the effects of even a small modification. Avoiding surprises requires coordination that diverts developers from their main role: writing solid and beautiful code.
Microservices, by contrast, claim to offer independence among system components. They will be most effective if those components really do perform independent functions, from a user’s perspective. It may be far from obvious how to divide a large system into microservices, each of which is convenient to access, while minimizing assumptions about the operation of other services.
igration from a monolith to a microservices architecture sets a bunch of challenges for developers and QAs. Also, this process requires continuous improvements and flexibility from all sides. However, it is definitely worth all the effort, as the benefits are tremendous: improvements in application structure and stability, supportability (easier to define and fixing the bugs) and maintenance (support for an environment that hosts the application), scalability and performance.
Serhiy Radzyniak, Softjourn’s developer.
2. Defining an API
In order to be used by outside processes or other microservices, the scoped functionality must be exposed as an API. The API forms a contract, describing the inputs that the service requires and the results that it must provide when those inputs are supplied. It implicitly defines a wall between the service implementation and the rest of the system. The more information required by the service (the bigger the holes in the wall), the more dependencies, i.e., coupling, between services. But insufficient information may impede the ability to implement the service efficiently.
Although other services needn’t concern themselves with the implementation of any particular service, they may well depend on its API. APIs should be defined with care and consistency, because any changes are likely to be expensive.
As with many other aspects of system development, tools are available to lessen the workload. Swagger is a popular framework that helps in defining APIs, documenting them, and making them more accessible.
3. Defining service scaling requirements
More involved contracts specifying not just the required result but service availability, throughput, and latency, may take the form of a service level agreement (SLA), which may have actual legal standing. In any case, it is useful to understand what demands are likely to be placed on the service. In a distributed network topology, services are not “everywhere-on” by default, but need to be implemented and made available as service instances on particular devices with access to appropriate resources. It is thus important to evaluate these demands in terms of how they are distributed through the network, in order to determine what resources are required and where, at every level of the service’s technology stack.
4. Implementing and testing the service
Developers need to code and test the microservice, as they would any application. Here, they reap the benefits of having a clear and concise definition of how the service needs to operate, uncluttered by references to other services.
Installing a Supporting Infrastructure
Building the individual microservices is in some sense the easy part. Getting everything to work together is much harder. Modern applications have any number of aspects that cut across functional units defined as services. The good thing is that installing each element of an infrastructure should only need to be done once, and will continue to provide benefits as additional services are added to the system.
1. Configuration management
Developers are familiar with make files, which manage the dependencies among components of a monolithic application, directing compilation, linking, or other updates to the appropriate pieces as triggered by changes to the system definition. Developers are now also quite familiar with version control, which enables them to manage frequent changes to a code base. In a microservices architecture, these concepts can be combined to allow versioning of configuration settings such as dependencies among services. Spring Cloud Config may be useful in configuration management of a microservice architecture.
2. Authentication
Security is an increasingly important element of every application. It is totally inappropriate to force individual service developers to handle security issues from scratch. Oauth was mentioned above as an example microservice. Netflix Zuul can be used to ensure that these crucial services are used consistently3.
3. Routing
We said that services can have multiple instances, but nobody should expect the caller of a service to know which instance they need to reach. Spring Cloud Eureka can allow general calls to a service to be directed to individual service instances that have registered with a discovery server.
4. Data offloading
What if a service instance fails? We wish to allow its work to be picked up by others. Various mechanisms can help make this possible. Among them is placement of crucial data outside the vulnerable confines of the service instance, in a database that is shared among all of the instances of that service. When such a shared database is cached, there is even a potential to speed up data access and improve application performance.
The main benefits of a microservice architecture are its singular responsibility and that every part is unique, so neither code nor functionality is repeated. In a monolithic system, code had to be copied for each customer, but with microservices the loading was reduced by placing our spending rule engine on a separate server. So it’s easier to work via microservices, each designed for a particular purpose
Dmytro Gorbenko, Softjourn’s .Net developer
5. Logging
As with any system, when a failure occurs it’s important to be able wade through the logs to see what went wrong. This task shouldn’t be left entirely to the authors of each service, to perform as they like. Logging may be performed consistently using a service built on a framework such as Spring Cloud Sleuth. It may also be enforced using Netflix Zuul, as with authorization above.
6. Monitoring
As might be expected, it’s a lot harder for the IT department to get a handle on what’s happening in a system with a microservice architecture as compared to in a monolithic system. There are simply a lot more moving parts in play. Interactions between the services, and thus the load on each service and service instance, may be unpredictable. This obviously can’t be solved by each service individually.
7. Adapting to service failures
We mentioned above the benefit of being able to replace failing components in a microservice architecture. Unfortunately, this doesn’t happen by itself. Netflix’s Hystrix supports this resilience through a notion of circuit breakers, which protect failing components by diverting some of their load elsewhere. It also performs the related monitoring mentioned above, to identify when these measures are appropriate.
Conclusion
Breaking a system down into microservices is a technique that offers a possibility of reducing the burden carried by the server while providing the array of services taken for granted in large business applications. This is accomplished by separating the server’s data and business logic into smaller web services that are then combined to deliver the required features to the user.
Are microservice architectures really new? Some claim that they are a regurgitation of software-oriented architectures (SOA), which have been around for over a decade and are themselves derived from earlier architectural patterns and standards, although others see interesting value in added requirements of microservice architectures, such as that services be independently deployable and available in distributed networks.
We’ve seen that microservice architectures offer numerous benefits. But as the number of services grows, you go from one problem (keeping people from stepping on each other’s toes when working on a common project) to another (having to make sense of a large number of services and to decide how to combine them to get real work done). Similarly, you pay for the flexibility of allowing different product teams to design, for example, their own database, when you need to perform analyses that cut across all of that data. Furthermore, it is easy to overstate the degree to which services can actually be constructed in isolation of each other. Notice that in addition to the infrastructure concerns, three of the four mentioned considerations in defining individual microservices to some extent involve interactions among the services. So, as usual, there are no panaceas, although the advantages of this promising approach in enabling a smooth transformation to a more agile and resilient enterprise should be kept close in mind as modern systems are constructed.