Coming up with a highly adaptive base structure for your SaaS is vital.
As an early-stage founder, your resources are scarce. And I’m not talking just about money. Your time and energy are even more critical.
But when you build on the wrong foundations, you’re putting them at risk.
Like a building on top of muddy ground, your system doesn’t hold up. You waste yourself into constant hesitation and technical battles that lead you nowhere. And in the meanwhile, you and your customers suffer.
A starting architecture that can grow with you and adapt to your evolving needs will increase your confidence, save you money, time, and energy, and help you focus on more meaningful matters.
Unfortunately, many founders get it wrong.
Pressure Always Kicks In, And They Don’t Have a Plan for Growing it Quickly.
In a previous article, we took a look at the big picture.
We saw a process for tackling complexity without prematurely taking on more than necessary. We looked at three crucial complexity sources in any software project: the logical, the temporal, and the physical architectures. And we saw how different configurations along these dimensions formed a ladder to climb up.
In today’s article, we’ll dig into the first stage of the ladder. We’ll see how to get to a neat logical architecture while keeping the temporal and physical distribution as simple as possible.
Want to grow a base architecture that grows with you without friction? Here’s how.
Step 1: Start At the Drawing Board by Unfolding Core Scenarios.
Start at the epicenter, pick a few core write scenarios, and unfold them end-to-end.
What are the steps (e.g., “PublishPost,” “PostToFeed,” “SendNotification,” “UpdateStreak”…)? What entities do these steps conceptually belong to (e.g., “Post,” “Feed,” “Notification,” “Streak”)? And how do these entities relate to one another?
Now, what are the read scenarios? What queries does the system have to support (e.g., “GetPosts”)?
You can use techniques like Event Storming to do it.
With that, you’ll clearly see how those scenarios should behave and the different domain concepts involved. Later, you’ll translate them into code.
But not yet.
Step 2: Find Contextual Scopes.
Now, what steps look close in meaning? You’ll find similar ones between scenarios: sending a notification, updating some metric, or modifying the inventory…these all point to independent scopes that should comprise their own language, code, and data.
In this case, they could be something like “Notifications,” “Metrics,” or “Inventory.”
These will be the services that make up your system to start with.
Step 3: Break Things Up Further by Finding Consistency Boundaries.
Many people miss this step.
The problem arises once they need to move into asynchronous request processing, eventual consistency, and microservices/SOA. Suddenly, intolerable data inconsistencies can appear, and they must go through this process anyway. But since they did not consider it from the beginning, now they stumble on a wall and need to face a problematic redesign.
So now better than later.
Find the chunks of data that must always stay immediately consistent. Those, and the code accessing them, must go together within the same scope.
This step can surface problems in your contextual boundaries and lead you to revisit them.
But once you get it right, it’s time to start coding.
Got it? Alright, now let’s pick one scenario and make it work.
Step 4: Lay Out the Skeleton.
Before installing the furniture, we need to lay the walls.
Shape the structure where your domain logic for the chosen scenario will sit on:
- Create independent modules for each contextual scope involved.
- Isolate domain and infrastructure through Ports and Adapters, both at the system and scope levels.
- Set up synchronous, in-memory messaging infrastructure for versatile communication between parties.
- Implement independent classes to encapsulate each consistency scope, its code, and data. And if you’re not using an object-oriented language, simply ensure everything within a consistency scope is self-contained through an entry API. These will be your Aggregates —in DDD jargon.
Step 5: Fill it In.
Now, implement each scenario step within its corresponding Aggregate.
Then, connect them with their respective context database to load and save the state. The Repository pattern is a good option here —but you can use the approach that better suits you.
Now, it’s time to stitch all steps together.
Step 6: Connect the Dots.
If you’re dealing with a write:
- Trigger the process through a Command type message. You can do this via a direct method call or indirectly through the messaging infrastructure you set up in step 3.
- Then, coordinate the rest of the steps through an orchestrated or choreographed approach. This is up to your taste, but I prefer the event-driven choreographed one for its low coupling. It makes it easier to organically introduce new activities into the process.
- Keep everything synchronous with the user request and save it within the same database transaction.
- And last, include causation and correlation IDs in the messages exchanged. This will help you later if, at some point, you need to process requests asynchronously.
For reads, always aggregate data on demand by querying source services, except for complex and frequent queries that demand a speedy response time. In those cases, use a separate model for reads and update it on writes —you’ll have heard about this under the term CQRS.
Step 7: Rinse and Repeat.
You made it!
Now you just have to repeat the process. Pick another of your initial core scenarios and go back to step 3 to build them.
Did you code them all yet? Then, pick a few new ones, return to the drawing board at step 1, and adjust things as needed.
Keep it Simple.
Sometimes you’ll have to go further and give up some flexibility to process user requests asynchronously and distribute services physically. But that’s often unnecessary.
So keep it simple for now.
Are you currently struggling with your SaaS architecture? Now there are no excuses. Do this for a few rounds, and you’ll get up and running sooner than you think.
You deserve it.
Say it out loud. Is there anything you disagree with? Anything missing that you’d like to add? If so, I’d love to hear your thoughts so, please, leave them in the comments.
Get on board. Do you want to receive more content like this right in your inbox? You can sign up for the newsletter here or in the form below 👇.
Share it. Do you think someone you know may enjoy this post too? If so, please forward it to them.
Have a creative time.