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,115 @@
<?php
declare(strict_types=1);
namespace MiniShop\Sales\Domain\Model;
use MiniShop\Sales\Domain\Event\OrderCancelled;
use MiniShop\Sales\Domain\Event\OrderConfirmed;
use MiniShop\Sales\Domain\Event\OrderPlaced;
use MiniShop\Sales\Domain\Exception\EmptyOrderException;
use MiniShop\Sales\Domain\Exception\InvalidOrderStateException;
final class Order
{
private OrderStatus $status;
/** @var list<OrderLine> */
private array $lines;
/** @var list<object> */
private array $domainEvents = [];
private function __construct(
public readonly OrderId $id,
public readonly CustomerId $customerId,
private readonly \DateTimeImmutable $placedAt,
) {
$this->status = OrderStatus::Draft;
$this->lines = [];
}
/**
* @param list<OrderLine> $lines
*/
public static function place(
OrderId $id,
CustomerId $customerId,
array $lines,
\DateTimeImmutable $placedAt,
): self {
if ($lines === []) {
throw EmptyOrderException::noLines();
}
$order = new self($id, $customerId, $placedAt);
$order->lines = $lines;
$order->status = OrderStatus::Placed;
$order->recordEvent(new OrderPlaced($id, $customerId, $order->total(), $placedAt));
return $order;
}
public function confirm(): void
{
if ($this->status !== OrderStatus::Placed) {
throw InvalidOrderStateException::cannotConfirm($this->status);
}
$this->status = OrderStatus::Confirmed;
$this->recordEvent(new OrderConfirmed(
$this->id,
$this->customerId,
$this->total(),
$this->lines,
));
}
public function cancel(): void
{
if ($this->status !== OrderStatus::Placed) {
throw InvalidOrderStateException::cannotCancel($this->status);
}
$this->status = OrderStatus::Cancelled;
$this->recordEvent(new OrderCancelled($this->id));
}
public function total(): Money
{
return array_reduce(
$this->lines,
static fn (Money $carry, OrderLine $line): Money => $carry->add($line->lineTotal()),
Money::zero($this->lines[0]->unitPrice->currency),
);
}
public function status(): OrderStatus
{
return $this->status;
}
/** @return list<OrderLine> */
public function lines(): array
{
return $this->lines;
}
public function placedAt(): \DateTimeImmutable
{
return $this->placedAt;
}
/** @return list<object> */
public function releaseEvents(): array
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
private function recordEvent(object $event): void
{
$this->domainEvents[] = $event;
}
}