How to get data back from a command bus?
Asked Answered
I

2

10

I'm fairly new to domain driven design concepts and I've run into a problem with returning proper responses in an API while using a command bus with commands and command handlers for the domain logic.

Let's say we’re building an application with a domain driven design approach. We have a back end and front end portion. The back end has all of our domain logic with an exposed API. The front end uses the API to make requests to the application.

We're building our domain logic with commands and command handlers mapped to a command bus. Under our Domain directory we have a command for creating a post resource called CreatePostCommand. It's mapped to its handler CreatePostCommandHandler via the command bus.

final class CreatePostCommand
{
    private $title;
    private $content;

    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content= $content;

    }

    public function getTitle() : string
    {
        return $this->title;
    }

    public function getContent() : string
    {
        return $this->content;
    }
}

final class CreatePostCommandHandler
{
    private $postRepository;

    public function __construct(PostRepository $postRepository)
    {
        $this->postRepository = $postRepository;
    }

    public function handle(Command $command)
    {
        $post = new Post($command->getTitle(), $command->getContent());
        $this->postRepository->save($post);
    }
}

In our API we have an endpoint for creating a post. This is routed the createPost method in a PostController under our Application directory.

final class PostController
{
    private $commandBus;

    public function __construct(CommandBus $commandBus)
    {
        $this->commandBus = $commandBus;
    }

    public function createPost($req, $resp)
    {
        $command = new CreatePostCommand($command->getTitle(), $command->getContent());
        $this->commandBus->handle($command);

        // How do we get the data of our newly created post to the response here?

        return $resp;
    }
}

Now in our createPost method we want to return the data of our newly created post in our response object so our front end application can know about the newly created resource. This is troublesome since we know that by definition the command bus should not return any data. So now we're stuck in a confusing position where we don't know how to add our new post to the response object.

I'm not sure how to proceed with this problem from here, several questions come to mind:

  • Is there an elegant way to return the post's data in the response?
  • Am I incorrectly implementing the Command/CommandHandler/CommandBus pattern?
  • Is this simply just the wrong use case for the Command/CommandHandler/CommandBus pattern?
Impoverish answered 10/5, 2016 at 23:35 Comment(1)
Possible duplicate of What should be returned from the API for CQRS commands?Neckpiece
A
12

First, notice that if we wire the controller directly to the command handler, we face a similar problem:

    public function createPost($req, $resp)
    {
        $command = new CreatePostCommand($command->getTitle(), $command->getContent());
        $this->createPostCommandHandler->handle($command);

        // How do we get the data of our newly created post to the response here?
        return $resp;
    }

The bus is introducing a layer of indirection, allowing you to decouple the controller from the event handler, but the problem you are running into is more fundamental.

I'm not sure how to proceed with this problem from here

TL;DR - tell the domain what identifiers to use, rather than asking the domain what identifier was used.

    public function createPost($req, $resp)
    {
        // TADA
        $command = new CreatePostCommand($req->getPostId()
                 , $command->getTitle(), $command->getContent());

        $this->createPostCommandHandler->handle($command);

        // happy path: redirect the client to the correct url
        $this->redirectTo($resp, $postId)
    }

In short, the client, rather than the domain model or the persistence layer, owns the responsibility of generating the id of the new entity. The application component can read the identifier in the command itself, and use that to coordinate the next state transition.

The application, in this implementation, is simply translating the message from the DTO representation to the domain representation.

An alternative implementation uses the command identifier, and derives from that command the identities that will be used

        $command = new CreatePostCommand(
                 $this->createPostId($req->getMessageId())
                 , $command->getTitle(), $command->getContent());

Named UUIDs are a common choice in the latter case; they are deterministic, and have small collision probabilities.

Now, that answer is something of a cheat -- we've really only demonstrated that we don't need a result from the command handler in this case.

In general, we would prefer to have one; Post/Redirect/Get is a good idiom to use for updating the domain model, but when the client gets the resource, we want to make sure they are getting a version that includes the edits they just made.

