How to Apply Object Orientation to Non-Entities: Implementing the Interaction in Your Business Model

I specialize in developing object-oriented java applications that aligns with business objectives, using Domain-Driven Design principles to ensure technical decisions drive tangible value. By focussing on a deep understanding of the business domain, I craft solutions that solve real problems while maximizing ROI. My approach evaluates the cost/profit ratio of every decision—only implementing technologies when the benefits outweigh the costs. I’ve been called in to revive stalled projects and address challenges where others have struggled. My focus is on creating software that not only meets but exceeds business expectations. Whether working with legacy systems or modern frameworks, I select the right technologies to maximize value—not just follow trends. I believe software should be a strategic asset, and this mindset guides every decision I make in development.
How modeling the scope of behavior dissolves complexity in persistence, events, and security.
Everyone Knows What an Interaction Is
Every real-world process consists of interactions. You walk into a store, choose an item, pay, and leave. That small, well-bounded experience — from greeting to receipt — is one interaction.
In a webshop, you see the same pattern repeated: browsing, adding items to a basket, checking out, paying, shipping. Each of those phases is a discrete interaction — a meaningful exchange between two parties that starts, evolves, and ends.
We instinctively understand where one interaction ends and another begins. But our software rarely does.
Most systems model entities, aggregates, and repositories — yet they forget to model the scope in which these objects actually live. And that’s where a huge amount of accidental complexity comes from.
The Forgotten Object
In traditional architecture, we let frameworks define our boundaries. A transaction annotation here, a request scope there, a thread-local session somewhere else.
We end up with code that “works,” but nobody can tell you precisely where the system is alive — where its operations start and end as cohesive business units.
The idea of Interaction changes that.
An Interaction is a domain object representing the active scope of a business transaction: the space in which the domain breathes, decisions are made, and persistence and side-effects occur.
It defines a context:
when things start,
when they end,
and who is acting inside them.
What Happens Without It
Without an explicit Interaction:
Transaction management leaks into every layer.
Event firing depends on arbitrary framework callbacks.
Temporary resources get forgotten or mismanaged.
Security contexts drift across threads and async calls.
We end up with technical solutions to semantic problems. And that’s backwards.
The boundaries of what belongs together are business concepts first — technology second. Once those boundaries are modeled explicitly, everything else can align behind them.
Modeling the Interaction
An Interaction has:
A defined beginning and end.
A set of attached resources — a persistence manager, an event list, temporary files, and a user identity.
Clear rules for what happens on success or failure.
Interaction.execute(() -> {
Order order = OrderFinder.find(orderId);
order.approve();
NotificationService.sendApproval(order);
});
Here the Interaction is explicit but lightweight:
The persistence context is opened when the Interaction starts.
The domain logic executes.
On success, flushables (like events) fire; on failure, everything rolls back.
Temporary files or buffers marked as cleanables are removed.
No extra frameworks, no annotations, no indirection — just visible lifecycle logic.
Attaching It to the Application Lifecycle
HTTP is the perfect host for this concept. Each request is, by nature, an interaction between client and server — bounded, atomic, and ephemeral.
You can tie your domain interaction directly to the HTTP lifecycle using a simple Filter:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
Interaction interaction = Interaction.newInteraction();
try {
chain.doFilter(request, response);
interaction.end();
} catch (Throwable t) {
LOGGER.error("Error ending interaction", t);
interaction.cancel();
}
}
Now every request automatically defines:
When an interaction begins and ends,
When the database transaction starts and commits,
When events are dispatched,
When cleanup occurs.
All boilerplate disappears. No scattered try-finally blocks. No thread-local transaction juggling. Just clean, predictable behavior tied to natural application flow.
Making Security Part of the Interaction
Every interaction has an actor. When a user — human or system — initiates a call, that identity belongs inside the Interaction itself.
At authentication time, simply attach it:
interaction.setUser(authenticatedUser);
From there, the user becomes available throughout the entire scope:
User current = Interaction.getInstance().getUser();
This gives you:
Consistent, thread-safe access to the authenticated user.
Easy audit trails (“who did what”).
Implicit authorization checks inside domain logic.
No need to pass user references through multiple layers.
The Interaction becomes the identity boundary as well as the transactional boundary — defining not only what happens, but who makes it happen.
A Practical Pattern for Readable Code
You rarely need to call Interaction directly. Instead, make it fade into the background by exposing its EntityManager through a static reference in each finder or adapter:
public final class ListTypeFinder {
private static final EntityManager ENTITY_MANAGER = Interaction.getEntityManager();
public static ListType find(UUID id) {
return ENTITY_MANAGER.find(ListType.class, id);
}
}
Now queries read naturally:
ListType listType = ListTypeFinder.find(listTypeId);
Developers use the familiar persistence API, but underneath, every query executes within the active Interaction scope — with proper transaction boundaries, user context, and cleanup automatically applied.
This small indirection drastically improves readability. It removes noisy technical concerns and keeps the focus on the intent of the code rather than the mechanics of execution.
Why Testing Becomes Effortless
Because the Interaction defines both lifecycle and context, testing becomes trivial.
In your test base class:
@Before
public void before() {
Interaction.newInteraction();
}
@After
public void after() {
Interaction.cancel();
}
Combined with an in-memory H2 database, you now have clean, isolated tests with real persistence, no mocking, and automatic rollback between tests.
You can spin up an Interaction per test or per suite — whichever matches your test’s behavioral scope. The result: reproducible, side-effect-free tests that mirror real runtime behavior.
The Interaction dissolves a host of setup and teardown complexity.
ACID as a Behavioral Contract
Most people think ACID — Atomicity, Consistency, Isolation, Durability — is about databases. It isn’t. It’s about behavior.
An Interaction naturally follows those same principles:
Atomicity – It either completes or cancels entirely.
Consistency – The domain moves from one valid state to another.
Isolation – Parallel interactions don’t leak data or state.
Durability – When it ends successfully, results persist and events are published.
By making ACID a property of your Interaction, you make reliability a domain concern, not a framework feature. And because ACID is behavioral, not technological, your model stays valid across databases, ORMs, or even message-based systems. The implementation can change. The guarantees remain.
The Broader Impact
When you model Interaction explicitly, several deep simplifications emerge:
Transactional clarity – You always know when persistence begins and ends.
Event discipline – Events fire exactly once, at the end of successful interactions.
Security coherence – The acting user is always known, consistent, and auditable.
Test simplicity – Each test runs inside a clean, disposable interaction.
Cognitive focus – Code reads in the language of the business, not in the jargon of the framework.
In effect, the Interaction absorbs the infrastructural complexity that usually spreads across your codebase. It becomes a structural skeleton around which technical and behavioral consistency naturally form.
These impacts compound: Fewer bugs from mismatched boundaries, faster onboarding for new developers, and easier evolution as requirements change. What was once a tangled web of callbacks, annotations, and manual orchestration now collapses into a single, owned concept. The Interaction doesn't just manage lifecycles—it eliminates entire classes of errors by making implicit assumptions explicit.
Opening Doors: Owning the Start and End
The true power of the Interaction lies in its conceptual ownership of beginnings and endings.
By giving a single object explicit control over these boundaries, you unlock extensibility that's not tied to any specific technology:
At the start, you can inject setup logic: initialize caches, log entry points, enforce preconditions like rate limiting — all without scattering code across entrypoints.
At the end, whether success or failure, you can hook in post-actions: metrics collection, telemetry, or even chain to subsequent interactions (e.g., triggering a background job on commit).
This isn't limited to persistence or web requests. Imagine applying it to batch jobs, message handlers, or CLI commands — anywhere a bounded process exists.
The Interaction becomes a pluggable extension point, allowing domain-specific behaviors to attach naturally. By owning these hooks conceptually, the model invites innovation: plugins for observability, error recovery strategies, or even AI-driven validations.
Behavior First, Implementation Second
The deeper lesson here isn’t about transactions or filters. It’s about modeling behavior explicitly.
When the model defines the boundaries of reality — when it tells you what can happen, when, and by whom — infrastructure stops dictating architecture.
The system becomes timeless: stable by meaning, adaptable in implementation.
Closing Thought
Every meaningful business process is an interaction — we’ve just forgotten to give it a place in our designs.
By reintroducing it as a domain object, we gain natural transaction scoping, event consistency, clean security, and trivial testing — not by adding frameworks, but by modeling behavior correctly.
The result is software that feels alive but controlled — systems that behave like the world they represent. When the model absorbs complexity, the resulting code becomes almost childishly simple. The hard work moves where it belongs — into design, not syntax.






