Relationships: Start with Several
lukebechtel.com
lukebechtel.com
When users are only doing one-to-one right now, that's what you're going to be designing the UI for. Nobody likes a pointless dropdown to select the one possible option, after all. So how do you build a "go to pet" button? You just take the first element of the list.
But that's going to silently break the moment you start allowing one-to-many, because that button cannot exist anymore. It'll still compile without any issues - it'll just be fundamentally broken. So when transitioning to one-to-many you still need to go over all code touching it to check that no shortcuts snuck in - but this time you don't have the assistance of a typechecker to verify that you rewrote it to accept a list instead of a single object.
I've worked on one too many projects where the stakeholders wanted "infinite adjustability": it's going to massively blow up your development time, make the UX a living hell, and end up never getting used at all. Unless you already have an "upgrade to one-to-many" backlog ticket planned and the project is just one-to-one for now to get the MVP out the door, starting with one-to-many just because you might need it at some point in the distant feature is almost certainly a bad idea.
Your UI does not necessarily have to expose the full flexibility of your underlying data structures.
Have an owners table, a pets table, and an owns table. Right from the start. It's the shape of the data, model it correctly from the beginning. Two people can own one cat. One person can own two cats. Model this relationship in the store.
Are there rare cases where a one-to-n rather than m-to-n relationship obtains? Maybe, but then again, you should assume you're wrong about that and interrogate the subject very carefully before deciding that one-to-one or one-to-n is correct.
The thing is that those cases can be modeled as m-to-n where one or both happen to be 1. Unless some error would arise from not preserving the singleton invariant, don't bake it in.
A system I worked on recently modeled users as potentially belonging to more than one organization, even though there wasn't a good use case for that and there wasn't any plan for how it should work if implemented. It made all of the code to get the organization from a user more complex than it should have been.
Another system I worked on had a view hierarchy that allowed a view to have more than one parent, even though there was absolutely no support anywhere in the system for a view to actually have multiple parents in practice; if you tried, everything would just crash or get into an endless loop. The only use case suggested in comments was that a cell could be a child of both a row and column in a grid, but that was just theoretical - it didn't allow for any functionality that couldn't be achieved some other way.
If you know how something should behave with a one-to-many relationship, and you know it's a use case that you might want to support, then yes, you should model it that way.
But, if you don't know how it would behave, and you can't think of a use case, then don't overcomplicate things.
Yes, a person can have more than one pet. But a person can only have one head, so why complicate things when there's no known use case for a two-headed person?
I do agree with you that many-to-many for everything isn't always the right choice though. It's a pretty tough call to make.
I've had plenty of conversations with product people where I've said "Are you absolutely SURE that the user will only ever have ONE of this thing? There are no possible situations where they might have more than one?" and had the answer "Well, OK there's this one rare case where they might need more than one... but it's hardly ever going to happen..."
For example, you start off saying 'customers should have an email address'. And you build a lot of stuff on the assumption that customers have a single email address. But then customers insist that they want to be able to change their email address. And you'll find yourself in a better place if handle that by making email addresses into a collection, adding another email address to the customer, and changing which one is marked as 'current', than if you just overwrite the single email address.
On those scenarios, a 'to-one' model is an implicit requirement imposed by law.
Years ago I worked on a system that had relations as a convenient in-memory data-structure. The jump in productivity for expressing business logic felt like going from C to Python. Operations like maps and filter and joins etc are very powerful.
(Funny enough, we used a lot of 'relational object mappers', because the other systems we dealt with were more object oriented so we had to translate into our own relational view.)
EDIT: what was the system, by the way?
Codd's original paper about how relational databases are more flexible than hierarchical databases applies just as well when comparing relations against key-value stores like Python's dictionaries (or Haskell's Data.Map, or 'normal' ways to structure data in applications, which can be seen as the equivalent of statically typed dictionaries.)
It was just provided via a library and very easy to use. Haskell is pretty good about making user-provided libraries as convenient to use as things built into the language.
There are many cases where its important things are 1:1.
I think the core impulse is to consider 1:n as an extension of 1:1. I think that is backwards. 1:1 is an extended version of 1:n with a specific restriction n=1. Often this restriction is critically important to the app. When you need 1:1 you should use it. If you don't know, use the generic 1:n form.
P.s. weirdest clickbait headline ever.
I think the core thing I've learned over time (in a startup) is that I rarely actually know for sure if there should be more of something, and 1:1 tends to create more "permanent" or "hard to reverse" changes to the way you design your system.
From the database perspective, the one-to-many model already requires introducing the third Relationship object. If you have "persons", and you have "pets", you're probably going to end up with "person_pets".
Yes, for a one-to-many you can certainly just stick an "owner" column in "pets" instead of having a separate "person_pets" table (ideally called something more sensible like "pet_owners"), but splitting it up doesn't really entail much more effort.
Beginning with them is way easier than patching afterwards
Want a PC with an incompatible separate part? Can't be done. Create two orders please.
Unrelated but same place, during some profiling I wondered why the massive server spent 1% of its time on a user table scan. It was converting the entire user base's username column from varchar to user_name()'s nvarchar on every interaction with the DB. These guys (and the database optimiser) were all really solid, but stuff sneaks in.
That's what reading this feels like. WTF? Nowhere is it mentioned that you should understand what you're building. The actual way to solve this problem is to learn more about what you're doing, and model it well. Don't start driving by turning right because right turns are easier, that's completely insane. If you don't know where you're going, stop driving and figure it out.