If your reads and writes are using the same book of record, this isn't a problem -- whatever you read is always the most recent version available.

However, is a common architectural pattern in domain driven design, in which case the write model (handling the post) will redirect to the read model -- which is usually publishing stale data. So you may want to include a minimum version in the get request, so that the handler knows to refresh its stale cache.

Is there an elegant way to return the post's data in the response?

There's an example in the code sample you provided with your question:

public function createPost($req, $resp)

Think about it: $req is a representation of the http request message, which is roughly analogous to your command, and $resp is essentially a handle to a data structure that you can write your result into.

In other words, pass a callback or a result handle with your command, and let the command handler fill in the details.

Of course, that depends on your bus supporting callbacks; not guaranteed.

Another possibility, which doesn't require changing the signature of your command handler, is to arrange that the controller subscribes to events published by the command handler. You coordinate a correlation id between the command and the event, and use that to pull up the result event that you need.

The specifics don't matter very much -- the event generated when processing the command could be written to a message bus, or copied into a mailbox, or....

Actinochemistry answered 11/5, 2016 at 5:29 Comment(8)
Though I agree it is one possible solution, I'm not totally in line with the reason you give. Why would you need to make a Create command idempotent ? If the goal is to make it repeatable, in what case would you want to repeat it (with the same ID, that is) ?Neckpiece
Also, I'm not sold on the distinction you make between Application (Service ?) and Command Handler, and the implications it has. Does it mean that the Controller is the Application Service in your case ? Do you place the command handler in the Domain ?Neckpiece
@Neckpiece When using a durable service bus, you want idempotency in any message handler to prevent duplicate changes. That command handler has the role of an application service in DDD.Distressful
@Distressful No, VoiceOfUnreason makes a clear difference between the "Application" and the command handler. " If you defer the generation of the identity to the command handler itself, it has no way to distinguish [...]" ... "In short, the application [...] owns the responsibility of generating the id"Neckpiece
@Distressful The answer explicitly states that the problem is the same without a bus (if we wire the controller directly to the command handler), and the code samples have no trace of a bus.Neckpiece
@Neckpiece from my point of view, the controller and the command handler are both part of the application component. The section about identifier generation needs a rewrite (specifically, it really needs to be made clearer that the seed of the identifier is contained in the request).Actinochemistry
OK, that's clearer. My original reaction about idempotence was with "this is commonly done" - because typically, POST is not idempotent and clients live with it. But it seems you're transporting idempotent commands over non-idempotent HTTP idioms. That makes sense.Neckpiece
Both yes and no -- idempotent POST handling is useful because HTML forms don't have support for PUT. But that said, it's just a message -- the PUT verb (in the message) doesn't force you implement your message handler in an idempotent way, and that's the important bit. By happy accident, messages designed for idempotent handling also give you nice options for a subsequent query of the model, which is what the original question was after.Actinochemistry
D
2

I am using this approach and I am returning command results. However, this is a solution which works only if the command handlers are part of the same process. Basically, I'm using a mediator, the controller and the command handler get an instance of it (usually as a constructor dependency).

Pseudo code controller

var cmd= new MyCommand();
var listener=mediator.GetListener(cmd.Id);
bus.Send(cmd);
//wait until we get a result or timeout
var result=listener.Wait();
return result;

Pseudo code command handler function

var result= new CommandResult();
add some data here
mediator.Add(result,cmd.Id);

That's how you get immediate feedback. However, this shouldn't be used to implement a business process.

Btw, this has nothing to do with DDD, it's basically a message driven CQS approach which can be and it is used in a DDD app.

Distressful answered 11/5, 2016 at 13:32 Comment(2)
Not clear why the command handlers need to be part of the same process; it looks like a straight forward message exchange, which works across process boundaries.Actinochemistry
@Actinochemistry Because everything crossing the process boundaries has the potential to become long running and everything becomes very complicated. This is a simple solution which works great with that constraintDistressful

© 2022 - 2024 — McMap. All rights reserved.