いっきのblog

技術とか色々

LaravelでJSON Web Tokenを使った認証方法

Laravel + Reactでサービスを構築した場合に、APIによる認証方法としてJSON Web Token(JWT)を使った例を紹介しようと思う。

JSON Web Tokenとは?

ネットわかりやすく書いてあったので引用させていただくと

JWT(ジョット)とは JSON Web Token の略で、電子署名付きの URL-safe(URLとして利用出来る文字だけ構成される)な JSONのことです。 電子署名により、JSON の改ざんをチェックできるようになっています。 ざっくり言うと、改ざんできない JSON ということになります。 引用:https://qiita.com/gctoyo/items/8d0ffb265845ab8cc87c#jwt-%E3%81%A8%E3%81%AF

というセキュアで便利なものである。

認証として利用する

認証として利用するためには、とても便利なライブラリがあるのでそちらを使う。

github.com

インストールに関しては、公式の方を参考にする。

Laravel Installation - jwt-auth

モデルの設定

設定自体もとても楽で、Laravelにすでにある認証方法の一部変更するだけでいい。
実際の設定しているconfig/auth.phpはこのようになる。

<?php

return [


    'defaults' => [
        'guard' => 'api',
    ],


    'guards' => [
        'api' => [
            'driver' => 'jwt', # ここをjwtにする
            'provider' => 'client',
        ],

    ],


    'providers' => [
        'client' => [
            'driver' => 'eloquent',
            'model' => App\Models\ClientAccount::class,
        ],
    ],

];

ログイン

ログイン時にはいくつかバリデーションを行い、レスポンスにはトークンを返している。

<?php
public function login(Request $request){
    config(['jwt.user' => Client::class]);

    $account = $this->clientAccount->getLoginUserByEmail($request->email);

    // Emailまたはstatusがマッチしない場合
    if(!$account){
        throw new AuthenticationException;
    }

    // passwordがマッチしない場合
    if (!Hash::check($request->password, $account->password))
    {
        throw new AuthenticationException;
    }
    
    // ログイン情報を保存するにチェックを入れた場合
    if($request->remember){
        if (! $token = Auth::guard('api')->setTTL(config('jwt.ttl_r'))->login($account)) {
            throw new AuthenticationException;
        }
    }else{
        if (! $token = Auth::guard('api')->login($account)) {
            throw new AuthenticationException;
        }
    }

    return compact('token');
}

ユーザー側からの利用

トークンの利用については、API利用時にAuthorizationヘッダーにBearer: {取得したtoken}を付与すればできる。以下の記事が参考になる。

qiita.com

感想

  • JWTを使った認証もライブラリのおかげで楽に実装
  • ただ、JWTに関しては色々言われてるので利用する場合はちゃんと勉強しとかないとなとおもった

Mackerel サーバ監視[実践]入門を読んで、監視の第一歩目を始めた話

はじめに

うちのサービスは1月の後半に正式リリースをして約2ヶ月。
スピード重視という言い訳をしつつ、監視をあまりやってこなかったのでここらへんでMackerelを使って監視していきたいと思う。

Mackerel サーバ監視[実践]入門

↓ 監視ちゃんと取り込むきっかけになった自分のブログ

kzkohashi.hatenablog.com

Mackerelはサーバーの監視をするためのSaaSである。
特に何が良いって、はてなのインフラのノウハウがベースなのと、日本語ってのがいい。

mackerel.io

似ているサービスとしては海外のDatadog

www.datadoghq.com

同じく海外のNew Relicがある。

newrelic.com

そもそも監視の勉強が先?

自分は監視に関してはやんわりしか理解してないため、まずは入門本を読もうと思っていた。ただ、まずは監視の設定を先にやらないと今このとき何か起こったとき間に合わないため今回はMackerelの方を先に読んだ感じだ。
こちらは読む予定の本。

ソフトウェアエンジニアのための ITインフラ監視[実践]入門

目次

  • 1章 Mackerelとは何か
  • 2章 Mackerelをはじめる
  • 3章 監視する
  • 4章 アラートを通知する
  • 5章 プラグインを作る
  • 6章 各種ツール連携と運用の効率化
  • 7章 クラウド環境におけるMackerel
  • 8章 発展的な機能
  • 9章 付録

