olly - Fotolia
A microservices application architecture offers flexible scaling, independent feature updates and segregated responsibilities so teams can focus on their work. But, it has yet to reach mass adoption within the IT ecosystem. Legacy applications are generally monolithic, and when software teams build an application from scratch, it's easier to start off with a monolith.
Monolithic builds are architecturally simple, but only at the start. For example, Google Cloud engineers working on the organization's release team used a monolithic architecture to stand up two applications quickly, but found the design difficult to maintain and manage over time. Onboarding was especially burdensome, as new team members had to learn the entire architecture to start work on the application.
A migration to microservices enables clear division between applications features and simpler updates over time, where individual services can change without affecting others. To break a monolith into microservices, start with a solid plan for the source code and tech stack. Then, run microservices smartly with a focus on interservice communication traceability and failure isolation.
Planning the migration
Before starting the migration to microservices, assess the situation to adopt the right strategy. Here are a few key aspects to consider.
Appropriate service granularity. When breaking apart the architecture of an application, you could create numerous small services or a few large services. Neither extreme is necessarily helpful. It's critical to have the right size for each service, because with more services come more overhead and complexity. However, if services are too large, you risk coupling components too tightly and losing your ability to update features independently.
Start by reviewing the application source code and design documents. Separate services, and use the design doc to dictate how granular each service should be. Keep service data repositories sharable and accessible to facilitate collaboration across related teams.
A diverse tech stack. Microservices require an architecture that separates the front end of an application from the back-end infrastructure. This is a fundamental part of the polyglot approach to development advocated by Martin Fowler. Consider using a framework like Polymer, which allows you to manage various front-end components independently.
The actual migration. To implement the migration, use techniques to gradually decompose the monolith. For instance, engineers at Google have adopted the strangler pattern. Begin by setting up a proxy to redirect traffic to the monolith itself. Then start with the simplest parts of the monolith, and slowly work toward more complex ones. As you build services one by one, you can send traffic to the new microservices in a phased manner. Techniques such as this one minimize user-facing disruption while the architect and developers rebuild the monolith into microservices.
A migration to microservices isn't complete without a way to monitor and manage the application on a granular level. Microservice communication issues and service failure and rollback take the most time after the migration is complete.
Manage services' communication. For a microservices application to function optimally, services should communicate seamlessly. For external communication, an API gateway, or a similar message management service, is essential. These message gateways should handle routing, rate limiting and request filtering. It's also helpful to deploy a mechanism in the app that can store request data and assist in retries if the request fails.
For internal service-to-service communication, distributed tracing provides visibility into how the design handles requests, often through multiple microservices. Various tools, such as the open source Jaeger and Zipkin, can manage that job. Service mesh technology like Istio has also gained popularity and comes with integrations for Jaeger and Zipkin. As an inter-service communication mechanism, service mesh plays a huge role in service discovery and monitoring service-to-service communication.
Handle service failure. In the event of service failure, it's just the faulty service that would not function, leaving the rest of the application unaffected. Here, it helps to use a circuit breaker pattern. A circuit breaker creates a component that resembles a traditional electric circuit breaker and sits between requesting services and the services' endpoints. It employs a trip mechanism to break any connection that experiences a fault, continuously retry the connection to test it, and restore the connection between the components once the failure is resolved.
Microservices architectures enable individual service updates without affecting the rest of the application. But that doesn't mean every update will work. Configure services to automatically roll back to previously functioning states in the case of a failure. Every service needs to regularly report its health, so look for tools to help monitor service health and facilitate self-repair mechanisms.
Don't discount the monolith
It's easy to assume that a microservices-only architecture is the goal. However, for practical purposes, starting with a monolith may be a better option. There will always be a need to restructure applications to make the migration to microservices. It takes intense planning to detail the microservices architecture that's best for you, and the actual approach to that migration. The success of microservices then depends on how well services communicate with external apps and neighboring services. The payoff is huge -- fault tolerance, better observability, polyglot programming, simpler operations and a better user experience -- but getting there takes work.