Design Question: How to Keep Domain Modules Free from Implementation Concepts in Modularization for DDD?

This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.

Another design question, although maybe less general to modularization, and more specific to DDD folks–

We’re trying to modularize around business (problem) domains… subsystems / packs including “products”, “inventory”, “orders”…
I’d like your input about concepts from the implementation (solution) leaking into the domains / subsystems / packs.
For instance… some of our user operations can’t be satisfied quickly (performance)… the user may have to wait 5, 10 or even 30 seconds. So, we design in the user interface / application space to allow for this. But those delays aren’t first class citizens captured in the domain. But as our system starts to integrate with more external ones, and those external ones may fail… we’re faced with wanting to track the fact that the operation being performed by the user is (currently) dispatched to an external system (solution space / implementation concept).

So the question in a nutshell-- are you able to keep your domain-specific modules free of implementation-specific concepts like a status of “requested ad to be booked” (from an external system), or a flag like “failed to book in external system”, when those are temporal / ephemeral ideas (which will go away as our system grows in capability)

Another related one… just initiating a long-running process, but one which you want to prevent a user (or multiple users) from initiating multiple times concurrently. In that case, the UI needs to recognize that the operation is in progress (even beyond a page refresh), and block the user (any user with access to that resource) from initiating it again.

How do you model this idea of an operation in progress?

On a very abstract level, it’s support for asynchronous operations. This concept can be modeled without spreading too much knowledge about the exact things going on.

The frontend needs to learn that things can happen asynchronously, to be able to display some form of loading state, but it does not need to know what exactly is happening. At least in theory. ^^

Where would that information be stored, such that it is associated with the resources?

Something like a [LongRunningProcess]Job model?

stored in which domain?

or, in which subsystem?

Hard to say without knowing more, but spontaneously I’d place it near the code that writes / changes / owns the state.

Am I understanding you right, Franz, in that you are suggesting the async nature of execution is part of the domain? In a general sense, I would expect for that to show up in an increase in the number of states that the domain models.

@stephan I think the part that’s throwing me is to consider async execution / delayed completion of operation to be a part of the domain… my domains are things like “inventory” and “orders”… my concepts are things like “ads” and “line items”… how does “delayed execution” or “job” fit into that?

I’m more thinking about capturing those solution space ideas (data_sync_jobs table) in the application space… to communicate to the. user that something is in progress; to prevent the same operation from being invoked multiple times concurrently; but not in the domains.

I think I understand how delayed execution does not fit. But does order (probably using bad terms) have notions like open vs filled and if so, how does modeling filling change the domain? And, more importantly, doesn’t that fit?

Not sure if the example was related, but if we model filling we could handle “but one which you want to prevent a user (or multiple users) from initiating multiple times concurrently”

@stephan You’re not wrong… we have Ads which are “planned” and “booked”… and we have a time-expensive operation to go from “planned” to “booked”. So, I’d be ok introducing “booking”… but if I follow that line of thinking, my state machine kind of explodes with solution-space concerns, obfuscating the real “domain” ideas.

My 6 states convert to 12 to accommodate “well, that takes a while”.

Maybe just a flag on the concept (eg, “Lock”), which prevents write operations… Orders::API.lock_ad(…) , or rather, Orders::API.update_ad(..) fails sometimes with a ReadOnlyException

Even when you use a flag, you’re still modeling what is effectively a 12-state state machine. Sure, you could call it defunct in the sense that only a certain aspect of the domain behavior changes with this state “aspect.” Maybe think about it this way: you’d write 12 tests to verify the behavior in all cases right (flag on / flag off)? If that sounds right, simply implement the domain so it feels most domainy and passes the tests.

In my experience, whenever I can see a state machine lurking I end up having turned it into an explicitly modeled state machine too late. It is maybe verbose, but super easy to reason about, and easy to adapt. That’s why I, given what I know so far, would look into modeling the domain with one.

@stephan So you’re saying that introducing this solution-space idea into the domain is better than not because it helps remove defects arising from a state machine implementation done badly?

Not quite. I think I am saying two independent things:

• Avoid state machines done badly - just do them right
• From what I am reading, I believe the state transitions can reasonably be considered to be part of the domain

That’s the rub… are states unrelated to the business or domain really in the domain? states introduced to accommodate a particular architecture or technology limitation (or convention), that wouldn’t be there with a different architect / technology stack… those are part of the business domain?

I am not buying that you can get rid of the transition time with any architecture given how you described your domain