如何使用DTO处理与命令总线方法的关系

In a new greenfield project I'm using a command bus approach, problem I'm encountering is how I should handle relationships.

For example, an Order has multiple OrderLines, a Shipping and Invoice address.

Since I'm using a command bus approach I want to pass a DTO to my command bus holding all the information necessary to create an Order.

Should this command also hold relationships (i.e. an CreateOrderTask, CreateOrderAddress)?

My directory/file structure looks like this:

- Infrastructure
-- Ui
--- Web
---- CreateOrderController.php
-- ....
- Application
-- CreateOrder
--- CreateOrder.php
--- CreateOrderHandler.php
--- CreateOrderLine.php (?)
- Domain
-- Order
--- Order.php
--- OrderLine.php
--- Address.php
-- ...

So should the CreateOrder class look like this:

// CreateOrder.php

<?php

class CreateOrder {

    /** @var CreateOrderLine[] */
    protected $createOrderLines;

    public function __construct(array $createOrderLines) 
    {
        $this->createOrderLines = $createOrderLines;
    }

    public function getCreateOrderLines()
    {
        return $this->createOrderLines;
    }
}

Although I think there are many correct answers, I'm looking for a best practice. What are your thoughts?

It depends on your transaction boundaries.

If you need that all the commands must succeed or fail together then you in fact must have a bigger command with all the data needed.

If any of the sub-commands fails and the system is still in valid state then you don't need a bigger command and you may send multiple commands in some kind of batch, each one having its own transaction boundary.

eCommerce is quite popular domain and here there might be more interesting to look at what business things of the behaviour of your system.

Moving away from CQRS a little, I would ask you some "DDD-style" questions:

  • Where does the order come from?
  • What CreateOrder means? Who creates them?
  • What about the shopping cart? Isn't it being populated by one line?
  • Doesn't your business need to know about aborted shopping carts too?

I would warn you from over-simplifying the domain in question and over-engineer the solution...

If you at the end of the day will have a shopping cart - you probably have commands to add goods to it. These commands will be issued asynchronously. There will be no AddShoppingCartHeader command because it does not make any sense for the business. On checkout, the cart will probably be converted to an order. Or, after a timeout, it will discarded and a lead will be created. If this is all valid - you probably would want to look at the process manager pattern (also called Saga in many messaging frameworks).