Lesson 6: Asynchronous Handling
Asynchronous PHP Workers
Not having code for Lesson 6?
git checkout lesson-6
Ecotone
provides abstractions for asynchronous execution.
We got new requirement:
User should be able to place order for different products.
We will need to build
Order
aggregate.Let's start by creating
PlaceOrderCommand
with ordered product Idsnamespace App\Domain\Order;
class PlaceOrderCommand
{
private int $orderId;
/**
* @var int[]
*/
private array $productIds;
/**
* @return int[]
*/
public function getProductIds(): array
{
return $this->productIds;
}
public function getOrderId() : int
{
return $this->orderId;
}
}
We will need
OrderedProduct
value object, which will describe, cost and identifier of ordered productnamespace App\Domain\Order;
class OrderedProduct
{
private int $productId;
private int $cost;
public function __construct(int $productId, int $cost)
{
$this->productId = $productId;
$this->cost = $cost;
}
public function getCost(): int
{
return $this->cost;
}
}
And our
Order
aggregatenamespace App\Domain\Order;
use App\Infrastructure\AddUserId\AddUserId;
use Ecotone\Messaging\Attribute\Asynchronous;
use Ecotone\Modelling\Attribute\Aggregate;
use Ecotone\Modelling\Attribute\AggregateIdentifier;
use Ecotone\Modelling\Attribute\CommandHandler;
use Ecotone\Modelling\Attribute\QueryHandler;
use Ecotone\Modelling\QueryBus;
#[Aggregate]
#[AddUserId]
class Order
{
#[AggregateIdentifier]
private int $orderId;
private int $buyerId;
/**
* @var OrderedProduct[]
*/
private array $orderedProducts;
private function __construct(int $orderId, int $buyerId, array $orderedProducts)
{
$this->orderId = $orderId;
$this->buyerId = $buyerId;
$this->orderedProducts = $orderedProducts;
}
#[CommandHandler("order.place")]
public static function placeOrder(PlaceOrderCommand $command, array $metadata, QueryBus $queryBus) : self
{
$orderedProducts = [];
foreach ($command->getProductIds() as $productId) {
$productCost = $queryBus->sendWithRouting("product.getCost", ["productId" => $productId]);
$orderedProducts[] = new OrderedProduct($productId, $productCost->getAmount());
}
return new self($command->getOrderId(), $metadata["userId"], $orderedProducts);
}
#[QueryHandler("order.getTotalPrice")]
public function getTotalPrice() : int
{
$totalPrice = 0;
foreach ($this->orderedProducts as $orderedProduct) {
$totalPrice += $orderedProduct->getCost();
}
return $totalPrice;
}
}
placeOrder
- Place order method make use of QueryBus
to retrieve cost of each ordered product.
You could find out, that we are not using application/json
for product.getCost
query, ecotone/jms-converter
can handle array
transformation, so we do not need to use json
.You could inject service into
placeOrder
that will hide QueryBus
implementation from the domain, or you may get this data from data store
directly. We do not want to complicate the solution now, so we will use QueryBus
directly. We do not need to change or add new
Repository
, as our exiting one can handle any new aggregate arriving in our system.Let's change our testing class and run it!
class EcotoneQuickstart
{
private CommandBus $commandBus;
private QueryBus $queryBus;