Any Color You Like

We know what we’ll get at Dearborn Motors, and we don’t need to know anything about manufacturing to get it. For just a few hundred dollars and a signature on the dotted line, there’s a shiny black car eager to go wherever adventure may lead.

<Car customer="rj" received="850.00" currency="USD" />

Contrast this batteries-included specimen with Dearborn’s only other product–a nuts-and-bolts DIY guide–and we understand why declarative interfaces are so appealing: if we can reach a desired state simply by describing it, we get to skip the oil and grime of the gritty details it takes to get there.

But after a few years of getting lost in between all the other black cars crowding the highway, gee—wouldn’t it be nice to have a blue one instead?

If we owned Dearborn some fresh colors would be cake. One new field on the purchase order and a few adjustments in the paint shop and a shiny blue car could be in our hands by tomorrow. But we unfortunately don’t own Dearborn Motors and their recalcitrant support team dismisses our request. Black—and only black—shall it be.

We’ve just learned an important lesson: while declarative interfaces provide terrific simplicity, they claim total jurisdiction over their domain. If we need a feature beyond their purview, our only option is to expand the domain to allow it:

<Car color="blue" customer="rj" received="850.00" currency="USD" />

This raises its own challenges: we’re now on the hook to support a new permutation (color) and all of the machinery it requires, and if back-compatibility (or a stubborn vendor) keeps us from expanding the domain, well, we’re out of luck. Nothing for it but to hand over the keys and go home.

Though Dearborn has long since retired their DIY manual, it’s hard to forget the flexibility imbued in those oil-slicked plans. How different would history be if we could customize the car on our own?

let paintedCar = paint(car, 'blue');

Not bad! But why stop at color? If it were up to us, the roads could be jammed with jalopies of every shape and size.

let car = VehicleFactory.create('cabriolet')
car = addEngine(car, 'v8')
car = addTransmission(car, 6)

for (i = 0; i < 4; i++) {
  car = addTire(car);

let paintedCar = paint(car, 'blue');

These instructions can be modified to produce many different types of vehicle. Customers have to get under the hood, but once there they can use, replace, and compose our vehicle-production facilities for whatever cases they may have.

Here we’ve come full circle: even while building vehicles up from the ground, we’re still employing declarative interfaces. While addTire is fully self-contained, addEngine delivers the specified engine while insulating us from the details of installation. The trick is that the functions here each claim a much smaller domain than Dearborn Motors’ <Car /> API.

While a declarative interface can provide simple, predictable access to the sophisticated machinery beneath, we may not be quite ready for it. In tight, well-understood domains a declarative solution will save us writing code. But it will also lack flexibility–the cold, hard currency of shifting requirements and as-yet-unknown use-cases. If we aren’t yet confident our prescription will fit every diagnosis; if we aren’t ready to claim the domain, well, let’s roll up sleeves and get back to work!