1

Here is the case: according to ubiquitous language - payment can be sent for processing to payment gateway (which is in different bounded context integrated via ACL (anti-corruption layer) + OHS/PL)

In local context, payment is aggregate root so I would expect design like this:

payment.send();

but the problem here is that payment needs to know how to send its state to other bounded context via ACL to OHS. So I need to inject this PaymentGatewayService/Adapter as argument:

payment.send(paymentGatewayService);

In this case paymentGatewayService returns some value object that can be processed with payment inside the send() method. But I heard that this is not quite good practice to inject services to aggregate root via method arguments or DI, but maybe it's OK? The benefit here is that I'm not exposing aggregate structure to outside. Or should I create some domain service to cover it like:

Payment payment = ...
payment = paymentGatewayService.send(payment);
paymentRepository.store(payment);

In this case, I don't really like this inversion, because I must directly expose aggregate structure so gateway service can translate to its model. Also this is causing anemia in payment aggregate.

How can I handle such situations properly??

  • Check this very good article on how to deal with long running processes in DDDed application. – Andy Sep 29 '17 at 7:49
  • Thanks for answer, but i think that sagas are more related to long or longer lasting processes and its suitable for bigger integrations between maybe multiple bounded contexts or when multiple aggregates are involved. In my case only 1 aggregate is involved - payment, but for its complete modification i need to call external service. – Slimer Sep 29 '17 at 8:26
  • 1
    From my experience with payments, paying for something is not a one-time operation and requires some sort of confirmation and as such should be modeled as a process. I do not know your payment vendor but I have worked with multiple payment gates and they all support payment confirmation mechanism which you are not required to but really should use. And this mechanism is usually async, so you need a way to track your payment process anyway. – Andy Sep 29 '17 at 9:11
4

But I heard that this is not quite good practice to inject services to aggregate root via method arguments or DI, but maybe it's OK?

There are trade offs.

Evans describes domain services as a construct to serve this role. So we pass to the aggregate an adapter that expresses the gateway concept in the domain language, so that the model can interact with it. The underlying implementation of the domain service probably delegates the actual work to an application service or an infrastructure service.

So, done correctly, it is completely consistent with the domain focused approach.

But it introduces some coupling between the side effect and the aggregate that you may not want

  • the failure modes of the side effect now pass through the aggregate
  • the aggregate and the side effect are coupled in time

Let's take a more careful look under the covers of your first example

payment.send(paymentGatewayService);

And try to break down explicitly what is going on

Payment::send(paymentGatewayService):
    x = this.getCurrentState()
    m = createPaymentMessage(x)
    y = paymentGatewayService.send(m)
    z = calculateNewState(x, y, m)
    this.setState(z)

Which is to say, that what we have here are three steps

  • we query the aggregate to discover the appropriate message to send, without touching the payment gateway
  • we dispatch the message to the payment gateway, without touching the aggregate
  • we update the aggregate using the response from the gateway as an input

and the trade off is: do we want those side effects in the aggregate, in the application, or possibly tucked into a different domain service that is responsible for the coordination?

PaymentProtocol::send(payment, paymentGateway):
    m = payment.createPaymentMessage()
    y = paymentGatewayService.send(m)
    payment.onPaymentSent(y)

In this design, the payment protocol orchestrates the side effects on the payment and the payment gateway. This gives you a clean aggregate (its responsibility is maintaining its own invariant), and keeps all of the process located in a single place.

In this case, I don't really like this inversion, because I must directly expose aggregate structure so gateway service can translate to its model. Also this is causing anemia in payment aggregate.

That's not quite right - the aggregate is still completely responsible for maintaining its own invariant; it's just absolved of the responsibility for managing a side effect that it is interested in. The aggregate is never exposing its own structure, it's only ever exposing a message that it created using its own structure.

It's still OK not to like it, of course.

  • Thanks for answer! I also was thinking of some sort of payment protocol and i really like this approach especially this createPaymentMessage factory method where you're not really exposing aggregate inner structure. You also made a good point, that we don't want care about side effects on aggregate. – Slimer Sep 29 '17 at 13:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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