Friday, October 4, 2013

The Power of Weakness

Make things weak, to keep things simple.

One of my teams works on a system that lets developers define licenses that enable customers to use our features.  The license definitions contain settings, such as permissions and numeric limits, that apply to various parts of the system.  A purchasable product contains one or more license definitions.  Buy the product and you get all the permissions and numeric limits its license definitions contain.

It turns out this system has to handle a lot of complexity.  For example, if you have a few different licenses that contain the same setting ("file storage", let's say), how should they be combined?  Do they get added, or does the highest value win, or do they all need to be the same?  There are use cases for each.  In some cases, there are dependencies between settings; you can't have feature B unless you also have feature A, so should the licensing system represent that dependency in some way?

We now support licenses not only for our own system, but for a number of others, such as companies we've acquired, that have their own cloud-based systems.  So our license definitions need to support settings for arbitrary other systems.

We are continually tempted to make the system very flexible and expressive.  Any license definition could, in principle, contain other license definitions; it could depend on (but not include) other definitions; it could contain settings for any part of any system.  Any setting could be specified explicitly, or have a complex system of defaults and fallbacks if it is not present in a definition.  A setting could be calculated based on other settings; for example, "file storage" could be the product of "number of users" and "storage per user", or it could be the larger of that value and some other value.  In principle we could define a whole calculus of settings and definitions.

But we know better.  We went that way in an earlier version of the system.  What we found was that when customers didn't get what they expected, it took days of dev effort and troubleshooting just to figure out what they were supposed to be getting, let alone why they weren't getting it.  Every new dev had to rediscover the complexities, often the hard way (by writing bugs).  We couldn't trust new license definitions without testing them, because there were so many unexpected emergent behaviors of the system.  The license definitions had ceased to be data, and become program code that ran against an ill-defined interpreter.

So now our license definitions are as simple and explicit as we can make them.  No clever defaults; no calculations (except a few we had to grandfather in).  They're verbose; but we have tooling to help with that.

We're wrestling right now with whether a license definition should only contain settings for a single resource pool within the system.  If we have a license that allows a specific group of users to read and write the Foo entity, should that license definition also turn on the Foo Tracking feature for the customer company (which applies to the whole company)?  Or are those two separate licenses, which can be bundled into one product?

On the one hand, forcing licenses to have just one target keeps them simpler.  On the other hand, it just forces the complexity into the product definition; now we have to start talking about hierarchies of products, product validation tools, and so forth.

I don't know the answer yet, but I know what my guiding principle will be: I'll do whatever reduces the number of possible ways to achieve the same end result.  I'll do whatever makes the system less powerful.