In Part 2, I talked about how an almost trivial data simplification can turn a difficult-to-maintain system into a system with a surprising amount of power and flexibility. In this third and final post, I want to discuss a design principle that could help us to design simple, flexible systems without having to go throw a painful, labor-intensive set of iterations first.
The main design principle is this: take away as much as you can take and still have an independent, meaningful, and useful entity. Let's take the user model for example. What is a user, really? We could say, well, a user---as a data entity---is basically a username, password, name, address, email, and phone number. But is that the simplest model we could have for a user? Could we have a user without a phone number, for instance?
Remember, we're talking data models here. We're not asking whether any one user could be phone-less. We're talking about modeling our User object so that there's nowhere to keep a phone number for any of our users, at least in our user record. So we might say, "No, we can't take away the phone number."
But here's the trick: what if we made phone number a separate entity? Aha, now we've done two things: we've not only simplified our user, we've also made it possible for one user to have multiple phone numbers, and one phone number to be shared by multiple users. Users will still have phone numbers, but now we get phone numbers by composing two simpler entities.
Functional programmers talk a lot about composing functions, and the benefits you get by being able to link together a string of simpler, more pure functions. And they're right, you do get some much higher quality code by composing pure functions. But I think you also get much better data models by composing simpler data entities.
And notice here I'm not talking about just standard SQL data normalization. I'm talking about the conceptual model for the domain as a whole, regardless of how it's stored in the database. Let's go back to our user object again. Is a user a composition of phone number(s) with username, password, name, address, and email? Is there more we can take away from our user model and still have something that's meaningful, useful, and self-consistent?
I think there is. Why split out just the phone number? Conceptually, the name, address, and email (composed with the phone number) could be split out as a "Profile" entity, separate from the username and password. Why would we want to do that? Because the username and password fields are sensitive information. We really only need it when the user is either logging in or changing their password. The rest of the time, we don't need or want to be passing around the user's credentials along with the rest of their data, whenever we access the user's record.
But there's still one more step we can take. We can split out the login information as a separate Credentials entity. Now all we have left of the user is the user ID---the essence of what a user identity is. We've stripped away everything but the essentials, and have the simplest possible entity that's still useful and meaningful. We then compose that identity with other simple entities, like Credentials and Profiles, or we associate it with related entities like Owner ID. But the entity itself is as simple as it can possibly be.
Maybe on the back end it's all one table: username, password, first name, last name, address, email, phone, etc. But conceptually, we build our systems around the composition of simple types, and thus give ourselves the maximum flexibility for when the business needs evolve in unexpected directions.
No comments:
Post a Comment