No doubt you're familiar with one of the buzzwords of the moment: "microservices". Familiar with the word at least, if not in practice. If you've been doing CFML development since the before Adobe bought out Macromedia, possibly even before Macromedia bought out Allaire, you've no doubt slogged through your share of procedural, template-based CFML. You know just the app(s!) I'm talking about, don't you:
- Templates which seemingly go on and on... and on... and on...
<cfif>
statements nested deeper than the mysteries of life (with the odd switch/case or two thrown in for variety)- Inexplicable lines of code commented out with a developer comment like
<!--- This isn't working. We need to figure out why --->
left sitting there, as useful as an inflatable dart board
<cf_..>
custom tags appearing out of knowhere, referencing an expected upstream declaration, which your IDE now chugs away, searching 10,000 files in the repo, to find.- That sense of dread, followed by fear, which ultimately leads to procrastination when a new feature request comes in.
I'm not critizing the author of "that" application(s!), mind you. I've got my fair share of old, working but almost unserviceable, CFML code hanging around out there. What's important is not to look at the past, but to identify and proceed with a way forward.
Often we pick up the "Rewrite!" chant, only to have it silenced by business needs, budgets, or the "if it ain't broke, don't fix it" response. Our challenge, then, is to find a way forward that not only allows us to service and upgrade the application, but to do so in a way that makes it increasingly more serviceable and more upgradable as we do so.
What does this have to do with "microservices", you ask? A lot, actually.
Most applications evolve in a monolithic style; even if they start out small, they grow within the same repository because the components are interconnected and are part of a common business outcome. Sometimes, parts may broken out for re-use (e.g. - the ubiquitous "shared code" dependency that is even more daunting to upgrade), but the application was and is essentially a tightly-coupled entity.
In the broadest definition of the term, a microservice is a de-coupled component of an application, focused on specific functionality. More specifically to quote Martin Fowler, microservice architecture is “an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms.”
When you take a moment to step back and look at the pain points in your current application, aside from the daunting task of iterating and upgrading it, you begin to identify components focused on specific functionality, which are candidates for de-coupling and, yes, implementation as microservices.
A great example, often pain points for monolithic apps, are the resource-intensive tasks of image processing, alternative formats generation, and data import/export. Take image processing, for example. The monolithic approach would be:
- User uploads an image via a form.
- The image is processed (cropped, resized, whatever) while the user waits for the task to complete.
- Upon success, the user receives a message
Pretty basic stuff, right? The downside being that, if you need to do anything more advanced than upload resources are allocated away from the total resources available for all monolithic happenings in your app to perform that task. If a really big file needs to be processed, or some other resource-intensive task is happening at the same time, the possibility for bad things to happen increases.
The challenge in breaking the monolithic approach is that there are probably functions throughout your Big Honkin' App (going forward, referred to as BHA) that deal with images too and they are all, at this point, interconnected. Let's look at what a possible microservices re-architecture might look like:
- Developers identify all points in the code which deal with image processing, uploads, or assignment
- A new application is created ( or, even better still, a ColdBox module which allows portability), focusing entirely on the functionality surrounding images and the existing image processing functionality is migrated, with unit tests provided to ensure expected functionality and to prevent future regressions.
- A specific-functionality API is developed, provides an HTTP endpoint for all of the current processing tasks big honkin' app does around images.
- All of the existing code in BHA is modified to use the microservice endpoints. ( On a side note, one of the performance beneifits of leveraging microservices is that calls to them can be threaded more easily than can tightly-coupled, procedural blocks )
Now the user experience looks something like this:
- User uploads an image via a form. The application accepts the image and notifies the microservice of the image's location, specifying any tasks which which need to performed. The microservice returns a 202 (Accepted) status code, along with an endpoint
href
to check the status of the task it has been requested to perform. - The user now sees a message stating that their upload has been accepted is being processed. The UI starts to ping the microservice status endpoint and when the 201 (created) status code is returned, the data provided by the microservice supplies all of the endpoints needed by the UI to update itself accordingly.
If it seems like we've added additional complexity, consider the next feature request that comes in to add additional image functionality to BHA:
- We no longer have to slog through lines and lines of procedural code to implement that request. If it doesn't already exist in our microservice, we can implement it, test it and deploy it independently of BHA. Then we simply add the updated interaction in the affected parts of BHA.
- If the feature request is more resource-intensive, we provide the resources to our microservice accordingly. On the whole, though, allocating resources to our microservice requires a much smaller allocation than does allocation to the BHA monolith, because it has less to do and, therefore, requires fewer concurrent resources to be available. For example, you may only need to allocate 256MB to your microservice application, while being able to decrease the resource requirements of BHA by 1GB.
- If we require more services to be allocated to images, we can spool up another instance of the image microservice ( did we mention that containers are cool? ) and cluster (now 256MB x 2), rather than having to throw more ( exponentially huge ) resources at BHA to ensure stability.
- Bug reports and fixing regressions are now a much more straightforward process as a result.
Over time, with each iteration, your app continues to from the monolith it once was to a functionality-based, maintainable application consuming fewer resources. Eventually the "Rewrite!" chant is taken up by all stakeholders because the bulk of the process of converting legacy logic has been performed in microservice-based iterations.
In the above example, we're using an API for our microservice. In reality, we could use a separate process altogether to provide our microservice: AWS Lambda functions, OS directory watchers, etc. The benefit of microservices architecture is you have the flexibility to choose the most efficient tool for the task at hand.
Now factor in our above service implementation and then multiply it by the number of resource-intensive pain points in your legacy application. Once you go through the initial paradigm shift from monolithic to microservice architecture, opportunities for segementation and delegation will begin to appear left and right.
If you want to learn more about refactoring legacy CFML applications and leveraging microservices, join us for Into the Box 2017. Day 2 will begin with a 1-hour group session, hosted by Brad Wood and myself entitled "Bringing Legacy Apps Back To Life with *Box Microservices."
We'll provide real-world examples and strategies for evolving your old code in to bite-size, easily-maintainable services which require fewer resources and, ultimately, deliver greater customer satisfaction.
Add Your Comment
(1)
Jun 20, 2017 15:54:12 UTC
by Rene Ramos
Hi Jon, Very nice explanation on how to commence the break up of monolithic apps into microservices. I have shared this page with members of my team, as we are currently considering a conversion of one of our monolithic apps into microservices. Thanks, Rene