Laravel で Read/Write 構成のデータベースに対して明示的に Write コネクションを指定する方法

# 手っ取り早く結論
* Laravel で明示的に Write コネクションを指定する方法はざっくり 3 つある
    * Eloquent Model で `protected $connection` を指定する
    * `onWriteConnection()` を使う
    * トランザクションを切る

# 前提となる背景
* Laravel[^lumen] でデータベースのコネクション設定を Read/Write 構成にすると、参照系のクエリは暗黙的に Read コネクションを使う
* 参照系のクエリで Write コネクションを利用したいときがある
    * Read コネクションがリードレプリカに向いていて、書き込み遅延がある
    * 更新直後に異なるリクエストによる参照が発生することがあり、書き込み遅延が間に合わず最新のレコードを取得できない
    * 更新直後に最新のレコードを取得したいため、一部の参照系で Write コネクションを利用したい

根本的には設計のミスだと思っています。
しかし一旦、この設計は変えられない前提で、小手先のテクニックでなんとかする方法を紹介します。

# 確認バージョン
* Laravel 5.5
* Laravel 6.x

# データベース設定
このような設定のとき、参照系のクエリのコネクションに `DB_HOST_WRITE` を使いたい。

```php:config/database.php
'mysql' => [
    'driver'    => 'mysql',
    'read' => [
        'host' => env('DB_HOST_READ', env('DB_HOST', 'localhost')),
    ],
    'write' => [
        'host' => env('DB_HOST_WRITE', env('DB_HOST', 'localhost')),
    ],
```

# `protected $connection`

Eloquent Model の `protected $connection` に `'コネクション名::write'` を入れましょう。
特定のテーブルや Eloquent Model で常に Write を参照したい場合に有効です。

```php
class User extends Model implements AuthenticatableContract, AuthorizableContract
{
    use Authenticatable, Authorizable;

    protected $connection = 'mysql::write';
```

このプロパティがどういう働きをするのかは、このあたりのコードを読むと理解できます。
https://github.com/laravel/framework/blob/5.5/src/Illuminate/Database/DatabaseManager.php#L87

<dl>
  <dt>Pros</dt>
  <dd>常に最新のレコードを取得できる</dd>

  <dt>Cons</dt>
  <dd>Write コネクションへの負荷が増える</dd>
</dl>

## 総評
* 社内管理画面など、一部のユーザーが利用するような低トラフィックなアプリケーション＆セッションストアにデータベースを採用している場合に有効だと思います
* 高トラフィックで常に最新のレコードを取りたい、という場合には性能的な厳しさに直面すると思います
    * 更新時に Redis にキャッシュするなど、他の高速なデータストアの利用を検討したほうが良さそう

# `onWriteConnection()`

Eloquent Model の特定クエリで Write を参照したいときに便利です。

```php
$user = User::onWriteConnection()->first();
```

https://github.com/laravel/framework/blob/5.5/src/Illuminate/Database/Eloquent/Model.php#L344


<dl>
  <dt>Pros</dt>
  <dd>Write コネクションの負荷を最小限に抑えられる</dd>
  <dd>特定のユースケースで最新のレコードを取得できる</dd>

  <dt>Cons</dt>
  <dd><code>onWriteConnection()</code> を書くべきところで書き忘れるとバグる</dd>
  <dd>遅延によるのでたまにしか再現しなかったりする</dd>
</dl>

## 総評
* 小手先テクニックとしては個人的にオススメです
* 書き忘れが怖いなら `protected $connection` を検討しましょう
* このやり方がツラいときは設計から見直したほうが良いでしょう

# トランザクションを切る
Laravel はトランザクションを切ると暗黙的に Write コネクションを利用します。
これはトランザクション本来の用途ではありません[^laravel-transaction]が、１リクエスト中の複数の参照クエリで読み取り一貫性をもたせたいという要求もセットで叶えるならこれです。
Laravel でトランザクションの切り方はドキュメントを見てください。

https://laravel.com/docs/5.5/database#database-transactions

<dl>
  <dt>Pros</dt>
  <dd>１リクエスト中の複数の参照クエリで読み取り一貫性をもたせられる(※)</dd>
  <dd>特定のユースケースで最新のレコードを取得できる</dd>

  <dt>Cons</dt>
  <dd>暗黙的に Write コネクションを利用しているため、知らない人はコードを読んでも分からない</dd>
  <dd>トランザクションを切っている理由をコメントに書いてあげると親切ですね</dd>
</dl>

(※)**トランザクション中の実際の読み取り一貫性はデータベースエンジンやその設定によって左右されます**。設定がどうなっているか、実際にトランザクションを切ってどういう振る舞いをするかは確認してください。

## 総評
* 複数の参照クエリで読み取り一貫性を持たせたい場合はこれしかないです
* 単に Write コネクションを利用したいだけなら `onWriteConnection()` か `protected $connection` を最初に検討しましょう

# まとめ
* Laravel で明示的に Write コネクションを指定する方法はざっくり 3 つある
    * Eloquent Model で `protected $connection` を指定する
    * `onWriteConnection()` を使う
    * トランザクションを切る

# 参考
* [Laravel Read/Write Connection - Specify Explicitly - Stack Overflow](https://stackoverflow.com/questions/37550722/laravel-read-write-connection-specify-explicitly)
* [Laravel の DB のマスター/スレーブでマスターで SELECT したりスレーブでトランザクションしたり - Qiita](https://qiita.com/ngyuki/items/a3ab2ea615e8ba73a491)
* [Database: Getting Started - Laravel - The PHP Framework For Web Artisans](https://laravel.com/docs/5.5/database)

[^lumen]: Lumen でも同様
[^laravel-transaction]: Laravel が明示的なトランザクションで Write コネクションを使うようにしているだけ