Using Bindable Aggregates
A systems thinking approach to transaction consistency
In the world of distributed systems, maintaining consistency while processing something as everyday as a sales order is surprisingly difficult. Systems need to validate customer information, check inventory levels, price items accurately, and handle fulfillment — often across different services and databases.
If these steps are too loosely coupled, inconsistencies creep in. If they’re too tightly coupled, the system becomes brittle and hard to evolve.
Domain-Driven Design (DDD) tackles this by introducing the concept of an aggregate — a consistency boundary that ensures operations affecting related entities are validated and committed as a unit. Systems thinking embraces the spirit of aggregates but rethinks the implementation to better suit modern, stateless, distributed architectures.
The Problem: Distributed Inconsistency
Let’s say a customer creates a new sales order. The front end collects line items, quantities, and shipping information. It must validate that each product is in stock, ensure the prices haven’t changed, and confirm the customer’s account is in good standing.
If these checks happen piecemeal — through isolated API calls or loosely connected microservices — there’s a good chance that a race condition, outage, or user error will create an inconsistency the business later has to resolve manually.
This is where aggregates shine. They gather all related data and rules in one place and ensure they are applied before state is persisted. Systems thinking takes this idea further.
NOTE: Standard bindable components persist data on every successful create, update,
or delete
request.
How Systems Thinking Does Aggregates
With systems thinking aggregates are:
- Defined explicitly using domain models rooted in DDD
- Stateful on the client, not the server
- Validated server-side on every request
- Persisted only upon explicit client commit
That last point is key. Traditional architectures often treat the server as the holder of state across multiple calls. With systems thinking, the client carries the aggregate state in each message and receives updated versions in each response. No server-side session or partial persistence is involved. This allows for more flexible UI design and a radically simpler back-end model.
Message-Driven Consistency
All interactions between client and server in this model occur through a small set of messages: get
, create
, update
, delete
, and commit
that make up the Bindable interface. For sales orders, the lifecycle looks like this:
Creating a New Order
- Client sends a
create
message with an initial Sales Order. - Server validates the data and may apply default values or enrich the response.
- Client sends additional
update
messages as the order is refined. - Once ready, the client sends a
commit
message to persist the order.
Updating an Existing Order
- Client issues a
get
message to retrieve the existing order. - Subsequent changes are made via
update
messages. - When ready, the client sends an
commit
to persist the changes.
Between create
and commit
, or get
and commit
, the server remains stateless. No data is persisted. It only knows the aggregate as it appears in each request from the client. This stateless model reduces coupling, simplifies scaling, and makes failures less disruptive.
Modeling the Sales Order Aggregate
The aggregate root is the SalesOrder, which owns and controls:
- Sales order metadata: order ID, customer ID, status, timestamps
- A list of LineItems, each with product ID, quantity, and pricing
- Derived fields such as totals, taxes, and shipping cost
The SalesOrder aggregate enforces key business rules:
- The customer must exist and be in good standing
- All products must be available in the requested quantities
- Prices must be valid according to current or quoted rates
- Once committed, the order cannot be modified without starting a new
update
transaction - Status transitions follow defined rules: Draft → Submitted → Fulfilled → Cancelled
Referenced entities like Customer or Inventory are non-persisted parts of the aggregate — they are consulted by the aggregate during validation — and are available to enrich the UI.
A Stateless Model With Stateful Intent
What makes this approach powerful is that state lives with the client — not the server. This creates a clear separation of responsibility:
- The client manages the in-progress order
- The server validates rules, calculates derived values, and returns an updated aggregate with enriched data
By the time the client sends a commit
message, the server has already seen and validated each stage. This reduces the chance of surprises during commit and makes rollback as simple as never calling commit
.
Why It Matters
Bindable aggregates don’t just encode business logic — they act as contractual validators that ensure everything is correct before a single byte is stored. The simplicity of having the client manage state means:
- No complex session storage or server-side coordination
- Easy UI-level implementation without risk of partial persistence
- Predictable and repeatable business logic enforcement
For teams building distributed systems that need to evolve, integrate, and scale without losing data integrity, this model offers a refreshing middle path between the rigidity of traditional DDD infrastructure and the chaos of ad hoc service communication.
In the next part of this series, we’ll walk through how to implement this SalesOrder aggregate using Java records, Bindable components, and message moderation.
Stay tuned.