We’ve mentioned it before, but we’re currently in love with breaking functionality out of our monstrous core app into smaller, more manageable service apps. There’s still a lot going on in PipelineDeals City, but some nice suburbs are popping up that offer all sorts of amenities. (You simply can’t beat the freshness of the farmers' market in Calendar Sync Township. Starting to wish that’s how we actually named these things.)
Life has been pretty great living on the outskirts. Domain knowledge is nicely contained and kept safe from the high crime rates and outrageous rents we were paying in the big city. But every now and then, we start to miss the convenience of what we once had. As crazy as it seems, it feels like life was simpler back then. But that just can’t be.
I want to take a look at some of the problems we’re facing in this new world. We’ve overcome them all in temporary ways, but let’s ignore those solutions for now. Let’s figure out together how to make them better before we get lost for good. (These new towns always take forever to show up on the map.)
We can talk about the issues deploying Rails for hours, but you can’t deny it’s built very well for one environment: your local machine. One command in a terminal, and you’re ready to roll. What you quickly find, however, is that rolling gets a little bumpier when you start adding a handful of service apps.
To get the full functionality of the app, you may need to run additional service apps alongside your core one. Each of those apps is — hopefully — a separate git repo that you’re going to have to manage and keep up to date. You need to be able to start all these apps locally and point them to each other. It’s possible
localhost:3000 isn’t going to cut it anymore.
Now, as complicated and tangled as that can get, let’s zoom in for a moment to see a more likely scenario. Perhaps you don’t need everything running to fix your list of tickets for today or to add that new feature. You don’t care about sending email or running billing for an account. OK, nice. Things just got simpler. Well, maybe not.
Are you sure you don’t need email? What if some action you perform triggers some in-app transactional email? What’s the app going to do when that service is down? Maybe you’re not going to touch the billing code. But perhaps that code handles telling your app which features a given account has paid for. If you don’t run it, what’s going to happen? You can mimic the responses the service would give were it around, but now you’ve deviated from how production runs. Are you sure you’re developing against accurate behavior?
From our point of view, a dozen apps – each doing its own thing – sounds great. But the user doesn’t (and shouldn’t) care about that kind of thing. Any time the user can feel the disjointedness we’re making, we’ve failed. So, how do you fix that?
Caching goes a long way here. Most of the time, the core app is making one or two requests to these apps to learn something we’ve made it forget. An account isn’t going to suddenly act wildly differently within a matter of minutes, or even hours. So, once the core knows what’s going on, it can cache that data for a little while. Smooth sailing for the user, right?
Well, now you’ve got some decisions to make. How long should you cache the response? Each request has a different answer. What about invalidating those caches? What actions can the user take that should force you to forget all over again? Can any external actions require invalidation?
Caching is not a foreign concept, even to a monolithic mega-app, but you may have some new concerns to consider. Up until now, the state of data you requested from far-off villages was predictably stable and cacheable. But it can become more refresh-worthy when it’s all inside – what feels to the user like – one self-contained world. Every service app you add brings its own list of questions, and the answers are rarely the same for any two apps.
Sometimes you need to see the big picture. You want to step back and see how many accounts are using the such-and-such feature? How many new trials signed up today? What’s the average age of a cancelled account? Reasonable questions, perhaps. But wait, you can’t craft a clever query in your console and get that data.
Ideally, you build some clean interfaces to allow you to pull all the data you need together in one place for viewing and analysis. But where do you find the time to do that? You spent time building it this way because it
makes things so much simpler (and it does). But now, you need a way to describe the state of the union, and it’s a little tougher than it was before. Is it worth tacking on another week to make it feel more seamless on your side?
Even if you get the time to create these channels, there are going to be data sharing issues that arise as you use the system more. You designed Service XYZ to know just its own little world. But as the use case grows, it may need to start interfacing with more of the core or perhaps more of these external services. Let’s say it manages a feature that’s limited to a certain number of uses. Now, you decide to offer certain accounts more uses. Well, now this service needs to know more about accounts. Do you sacrifice a little of that Single Responsibility Goodness™ for simplicity’s sake? Or do you teach it to talk to these other endpoints to gather the full picture it needs? Does that introduce caching concerns as mentioned above?
Venturing for Gain
None of this is to say we’re unhappy with our decisions. I wouldn’t even say we weren’t prepared. We planned out some rather robust little worlds that make a lot of our app’s functionality so much easier to manage. I don’t expect us to change gears at any time. But there are definitely some things we did not and/or could not foresee. Before we dive deeper into establishing more of these suburbs, I’d love to look back at where we’ve ended up and think about future-proofing the roadmap.
Have you broken your apps into these bite-sized pieces? Maybe you built it that way from the start. Did you tackle any of these issues? Or perhaps you’re not really into the plan at all. Any more insight on why?