Step 00 — Squelette + intégration naïve

3 Bounded Contexts (Sales, Invoicing, LegacyFulfillment) avec :
- Domaines complets (agrégats, VOs, événements, invariants)
- Couche application (commands, queries, ports)
- Infrastructure in-memory (repos, gateway fake)
- Controllers HTTP Symfony
- Couplage naïf synchrone entre BC via NaiveSalesEventPublisher
- 20 tests unitaires et d'intégration passants
This commit is contained in:
2026-03-04 00:27:15 +01:00
commit a4a14e441b
86 changed files with 7059 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace MiniShop\Sales\Interfaces\Http;
use MiniShop\Sales\Application\Command\CancelOrder;
use MiniShop\Sales\Application\Command\CancelOrderHandler;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/sales/orders/{orderId}/cancel', name: 'sales_cancel_order', methods: ['POST'])]
final readonly class CancelOrderController
{
public function __construct(
private CancelOrderHandler $handler,
) {}
public function __invoke(string $orderId): JsonResponse
{
($this->handler)(new CancelOrder(orderId: $orderId));
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace MiniShop\Sales\Interfaces\Http;
use MiniShop\Sales\Application\Command\ConfirmOrder;
use MiniShop\Sales\Application\Command\ConfirmOrderHandler;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/sales/orders/{orderId}/confirm', name: 'sales_confirm_order', methods: ['POST'])]
final readonly class ConfirmOrderController
{
public function __construct(
private ConfirmOrderHandler $handler,
) {}
public function __invoke(string $orderId): JsonResponse
{
($this->handler)(new ConfirmOrder(orderId: $orderId));
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace MiniShop\Sales\Interfaces\Http;
use MiniShop\Sales\Application\Query\GetOrderById;
use MiniShop\Sales\Application\Query\GetOrderByIdHandler;
use MiniShop\Sales\Domain\Model\OrderLine;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/sales/orders/{orderId}', name: 'sales_get_order', methods: ['GET'])]
final readonly class GetOrderController
{
public function __construct(
private GetOrderByIdHandler $handler,
) {}
public function __invoke(string $orderId): JsonResponse
{
$order = ($this->handler)(new GetOrderById($orderId));
return new JsonResponse([
'orderId' => $order->id->toString(),
'customerId' => $order->customerId->toString(),
'status' => $order->status()->value,
'total' => [
'amount' => $order->total()->amount,
'currency' => $order->total()->currency,
],
'lines' => array_map(
static fn (OrderLine $line): array => [
'productName' => $line->productName,
'quantity' => $line->quantity,
'unitPrice' => $line->unitPrice->amount,
'currency' => $line->unitPrice->currency,
'lineTotal' => $line->lineTotal()->amount,
],
$order->lines(),
),
]);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace MiniShop\Sales\Interfaces\Http;
use MiniShop\Sales\Application\Query\ListOrdersByCustomer;
use MiniShop\Sales\Application\Query\ListOrdersByCustomerHandler;
use MiniShop\Sales\Domain\Model\Order;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/sales/customers/{customerId}/orders', name: 'sales_list_customer_orders', methods: ['GET'])]
final readonly class ListCustomerOrdersController
{
public function __construct(
private ListOrdersByCustomerHandler $handler,
) {}
public function __invoke(string $customerId): JsonResponse
{
$orders = ($this->handler)(new ListOrdersByCustomer($customerId));
return new JsonResponse(array_map(
static fn (Order $order): array => [
'orderId' => $order->id->toString(),
'status' => $order->status()->value,
'total' => [
'amount' => $order->total()->amount,
'currency' => $order->total()->currency,
],
],
$orders,
));
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace MiniShop\Sales\Interfaces\Http;
use MiniShop\Sales\Application\Command\PlaceOrder;
use MiniShop\Sales\Application\Command\PlaceOrderHandler;
use MiniShop\Shared\Technical\UuidGenerator;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/sales/orders', name: 'sales_place_order', methods: ['POST'])]
final readonly class PlaceOrderController
{
public function __construct(
private PlaceOrderHandler $handler,
) {}
public function __invoke(Request $request): JsonResponse
{
/** @var array{customerId: string, lines: list<array{productName: string, quantity: int, unitPriceInCents: int, currency: string}>} $data */
$data = json_decode($request->getContent(), true, flags: JSON_THROW_ON_ERROR);
$orderId = UuidGenerator::generate();
($this->handler)(new PlaceOrder(
orderId: $orderId,
customerId: $data['customerId'],
lines: $data['lines'],
));
return new JsonResponse(['orderId' => $orderId], Response::HTTP_CREATED);
}
}