03
2019

Laravel小規模アプリ開発時の設計方針とかメモ

CATEGORYPHP
最近開発してたLaravelアプリ(APIサーバー)が、だいたい良い感じに(?)出来上がってきたので、参考用に開発時に決めた設計方針とかこういうクラス構成になったとか、そういうのをまとめてみる。
なお、実際にこの方針をいくらか試した、お勉強用リポジトリがこちら。バージョンは5.8。

前提

まず最初に前提条件をまとめる。
  1. ごく小規模なアプリである(サーバーサイド開発は俺一人)
  2. 性能要件も低い(想定リクエスト数は数rpsレベル)
  3. 少なくとも2, 3年は運用する
  4. インフラは決まっている(FaaSとかは不可)

大方針

次いで全体的な方針とそれによって決めたことなど。
  1. 性能要件が低いから、出来るだけごく普通の定番の構成で済ませ、他の人に渡しやすいようにする。
    → PHP + Laravel + MySQL + Redis
  2. かつ小規模なので、アプリの造りもLaravelの標準的なやり方で行う。Laravel経験者ならすぐ触れるソースを目指す。
    → 普通のMVCでやる。オレオレフレームワーク的な仕組みは極力避ける(ただしサービス層だけは設けた)。
  3. OSSライブラリなどはあれば出来るだけ用いる。自前で実装するより有名なライブラリを使う。
  4. 管理画面的なものも必要だが、Laravel側ではAPIのみ提供する形にして、造りを統一する。
    (実際の管理画面のフロントはAngularで実装。既にAngularの資産があったことと、JSで作った方がユーザー的に使い易いので。画面についてはLaravelとは別扱いとして割り切り。)
  5. 機能ごとにアプリを分割したりはしない。1アプリに全て(通常のAPI、管理画面用のAPI、バッチ等々)含める。

DB方針

DB設計の方針やルールなど。
  • 出来るだけシンプルなテーブル構成にする。
  • が、無理に正規化を突き止めない。その方がシンプルになるなら随時JSON型も使う。
  • DBのスキーマは、運用上の取り扱いなどを考えて分ける。マスタデータとユーザーデータ、履歴データで分割。
    (ただこれは、それほど必要性がなかった上に、後日トランザクションや集計処理がやや面倒になったので、失敗だったかも?)
  • 履歴系テーブルはMySQLのパーティションで古いものをDROPできるようにする。
  • DBの列名は、Laravelの作法に則りスネークケースにする。IDやタイムスタンプ等の型も、LaravelのデフォルトがUNSIGNED INTやTIMESTAMPなのでそれに従う。

API方針

API設計の方針やルールなど。
  • フォーマットはJSON。
  • REST的な命名規則を用いる…つもりだったが、APIの粒度と合わなかったので断念。
    (管理画面用のAPIなど問題ないところはRESTっぽい名付けをした。)
  • JSONのキー名は世の主流(?)に合わせてキャメルケースにする
  • レスポンスを独自のペイロードで包まない。追加情報はHTTPヘッダーでやり取り。
  • その他、GET/POST/PUT/DELETEを使い分けたりレスポンスコードをちゃんと返したり、HTTPの流儀に従う。
    (クライアントはブラウザ想定じゃないけどちゃんとやる。)

アプリ構造

上記の方針の元、実際に作成したアプリの構造や設計など。

コントローラ

SwaggerによるAPI定義と、モデルやサービスの呼び出しを行う。

バリデーションは、複雑なものや繰り返し利用するものはフォームリクエストとして整備。
1 APIだけの簡易的なものは、そこまでせずコントローラ内でバリデーション実施。

ビジネスロジックは当然だが含めない…が、原理主義的に徹底してはいない。
モデル呼び出し時にいろいろメソッドチェーンしている部分も。
(理想ではこういうのも切り出すべきだろうが。)

モデル

DB定義や、ゲッター/セッター的な処理、そのモデルで共通的な処理や、また特定のモデルのみに紐づくビジネスロジックを実装。

Active Record的にはビジネスロジックはモデルでやるものだが、モデル内の処理の粒度をある程度整理したかったのと、そもそもモデル横断の処理は何かしら仕組みを考えないといけないので、次項のサービスに役割分担している。

サービス

いわゆるトランザクションスクリプト層。
複数のモデルに跨るビジネスロジックや、特定のAPI専用であまりモデルに置きたくないビジネスロジックをこちらに実装。
トランザクションも基本的にサービス層で実施(別に必要ならモデルでやってもいいが)。