学んだこと

Mackerelの設定本として買ったつもりが、監視をする意義や監視すべき設定など細かく書いてあってすごくためになった!監視の初学 + Mackerelの設定が同時にできるいうお得な気分だ。

第2章: Mackerelをはじめる

はじめてみた

導入して最初に目がついたのが、CPU200%。。。。(笑えないやつ) f:id:kzkohashi:20180319165517p:plain

でも、バッチ自体が止まったことないしなーとおもって細かくみてみると f:id:kzkohashi:20180319165539p:plain

stealに200%もとられている。 t2インスタンス特有?のCPUクレジットの枯渇が問題っぽいのかー。
確かに最近バッチサーバーはずっと動いてたから、こちらは他のインスタンスに変更しよう。

blog.a4works.co.jp

キャパシティプランニングへの応用

こういう言葉があること自体初めて知った。

システムに求められるキャパシティ要件として、ビジネスレベル(データ処理需要、同時利用ユーザー数、同時セッション数など)、サービスレベル(トランザクション量、ネットワークトラフィック量、応答時間など)、リソースレベル(CPU利用率、ディスク利用率、ネットワーク利用率など)を検討し見積もりをする。この際、現状の最大負荷だけでなく、将来予測される最大負荷時にもサービスの水準を維持できるような設計を検討する必要がある。
引用: https://it-words.jp/w/E382ADE383A3E38391E382B7E38386E382A3E38397E383A9E383B3E3838BE383B3E382B0.html

まずはリソースを可視化したら、自分たちのCPUなどがどれくらい使われているのか?ピーク時には足りているのか?余らせてないか?などみてみる。
ちなみに自分たちのAPIサーバーは
f:id:kzkohashi:20180319165626p:plain

やばい。使わなすぎている。無理して良いの使いすぎた・・・下げよう。

サービスメトリックス

PVなどもサービスの状態を可視化して経営層とみよう!ってことが書かれていたけど、今時別のツール使ってみてないかな?というツッコミ。

第3章: サーバ監視

何を監視

  • 外形監視
  • 死活監視
  • リソース監視
  • その他監視

ミドルウェアの監視

mysqlなどの場合別途プラグインをいれる。
行ロックなどの数も取れたりするみたい。

RDSの場合はこちらから。(後の章で説明ある)

mackerel.io

監視ルールの考え方

「実際に対応するものだけに限定すべき」

設定する前は適当にCloudWatchで色々設定してしまってたの反省。

オススメの監視設定フロー

① 全てのサーバに対してのCPU使用率、メモリ使用率、スワップ使用率、ファイルシステムの使用率の監視ルールを設定する
② 各種エラー系のメトリックの監視ルールを設定しておく
③ いったん様子見を見て、特定のロールだけ頻繁にアラートが来るようなら除外条件で無視するか、別途当該ロール用の監視ルールを作成する
④ 障害が発生したときに、特徴的な値の変化をしたメトリックについて監視ルールを作成する。同じ障害を早めに検知できるように、回帰的に監視ルールを育てる。

監視ルールを育てるって良い。 ②までやったけど、③と④はサービスごとによしなに設定だからこれからうちのも育ててこう。

Webシステムにおけるロール編成

構成については以下を参照とのこと。

blog.yuuk.io

Linuxでより多くのパラメーターを取得するには、mackerel-plugin-linuxでとれる。 Mackerelプラグインは豊富で誰でも作れるのでよさそう。

github.com

システムメトリックスの見方

コラムなのに6ページくらい使って監視項目の見方とか書いてくれてる。 少しだけ抜擢すると

ロードアベレージ
「ロードアベレージがある値以上になったら即座に問題である」はあまり意味がない。
それ以上に、どんどん右肩上がりのグラフになっているかの方が問題。マルチコアの考慮もして「loadavg/コア数」を指標とする方が良いみたい。
mackerel-pluglin-multicoreとかでうまく対応できる。

ここら辺のコラムが一番参考になったかもしれない・・・。

CPUのほうはメモ

