Here's the honest version of how most SaaS projects start: you sit down, you define your entities — users, products, orders — you draw a schema, maybe sketch out a few services, and it all looks clean and sensible. Then you ship a few features and reality slowly dismantles the whole thing.
I went through this building a product in the food space. What eventually made the system make sense wasn't a smarter architecture pattern or a better ORM. It was a different starting question.
The question I started with this time
Like any 15+ years of experience developer, I was tempted to ask myself the question "what relational schema do I need" before starting it out. But this time it was very different, I had a real understanding of the problem before I even started. I like being a host, and I like preparing food and cocktails for my guests. As such, I had a perpetual problem that always fed into itself:
I would always ask myself what I want to prepare, then what the recipe is, for buying the ingredients, then what the recipe is again for preparing the thing, and then a few weeks later it would all start again, with the added complexity of "what if I have too much of X already" or "do I still have enough X left after my last supply run?"
In essence, the entities/purposes were simple: recipes, ingredients, users. Those are real things, they obviously need to exist somewhere, so it feels like a reasonable place to start.
Start with purpose, not with tables
The very first working version of the product had exactly these core concepts: recipes and shopping lists. Not because they were some perfectly-modeled abstraction, but just because they matched what I was actually needing in that situation. There was no inventory system yet, no complex ingredient graph. Just: "I want to cook this. What do I need to buy?"
That was enough to deliver a properly working product with actually logical flows that didn't need a specialized user research study. But something kept bugging me as I was developing it: Seeing how I was thinking of the purpose and not the entity, my focus was not "how do I make this or that data connection" but "how do I make it work in real life" - which naturally had me thinking about the whole inventory module long before I even wrote my first line of code about it.
The loop that defined everything
Once inventory was in, something clicked. The product wasn't a collection of features anymore — it was a cycle:
→You generate a shopping list from your event
→You go buy the stuff
→Purchased items land in inventory(pantry)
→You cook, consuming SOME of the ingredients
→Stock runs low for some, you got too much of others
→New shopping list needs to be generated
↻Repeat
That loop ended up driving more architectural decisions than any schema diagram I have ever drawn. It told me where the dependencies were, what needed to talk to what, and — crucially — what didn't need to be connected, or even stored in the DB.
A concrete example: generating shopping lists from recipes
If you want to generate shopping lists from recipes you'll need the following simplified constraints:
- Combine ingredients across multiple recipes
- Scale quantities by serving size
- Normalize units so that 200g and 0.2kg don't become two separate line items
- Merge duplicates intelligently
- Subtract what's already in inventory to avoid overbuying
The key insight there is that this feature doesn't cleanly "belong" to the recipes domain or the ingredients domain — it belongs to your continued purpose of knowing what to buy, then what to use, then why to buy to use again. That mindset matters when you're deciding where logic should live.
Why entity-based design creates friction
When you organize everything around entities, you end up with services like RecipeService, IngredientService, InventoryService. And then any feature that touches real user behavior — which tends to cut across multiple entities — starts pulling in three services at once. The logic for "generate shopping list" starts leaking across all of them with no clear home.
You get duplication, you get tight coupling, and eventually you get the thing where nobody's quite sure where a given piece of logic is supposed to live. The system becomes technically correct and practically painful.
Organizing by purpose meant asking "what is this feature for?" instead of "what data does it touch?" Those are different questions and they lead to different systems.
Thinking in terms of planning, purchasing, and inventory management as domains — rather than recipes and ingredients as entities — gave each area clear ownership and made the architecture feel like it was developing itself - rather than an outside, arbitrary approach.
Architecture as a side effect
The mental shift that made the biggest difference: good architecture isn't something you design upfront, it's something that reveals itself when you just logically solve the problem.
If I would have built out the inventory system on day one, it would've been wrong — modeled for imagined use cases rather than real ones. If I'd spent the first sprint over-engineering the ingredient model, I'd have created complexity I didn't need and painted myself into corners I couldn't foresee. Instead, this new frame of mind actually led me well into building my next restaurant and coffee shop management features
Instead: start small, follow the user's actual workflow, and let the missing pieces show themselves when they're genuinely missing.
Top comments (0)