Domain Prototyping · The Method
What is the simplest thing
we could possibly build?
The hardest thing about building products is to decide what not to do. At any time there are almost infinite options for what to do next — how do we know what we should focus on, right now? What's the one thing that will provide the most value?
In Domain Prototyping, we ask and answer that question every day, all the time — and then we build it to learn from immediate feedback. Each iteration feeds the next. By combining well-established methods from UX and Domain-Driven Design with a novel way of evolving architecture, it allows us to grow our whole system organically, as we learn about our product.
Domain Prototyping gives us a four-phase compass for navigating that journey, with a rich toolbox to implement the try-observe-learn loop on a collaborative, organizational, and architectural level.
Use this page as a starting point, and explore in detail on these other pages:
- A detailed description of each phase and how UX design and Domain-Driven Design work together on the Phases page.
- A growing list of known self-similar Patterns that allow us to keep system architecture malleable throughout the evolution of our product.
- The Argument making the case for why Domain Prototyping works, and why it matters.
Chapter 0 — Navigating complexity
Situational Awareness
Building software products is a complex problem. That means we can't solve it by analysis, we can't break it apart into smaller pieces, and we won't know if we're right until we actually try it. But building a market-scale product without knowing it will succeed is also a huge risk, and it requires significant investments upfront.
We can't eliminate the complexity, but we can adjust our behavior to reduce the risk — by answering the most critical questions first.
The key to deciding what to do is knowing where we are.
A typical software product lifecycle from the first idea to market success can be charted as a succession of four phases:
- 1.Inception: Where we have an original idea
- 2.Conception: Where we turn our idea into a marketable product
- 3.Concretization: Where we hone and improve our product to make it lovable
- 4.Realization: Where we make our product work at scale
In each phase, making progress on the path to success means asking and answering the right questions, verifying assumptions, and learning from customer feedback. But the kind of questions to ask, and the breadth and scope of the topics to work on differ significantly.
To answer the key questions, we must adjust our strategy to always develop the best-suited deliverable for immediate and actionable feedback. Earlier in the journey, prototypes are fast and throw-away — we will try things out and change our mind often, so building for the long term means wasting time and resources. Later in the journey, as we get closer and closer to our target product, we will focus on details and variations, and the knowledge built into our solution will grow continuously. Rebuilding then becomes more and more expensive — so we need to create prototypes that stay with us, that evolve and easily adapt to changes.
Chapter 1 — UX Design x Domain Driven Design
The Try-Observe-Learn Loop
If we are going to evolve a product continuously, and without handovers or gaps in the process, we must integrate the two core aspects of a software product — UX design and systems design — into a single feedback loop. This requires us to apply the same core strategy to both disciplines (with different vantage points), and combine the collected feedback into shared understanding.
Historically, this has been quite challenging.
The Cynefin framework gives us the general approach for solving complex problems: probe, sense, respond. In other words, we must first do the things we think will lead to progress, in order to find out whether they will work. We achieve this by running a series of controlled experiments, each with one or more hypotheses to validate.
When software teams implement this approach, it becomes more specific:
- Try. Put something real in front of reality. A prototype, a release, a running system — anything that produces an observable response from the world.
- Observe. Watch what actually happens. Not what was supposed to happen; what did.
- Learn. Update the next attempt based on the gap between the two. And repeat.
UX research does this with users. The object is the product; we observe user behavior; the learning feeds back into product design. Short cycles, real contact, small bets.
System design must also do this, with a running system. The object is the current solution; we observe system behavior — under load, under failure, under integration with the estate, under evolution of the product it supports; the learning feeds back into the architecture.
Domain-Driven Design (DDD) — the practice of modeling software around the business domain it serves — implements try, observe, learn. The development team and subject-matter experts ‘crunch knowledge together’ (Eric Evans) to develop a shared understanding of the domain, build an executable model that represents that knowledge, and then use that model to reflect and shape the next iteration. It does for architecture what UX design does for the value proposition.
Tightly integrating UX design and DDD significantly improves the chances of creating a better product. Together, they see the product and the system it embodies as one thing, not two. They share the same feedback loop, use the same prototypes, integrate their feedback, and grow a coherent product, with a cross-functional team.
In practice, this means applying principles and methods of either DDD or UX design, as they fit the current situation. Each bring a rich catalogue of workshop formats, modeling techniques, and mental concepts that shape a common understanding and continuously move the product forward on its journey. Each activity will yield data and insights. Combining these outcomes allows us to reliably build the most effective next iteration of experiments, prototypes, and ultimately, the market-ready product.
Chapter 2 — Systems Design Challenges
Malleable Architecture
Building a product is like shaping a statue out of clay. Every round of user contact reshapes it — first, very broadly, then the details. The question that decides whether the work is possible at all is whether the clay stays malleable — whether, on the hundredth pass, we can still reshape the hand without the arm falling off.
Most architecture does not stay this malleable. It solidifies. Early prototypes become permanent; early abstractions outlive the assumptions they rested on; the system becomes something that can only be changed under duress. Once that has happened, try-observe-learn at the architecture level stops working, because the cost of try has grown larger than the value of learn. The loop breaks.
Self-similar patterns are how the architecture stays malleable. The idea, stated simply:
- We start with a small-scale domain prototype.
No infrastructure dependencies. Just the business logic, expressed as code, with whatever the simplest possible representation of the surrounding world is. An in-memory hashmap instead of a database. A function call instead of a service boundary. A local eventbus instead of a broker. - We refactor it with the same rigor we'd apply to production code.
Good patterns at the code level — clean interfaces, separated concerns, honest abstractions — are the same patterns we want at the architecture level. If we keep the code clear and well-tested, the system will already have the right shape — on a smaller canvas. - We "extrude" to larger-scale patterns only when the load demands it.
When the prototype needs to accommodate multiple users, it's time to separate client and server. When user sessions need persistence, we go from in-memory to local storage. When users need undo/redo, we may want to introduce event sourcing. When the in-memory queue starts to need durability, we replace it with an external message queue. Whatever the driver — the code at a small scale already knows how to accommodate the mechanism in the large; the shape is the same. When a function call needs to cross a process boundary, it becomes a service call, and the caller doesn't care that the implementation moved.
The payoff is that Architecture can be evolved along with the product. We try something small, see it behave, reshape and improve based on insights gained — and only refactor toward a more elaborate version of the pattern, when the load demands it (and by then we know it's the right structure). The clay stays malleable. The statue can be reshaped on another pass.
Chapter 3 — The right metrics
Goals shift. The need for learning does not.
Because we cannot really be sure what the best end result of a product journey will be, we cannot resort to the usual KPI to track our progress, and assess our success. Story points are not a reliable measure, when we a) don't have any stories, and b) can't be sure which and how many features we will need to build in the first place. The only thing we know for sure is that the game will be won by whoever can try-observe-learn the most often, with the best insights.
That is why we need to optimize our entire development process for it:
- Stay aware of our assumptions and what we need to verify.
This means reflecting and rethinking often.1 - Learn and practice methods to find and build the simplest possible experiments that could confirm or negate those assumptions.
There are plenty of resources from the UX community - we must make a habit of trying new ones, and growing your toolkit.2 - Hone our ability to conduct experiments with, and collect feedback from, real customers.
User tests can provide an extremely rich source of insights.3 But writing good qualitative interview questions, test moderation, data collection, and coming up with good scenarios, are all skills that won't just come without practice.
Chapter 4 — Organizational culture
Collaboration over governance
Learning is almost never a unidirectional process. In any reasonably cross-functional team, we will encounter people with vastly different skillsets, interests, and perspectives. That means, that for most situations, most of the time, there will be one or more people on the team with more experience and trained reflexes, and others with... questions.
It would be a waste of time and resources to let them each sit on their own, waiting for their co-workers to hand over work items. Each expert would take ownership only of what they contributed. Knowledge sharing would almost never happen, unless specifically coerced. Some individuals would inevitably become the bottleneck, with the whole team waiting for them to finish tasks. In case of sickness or vacation, this could even lead to weeks of standstill.
Many organizations experiencing similar issues try to counter these effects by exerting governance: By mandating stand-ins, increasing team sizes, and using change management boards to throttle the flow of work through the system. This all creates extra effort, significantly slows down throughput, and minimizes learning.
Counterintuitively, the much more effective and learning-boosting approach is to work together collaboratively, as a single team, all of the time - using a method called Software Teaming, aka Ensemble or Mob Programming.4
What often feels awkward and uncomfortable at first is in fact a secret weapon: UX designers may feel slightly out of place when the team discusses database partitioning, for example. But they will pick up the team's reasoning, tricks and tools, while working with their expert teammates - and find themselves fully participating in discussions in no time, even able to take over some of the maintenance work when the dedicated expert on the team is not around. Conversely, backend developers will learn enough about the reasoning and decisions the designers make to be able to fix usability issues, at least temporarily - so that work can continue.
In fact, in a team like this, the work never slows, and never stops - the team is always learning at maximum bandwidth, constantly improving, steadily delivering value at the highest possible quality that the entire team can deliver. Individuals having a bad day can have high impact on throughput in a scenario where everyone works on their own, and in parallel - a Software Teaming team always delivers.
Chapter 5 — Organizational growth
Growing to market size
Only when we reach the Realization phase, the team must eventually grow beyond the famous two-pizza-threshold — usually under a lot of pressure. We have to be prepared to encounter challenges that weren't around before: Merge conflicts, CI/CD pipelines breaking, code ownership discussions. This is the time when traditional Agile and DevOps practices may come in handy — whichever variations suit our organization best.5
Since we are already mostly committed on what the best version of our product will look like, our UX design efforts will center around improving the look and feel, A/B testing different feature versions, and more and more toward marketing, documentation and onboarding.
Since we already established the basic shape our architecture should take, and which parts of the system need to be extruded toward technical complexity, we can now commit on designing for long-term criticality. Residuality Theory allows us to determine a target design that will carry the product to market readiness, and beyond.
The greatest challenge, though, is growing the organization rapidly without sacrificing all the unique knowledge and team character that made the product evolution succeed this far. Luckily, because we spent all our efforts on the critical questions up to this point, we can now afford to concentrate the brunt of our resources on fostering organizational culture, and flow.
Team Topologies and Flow Engineering fit in nicely to help us navigate this.
For additional information, be sure to explore these other pages:
- A detailed description of each phase and how UX design and Domain-Driven Design work together on the Phases page.
- A growing list of known self-similar Patterns that allow us to keep system architecture malleable throughout the evolution of our product.
- The Argument making the case for why Domain Prototyping works, and why it matters.
Notes
The Value Proposition Canvas and the Business Model Canvas are great tools for taking a quick snapshot of our current understanding.
↩A trove of great methods and formats can be found in Design Sprints, even if we're not going to stick to their 5-day timebox.
↩For a comprehensive overview of all the different ways we could conduct user research, and when and why best to use which method, check out this article by the Nielsen Group.
↩This does not mean people cannot take breaks, or work on some tasks by themselves. Team collaboration is exhausting. Most teams will decide to agree on a core time, e.g. between 10am and 4pm, where they work together. The mornings and late afternoons are for emails, individual chores, or the tedious and boring tasks that drag on the team's nerves if done in a group: Updating documentation, writing missing tests, fixing configuration problems - or trying out new design ideas in quiet. Whenever necessary, people can just leave the group to take a break. The team will continue to work without interruption. When the break is done, they simply rejoin - the flow is seamless.
↩Although we could use Team Mitosis to keep the established work rhythm alive, and continue the Software Teaming approach with multiple teams... Before you abandon what is already working, remember this is an option, at least for stream-aligned teams. YMMV.
↩