(This article is the second in a series. The first one is over here.)

There’s a heartbreaking vertical/horizontal dilemma when we embark on ambitious software projects. Should we focus on a small number of cherry-picked use cases and build the best version of those we know how to, or invest effort in platform facilities that will make future, as yet unforeseen, use cases radically easier to deliver?

My nicknames for these two patterns are cooking the use case and raising the floor. I’ve always had a strong bias for raising the floor, but I’ve seen it fail enough times that my self-doubt is never far from the surface.

As with any of these false dichotomies (“all models are wrong; some are useful”), sticking with either extreme is a recipe for disaster:

If we exclusively build platform, no matter how good a job we do, we can’t be confident that we fully understand, and have prioritized correctly, the use cases and requirements we’re intending to support. I always try to open up a huge use case funnel to help us test-fit particular requirements against our general-purpose plans, but in my experience, it’s very difficult to get people to participate in that process for long enough that I feel confident that our abstractions will actually support a lot of value generation. If we spend all our time preparing for possible futures, a lot of valuable stuff can fly by when we’re not ready for it.

If, instead, we go fully vertical, and cook all the use cases, we may deliver compelling demos and prototypes and functionality in a reasonable time, but we run several very real risks:
1. The magic black box antipattern, in which we make something that perfectly solves a concrete problem. If we’re lucky, and we truly, madly, deeply understand our target personas, use cases and industries, this can really work! Unfortunately, customers are not all the same, and you’re likely to hear “Wow, that’s really cool! But can it…” In general, our likelihood of solving problems that are merely very similar to customers’ actual needs but not an exact match, is very high.
2. The tech debt toxic waste plant, in which the cooked use cases don’t really generalize, and end up as a muddy ball of one-off solutions that need constant care and feeding and patching, and don’t allow us to apply leverage in solving multiple problems at once.

What is to be done? To be honest, this is the biggest dilemma of my career thus far, and I don’t have any easy answers. We did a pretty good job at Layer 7, where the core dev team was focused on building platform, and a “tactical team” of roughly equal size was aimed at solving very concrete customer problems, using the composable platform facilities wherever they applied, but being absolutely willing to build one-offs when there wasn’t a clean match between the vision and the present. It worked pretty well!

One of the dilemmas I’ve managed to resolve to my own satisfaction over the past couple years: How do we simultaneously protect the stability of our platform, without unduly burdening experimental, agile, vertical, one-off hacks? The only useful general answer I’ve got is modularity. Unfortunately, building-in modularity from early in a project can be really expensive and time-consuming, but it’s probably quite a bit more expensive to do it later.