cpu.user: カーネル以外が使用した時間の割合
cpu.iowait: I/O街により、アイドル状態であった時間の割合
cup.system: カーネルが使用した時間の割合
cpu.idle: I/O街がなく、かつCPUがアイドル状態であった時間の割合

6章: 各種ツール連携と運用の効率化

tmux-cssh x MackerelAPIで同時に多数のサーバーに入る技。

blog.yuuk.io

以前それを改造してawsのログイン機能作ったのでついでに(便利だよ)。

kzkohashi.hatenablog.com

7章: クラウド環境におけるMackerel

AWSインテグレーションを使って、勝手に連携できる。
RDSとかも含めて複数台でパラメーターとってる場合はどうするんだろうっておもってたけど、同じホストとしてパラメーター取得でもできるようで便利。

8章: 発展的な機能

CLIを通じて、グラフなどもすべてJSONで記述が可能とのこと。
Infrastructure as Codeができるし、Ansibleなどと合わせてインフラ周りを全てコードで管理できそう。
以前の会社でもdatadogのグラフ情報をコードにしてる方もいたなぁ。

まとめ

Mackerelを設定するために読み始めた本だが、監視のいろはなども書いてあって凄く勉強になった。とりあえず監視を始めてみよう!って方にはオススメ。
まだまだデータ取り始めたばかりなのでこれからどんどん育てていった結果もかきたい。

PHPerKaigi 2018に行ってきたけどすごく良いイベントだった!

どうも、くずき(@kzkohashi)です。
2018年 3月9日(金)〜3月10日(土)に開催されたPHPerたちのイベントにいってまいりました。

phperkaigi.jp

土曜日のほうしか参加できなかったけど、すごくよかった。
参加したセッションの内容だけメモります。

今からでも出来る!Webサービスモニタリング!!

発表時間ぎりぎりに登場する、そーだい(id:Soudai)氏の発表。
モニタリング全般の内容。

感想

  • やろうやろうと思って機能作りを優先していた自分にはとても耳が痛い
  • モニタリングしてないからインフラのリファクタもできないんだよ、ってのはすごく響いた
  • カカカカックの人もよく言ってた気がするからそこら辺も参考にしよう
  • 関係ないけどそーだいさんのブログ読み漁るとすごく参考になる

質問してみた

発表終了後に、質問させていただいた内容

Q1. サービスやインフラで何をモニタリングしていいのかがわからない場合はどうすればいいですか?今はDatadog検討してます。

答えてもらったんだけど、自分が勘違いしてるから割愛! (ちゃんとmackerel使いこなしたら理解できるはずなので、その時追記)

サーバーが完膚なきまでに死んでもMySQLのデータを失わないための表技

MySQL大好き@yoku0825氏の発表。
MySQLのデータをどうやって復旧させるのか、保存するのかっていう話。

感想

  • 難しいところがところどころあるけど、データベース詳しくない自分でもとりあえずこういうふうに保存しておけばいいよっていうのが理解できてよかった
  • RDSで自動バックアップで楽々してる人は知識として診たほうがいいと感じた
  • データベースが得意そーだいさんが質問してたけど、むずすぎてよくわからない

質問してみた

帰り際に質問させていただいた内容

Q1. 現在RDSを使っていて、フルバックアップを使ってます。自分たちのサービスではとれくらいまでのデータを保存すればいのかとかあまりわかってないんですが・・どうしたらいいでしょうか?

(懇親会後で若干出来上がった状態で聞いたので、間違ってるかもしれません)

A1. binarylogを使って途中の状態を復帰した事例は、自社のサービスをいくつも運用していて、5年間で1度だけ。なので、サービスによるけど、基本的にはフルバックアップをしっかりとってれば最悪は免れる。(ここでそーだいさんもはいってきてどっちが言ったか忘れました)会社にリソースないなら、RDSならフルバックアップが30日間くらい保持できるから、それ+24時間分のbinary logをしっかりとっておけば問題ない。復帰もインスタンスたてるときにぽちぽちと設定するだけでできるから簡単。

