Share this post:
In this 6-part series on microservices application development, we provide a context for defining a cloud-based pilot project that best fits current needs and prepares for a longer-term cloud adoption decision.
Here in part 4: we consider the patterns for developing microservices applications.
This is a guide to the overall series:
- An overview of microservices (part 1), after providing context for business pressures requiring faster app development and delivery, steps through the process a team went through in transforming a specific monolithic application.
- Architecting with microservices (part 2) lays out the common capabilities of an architecture for rapidly developing applications based on a microservice. You’ll need these capabilities whether you’re transforming a monolith or developing a cloud native application.
- Implementing a microservices application (part 3) provides a method for implementing your own microservices project.
- Using microservices development patterns (this part) presents common development patterns available for implementing microservices applications.
- Using microservices operations patterns for resiliency (part 5) presents common operations patterns for achieving resiliency in your microservices applications.
- Designing and versioning APIs (part 6) offers best practices for managing the interfaces of microservices in your application.
Martin Fowler originated the design principle that a team should organize the microservices of an application around essential business capabilities. There are established patterns for developing microservices for monolithic applications your team is transforming, and for developing microservices in a green-field, cloud native context.
Whatever path you’re on with cloud adoption, these patterns provide a basic framework for evolving a microservices-based application.
Single Page Application (SPA) pattern
Backend for Frontend (BFF) pattern
While a single-page application works well for single channel user experiences, that pattern delivers poor results across user experiences through different channels, sometimes overloading a browser with managing interactions with many asynchronous REST-based backing services.
A Backend for Frontend pattern evolved in which a backend aggregator service reduces the overall number of calls from the browser and in turn handles most of the communication within external backing services, finally returning a more easily managed single request to the browser. The pattern allows front-end teams to develop and deploy their own backend aggregator service (the BFF) to handle the entirety of external service calls needed for their specific user experience – often built for a specific browser, mobile platform, or IOT device. The same team builds both the user experience and the BFF, often in the same language, leading to both an increase in overall application performance and application delivery.
In this architecture, for example, you see a layer of microservices reached through the frontend API gateway. These BFFs (Mobile, Web, Shared) invoke another layer of reusable Java microservices. In a real-world project, a different team will usually write this. The BFFs and Java microservices communicate with each other using a microservices fabric (such as Istio).
Entity and Aggregate patterns
An entity is an object that is primarily distinguished by its identity. Entities are the objects in the modeling process that have unique identifiers.
In his book Domain-Driven Design, Eric Evans explains that entity objects need well-defined object life cycles and a good definition of what the root identity relationship is – what it means to be the same as another thing.
We know from entity-relationship modeling that sometimes entities are well-defined and have a specific well-known identifier, but may not ever live independently. A combination of entities is an aggregate. In the cases where we have a cluster of entities that need to be maintained consistent in unison, we can refer to those entities as dependent entities, and we need to make sure we know what the root of the aggregate is, since the root defines the dependent entities’ lifecycle.
For development teams who are not used to designing in terms of business interfaces, Entity and Aggregate patterns are useful in identifying specific business concepts that map directly into microservices, performing comprehensive business functions.
Deciding to implement each business entity as a microservice is not the end of your design problems, of course. You must also think about how you would implement the microservice and how that microservice relates to the other services in your overall business application.
Business microservices tend to be stateful, and tend to own their own data in a database that they manage.
Services patterns offer a way to map operations that do not conceptually belong to any specific object/entity or aggregate into an entity-based approach.
Evans suggests we model such objects as standalone interfaces called services that follow these rules:
- They should be stateless
- The service’s interface is defined in terms of other elements of your domain model (entities and value objects)
- The service refers to a domain concept that does not naturally correspond to any particular entity or value object.
Adapter Microservices pattern
Adapter Microservices pattern adapts, as needed, between a business-oriented API built using RESTful or lightweight messaging techniques–with the same domain-driven techniques as a traditional microservice–and a legacy API or traditional WS-* based SOAP service.
Adapting is necessary, for example, when a development team does not have decentralized control over an application’s data source.
An adapter microservice wraps and translates existing (usually function-based) services into an entity-based REST interface. This type of microservice treats each new entity interface as a microservice and builds, manages, and scales it independently.
In many cases, converting a function-based interface (for instance one built using SOAP) into a business-concept-based interface is straightforward. Think of it as moving from a verb-based (functional) approach to a noun-based (entity) approach.
Often, the functions exposed in a SOAP endpoint correspond one-to-one to CRUD (create, read, update, delete) operations on a single business object type, and therefore map easily to a REST interface.
These operations would then simply send the corresponding SOAP messages to the existing SOAP endpoint and then translate the XML data types from the corresponding SOAP operations to JSON data types for the new REST interface.
Strangler Application pattern
This pattern addresses the fact that businesses and applications never actually live in a greenfield world. The Strangler pattern helps us manage the refactoring of a monolithic application in stages.
Since this pattern is so central to transformation existing applications into microservices, which is a recurrent focus in this series, it’s worth focusing on it in some detail. The pattern gets its metaphorical name from the the garden phenomenon of a vine that strangles the tree it’s wrapped around.
The idea is that you use the structure of a web application – the fact that they are built out of individual URIs that map functionally to different aspects of a business domain – to split up an application into different functional domains and replace those domains with a new microservices-based implementation one domain at a time. These two aspects form separate applications living side-by-side in the same URI space. Over time, the newly refactored application replaces the original application until finally you can shut off the monolithic application.
The Strangler Pattern includes these steps:
- Transform – Create a parallel new site (in a cloud platform like Bluemix, for example, or on your existing environment) that is based on the approach discussed in the previous post.
- Coexist – Leave the existing site where it is for a time. Incrementally redirect from the existing site to the new site for newly implemented functionality.
- Eliminate – Remove the old functionality from the existing site (or simply stop maintaining it) as traffic is redirected away from that portion of the old site.
The great thing about applying this pattern is that it creates incremental value in a much faster time frame than if you tried to do everything in one big migration. It also gives you an incremental approach for adopting microservices – one where, if you find that the approach doesn’t work in your environment for some reason, you have a simple way to change direction if needed.
How best to apply the Strangler pattern?
If a single page is too small and an entire application is too large, what is the right level of granularity for applying the Strangler application? To be successful, you have to interweave two different aspects of application refactoring:
- Refactoring your back end to the microservices design (the inside part, let’s say)
- Refactoring your front end to accommodate the microservices and also to make any new functional changes that are driving the refactoring (let’s call that the outside part)
Let’s begin with the inside part:
- Start by identifying the bounded contexts in your application design. A bounded context is a shared conceptual framework constraining the meaning of a number of entities within a larger set of business models. In an airline application, flight booking would be a bounded context; whereas the airline loyalty program would be a different bounded context. While they may share terms (such as “flight”), the way in which those terms are used and defined are quite different.
- Choose the bounded context that is the smallest and least costly to xxx. Rank your other bounded contexts from least to most complex. You want to start with the least complex bounded contexts to prove out the value of your refactoring (and resolve any problems in adopting the process) before you take on the more complex (and potentially costly) refactoring tasks.
- Conceptually plan out the microservices within the context (by applying Entity, Aggregate and Service patterns). At this point, we’re not actually trying to do a detailed design of all of these microservices. We’re just trying to get an understanding of which microservices likely exist so that we can use that approximation in the next set of steps.
After completing that step, move on to the outside part:
- Analyze the relationships between the screens in your existing UI. In particular, look for large scale Slows that link several screens together If you are building an airline website, one Slow may be booking a ticket – which would be comprised of several related screens that provide the information necessary to complete the booking process. A different Slow might be centered on signing up for the airline’s loyalty program. In any case, understanding the set of Slows helps you to move on to the next step of your refactoring.
- As you either examine your existing UI or your new UI, you want to look for the aspects of your UI that correspond to the microservices identified in the inside part. You need to identify which flows correspond to which microservices. The output of this step is a list of Slows down one side of a page, with a list of the microservices that might implement each Slow down the
- Size your chunk based on the assumption that the UI changes must be self- consistent. So, assume that one or more Slows are the minimum size of change that can be released to a customer, but the chunk itself may be bigger than one For instance, you may consider all of customer loyalty to be a single chunk, even though it may be made up of two or three separate flows.
- Choose whether to release an entire chunk at a time or a chunk as a series of slivers.
What to do from here:
Roland Barcia (IBM Distinguished Engineer/CTO) and Kyle Brown(IBM Distinguished Engineer/CTO) collaborated with Rick on this post.