5

I have two separate, isolated Domain-Driven Design contexts, and have a feature that needs to be implemented to integrate them together in some way.

Both of these are broken down in a Hexagonal/Ports and Adapters style. The feature is implemented using an Event Subscriber, with an event messaging mechanism for communication.

The feature is that an upstream Event, triggered from an Aggregate change, will trigger an Aggregate change in the downstream model.

The book Implementing Domain-Driven Design mentions a couple of things with regards to cross-context event publishing.

Forwarding the Event via a messaging infrastructure would allow asynchronous delivery to out-of-band subscribers. Each of those asynchronous subscribers could arrange to modify an additional Aggregate instance in one or more separate transactions. (Vernon, p. 302)

The use of any such messaging mechanism between Bounded Contexts requires that we adopt a commitment to eventual consistency. It can't be fought. (Vernon, p. 303)

Subscriber systems may not even be running when the publisher sends messages through the exchange. Each subscriber is responsible for handling messages in its own time frame, ensuring that it properly carries out any necessary domain behavior on its own model (Vernon, p. 317)

With regards to the Ports and Adapters architecture, the book mentions

There is not a strict definition of what a Port means, making it a flexible concept. In whatever way Ports are partitioned, client requests arrive, and the respective Adapter transforms their input. It then invokes an operation on the application or sends the application an event. Control is thus transferred to the inside.

Based on this, the Subscriber is responsible for retrieving the Event from the upstream Context, and somehow translating that into a command for the downstream Application Service. This makes it seem like the Subscriber should exist as an Infrastructure Adapter to the downstream Context, through the "upstream Context, messaging mechanism" port. After all, it's fetching an upstream Event, and doing something in the local/downstream context. Although, it is not doing so like a passive subscriber; this needs to actively seek out the client input (the upstream Events).

However, something needs to trigger this Subscriber to fetch the Events. After all, the Subscriber may not be running when the Event is published, for whatever reason, so it's the responsibility of the Subscriber to be, or stay, up-to-date. There's some amount of timing or scheduling that needs to happen in order for the retrieval to occur. This makes it seem more like an operational concern (frequency, timing), and should therefore be in the Application Layer, and not necessarily an Adapter. If this is really an Application Service, or a Synchronizer, then perhaps it should instead retrieve the upstream Events, translate them into appropriate downstream commands, and publish those Commands through a separate Command Adapter, which would interact with the downstream Application Service, instead of interacting with the Application Service directly.


With that all out of the way, is a component that

  • is interested in Events from an upstream source,
  • provides an integration not directly owned by either context,
  • is not required by either context to operate correctly,
  • causes an Aggregate to be modified in a separate downstream transaction,
  • translates an upstream event into a downstream command,
  • actively retrieves the events from the upstream context, based on a recurring trigger,
  • does not passively receive the events it's interested in,
  • may not be running or available when the upstream event occurs,
  • may exist in a third, different context, than the upstream or downstream,

an Application Service, an Adapter, an Infrastructure Service, or something else entirely?


Reasons why it might be

  • an Application Service
    • it has some operational concerns (event polling, frequency, scheduling)
  • an Adapter
    • it's a new client which needs to feed input into the downstream context
    • reacts to upstream events to trigger something in the downstream context
    • receives its input through a specific port (messaging mechanism)
  • an Infrastructure Service
    • exists outside of the Application Layer and Domain Model
    • calls existing Application Service methods

Reasons why it might not be

  • an Application Service
    • is not the direct client of the Domain Model
    • relies on existing Application Service to function
  • an Adapter
    • actively retrieves events from upstream, instead of passively reacting to them being delivered
  • an Infrastructure Service
    • does not implement any interface in the Application Layer or Domain Model

These contradictions kind of signal to me that it's "something else" but what exactly it is, and where exactly it lives, I'm not sure.


For clarification, the question is about what this type of component is, based on its interactions with others. A comment said this could be a Client or Consumer. But these aren't terribly specific, and don't provide a lot of insight into its interactions with its surroundings. Any code that calls other code is a Client or Consumer. But the Direct Client of the Domain Model is the Application Service. This provides context to its interactions with its surroundings. The Domain Model can call some code on an implementation of an interface in the Domain Model. Here, the Domain Model is the Client of an Infrastructure Service. Some Event can come through a messaging mechanism, and call some method on the Application Service. Here, the Event Subscriber is an Adapter, that passively receives the Event from the Port, and becomes a Client for the Application Service.

6
  • 2
    In technical terms that would be a client or a consumer. How is the intro relevant? It goes on and on adding little to the title. Commented Apr 28, 2021 at 5:08
  • 1
    @MartinMaat The intro was primarily to introduce the pieces that were similar, with references to literature that seems to be well accepted, so that I could highlight the seemingly conflicting, or overlapping parts that caused confusion. What would you suggest I do? I'd be glad to trim down the intro, or look over any edits you could suggest.
    – Zymus
    Commented Apr 28, 2021 at 5:24
  • 1
    I thought you were after a classifying term for an event subscriber in the context of a service oriented architecture. But then I may have to read it more like "who or what kind of entities would subscribe to an event?". I came up with some general terms but am a bit puzzled because "subscriber" really says it all. It is just any party interested in those events. Either way, a subscriber is external to the sub-system in scope so it could be anything. Detailing a particular application does not make the question any more clear. Commented Apr 28, 2021 at 6:18
  • 1
    @MartinMaat Thanks for the clarification. My question is really about how best to describe this component, so I'll remove the surrounding fluff, and just focus on the characteristics. I believe that the best descriptor will probably be predicated based on its environment, which will help come up with the best descriptor. For instance, Client and Consumer are very abstract. "Application Service is the Direct Client of the Domain Model" is more specific. An Event Subscriber is abstract. An Event Subscriber could be in the Domain Model, Application Service, or Infra Service or Adapter...
    – Zymus
    Commented Apr 28, 2021 at 16:12
  • 1
    ... But Event Subscriber doesn't provide enough information about its environment to decide which of those options it is. An Event Subscriber that has an operational concern, like timing or frequency of retrieval might be better described as an Application Service, and would subsequently live in an Application Layer. If the Event Subscriber implemented some interface from the Domain Model, it might be an Infrastructure Service, and would live in the Infrastructure Layer. Where it lives, what its characteristics are, and what its name is are all linked together.
    – Zymus
    Commented Apr 28, 2021 at 16:17

1 Answer 1

3

The main issue here is that you're describing a feature, while asking what component to put it in. That's not guaranteed to work, because the mapping between features and component is not limited to one-to-one.

Reinterpreting the requirements as feature and treating each point separately, it becomes pretty clear what pieces you'll need (the names are arbitrary):

  • EventRecorder: an Infra Service that accumulates events (so that dormant listener wouldn't miss anything) and notifies about updates (so that listeners wouldn't need to poll).
    • It's probably connected to EventRepository, but that's an implementation detail.
  • EventSink: an adapter that pumps events from dependency Domain to EventRecorder.
  • EventSource: an adapter that pumps events from EventRecorder to dependent Domain.
    • When connected to EventRecorder, it should subscribe for notification and immediately request all unread events. Further event reads must be triggered via the notification only.
  • EventListener: an Application Service that listens to EventSource.

More implementation details. How to coordinate unread events depends on a persistence. If persistence is used, it may be sufficient to compare max id in respective tables. Otherwise, unread events have to be accumulated, but an event consumer may still track the last consumed event id to allow grouping few updates into one.

1
  • Thanks for the answer. After reading this answer, and parsing it for a few days, I realize now where the crux of my confusion lies.
    – Zymus
    Commented May 6, 2021 at 4:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.