ArticlesSystem designEngineering

Logical groups

Thoughts on avoiding coupling in modeling, using users and database partitions as examples.

Logical grouping is a common problem in architecting software systems. We've got so many names and words for grouping in the language of software engineering, it's a bit crazy just how little I see it discussed.

I primarily want to discuss two themes I've had a passion for digging into and understanding.

Product modeling

This is how we design our software as it is to be understood by end-users. The quickest example common to almost every business application: how we model "users" themselves.

In business-to-consumer (B2C) you might choose to model your users as each being their own User (mapping to some single representation and ID).

In business-to-business (B2B), that's not enough: people work together therefore you need Organizations as well. Here we have to answer a few questions on the relationship these models share:

Can an organization exist without any users? Usually we say, "no, an organization must have at least one user (usually the 'organization owner')."

Can a user belong to multiple organizations? This one is less straight-forward. A user as mapped to a human can co-exist and work within multiple organizations. However, are we modeling a human or are we modeling a participant in an organization? Throw in GDPR, data privacy, and access control considerations for good measure. Who owns the data: is the user the owner or is the user a contributor to the organization which owns the data? If we're modeling humans, can the organization modify the user's identity at all, globally, or just in the context of that organization?

I'll throw out some examples to answering this question:

AWS has two types of users:

  • Root users, the free floating users which have standalone identity with passwords that can access multiple accounts and can own accounts
  • Account-scoped users, users that only exist within the context of a single account

Customers pick and choose which model they lean into and how aggressively, but AWS steers folks towards the account-scoped users and suggests keeping few, well secured root users. No accounting for capturing human identity, manage it how you will. This is classic AWS: many options, a few right ways, and some exceptions and overloaded terms at times.

Companies will answer this question primarily based on how they start out and whether they've managed to evolve out of their initial choice. If you start as a B2C, users map to humans and this is an identity containing {name, email, birthday}. Then when you find you need organizations you have options:

  • Users are now organizations, you just allow multiple "logins" and eventually those "logins" become the users.
  • Organizations are created as optional. Some users will be in organizations and some will not. The user that creates an organization will be the owner.
  • No overlap, a free-floating user cannot participate in an organization. Create an organization, start from scratch as a user in that organization.

And similarly a B2B moving to support B2C:

  • "Users might want to become an organization eventually. Let's create a shadow organization behind the scenes that each user is a part of."
  • Organizations are users. Give them profiles and special case when the organization only has a single User.
  • No overlap. Cut a clean slate and have free-floating users.

Enterprise B2B will often throw more wretches into the mix: sub-organizations, then full hierarchies. Basically you can end up taking a company's org chart and mapping it to your product.

Answering all these modeling questions requires additional context and knowing the customers you're working with.

If the right answer needs additional context, context that may change over time as a business grows, could the wrong answer be to pick any particular model?

Surely we must start somewhere, so we must pick something. The elephant in the room is not all the business strategies or customer feedback to sift through to inform a decision, it's all the technical debt we take on when building on the wrong model of the world.

We have a word for this, where changes in one aspect effects or inhibits another: coupling. We know this stuff is bad. We slice and dice conceptual problems into smaller ones to avoid being boxed into a corner. And yet most software we write for business applications tends to cling to whatever the user model flavor of the time is. Code is riddled with user concerns.

This leaves us in tough places, especially in enterprise as more and more structure is introduced and we are fighting not just with the business problem but how we fit it into our existing models to make it easiest for us to deliver just enough until the next assumption breaks. This will repeat and repeat, only getting more difficult to eventually claw back and restructure.

Database partitioning

Let's look at a lower-level, similar problem: 'at scale' databases cannot contain infinite amounts of data. For efficient querying, availability, redundancy, etc, you'll eventually need to split up one data store into many.

Most often the shard key in most systems is the organization, user, account, workspace, repository, or some user-understood container of resources. It's never quite right, but going from no partitioning (as almost everything should start) to some partitioning, it is the simplest choice at the moment.

