From 42e5e524cb61cc18522512fbc0bbe66c0e16a02e Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Thu, 16 Apr 2026 22:36:46 +0900 Subject: [PATCH] Add Been proof-of-existence to demos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inject Been into Final objects that have meaningful domain events to record. The Final asserts its own causal chain in production code, moving proof from test files into the domain. Demos with Been: - contact-form: receipt↔email link - user-registration: email causal chain (assert) - order-processing: moment completion status (assert) Demos without Been (no meaningful context to prove): hello-world, blog-publishing, medical-triage, loan-application, insurance-claim Co-Authored-By: Claude Opus 4.6 --- README.ja.md | 14 +++++++++++++ README.md | 14 +++++++++++++ .../src/Context/ReceiptGeneratedContext.php | 20 +++++++++++++++++++ .../src/Final/ContactReceived.php | 14 +++++++++++++ demos/medical-triage/src/Module/AppModule.php | 3 +++ .../src/Context/OrderFinalizedContext.php | 19 ++++++++++++++++++ .../src/Final/OrderConfirmed.php | 12 +++++++++++ .../order-processing/src/Module/AppModule.php | 3 +++ .../tests/Final/OrderConfirmedTest.php | 11 ++++++++-- .../src/Context/UserCreatedContext.php | 19 ++++++++++++++++++ .../src/Final/UserRegistered.php | 13 ++++++++++++ 11 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 demos/contact-form/src/Context/ReceiptGeneratedContext.php create mode 100644 demos/order-processing/src/Context/OrderFinalizedContext.php create mode 100644 demos/user-registration/src/Context/UserCreatedContext.php diff --git a/README.ja.md b/README.ja.md index 3d7c937..f28bfd0 100644 --- a/README.ja.md +++ b/README.ja.md @@ -158,6 +158,20 @@ flowchart LR --- +## Been — 存在証明 + +一部のデモでは `Been` を実装しています。`Been`はFinalオブジェクトが「なぜ今の状態にあるか」を証明する不変キャリアです。Finalは`#[Inject]`で`Been`を受け取り、`with()`でドメインイベントを記録し、`assert`で自らの因果を検証できます — 証明をテストコードからプロダクションコードへ移動させます。 + +| デモ | 証明する内容 | +|---|---| +| [contact-form](./demos/contact-form/) | レシートが正規化されたメールに対して生成された | +| [user-registration](./demos/user-registration/) | ユーザーが入力メールで作成された(assert) | +| [order-processing](./demos/order-processing/) | 全Momentがconfirmedステータスで完了した(assert) | + +概念の詳細は Be Framework ドキュメントの[意味的ログ](https://be-framework.github.io/manuals/1.0/ja/10-semantic-logging.html)を参照してください。 + +--- + ## テスト実行 ```bash diff --git a/README.md b/README.md index 2695c6f..f15eaa9 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,20 @@ Insurance claim processing. `ClaimInput` and `PolicyInput` both declare `#[Be([C --- +## Been — Proof of Existence + +Some demos demonstrate `Been`, an immutable carrier that a Final object uses to prove why it is what it is. The Final injects `Been` via `#[Inject]`, records domain events with `with()`, and can assert its own causal chain — moving proof from test code into production code. + +| Demo | What is proved | +|---|---| +| [contact-form](./demos/contact-form/) | Receipt was generated for the normalized email | +| [user-registration](./demos/user-registration/) | User was created with the input email (assert) | +| [order-processing](./demos/order-processing/) | All moments completed with confirmed status (assert) | + +See [Semantic Logging](https://be-framework.github.io/manuals/1.0/en/10-semantic-logging.html) in the Be Framework documentation for the full concept. + +--- + ## Running tests ```bash diff --git a/demos/contact-form/src/Context/ReceiptGeneratedContext.php b/demos/contact-form/src/Context/ReceiptGeneratedContext.php new file mode 100644 index 0000000..8c7f545 --- /dev/null +++ b/demos/contact-form/src/Context/ReceiptGeneratedContext.php @@ -0,0 +1,20 @@ +receiptId = $receipt->generate(); $this->receivedAt = date('Y-m-d\TH:i:sP'); + $this->been = $been->with(new ReceiptGeneratedContext( + receiptId: $this->receiptId, + email: $normalizedEmail, + receivedAt: $this->receivedAt, + )); + + // Proof: the receipt was generated for this email + $event = $this->been->events[0]; + assert($event instanceof ReceiptGeneratedContext); + assert($event->email === $this->normalizedEmail, 'Receipt must be for the normalized email'); } } diff --git a/demos/medical-triage/src/Module/AppModule.php b/demos/medical-triage/src/Module/AppModule.php index 2f425a7..59e6af2 100644 --- a/demos/medical-triage/src/Module/AppModule.php +++ b/demos/medical-triage/src/Module/AppModule.php @@ -4,6 +4,7 @@ namespace Be\Pattern\MedicalTriage\Module; +use Be\Framework\Module\BeModule; use Be\Pattern\MedicalTriage\Reason\JTASProtocol; use Ray\Di\AbstractModule; @@ -11,6 +12,8 @@ final class AppModule extends AbstractModule { protected function configure(): void { + $this->install(new BeModule('Be\Pattern\MedicalTriage\Semantic')); + $this->bind(JTASProtocol::class); } } diff --git a/demos/order-processing/src/Context/OrderFinalizedContext.php b/demos/order-processing/src/Context/OrderFinalizedContext.php new file mode 100644 index 0000000..e49953a --- /dev/null +++ b/demos/order-processing/src/Context/OrderFinalizedContext.php @@ -0,0 +1,19 @@ +inventory->be(); @@ -34,6 +38,14 @@ public function __construct( $this->orderId = $this->generateOrderId(); $this->status = 'confirmed'; + $this->been = $been->with(new OrderFinalizedContext( + orderId: $this->orderId, + status: $this->status, + )); + + $event = $this->been->events[0]; + assert($event instanceof OrderFinalizedContext); + assert($event->status === 'confirmed'); } private function generateOrderId(): string diff --git a/demos/order-processing/src/Module/AppModule.php b/demos/order-processing/src/Module/AppModule.php index 790815b..f7e9e16 100644 --- a/demos/order-processing/src/Module/AppModule.php +++ b/demos/order-processing/src/Module/AppModule.php @@ -4,6 +4,7 @@ namespace Be\Pattern\OrderProcessing\Module; +use Be\Framework\Module\BeModule; use Be\Pattern\OrderProcessing\Attribute\Address; use Be\Pattern\OrderProcessing\Attribute\Amount; use Be\Pattern\OrderProcessing\Attribute\CardNumber; @@ -28,6 +29,8 @@ final class AppModule extends AbstractModule { protected function configure(): void { + $this->install(new BeModule('Be\Pattern\OrderProcessing\Semantic')); + // Order processing - Reason bindings (via interfaces for testability) $this->bind(WarehouseLocator::class); $this->bind(InventoryChecker::class); diff --git a/demos/order-processing/tests/Final/OrderConfirmedTest.php b/demos/order-processing/tests/Final/OrderConfirmedTest.php index c48b04c..533ee50 100644 --- a/demos/order-processing/tests/Final/OrderConfirmedTest.php +++ b/demos/order-processing/tests/Final/OrderConfirmedTest.php @@ -4,6 +4,8 @@ namespace Be\Pattern\OrderProcessing\Tests\Final; +use Be\Framework\SemanticLog\Been; +use Be\Pattern\OrderProcessing\Context\OrderFinalizedContext; use Be\Pattern\OrderProcessing\Final\OrderConfirmed; use Be\Pattern\OrderProcessing\Moment\InventoryReserved; use Be\Pattern\OrderProcessing\Moment\PaymentCompleted; @@ -11,6 +13,7 @@ use Be\Pattern\OrderProcessing\Reason\InventoryReserver; use Be\Pattern\OrderProcessing\Reason\PaymentGateway; use Be\Pattern\OrderProcessing\Reason\ShippingArranger; +use Koriym\SemanticLogger\SemanticLogger; use PHPUnit\Framework\TestCase; class OrderConfirmedTest extends TestCase @@ -28,13 +31,16 @@ public function testOrderConfirmed(): void $shipping = new ShippingArranged('YAMATO', '〒150-0001 渋谷区神宮前1-1-1', $shippingArranger); // Converge into Final (realizes all potentials) - $final = new OrderConfirmed($inventory, $payment, $shipping); + $been = new Been(new SemanticLogger()); + $final = new OrderConfirmed($inventory, $payment, $shipping, $been); $this->assertStringStartsWith('ORD-', $final->orderId); $this->assertSame('confirmed', $final->status); $this->assertSame($inventory, $final->inventory); $this->assertSame($payment, $final->payment); $this->assertSame($shipping, $final->shipping); + $this->assertCount(1, $final->been->events); + $this->assertInstanceOf(OrderFinalizedContext::class, $final->been->events[0]); } public function testOrderIdFormat(): void @@ -48,7 +54,8 @@ public function testOrderIdFormat(): void $shippingArranger = new ShippingArranger(); $shipping = new ShippingArranged('YAMATO', '〒150-0001 渋谷区神宮前1-1-1', $shippingArranger); - $final = new OrderConfirmed($inventory, $payment, $shipping); + $been = new Been(new SemanticLogger()); + $final = new OrderConfirmed($inventory, $payment, $shipping, $been); // Order ID format: ORD-YYYYMMDD-xxxxxxxx $this->assertMatchesRegularExpression('/^ORD-\d{8}-[a-f0-9]{8}$/', $final->orderId); diff --git a/demos/user-registration/src/Context/UserCreatedContext.php b/demos/user-registration/src/Context/UserCreatedContext.php new file mode 100644 index 0000000..f066596 --- /dev/null +++ b/demos/user-registration/src/Context/UserCreatedContext.php @@ -0,0 +1,19 @@ +userId = $idGen->generate(); $this->welcomeToken = $tokenGen->generate($email); + $this->been = $been->with(new UserCreatedContext( + userId: $this->userId, + email: $email, + )); + + // Proof: the user was created with the input email + $event = $this->been->events[0]; + assert($event instanceof UserCreatedContext); + assert($event->email === $this->email, 'User must be created with the input email'); } }