Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions demos/contact-form/src/Context/ReceiptGeneratedContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Be\Pattern\ContactForm\Context;

use Koriym\SemanticLogger\AbstractContext;

final class ReceiptGeneratedContext extends AbstractContext
{
public const string TYPE = 'receipt_generated';
public const string SCHEMA_URL = 'https://be-framework.github.io/schemas/contact-form/receipt-generated.json';

public function __construct(
public readonly string $receiptId,
public readonly string $email,
public readonly string $receivedAt,
) {
}
}
14 changes: 14 additions & 0 deletions demos/contact-form/src/Final/ContactReceived.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Be\Pattern\ContactForm\Final;

use Be\Framework\SemanticLog\Been;
use Be\Pattern\ContactForm\Context\ReceiptGeneratedContext;
use Be\Pattern\ContactForm\Reason\ReceiptGenerator;
use Ray\Di\Di\Inject;
use Ray\InputQuery\Attribute\Input;
Expand All @@ -18,15 +20,27 @@
{
public string $receiptId;
public string $receivedAt;
public Been $been;

public function __construct(
#[Input] public string $name,
#[Input] public string $subject,
#[Input] public string $message,
#[Input] public string $normalizedEmail,
#[Inject] ReceiptGenerator $receipt,
#[Inject] Been $been,
) {
$this->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');
}
}
3 changes: 3 additions & 0 deletions demos/medical-triage/src/Module/AppModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

namespace Be\Pattern\MedicalTriage\Module;

use Be\Framework\Module\BeModule;
use Be\Pattern\MedicalTriage\Reason\JTASProtocol;
use Ray\Di\AbstractModule;

final class AppModule extends AbstractModule
{
protected function configure(): void
{
$this->install(new BeModule('Be\Pattern\MedicalTriage\Semantic'));

$this->bind(JTASProtocol::class);
}
}
19 changes: 19 additions & 0 deletions demos/order-processing/src/Context/OrderFinalizedContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Be\Pattern\OrderProcessing\Context;

use Koriym\SemanticLogger\AbstractContext;

final class OrderFinalizedContext extends AbstractContext
{
public const string TYPE = 'order_finalized';
public const string SCHEMA_URL = 'https://be-framework.github.io/schemas/order-processing/order-finalized.json';

public function __construct(
public readonly string $orderId,
public readonly string $status,
) {
}
}
12 changes: 12 additions & 0 deletions demos/order-processing/src/Final/OrderConfirmed.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace Be\Pattern\OrderProcessing\Final;

use Be\Pattern\OrderProcessing\Context\OrderFinalizedContext;
use Be\Pattern\OrderProcessing\Moment\InventoryReserved;
use Be\Pattern\OrderProcessing\Moment\PaymentCompleted;
use Be\Pattern\OrderProcessing\Moment\ShippingArranged;
use Be\Framework\SemanticLog\Been;
use Ray\Di\Di\Inject;

/**
Expand All @@ -21,11 +23,13 @@
{
public string $orderId;
public string $status;
public Been $been;

public function __construct(
#[Inject] public InventoryReserved $inventory,
#[Inject] public PaymentCompleted $payment,
#[Inject] public ShippingArranged $shipping,
#[Inject] Been $been,
) {
// Self-completion: realize all Moments (parts of self)
$this->inventory->be();
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions demos/order-processing/src/Module/AppModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
11 changes: 9 additions & 2 deletions demos/order-processing/tests/Final/OrderConfirmedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

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;
use Be\Pattern\OrderProcessing\Moment\ShippingArranged;
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
Expand All @@ -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
Expand All @@ -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);
Expand Down
19 changes: 19 additions & 0 deletions demos/user-registration/src/Context/UserCreatedContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Be\Pattern\UserRegistration\Context;

use Koriym\SemanticLogger\AbstractContext;

final class UserCreatedContext extends AbstractContext
{
public const string TYPE = 'user_created';
public const string SCHEMA_URL = 'https://be-framework.github.io/schemas/user-registration/user-created.json';

public function __construct(
public readonly string $userId,
public readonly string $email,
) {
}
}
13 changes: 13 additions & 0 deletions demos/user-registration/src/Final/UserRegistered.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Be\Pattern\UserRegistration\Final;

use Be\Framework\SemanticLog\Been;
use Be\Pattern\UserRegistration\Context\UserCreatedContext;
use Be\Pattern\UserRegistration\Reason\UserIdGenerator;
use Be\Pattern\UserRegistration\Reason\WelcomeTokenGenerator;
use Ray\Di\Di\Inject;
Expand All @@ -21,6 +23,7 @@
{
public string $userId;
public string $welcomeToken;
public Been $been;

public function __construct(
#[Input] public string $email,
Expand All @@ -29,8 +32,18 @@ public function __construct(
#[Input] public string $avatarUrl,
#[Inject] UserIdGenerator $idGen,
#[Inject] WelcomeTokenGenerator $tokenGen,
#[Inject] Been $been,
) {
$this->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');
}
}
Loading