Gastro Solutions set out to build a robust business application to manage menus, orders, and payments for a restaurant franchise. But their traditional transaction script approach relied heavily on a “smart UI” designtreating the entire system almost like a CMS with no separation of concerns, layering, or contextual boundaries. I was brought in to apply strategic and tactical domain-driven design, define bounded contexts and models, and deliver a report on current practices and future direction.
Understanding the requirements
Domain modelling often tempts us to lean on generic patterns or familiar structures especially in domains that superficially resemble well-known business processes. At first glance, ordering and paying at a restaurant might seem like e-commerce, but the domain introduces unique challenges:
Eating out at a restaurant is a rather long and somewhat disorderly interaction between one or more guests and a restaurant: Order items (foods and drinks) are added, prepared, and served sequentially (by course) and they are cancelled or changed often. Guests might spontaneously move to another table and/or join other guests there. Finally, what a guest has ordered rarely matches what they will eventually pay: A guest may pay everything ordered at the table. But in this day and age splitting or dividing the bill is also customary. This shows how a linear order->pay->deliver process familiar from online shopping or buying groceries at the supermarket cannot work for restaurants.
Dining at a restaurant is a lengthy, often unpredictable interaction between guests and staff. Orders for food and drinks are added, prepared, and served sequentially by course, frequently modified or cancelled along the way. Guests may switch tables or join others. Crucially, what a guest orders rarely aligns with what they pay: one person might cover the entire bill, or the group may split or divide it in various ways. This fluid, social dynamic breaks the linear order→pay→deliver model familiar from e-commerce or grocery shopping.
Unfortunately, restaurants face the same legal obligations for recording payments as other cash-based businesses. To prevent tax evasion, EU regulations require all orders and payments to be logged as they happen and in an immutable format — a process named, somewhat confusingly, “fiscalising.” In addition, businesses must generate and submit a daily closing report summarising all fiscalised transactions.
Evaluating the current solution
The team’s initial solution was narrowly focused on the legal requirement of recording payments. As a result, the ubiquitous language diverged sharply from that of restaurant staff. Concepts like tables, guests, orders, and payments were replaced by a single abstraction: The “Business Transaction.” This disconnect permeated the entire application, leading to misaligned models, delayed event dispatching, and incorrect order total calculations among other issues.
Legally, a business transaction is any activity that increases or decreases a company’s value. Selling goods qualifies but so does buying ingredients, consuming products without payment (e.g. staff drinking coffee or the manager using the company car), paying taxes, and nearly every routine action in a restaurant. In stakeholder meetings, participants could clearly distinguish these events when using everyday restaurant language. But once they were asked to adopt the agreed terminology, such as “business transaction” discussions quickly became muddled and confusing. For instance, referring to a service staff member as an “on-site user” didn’t help either. If anything, it sounded more like someone taking heroin at a supervised injecting site than someone serving tables.
Bundling ordering and payment into one single flow led to a complete absence of context separation. The application lacked aggregates entirely and was built around a transaction script that tightly coupled both processes. This made it nearly impossible to enforce key rules and requirements from the actual restaurant–guest interaction.
For instance, for compliance with fiscal regulations, the system had to ensure that all tables were closed by the end of each business day. That meant every order needed to be either paid, exempted (with manager approval), or flagged as a bad account (e.g. when guests left without paying). Leaving any order open would result in an unresolved transaction, effectively suggesting the owner pocketed the cash. This is precisely the kind of tax evasion these laws are designed to prevent. Yet the team had implemented this critical safeguard as nothing more than a dismissible user warning.
Beyond strategic and tactical design gaps, the solution also suffered from technical oddities. It used a custom event broker that versioned events by time rather than sequence and broadcast these as CSV, a format prone to structural limitations, parsing issues, that’s incompatible with modern tooling.
Monetary calculations were prone to rounding errors due to chosen types. This was justified by the claim that including VAT in gross prices (as restaurants often do) makes such discrepancies less problematic. Aside from being incorrect, this argument eventually falls apart under European Fraktur-X regulations for B2B invoicing, which mandate net pricing and explicit VAT calculation rendering precision non-negotiable.
Results
Early on, I worked with stakeholders to create a comprehensive glossary of restaurant-specific terms and phrases. This truly ubiquitous language became the foundation for separating the menu, ordering, and payment contexts, and for developing new domain models for ordering and payment.
A deeper review of fiscalising requirements also revealed a regulatory option that allows restaurants to decouple ordering from payment as long as both remain linked by a common reference. This opened the door to modelling the two processes independently without compromising legal compliance.
Domain modelling
I introduced an “order” domain centered around a TableOrder aggregate, responsible for tracking all orders for a table party by course including cancellations, comped items, discounts, and total calculations. To support individual payments and the “move to another table” scenario, a seat property was added to order item value objects.
TableOrder also generated payment intent entities (i.e. bills) for all three payment modes: individual, split, and full-table. It handled item and partial amount allocations accordingly. This separation allowed the payment context to focus solely on its core responsibility: processing payments.
Given that splitting or dividing bills is now more common than one person paying for everything, especially in casual dining, one might reasonably ask why I didn’t model individual seat or guest order aggregates. In fact, that approach would offer several advantages and is equally valid. However, the client specifically requested that each table maintain only a single open order at any time to minimise errors, which guided the design.
With the new TableOrder aggregate and an explicit state pattern, tracking open and closed orders became straightforward. Orders could be closed through payment, but also by marking them as invitations, implemented as a bill paired with a credit note to comply with accounting standards, or by flagging them as theft. This approach ensured that all closure scenarios were modelled consistently and traceably.
Since fiscalising was required as orders and payments occurred but not necessarily in real time I proposed implementing it via domain events using an outbox pattern and the framework’s existing message queue. Given the difficulty of maintaining a truly immutable, tamper-proof record store, I also recommended integrating one of three available cloud fiscalising services through their REST APIs. These services are designed to meet regulatory standards and offload the complexity of secure transaction logging.
Final report
As part of the engagement, I delivered a final report outlining the key differences between the team’s procedural approach and the principles of Domain-Driven Design (DDD). It emphasised how DDD’s focus on bounded contexts, aggregates, and ubiquitous language could better reflect the realities of restaurant operations. Another central recommendation was to avoid treating restaurant staff as “human domain models” and shift rules and invariants from a front-end rule builder into the appropriate back-end aggregates, which was welcomed by the staff representative on the team.
For example, the admin interface required staff to define generic menu rules using mathematical operators to control availability by date and time, among other things. These rules were not only difficult for non-technical users to configure, but also impossible to extract and enforce on the back-end. Even for developers, reasoning about date comparisons using < and > can be surprisingly error-prone.
The report also included recommendations for further architectural restructuring, such as clearer separation of concerns, stronger context boundaries, and more robust event handling. To support ongoing alignment and domain understanding, I strongly encouraged the team to run an event storming workshop with all stakeholders.
Finally, I offered suggestions for strengthening foundational design skills particularly around modelling and abstraction. While not the central focus of the report, these recommendations were intended to help the team build greater confidence in applying DDD concepts effectively and sustainably.