こんなしょぼい質問にデータベースのスペシャリストの2人のお話聞けてよかった。
とりあえずちゃんと実践して、ブログで恩返ししよう・・(違。

追記 コメントで指摘していただいて、RDSはbinarylogでの復旧相当のやってくれていて、フルバックアップだけじゃなかった。 (そのぶん高いけど)

phpstorm/チーム開発/開発環境相談会に参加

Interactive Round Tableとよばれる、円卓でプロフェッショナルな方たちにと一緒に対話や相談ができるという素敵な場所で、@tadsan氏や@do_aki氏たちを中心とした円卓があったので相談させてもらった。(他にもいたのですが、全員把握しきれず・・)
面白くて、ほとんどの時間ここにいたので他のセッション聞くこと忘れてました。

内容を覚えてる限り書いてみる。
議論的な感じだったので、なんとなくよかった答えだけを書く

現在IntellijPHPを書いてるんですけど、PHPStormのいいところどこですか?

Inttelijと比べて、PHPに特化してるぶん初期設定が楽。
あと、Inttelijプラグインと比べて新しい機能とか早く出たと思うからいいよ。

レビューどうしてます?(ザックリすぎた)

A1. インデントとかそういった機械的な物に関しては、sidecipsr-2などで対応する。理由としては、人同士のレビューなどでどうしても細かすぎるとイラッとしてしまうため、そういうところはなるべく機械的にすること。

A2. 開発が5人とかに対してレビュワーが1人のため、コミットの分け方やコードスタイル(※多分社内の)に準じてない場合はすぐ直してもらっている。コードスタイルを公開したりするだけじゃすぐにはよくならないから、何回も指摘して、地道にいいコードをあげてもらえるよう努力している。

セキュリティどうしてますか?

A1. 外部のツールなど使って継続的にみている
(VAddy, AppScan, HACKER SAFEあたりの話がでていた)

リモートってどう思いますか?

A1. 拠点が違うだけでも会議とか難しいので、しっかり社内体制整えないとリモートは難しい。
A2. フルリモートじゃなくて、週1とかでやるタスクが決まっているならできるとおもう。社内業務委託的な感じで。

(感想) 今うちでもやってみてるけど、週1、2でやる場合ならやるタスクが決まってれば問題ない。ミィーティングなどに関しても今のところあんまり問題ないけど、これが複数人とかだと難しくなるんだろうなぁ。
misocaさんの参考になる。

Misocaのリモートワークのやり方2018 | toyoshiの日記

おまけ

まとめてくれて感謝

www.manasnote.com

phperkaigiの感想

運営の方々のおかげで円滑に進むし、
普通は話せないプロフェッショナルな方達と色々な話ができてとてもよかっし、
飯もビールも厳選されててうまいし、
知り合い増えたし、
すごく刺激にもなって良いことづくしの会議でした。(褒)

あとアンカンファレンス(参加者主導型のブース)も非常に面白くて、これはもっかいやってほしいなと思った。
関西か福岡のはどっちか行きたいなーと思えるいい会議でした。

Laravel Meetup Tokyo Vol.10で「Laravelを始めてからDDDを実践するまで」について発表してきた

どうも、くずき(@kzkohashi)です。
先日「Laravel Meetup Tokyo Vol.10」で発表してきたのでその内容と他の方の発表について書いていこうと思う。

laravel-meetup-tokyo.connpass.com

発表資料

Laravelを始めてからDDDを実践するまでの過程について書いた内容。
なぜこの内容にしたかというと、そもそも自分はコードの書き方とか設計思想をぼんやりしか持っていなく、以下のようなことで悩んでいた。

  • 設計する際に毎回設計内容がブレてしまう
  • フレームワークに依存する設計にしてしまっている(FWの恩恵を受けないのは論外だけども)
  • 設計について他の人と深い議論ができてない

ようは自分がいいなって思う設計を考えられてないってので悩んでいて、設計(今回だとDDD)について「理論」だけではなく、「実践」することで学習のイテレーションを回したいなって考えていた。

補足

  • EmailValue Objectに外部も関係しそうな振る舞いを使っている例はそもそも設計的にいいのか怪しい
  • クリーンアーキテクチャ気味に書いてたつもりだけど、色々混同してるっぽいのでそこらへんは参考にしないほうがいい

と、恥ずかしながら怪しいところばかりで・・。

逆に質問した内容

「実際、DDDを実践してどうでした?」とLaravel Meetupの主催者であるytake氏に聞いてみた。
ザックリ書くと(若干忘れたので間違ってたら補足くだせい)、

  • 要件に対して実装みれば大体わかる
  • パフォーマンス問題についてはクリーンアーキテクチャを実践していて、そもそもEloquentを使っていないのもあるけど困ったことがない

DDDを本格的に実践している方が言っていたので、もっと深くやりたいなと思いました。

反省点

  • DDD全くわからない人向けには不親切な資料になってしまった
  • わかる人にも中途半端なないようになってしまった
  • つまり、タイトルをとりあえず実践してみようぜ!!!的な実践することに特化した感じにすればよかった
  • 直前にスライド増やしたのもあって、練習通りの時間じゃなかった(19分で終わるはずが4分オーバー・・すいません)

Laravel環境で取り入れているテストTips4選

SCOUTERでCTOやってられる、@kotamats氏の発表。
E2Eのテストする際に便利なAPIspecや、 Golang界で人気なTable Driven Testなどの内容。
資料もみやすいし、Table Driven Testはなかなか熱そうなテストで面白そう。

[ GitPitch ] kotamat/pitch-20180308-laravel-meetup/master

そのあと、今回使用した資料作成ツールについてのブログも書いてた。
ソースコード書くときは使いたい!!

techblog.scouter.co.jp

Eloquentの使い方を考え直してみた(LT枠)

いわずとしれた、zuckey_17氏の発表。
すごくわかる。最初の方とか自分もwithとか便利なのあるの知らず、まじつらだった。

LumenでのAPI E2Eテストの実装例 (LT枠)

若干競合会社(書いてて気づいたw)で働いていられる、@shaka0maru氏の発表。
apidocはよさそう!最近swaggerで疲弊してきたし。

Laravelから学びレガシーと闘いはじめた(LT枠)

同じ苗字の大橋さんである,@blue_goheimochi氏の発表。
途中に差し込まれるphperkaigiネタが面白すぎて、それしか覚えてないけど・・、どうやってレガシーなコードをチームごとに落とせるかっていうことがよかった。後から気づいたことだけど、phperkaigi2018のスタッフだから差し込んでたのか・・。

全体的な感想

  • 会場提供者であるistyleさんが技術的におもしろいことばかりしてる会社だった
  • クラフトビールよかった(提供: 転職ドラフト)
  • 初めてLaravelのコミュニティに参加してみたけど、みんないいひとでよかった
  • 20分は長いようで短い

Laravelでバリューオブジェクトのみを実装する

どうも、くずき(@kzkohashi)です。
今回はDDDにおける、バリューオブジェクトをLaravelでどう表現するかについて書きたいと思います。

なぜバリューオブジェクト?

DDDを学んでいる中で、実際にどうやったら実装に落とし込めるだろうか?って考えたときに、一番最小構成であるバリューオブジェクトから手をつけたほうがいいんじゃないかという話になった。
(もちろんユビキタス言語などを見つけてからの前提)

実装

Eloquentから返却される値をすべてバリューオブジェクトにしてたら、時間も手間もかかってしまうので、一部ずつ変えていく。
今回はUserの1カラムであるEmailを例に変更してみる。

バリューオブジェクトの生成

<?php
class Email implements \JsonSerializable
{
  /**
   * @var string
   */
  protected $value;

  /**
   * @param string $value
   */
  protected function __construct(string $value)
  {
      if (is_null($value)) {
        throw new \InvalidArgumentException("Argument must be set. Passed value is empty");
      }
      
      // 正しいメールアドレスかのチェック
      if (preg_match('/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/iD', $value)) {
        throw new \App\Exceptions\EmailException();
      }
      
      $this->value = $value;
  }

  /**
   * @return string
   */
  public function value(): string
  {
      return $this->value;
  }

  /**
   * @param $string
   * @return bool
   */
  public function equals(self $string)
  {
      return $this->value === $string->value;
  }

  /**
   * @param string $value
   * @return static
   */
  public static function of(string $value)
  {
      return new static($value);
  }

  /**
   * @return string
   */
  public function __toString()
  {
      return (string)$this->value;
  }

  /**
   * @return mixed|string
   */
  public function jsonSerialize()
  {
      return $this->value;
  }

}

constructでは、バリューオブジェクトを作成する際のルールなどを書いておく。
JsonSerializableを継承しておく理由としては、response()->jsonなどを使用する際に、バリューオブジェクト->値 などになるため入れておいたほうが良さそう。

モデル

<?php

class User extends Models
{
  
  
  public function toValueObject()
  {
    // イミュータブルにするため
    $replication = $this->replicate();
    
    // すでに作成してあるバリューオブジェクトを入れる
    $replication->email = new Email($this->email);

    return $replication;
  }
}

実際の利用方法

<?php
  // カラムをバリューオブジェクトに変換
  $user = User::find(1)->toValueObject();
  
  // バリューオブジェクトの振る舞いを利用
  $user->email->xxxx();

こんな形で利用している。
ただ、ロジックが大きくなってしまう仕様や、判定ロジックは外部に切り出したりしている。
ここガッツリ書けなかったので、この方の記事が参考になると思います。

pospome.hatenablog.com

また、バリューオブジェクト=不変性だと思っていたのですが、かとじゅんさんが昔に書いた記事が参考なります。

d.hatena.ne.jp

感想

DDDが書いてある本などを読んでいると、設計方法はやんわりわかったけど実際動いてるコードはどこから手をつけたらいいかわらかなかった。
今回はバリューオブジェクトから始めることによって、ドメインロジックに集中するって大切さが少しわかってきた。

Laravelでリポジトリーパターンを実装して見た感想

どうも、いっき(@kzkohashi)です。
Laravelを使い始めて1年くらいたちそうなので、いくつか試している実装パターンの感想でも書こうと思う。
今回は、Repositoryパターンについて書く。

---追記---
リポジトリーパターンを採用しつつバリューオブジェクトについても書いた。

kzkohashi.hatenablog.com

また、Laravel MeetupでDDDについても発表したまとめ。

kzkohashi.hatenablog.com

Repositoryパターンとは?

Repositoryパターンとはビジネスロジックとデータ操作のロジックを分離し、データ操作のロジックを抽象化するパターンと認識してる。
例えばユーザーが新規登録したとして、今までだと

View -> UserController -> UserSerive(ビジネスロジック/データ操作)

Viewから受け取ったフォームデータをコントローラーで受け、サービス層で登録処理などをやっていたと思う。

それをRepositoryパターンにおきかえると

View -> UserController -> UserSerive(ビジネスロジック) -> UserRepository(データ操作)

このようになり、データ操作のロジックとビジネスロジックを分離する。
Repositoryパターンのほうの実装を簡単に書くと(Controllerは省略)

<?php

interface UserRepository
{

  public function create($name, $email, $password_hash);
  
}


class EloquentUserRepository implements UserRepository
{
  private $user;
  
  public function __construct(User $user)
  {
    $this->user = $user
  }
  
  public fucntion create($name, $email, $password_hash)
  {
    $data = [];
    $data['name'] = $name;
    $data['email'] = $email;
    $data['password] = $password_hash;
    
    return $this->user->create(data);
  }
}
<?php
class UserService
{
  private $user_repo;
  
  public function __construct(UserRepository $user_repo)
  {
    $this->user_repo = $user_repo;
  }
  
  public function regist($name, $email, $password)
  {
    $password_hash = bcrypt($password);
    $user = $user_repo->create($name, $email, $password_hash)
    $user->notify();
    
    return $user;
  }

}

注目すべきところはサービスからUserRepositoryが呼ばれているが、実装はEloquentUserRepositoryで行なっていること。
理由としてはインターフェースを通して実行することで、ORMによる依存を排除し、サービス側は実装の内容を意識しなくて良い。

f:id:kzkohashi:20180225230423p:plain 引用: https://terasolunaorg.github.io/guideline/public_review/ImplementationAtEachLayer/DomainLayer.html#id10

Laravelでの実装

簡単にテストまでの奴を書いた。

github.com

簡単に構成を説明すると、

  • インターフェース: App\Repositories
  • 実装: App\Infrastracture\Repositories
  • サービスコンテナの登録: App\Providers\RepositoryProvider

らへんを見てもらえるとリポジトリーパターンの実装の仕方がわかると思う。
リポジトリの作成は結構手間だけど、aiiroが実装してくれたコマンドを使えば容易に作れる。

php artisan app:make:repository ItemRepository

良かったところ

  • サービス層はデータ操作を除くビジネスロジックに集中できる
    • 何をやりたいかが見やすくなる
    • 永続先に依存するロジックを排除できる
  • DIにしたのでテストかきやすい

悪かったところ(反省点?)

  • Eloquentがサービス層に漏れている
    • 結局返す値がEloquentじゃデータ操作ができてしまう
    • 完璧なリポジトリーパターンを目指すには、Entityでのやりとりなどが必要

今後の改善点

リポジトリーパターンはDDDを行うための実装方法の一つで、実際すべてを行うのはなかなかしんどい。
なので次はEloquentの一部の値をValueObjectに置き換え、DDDに近づけていく。

LaravelのMacroを使ってBuilderに機能を追加する方法

どうも、くずき(@kzkohashi)です。
先日、正しいJSON APIのフォーマットにするために以下のライブラリを導入した。

github.com

paginateは正しいクエリーパラメーターを取っていない

恥ずかしながら最近知ったのだが、Eloquentで使用しているpaginateは、完璧なJSON APIのフォーマットに対応していない。
ページ番号で使われるpage=2などのクエリパラメーターは、正しくは、page[number]=2にすべきみたいだ。加えて、ページ番号だけでなく、そのページにどれくらい表示させたいかなどはpage[size]=20で表現する。

なので、導入前と導入後ではこうなる。
導入前
xxx.com/xxx?page=2&size=20 // sizeに関しては独自実装

導入後
xxx.com/xxx?page[number]=2&page[size]=20

さらにきになる方は以下を参照。

Note: JSON API is agnostic about the pagination strategy used by a server. Effective pagination strategies include (but are not limited to): page-based, offset-based, and cursor-based. The page query parameter can be used as a basis for any of these strategies. For example, a page-based strategy might use query parameters such as page[number] and page[size], an offset-based strategy might use page[offset] and page[limit], while a cursor-based strategy might use page[cursor].

JSON API — Latest Specification (v1.0)

ライブラリの実装を見て見る

ここからが本題。
このライブラリを導入し、設定ファイルを書くとjsonPaginate()が使えるようになる。

YourModel::where('my_field', 'myValue')->jsonPaginate();

どうやって実装したんだろうとコードをみて見るとBuilder::macroによって独自の関数を追加できるみたいだ。

<?php

  ...
  
  Builder::macro(config('json-api-paginate.method_name'), function (int $maxResults = null, int $defaultSize = null) {
      $maxResults = $maxResults ?? config('json-api-paginate.max_results');
      $defaultSize = $defaultSize ?? config('json-api-paginate.default_size');
      $numberParameter = config('json-api-paginate.number_parameter');
      $sizeParameter = config('json-api-paginate.size_parameter');
      $size = request()->input('page.'.$sizeParameter, $defaultSize);
      if ($size > $maxResults) {
          $size = $maxResults;
      }
      return $this->paginate($size, ['*'], 'page.'.$numberParameter)
          ->setPageName('page['.$numberParameter.']')
          ->appends(array_except(request()->input(), 'page.'.$numberParameter));
  });

github.com

これはLaravelMacroという機能で、Laravelが提供している機能に独自のメソッドなどを拡張実装できるみたい。
Builder以外には、HTML, Responseなどを拡張できる。

他にどう使えるか?

例えば、Eloquentが返すModelではなく、独自に実装したEntityを返すcollectionを用意するとか

<?php

Builder::macro('toDomainCollection', function () {
    /** @noinspection PhpUndefinedMethodInspection */
    return $this->get()->map(function (Domainable $model) {
        return $model->toDomain();
    });
});

レスポンスである条件の時に決まった内容などを送る時とか

<?php

Response::macro('notPermitted', function ($type = null) {
    if (strtolower($type) === 'success') {
        return $this->json(['data' => []], 200);
    }
    return $this->json(['data' => []], 403);
});

今まで作ってたやり方を見直すだけでも色々ありそーだなーと思います。