学習記録:12月11日(火):【輪読会まとめ 後編パート2】Laravel Webアプリケーション開発 Chapter5 データベース(リポジトリパターン)
これは 俺のインプットアウトプット記録 Advent Calendar 2018 の11日目のエントリーです。
学習記録:12月9日(日):【輪読会まとめ 後編パート1】Laravel Webアプリケーション開発 Chapter5 データベース の後編の続きなのですが、まずもって「リポジトリパターン」をOOPの理解を含めて自分の言葉にするのと、リファクタリングを行う前のソースコードを生成していくので2時間ほどかかりました。
よって後編パートをさらに分割し、今日はリファクタリング前のコードを書くところまで実行します。
【抽象化って】読み進め、その前に【なんやねん】
抽象化って言葉を掘り下げたい。いったいなんやねんな。まじわからんし、ふわっとしすぎなんじゃー!って思ったそこのアナタ。そう(強引)、僕もそうでした。
なので僕なりに解釈した「抽象化」を表してみます。
- 書籍販売のECがあるとする
- プログラム、層、サービス、などそれぞれに責務がある
- 例えばサービス(ドメイン?)の責務はユーザーにサービスを提供すること
- そこに「どのデータストアを使うのか」という責務は含まれない
- 書籍販売ならただ以下ができればいいはず(細かいことは割愛)
- 「書籍一覧を取得」できて
- 「購入ボタンが押せ」て
- 「カートに商品が入っ」て
- 「購入できる」
- つまり、書籍販売のECの責務からしたら、書籍販売ができれば良い
- 本来、MySQLなのかPostgreSQLなのかは意識させてはいけない
- より本質的な責務に集中し、追加しやすく変更に強い作りにする必要がある
- そのため、本質的なところ以外は「外出し」して、自分の責務に集中させる
- 本質的以外のところは意識の外に出す
- これが抽象化!
もちろん全然違うよ!こうだよ!って意見があると思うし、僕自身、理解の途上にいるので、異論反論などバンバン受け付けています!マサカリ大歓迎!
リポジトリパターン
5-5-1 リポジトリパターンの概要
- アプリケーションのデータストアは様々
- RDB、NoSQL、キャッシュ、ファイル、SaaSのAPI、etc...
- テストコードでは本番とは違うデータベース使うこともある
- そもそもDB使わない場合もある
- そんなデータストアの参照先が変わっても
- プログラムの変更範囲は限定的にしたい!
- それ、リポジトリパターンで解決できるかも!
ただ図をみるのもなんなので、書いてみました。僕の場合、図は自分で書かないと理解できないのです…
リポジトリパターン、僕なりの解説
①密結合
- 状態
- 問題点
- データストアが変わった場合、ビジネスロジックの層に改修が入る
- 影響範囲が大きい
- 適切に切り離していないのでテストしにくい
②ビジネスロジックからデータストアを切り離す
③インターフェースを用いて疎結合な状態に
- 状態
- 問題点
- 最初に設計し、インターフェースを実装しておく必要がある
- やはり後からの変更は大変
- 設計大事
文中の解説
5-5-2 リポジトリパターンの実装
- 最初はサービスクラスとデータベースアクセスが密結合
- リファクタリングで他のデータストアへの変更、モック差し替え
1.アプリケーション仕様
- いいねをつける機能をWebAPI
- URLは
/api/action/favorite
- テーブルはLaravel標準のusers, 「5-1」で作ったbooks, 新規作成のfavorite
表5.5.2.1:favoritesテーブル
カラム | 型 | 備考 |
---|---|---|
book_id | integer | FK |
user_id | integer | FK |
created_at | timestamp | |
updated_at | timestamp |
2.テーブルの作成
リスト5.5.2.2:favoritesテーブル作成するマイグレーションファイル
$ php artisan make:migration create_favorites_table Created Migration: 2018_12_11_140814_create_favorites_table $ ls -la database/migrations/*favorites*.php -rw-r--r-- 1 laradock laradock 598 Dec 11 14:08 database/migrations/2018_12_11_140814_create_favorites_table.php
リスト5.5.2.3:favoritesテーブルを作成するコードを記述
<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateFavoritesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('favorites', function (Blueprint $table) { $table->integer('book_id'); $table->integer('user_id'); $table->timestamps(); $table->unique(['book_id', 'user_id'], 'UNIQUE_IDX_FAVORITES'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('favorites'); } }
リスト5.5.2.4:マイグレーション実行
無事テーブル作成完了。ちなみにLaravelは複合主キーを扱えない(扱いにくい?)ため、 $table->unique(['book_id', 'user_id'], 'UNIQUE_IDX_FAVORITES');
とuniqueで代用しているそうです。
$ php artisan migrate Migrating: 2018_12_11_140814_create_favorites_table Migrated: 2018_12_11_140814_create_favorites_table
リスト5.5.2.5:テーブル定義
mysql> desc favorites; +------------+-----------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +------------+-----------+------+-----+---------+-------+ | book_id | int(11) | NO | PRI | NULL | | | user_id | int(11) | NO | PRI | NULL | | | created_at | timestamp | YES | | NULL | | | updated_at | timestamp | YES | | NULL | | +------------+-----------+------+-----+---------+-------+ 4 rows in set (0.01 sec) mysql> show create table favorites\G *************************** 1. row *************************** Table: favorites Create Table: CREATE TABLE `favorites` ( `book_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, UNIQUE KEY `UNIQUE_IDX_FAVORITES` (`book_id`,`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.02 sec)
3.コードの作成
以下3つのファイルを作る。
- データベースアクセスを受け持つEloquent(Favorite)
- ビジネスロジックを受け持つサービスクラス(FavoriteService)
- リクエストを受けるコントローラクラス(FavoriteAction)
Eloquentクラスの実装
以下のコマンドでファイル作成する。
リスト5.5.2.6:Favoriteモデルの生成
$ php artisan make:model DataProvider/Eloquent/Favorite Model created successfully.
リスト5.5.2.7:Favoriteモデルの実装
<?php namespace App\DataProvider\Eloquent; use Illuminate\Database\Eloquent\Model; class Favorite extends Model { protected $fillable = [ 'book_id', 'user_id', 'created_at' ]; }
サービスクラスの実装
- ここはartisanではなく手動でファイルを設置
- 主な仕様
- switchFavoriteメソッド内でEloquentを呼び出し
- 「いいね」データ登録を行う
- ただし、書籍コードとユーザーIDの組み合わせが既に存在する場合
- レコードを削除して「いいね」を取り消す
リスト5.5.2.8:ビジネスロジックを受け持つFavoriteServiceクラスの実装 (「これはいかんな」ってのが自分にもわかるぞ、と思いながら書いてた。)
<?php declare(strict_types=1); namaspace App\Services; use App\DataProvider\Eloquent\Favorite; class FavoriteService { public function switchFavorite(int $bookId, int $userId, string $createdAt) : int { return \DB::transaction( function () use ($bookId, $userId, $createdAt) { $count = Favorite::where('book_id', $bookId) ->where('user_id', $userId) ->count(); if ($count == 0) { Favorite::create([ 'book_id' => $bookId, 'user_id' => $userId, 'created_at' => $createdAt, ]); return 1; } Favorite::where('book_id', $bookId) ->where('user_id', $userId) ->delete(); return 0; } ); } }
Action(Controller)の実装
- 主な仕様
<?php declare(strict_types=1); namespace App\Http\Controllers; use App\Services\FavoriteService; use Illuminate\Http\Request; use Carbon\Carbon; use Symfony\Component\HttpFoundation\Response; class FavoriteAction extends Controller { /** * @var \App\Services\FavoriteService */ private $favorite; /** * Create a new Controller instance. * * @return void */ public function __construct(FavoriteService $favorite) { $this->favorite = $favorite; } /** * Switch to Favorite Status * * @param \Illuminate\Http\Request $request * * @return response */ public function switchFavorite(Request $request) { $this->favorite->switchFavorite( (int)$request->get('book_id'), (int)$request->get('user_id', 1), Carbon::now()->toDateTimeString() ); return response('', Response::HTTP_OK); } }
routing設定
エンドポイント(外部から叩くURI)を登録する。
<?php Route::post('/action/favorite', 'FavoriteAction@switchFavorite');
動作確認
エラー出てますな…。あとで直します。
修正しました。Eloquentでsyntax出てるやん…。お恥ずかしい。
ちゃんと200が返ってきました。
$ curl 'http://localhost/api/action/favorite' --request POST --data 'book_id=1&user_id=2' --write-out '%{http_code}\n' 200
データ確認
ちゃんとデータ登録されていますね。いやーよかったよかった。
mysql> select * from favorites; +---------+---------+---------------------+---------------------+ | book_id | user_id | created_at | updated_at | +---------+---------+---------------------+---------------------+ | 1 | 2 | 2018-12-11 15:44:23 | 2018-12-11 15:44:23 | +---------+---------+---------------------+---------------------+ 1 row in set (0.00 sec)
やってみて
分量が…
明らかに分量が増えすぎていて、今回でも終わりませんでしたな(遠い目
しかし、それだけ理解するためのスタートは切れているってことだし、これをアウトプットの形に結実させれば、リポジトリパターンと抽象化と疎結合に関しては、一度道を通ったことになるかなって思ってます。
後編パート2がさらに2つになってしまいそう。 / はてなブログに投稿しました
— まみー (@mamy1326) 2018年12月11日
学習記録:12月9日(日):【輪読会まとめ 後編パート2】Laravel Webアプリケーション開発 Chapter5 データベース(リポジトリパターン) - 叫ぶうさぎの悪ふざけ https://t.co/Fx8QWUyUfA #はてなブログ
抽象化
抽象化を本当に1分で言えと言われるとこうなるのかな、とか思いつつ、まだまだ自分の言葉にできてないよなって思ってます。そもそもこの章のコード動いてないし。もうちょいがんばろ。
抽象化って人間にわかるように説明すると何なのよ!となると「本来そのプログラムや層が本質的にやるべきことに集中するため」に行う「本質的じゃない内容の外出し」と理解してるんですが、その解説も含めたい。そもそもコードがまだ動いてないので、引き続き更新する。図を自分で書くと理解深まるなー
— まみー (@mamy1326) 2018年12月11日
抽象化を解説してみた
自分が本当に解説できるのか、というのと、エンジニアリングにまるで関係のない人に通じるのか、どう思うのか、を聞いてみました。思った通りというか、11月前半の僕と大して変わらない認識でちょっと安心。
これを奥さんに理解できるように解説できたらまずはよし、って感じかなあ。
- プログラムってのがあってだな
- 奥さん:(ぬいぐるみを頷かせる)
- こいつはユーザーにサービスを提供するのが責務
- 奥さん:(ぬいぐるみを頷かせる)
- その責務にどのデータベースを使うのか、は関係ない
- 奥さん:(ぬいぐるみを左右に傾かせて「はてー?」)
- データベースはMySQLとかPostgreSQLとかOracleとかいっぱいある
- 奥さん:(ぬいぐるみを頷かせる)
- それがサービスの成長にしたがって他のデータベースも併用しなくちゃいけなくなった
- 奥さん:(ぬいぐるみを頷かせる)
- もしこの時点でプログラムが「MySQLしか使えませーん」となってると全部修正しなくちゃいけない
- 奥さん:(ぬいぐるみをビクッと動かして驚いたアクション)
- めっちゃ時間もお金もかかる
- 奥さん:(ぬいぐるみを大きく動かして「大変だー」のアクション)
- じゃあどうするか。じゃーん!「抽象化!」
- 奥さん:(抽象化!と手を突き上げるアクション)
11月当初の僕なら、これすら言葉にできなかったんですが、ぺちオブ #phper_oop のおかげでだんだんと言葉にできるようになってきた。試しに奥さんに「抽象化」って言葉から何を連想する?と聞いてみたら、傍にあったぬいぐるみをちょこまか動かして「私!ぬい!抽象化!」って言ってたのです。
— まみー (@mamy1326) 2018年12月11日
その表情と動きだけで勉強する気持ちを根こそぎ持ってかれるくらいかわいらしいんですけど、それはグッと維持しつつ「やっぱりそうだよねえ」と言いながら、僕なりの「OOPでの抽象化」を言葉にして解説してみた。しかしちょこまかとぬいぐるみを動かして頷くだけで理解はしていただけなかった模様!
— まみー (@mamy1326) 2018年12月11日
というわけで引き続き…
僕の場合は、この抽象化を奥さんに解説できるレベルまでなんども話しては掘り下げ、言葉の意味を解説して僕自身も深く理解し、初めて他人に解説できるようになるんじゃないかなって考えてます。
— まみー (@mamy1326) 2018年12月11日
というわけでこの辺の話や思いを加筆修正しつつ、コードを動かすところまで持って行こう。
というわけで、リファクタリングは次回に続きます!楽しい!