In my last post I discussed the pervasive issue of relational modeling as a (poor) substitute for proper domain modeling, the reasons why (almost) everyone continues to build software that way, and the resulting problems that arise for those with ambitions to move their relationally-modeled software system to the cloud.
Given the prevalence of RDBMS-backed enterprise software and the accelerating pace of public cloud adoption, chances are pretty good you’re faced with this scenario today. Let’s discuss what you can do about it.
Scenario: We Built Our App Against A Relational Model… But Now We Want It In The Cloud!
My first piece of advice is make sure you really need public cloud. The cloud has well-documented scalability, elasticity, and agility benefits, but most of those arise only for software designed intentionally to take advantage of them. They also tend to prove most cost-effective when amortized across a relatively long application life span, or across a relatively large (and, ideally, increasing) number of transaction requests. If yours is a legacy app with modest and relatively static resource needs, the cost justification for a public cloud deployment may not be so obvious.
It may, in the final analysis, not exist at all. Sure, you can leverage IaaS capabilities to spin up VMs and perhaps buy yourself a modest additional level of deployment flexibility. For some, that might be a reasonable and justifiable decision; perhaps capital expense constraints preclude reinvesting in updated hardware for the private data center. But let’s face it, a “modest improvement” in deployment flexibility is hardly the stuff of IT legend.
Instead of diving blindly into the cloud and/or immediately devoting resources toward a cloud-focused refactoring effort, first consider your reasons for wanting to adopt public cloud. If your application performs poorly on existing private data center hardware, the cloud isn’t pixie dust to wave over it and make it better. In fact, for some legacy codebases cloud hosting could degrade performance even further! You must understand what you’re dealing with; find a trusted software architect to assess existing issues with your application, determine root causes, and recommend mitigation strategies. Know where your big resource-hogging transactions are. Understand which tables in your current relational database are hotspots, and what you can do to fix that. Understand what happens front-to-back-to-front as users click through your web UI and wait several seconds or more for page navigation, data refresh, etc. In many cases, additional (if non-trivial) refactoring might be enough to squeeze better performance from your existing infrastructure. If yours is a multi-tenant architecture, consider sharding your database layer by customer to better accommodate heavy load. This may also put you in a better position to eventually migrate to the cloud at a more appropriate time.
Perhaps your legacy app runs well enough for current user demands, but you anticipate additional capacity needs in the near future. This isn’t a bad reason to consider public cloud; but are you sure your app will scale well horizontally? Recall that scale-out (horizontal scaling) is far and away the preferred scalability mechanism in the cloud; scale-up (vertical scaling) is also possible but tends to be very expensive in the cloud and is only a good solution in relatively narrow cases. If scale-up is your preferred (or only?) strategy, it’s entirely possible you can achieve your goals more cheaply by migrating to bigger iron in your private data center than by paying hourly costs for super-sized cloud VMs. Maybe… maybe not. Again, you may need to find a trusted resource to help you do TCO analysis of cloud and private data center options. Be informed. Don’t make knee-jerk decisions, in either direction.
So far it sounds as though I’m recommending against legacy app migration to the cloud. Far from it… done correctly and for the right reasons it can easily justify the needed time and resource commitments. If your due diligence indicates that a move to the cloud makes sense for your legacy application, your next step is to determine whether the IaaS or PaaS hosting model makes sense for you (I’ll skip over the choice of public cloud provider, for now anyway).
We can keep this short: you’re almost certainly going to want to start with IaaS virtual machines. PaaS hosting is a great model for applications intentionally built to leverage the cloud and comfortable with the constraints imposed by the PaaS provider of choice (usually things like choice of technology stack, deployment options, etc). PaaS can have a superior TCO story relative to the IaaS hosting model. But as you’re starting with a legacy application that wasn’t built for the cloud, PaaS may not be a deploy-and-forget scenario for you. To leverage PaaS you’ll need a horizontally scalable application that minimizes dependencies on custom OS configuration and file system access (custom Windows registry keys and reliance on specific or deep file system hierarchies are all red flags). You’ll need to understand the configuration, monitoring, and authentication/authorization subsystems of your PaaS provider and ensure your software plays nicely with those. You’ll also need to enable access from your PaaS-hosted app to your database; many PaaS solutions provide such behavior but only for a limited subset of technologies and configurations. Non-standard setups either require manual configuration or might simply be disallowed. In summary, a PaaS-hosted application is a cloud-ready application; getting your legacy app to that state is going to involve some effort. Thus IaaS will usually be a better starting point for existing apps.
Of course, VM hosting in the cloud can help you more easily scale up your existing relational database to the limits of your data size and query patterns (and budget :-)) and help you correspondingly scale up/out your application tier. To the extent this gives you more flexibility than you would have otherwise had in your private data center, this is a good thing. But if your resource needs continue to grow, you’ll eventually end up needing to refactor your database and your code to scale horizontally. Where to start?
Here’s where proper domain modeling is needed. There are a number of techniques for this; my personal favorite is Domain-Driven Design. Set aside your existing software for the moment and build a conceptual model of the business problem its solving… something a user of the system would understand. Know that this is no easy task; prepare to spend time and money to do it right. But the overarching goal is two-fold. First, develop this conceptual picture of your business problem independent of any storage-centric model used to represent data. This will give you technology-agnostic building blocks through which you can sort through challenging aspects of the system. Second, subdivide that large conceptual model into smaller, easier-to-digest sub-models that describe discrete processes within the larger problem domain. In DDD these sub-models are known as bounded contexts; they provide ambient, point-in-time meaning for actions and state changes within the system (you’re not just adding line items to a purchase order, you’re building the user’s shopping cart and preparing it for processing) and they’re bounded by a primary reason for existence (sort of a modeler’s take on the Single Responsibility Principle). A single conceptual model will consist of many such bounded contexts.
Bounded contexts become the unit of transactional consistency within a larger system; that is, state changes within a bounded context should be encapsulated within a single database transaction. Thus the use of bounded contexts tends to largely eliminate a big problem of monolithic relationally-modeled applications: the tendency for state changes to cascade across large swaths of the relational model, which results in very large (and slow!) transactions. Of course, the need will arise to propagate changes from one context to another; in DDD this often occurs as a formally modeled state change event (“item X added to shopping cart”) which can be handled by any other context which previously registered interest in such an occurrence. The handling of these events occurs beyond the scope of the initiating transaction, however. For more information on how this works, read up on eventual consistency (or drop me a line!).
The use of bounded contexts has an interesting side effect for cloud-based systems. By subdividing your application model into smaller, self-consistent sub-models with small transactions, you make it possible to deploy storage independently for each context. In other words, you get horizontal scale of your database as a fundamental part of your design, which is the fundamental building block of a cloud-native application.
In practice, multiple bounded contexts will store their state on the same machine (even the same database instance); but there’s nothing stopping you from using multiple database machines or even multiple database products across multiple machines, if the need arises from a scalability perspective. In fact, that’s precisely how cloud-hosted data stores scale to handle request levels far beyond what was considered “big” even five years ago. In the cloud, you don’t need (or even want) One Big Database.
So back to your existing application. Once you’ve done some domain modeling and subdivided that into manageable sub-models, consider the work you did to identify performance bottlenecks and database hotspots, and map that list of issues to the sub-models you created. Granted the lines won’t always be neat and clean, but the goal is to prioritize sub-models that you can migrate from the existing relational model to their own self-encapsulated ones. Start with the highest priority items and work your way down the list. With time and patience you can convert that legacy, monolithic enterprise application into a horizontally-scalable, cloud-ready fire breather. Just realize that there is no silver bullet here; application migration to the cloud is a process that rewards steady persistence above all else.
A minor disclaimer: clearly I’m glossing over the details of this process, and focusing principally on the database layer. This is intentional, but it does omit other interesting and relevant topics like security, integration, deployment mechanics, legal and economic considerations of the cloud, etc. I haven’t even scratched the surface of all DDD has to offer. Those are fun discussions too :-). Ping me if you’d like to talk specifics of your situation.
Next time, I’ll talk about ways to avoid this whole mess if you’re writing a new cloud-targeted enterprise app… we have the technology!