User Interfaces are API Boundaries
Applying domain-driven design ideas to UI implementation.
Assumed Audience: software developers, especially those who work on user interfaces.
Domain-driven design, and its near neighbor the ports and adapters (hexagonal) architecture all emphasize the importance of distinguishing between your internal “business logic” and your interactions with the rest of the world. Much of the time, the “ports” that get discussed are API calls (e.g. over HTTP) or interacting with a database.
Yesterday, in the midst of a rollicking conversation about building forms in web apps,1 I realized:
User interfaces are API boundaries, too!
One of the key insights of DDD and the ports-and-adapters model is that every interaction with the world outside your program is a place of uncertainty. The API might have changed and you might be getting back different responses than you expect. The database might have been corrupted. The network might be down—or worse, degraded so that you get partial messages through, and have to deal with incomplete or nonsensical data. Your software design has to account for this. If you isolate the complexity of dealing with that to well-defined, well-constrained boundaries for your application, everything in between can be much simpler.
And the most reliably unpredictable source of data we have for any application… is users! People are complicated and distracted, and our interfaces are always imperfect, often misleading or confusing in various ways (our best intentions notwithstanding). So we get “bad” data from our users. I scare-quote “bad” here because the data is not (necessarily) morally bad (though: see Twitter!) and it is (usually) a mistake rather than malice at root (though: see all sorts of hacking). But from the perspective of our app’s internals, the data has to be validated and transformed to our model of the world, just as data returned from an API or a database does.
If you’re familiar with how these architectures suggest handling sources of data external to your program, the implication for user interaction is obvious: you need to treat it like you would an API. You should have a clean separation between the data model of a form and the data model used within your application. Put in common OO parlance: your form model is a kind of data transfer object.
Notice that this holds whether you’re using a traditional web form which submits a POST
request via HTTP, or building a rich SPA-style JavaScript app which will use the form data without ever sending it anywhere. You have to first validate the data to make sure it is complete and correct—presumably with a mechanism for letting the user know if it isn’t. You also normally need to transform the basically flat data you get back from your form into in a data structure which is appropriately rich for the domain you’re working with.
Again: all of this is bog standard for DDD and ports-and-adapters thinking. The point is that you should treat forms specifically and user interaction in general in much the same way as any other external data: because user interfaces are API boundaries.
In a future post, hopefully some time in the next week or two, I’ll trace out one of the implications of this for how I think about building forms in much more concrete terms!