サービスクラスの導入には賛否あるが、そうは言ってもこの手の処理の置き場所はどこかに必要だったので設けている。
(今回のアプリだとそれで事足りるし。)

イベント

Laravel標準のイベントの他、laravel-transactional-eventsを使って、モデルのsavingやdeletingに連動してコミット時に発生するイベントを定義。
更新ログ取得など、本来の処理とは直接関係ないが、データが更新された際に呼ぶ必要がある処理のために使用した。

ログ

普通のログ(開発者が好きに使ってよいもの)とエラーログの他、APIのリクエスト/レスポンスを出力するアクセスログと、バッチのコンソール出力を転送したバッチログを用意した。
普通のログには、デバッグ情報として発行される全SQLの内容も出している

が、このアプリではちゃんとした履歴はDBに保存しているので、ログはあくまで開発者が見る用や、エラー時にアラート出したりする用。

認証

認証は通常のAPIも、管理画面のAPIも含めてすべてjwt-authを使ってJWT認証にした。
(API使用者からセッションIDよりトークンが良いという要望があったので、ならと最近よく使われてるらしいJWTにした。)
トークンの有効期間は15分程度で、定期的にリフレッシュしてもらう想定。
なお、管理画面のロール管理はLaravelのGateとミドルウェアの組み合わせで行った。

例外

例外は、業務エラー用に独自のAppExceptionとかを定義して、全てこれをベースに管理するようにした(これの6)。
例外発生時のログレベルなどを、エラーコードマスタとして定義して、エラーハンドラーでマスタを見て制御。
Laravel標準のエラーも、一度AppExceptionに変換して判定している。

Enum

Enum的な定数は、各クラスで個別に定義するのではなく、EnumsディレクトリにEnumクラスを作ってそこで定義した。
が、これはEnumと名付けているのみで、実際には単なる定数値を列挙したクラス。
PHPでEnumを実現するライブラリは複数あるようだが、今回はそこまでの機能が必要ないのと、型変換など手間もあるので、使わなかった。

マイグレーション

DBのマイグレーション等は、Laravel標準のマイグレーションの機構を使用した。
ただし、各DBスキーマごとに個別に実施したかったので、ディレクトリを分割して、オプション付きで実行するようにした。

管理画面

前述のように管理画面用のAPIを提供する形で実装。
管理画面用のAPIは、ルートを /admin/ の下にまとめて、Authのガードや、適用するミドルウェアを切り替えている。
モデルやサービス自体は可能な限り共有。

ユニットテスト

工数の都合上、主にAPI単位の大ざっぱなテストのみ整備。
モデル層はモックにせず、DBをSQLiteにすることで実DBを使ってテストしている。


独自の仕組みになってしまった部分

上述のように出来るだけLaravel標準の仕組みで実装したかったが、一部機能については(Laravelの上に乗ってはいるものの)独自の機構となってしまった。

マスタ

マスタデータ周りは別にアプリの規模が小さいからといって機能が小さく済むわけではないので、フルに実装した。
Excelのマスタデータをインポートして、インポートの度にバージョンIDを採番して、動的にバージョンIDプレフィックス付のテーブルを生成して、参照するときは有効なバージョンのテーブルを見に行って…みたいなことをした。

ただし、モデルを使う側からは極力普通のモデルとして使えるようには心掛けた。

Redis用のモデル

Laravelには、Redisコマンドを実行するためのファサードはあるが、Redisデータをデータ構造として扱う機構がないため、独自にZSETをモデルとして扱うSortedSetModelみたいなものを作成した。
メソッド名など、普通のモデルに合わせてみたが、どうしても引数などは合わないので、ちょっと微妙。

BULK系SQL

Laravel標準ではBULK INSERTは可能(?)であるものの、MySQLのBULK UPSERTは行えないため、生SQLを組み立ててやった。
MySQL依存になってしまったので、前述のようにSQLiteのユニットテストだと動かないのが悩み。

DBのPARTITION分割

同じくLaravel標準のマイグレーションではDBのパーティション化は行えないため、同様に生SQLを組み立てて実現した。


以上こんなとこ。サービス周りとかいろいろ人によっては思うところもあるとは思うが、割とLaravelの上に乗っかってちゃんとできた?と思う。満足!
スポンサーサイト



Tag: PHP Laravel

0 Comments

Leave a comment