Code

小さなコードに、設計の境界を残す。

BEAR.Sundayのコードは、リソースが中心です。入力、依存、リンク、横断的処理が見える場所に残るため、読み手はアプリケーションの意味をたどれます。

Resource

URIに対応するリソースは、状態を決めて返す。

final class Profile extends ResourceObject
{
    public function onGet(int $id): static
    {
        $this->body = $this->profileQuery->item($id);

        return $this;
    }
}

Dependency Injection

必要な依存はコンストラクタで明示する。

public function __construct(
    private readonly ProfileQuery $profileQuery,
    private readonly ClockInterface $clock,
) {
}

AOP

横断的関心は属性とInterceptorへ逃がす。

#[Transactional]
#[Loggable]
public function onPost(string $name): static
{
    $this->body = $this->command->create($name);

    return $this;
}

Hypermedia

次に遷移できるリソースをリンクとして宣言する。

#[Link(rel: 'orders', href: 'app://self/orders{?id}')]
#[Embed(rel: 'profile', src: 'app://self/profile{?id}')]
public function onGet(int $id): static
{
    return $this;
}

CacheableResponse

キャッシュは時間ではなく、依存の変化で無効化される。

use BEAR\RepositoryModule\Annotation\CacheableResponse;

#[CacheableResponse]
public function onGet(string $id): static
{
    $this->body = $this->blog->entry($id);

    return $this;
}

DonutCache

ページはキャッシュ。コメントの“穴”は Do not キャッシュ。毎回新鮮。

#[DonutCache]
#[Embed(rel: 'comment', src: 'page://self/blog/comment')]
public function onGet(int $id): static
{
    $this->body += ['article' => '...'];

    return $this;
}

CLI

同じリソースは、属性ひとつでCLIにもなる。

use BEAR\Cli\Attribute\Cli;
use BEAR\Cli\Attribute\Option;

#[Cli(name: 'greet', description: '多言語で挨拶', output: 'greeting')]
public function onGet(
    #[Option(shortName: 'n')] string $name,
    #[Option(shortName: 'l')] string $lang = 'en',
): static {
    // この onGet が、独立した greet コマンドになる
    // --help も --name/-n も、宣言から自動で生成される
    return $this;
}

SQL

SQLはSQLのまま、型を渡し型で受け取る。現在時刻もDIが入れる。

interface OrderRepositoryInterface
{
    // 戻り値の型が意図:不変ドメインオブジェクト
    #[DbQuery('order_item', factory: OrderFactory::class)]
    public function getOrder(string $id): Order;

    // 型のある引数。現在時刻はDIが束縛。戻り値の型 = 更新行数
    #[DbQuery('order_close')]
    public function close(string $id, DateTimeInterface $at): AffectedRows;
}

What this enables

どれも同じ小さな宣言。だから長く読める。

リソースが状態を持ち、表現や転送は外側へ分かれる。依存は注入され、 横断的関心はAOPへ分かれる。この単純な分離が、テストと拡張の土台になります。

  • リソース単位でテストを書ける
  • 同じリソースをWeb API、HTML、CLIで使える
  • キャッシュやトランザクションを業務ロジックから分離できる
  • リンクとスキーマからAPIドキュメントを組み立てやすい

Start with one resource

まずは小さく作り、構造を残す。