However, whatever is chosen to use as the basis for the shard key is always wrong for someone. Say you're Twitter and you pick users, well some people are incredibly popular and some aren't so you've got to find another means to sub-shard per user. Say you pick organizations, well again there's always your top X organizations doing most of the business. They'll individually outsize a single physical database or dislike the notion that all their data is stored in a single place from an availability perspective.

So we're gonna get it wrong no matter what we pick, just like in the product modeling situation.

Getting it wrong on these two concerns often happens in lockstep. A B2B all-in on building features coupled to their current notion of organizations will design the database models and the sharding scheme (and all related tooling) around the current notion of organizations. Everyday development feeds a vicious cycle of coupling as it becomes too hard not to couple for the average engineer trying to make forward progress.

Tooling will be built for the 90% use case and everything that doesn't fit the model will need to be custom built.

You are fighting a war on two fronts: customers taking your product in unforeseen directions, wanting to slice and dice to solve their exact problem and reliability pressures on the inside as they do it. It's a war of peppering over problems half-heartedly and either bending over backwards to meet your customers' requirements or solving them kinda / close enough.

Decoupling

The solution is flashing in our faces: solve the coupling problems here. Take a page out of the code quality guide: enforce strict layers between your code software and the product you expose. The email infrastructure doesn't need to know about users to send emails. The billing code doesn't need to know "customers" map to organizations to charge credit cards. Keep software and data agnostic to the business mappings and have a thin layer of wiring to connect the two.

This seems obvious, but it's not when the bread and butter of day to day product development is down the path of this coupling. There is a serious momentum to continue down the road because the 90% case is well trodden and the objective is to build something now, not do what's best for tomorrow.

I've found this very friction to be a lot of the work in designing systems with these coupling issues. Sometimes I decide to lean into the coupling which exacerbates the long-term problem but solves an immediate one. Other times, I'm pushing the decoupled approach. Having a sense of when to choose is an extremely acquired taste, that one need not build up if they think about these problems either seriously enough to correct course or early enough to not have at all.

Decouple partitioning

I do mention database partitioning in particular here because this tends to be the make or break thing for me on whether to couple to business models. Consider this situation, when met with designing a new system:

  • You must design a reliable system. Reliable means no single points of failure, and multiple physical databases perhaps across multiple geographies.
  • All you have at your disposal is a single shard scheme. Let's say we are partitioning by organization.
  • The momentum of database tooling and organization is such that you cannot introduce a new shard scheme to be used in this system within the scope of this project.

In these constraints, an engineer must eat the cost of doing the wrong thing to make forward progress.

This situation isn't unusual in the evolution of a business. If not over-scaling, generally you'll start with no partitioning at all and when you first tip your toes into it, reliability has already become an indispensable part of new designs. So be wary of building too much tooling around the business-modeling-coupled shard scheme alone, if you do at all. You'll be setting your product development into a vicious cycle.

Decouple product

Aside: I could advocate simplifying product models here, but I won't share my biases on that one. To speak on and give advice for a generic set of customers and recommend a path forward for a business I don't have context on...Understanding customer needs is really, really hard and don't escape it.

What I can recommend is (1) get used to evolving. Database partitioning is one thing that's easy to get entrenched in and go far and fast without good, decoupled tooling. Build the muscles for data migrations, UI migrations, etc so you're not trading off building the right thing versus time to engineer given the current debt and how hard it is to pay off.

And (2) consider letting your customers evolve themselves. Those same muscles you build internally might make sense to reveal externally. Database products will sometimes just accept a shard key, call it what it is and call it a day. Ask if there's truly a competitive advantage in not revealing your hand and letting customers build their system inside yours instead of you building your system for theirs (and everyone else's).

Either in a product or in a system, you can't always know what will be best and what's best will change. So maybe let the customer define their own logical groupings and get on with building nicely decoupled capabilities.