Livewire v3を使って多層レイヤーアーキテクチャを考える

この記事は、[AEON Advent Calendar 2023](https://qiita.com/advent-calendar/2023/aeon)の18日目です🎉

こんにちは。イオンスマートテクノロジー株式会社のCTO室でテックリードをはじめました。うしおです。

ここまで順調にカレンダー公開されていますが、インフラやNewRelicネタが多いですね:innocent:
箸休めに[Laravel Livewire](https://livewire.laravel.com/)の話でもいかがでしょうか。

Livewireのv3がついに来ました。ググると8月だったようです。ようやくガッツリ触れましたのでコード付きでレポートしたいと思います。
ソースコード全体は[こちら](https://github.com/youhei-ushio/livewire-sample)です。

# 作ったもの
こんな感じの、ECサイトの商品一覧をイメージしています。
サンプルなので機能は限定的ですが、設計はちゃんとやっています。
![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124294/97a17b99-5247-e4a2-f116-1f9eb699e52c.gif)

# 使い勝手とか
v2の頃からそうでしたが、やはりイイです、Livewire:sparkles:
通常のWebアプリケーションだと、上のようなことをやるだけでajaxのエンドポイントがモリモリ増えていきますが、単なるアクションメソッドのように処理できるのが最高です。

# Livewire v3の良い点
わたし的に一番嬉しかったのが[シンセサイザー](https://livewire.laravel.com/docs/synthesizers)ですね。
こいつができてようやくLivewire ComponentとDomain Entityをつなげることができました。
[商品一覧のページ](https://github.com/youhei-ushio/livewire-sample/blob/master/app/Livewire/Pages/Products/Index.php)でやっている、ここのところ。
```php
final class Index extends Component
{
    /** @var IndexResult[] $products */
    public array $products;
    public FavoriteProduct $favoriteProduct;
    public Cart $cart;
    ...
```
`$products`がPresentation用のDTO、`$favoriteProduct`と`$cart`がDomain Entityなんですが、
Livewire v2のルールではプリミティブ型かWireableなオブジェクト[^1]しかプロパティにできませんでした。
かといってDomain EntityをWireableにするのは関心分離の点でやりたくない。
これがv3のシンセサイザーにより綺麗に解決できました。
たったこれだけ書けばFavoriteProductクラスのコードはそのままでWireableになります。
素晴らしすぎるのに[v3の機能紹介](https://laravel-news.com/livewire-v3-features)に入っていない。なぜだ。
```php
final class FavoriteProductSynth extends Synth
{
    public static $key = 'favorite_product';

    public static function match($target): bool
    {
        return $target instanceof FavoriteProduct;
    }

    public function dehydrate(FavoriteProduct $target): array
    {
        return [$target->toArray(), []];
    }

    public function hydrate($value): FavoriteProduct
    {
        return FavoriteProduct::fromArray($value);
    }
}
```
[^1]:Eloquent Model含む

# レイヤー構成
下記のような構成にしました。
LivewireのFull Page ComponentがControllerとViewを兼ねている形です。
ユースケースも単純なものしかないのでLivewire Componentに直接書いています。

 - Domain
   - Entity
   - Command
   - Query
   - Value (まだ無い)
   - Service (まだ無い)
   - Event (まだ無い)
 - Application  (まだ無い)
 - Infrastructure
   - Command
   - Query
 - Presentation
   - Query
   - Livewire Component + Blade View

# コンポーネント構成
赤枠で囲った部分がLivewireコンポーネントです。
①商品一覧
②お気に入り数アイコン
③カート商品数アイコン

![コンポーネント.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124294/871f4d07-8524-3f59-3d81-b5e99574e4d9.png)

①の操作でお気に入りやカート商品が変更される度、②や③へイベント通知しています。
イベント受信側がこうで、
```php
#[On('favorite-product-changed')]
public function updateCount(int $count): void
{
    $this->count = $count;
}
```
イベント送信側がこれだけ。便利すぎる。
```php
$this->dispatch('favorite-product-changed', count: $this->favoriteProduct->count());
```

# テストコード
テストもイイ感じですね。
データのアサーションが強力なのでロジックのテストがシンプルに行えます。
```php
/**
 * お気に入り追加ができること
 */
public function test_user_can_add_favorites(): void
{
    // 操作対象ユーザー準備
    $user = Models\User::factory()->create();
    // 対象外のユーザー準備
    $otherUser = Models\User::factory()->create();
    // 商品マスター準備
    $products = Models\MasterProduct::factory()->count(10)->create();

    // ユーザーがお気に入り追加のアクションを実行
    // お気に入りに追加されたことを検証
    Livewire::actingAs($user)
        ->test(Index::class)
        // これでアクション実行
        ->call('toggleFavoriteProduct', $products[0]['id'])
        // $favoriteProductを検証
        ->assertViewHas('favoriteProduct', function (FavoriteProduct $favoriteProduct) use ($products) {
            return $favoriteProduct->contains($products[0]['id'])
                && $favoriteProduct->count() === 1;
        });

    // 対象外ユーザーのお気に入りは変化が無いことを検証
    Livewire::actingAs($otherUser)
        ->test(Index::class)
        ->assertViewHas('favoriteProduct', function (FavoriteProduct $favoriteProduct) use ($products) {
            return $favoriteProduct->count() === 0;
        });
}
```

# おわりに
いやー、すごいです。非常に良質な開発体験を得られます。
Laravel開発にReactやVueが不要になる日も近いですね（もうすでに？）。
無条件でオススメできるのですが、採用する場合は必ず[ここ](https://livewire.laravel.com/docs/security)を熟読しましょう。

# 絶賛採用中です！
イオンスマートテクノロジーではエンジニアをはじめとした様々な職種を積極的に採用中です！
これからとてもおもしろいフェーズへ突入していくと思いますので興味のある方は是非カジュアル面談などで話を聞いてください！

https://hrmos.co/pages/ast