Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

レイヤードアーキテクチャを意識した PHPアプリケーションの構築 ver2

12,234 views

Published on

2015/09/14 DevLove関西

Published in: Technology
  • Be the first to comment

レイヤードアーキテクチャを意識した PHPアプリケーションの構築 ver2

  1. 1.  @shin1x1 2015/09/14 DevLove関西 レイヤードアーキテクチャを意識した PHPアプリケーションの構築 ver2
  2. 2. http://www.slideshare.net/shin1x1/lt-up-49929619
  3. 3. レイヤードアーキテクチャ
  4. 4. レイヤードアーキテクチャ (c) 2015 Masashi Shinbara @shin1x1 • アプリケーションをレイヤ(層)に分割 • レイヤは自身の役割を担う • レイヤ間で協調して、処理を行う
  5. 5. OSI参照モデル (c) 2015 Masashi Shinbara @shin1x1 7.Application 6. Presentation 5. Session 4.Transport 3. Network 2. Data link 1. Physical
  6. 6. MVC (c) 2015 Masashi Shinbara @shin1x1 View Controller Model
  7. 7. MVCの悩み
  8. 8. CakePHP (c) 2015 Masashi Shinbara @shin1x1 FatController -> FatModel
  9. 9. Fat Model (c) 2015 Masashi Shinbara @shin1x1 • 1,000行を超える Model • Model の役割が多すぎる • DAO / バリデーション / ビジネスロジックなど
  10. 10. MVC (c) 2015 Masashi Shinbara @shin1x1 View Controller Model
  11. 11. MVC + Service (c) 2015 Masashi Shinbara @shin1x1 View Controller Model Service
  12. 12. サービスレイヤを追加 (c) 2015 Masashi Shinbara @shin1x1 • Controller と Model の間のレイヤ • 主にビジネスロジックとバリデーションを担う • 1アクションメソッドに、1サービスクラス
  13. 13. 結果 (c) 2015 Masashi Shinbara @shin1x1 • Fat(Controller¦Model) を、ある程度解消 • レイヤの責務があいまい
 => サービスが、セッションを操作等 • レイヤ間の処理の流れが統一できていない
 => サービスが、コントローラを操作等
  14. 14. レイヤを意識
  15. 15. Laravel (c) 2015 Masashi Shinbara @shin1x1 • Laraval + AngularJS • Laravel は、REST API の提供のみ • UI 関連の処理は、AngularJS • レイヤの責務と流れを意識
  16. 16. レイヤ構造 (c) 2015 Masashi Shinbara @shin1x1 Routing Controller Eloquent(ORM) Service
  17. 17. レイヤの役割 (c) 2015 Masashi Shinbara @shin1x1 Routing Controller ORM Service ルーティング、認証、フィルタ HTTPリクエスト、レスポンス バリデーション、サービス実行 事前条件検証、ビジネスロジック データベースアクセス、 エンティティ固有の処理
  18. 18. 例: 書籍の予約 (c) 2015 Masashi Shinbara @shin1x1 •会員制書籍予約Webアプリケーション •利用者は認証トークンが必要 •書籍の予約を行う •予約する書籍と予約数を指定
  19. 19. 例: 書籍の予約 (c) 2015 Masashi Shinbara @shin1x1 •POST /reservation •X-Api-Token: ユーザ認証トークン •asin=書籍コード •quantity=予約数
  20. 20. Routingレイヤ (c) 2015 Masashi Shinbara @shin1x1 •認証はミドルウェア(フィルタ)で実行
 => 未ログインなら、401を返す •URIからコントローラを実行
  21. 21. Routing Route::group(['before' => 'api_auth'], function () {
 Route::post('/reservation', ReservationController::class.'@create');
 } ); 認証フィルタ
  22. 22. Routing Route::group(['before' => 'api_auth'], function () {
 Route::post('/reservation', ReservationController::class.'@create');
 } ); URIとコントローラのマッピング
  23. 23. Controllerレイヤ (c) 2015 Masashi Shinbara @shin1x1 •POSTパラメータ、セッションユーザ情報を取得 •バリデーション実行
 (asinとquantityパラメータの形式チェック) •サービスに必要なパラメータを渡して実行
 (HTTPの関心事はサービスに持ち込まない) •HTTPレスポンスを返す
  24. 24. Controller public function create()
 {
 $validator = (new ReservationValidatorBuilder())->create(Input::all());
 if ($validator->fails()) {
 return $this->responseValidationError($validator->messages());
 }
 
 $reservation = $this->service->book($this->getUser(), Input::all());
 
 return $this->responseCreated($reservation);
 } バリデーション
  25. 25. Controller public function create()
 {
 $validator = (new ReservationValidatorBuilder())->create(Input::all());
 if ($validator->fails()) {
 return $this->responseValidationError($validator->messages());
 }
 
 $reservation = $this->service->book($this->getUser(), Input::all());
 
 return $this->responseCreated($reservation);
 } サービスの実行
  26. 26. Serviceレイヤ (c) 2015 Masashi Shinbara @shin1x1 •事前条件の検証
 (書籍の在庫が足りているか?等) •予約処理を実行 •DB操作をEloquent(ORM)で行う
  27. 27. Service public function book(User $user, array $inputs)
 {
 $book = Book::where('asin', $inputs['asin'])->first();
 if (empty($book)) {
 throw new PreconditionException('book_not_found');
 }
 if ($book->inventory < $inputs['quantity']) {
 throw new PreconditionException('not_enough_book_inventory');
 }
 
 $reservation = new Reservation();
 DB::transaction(function () use ($user, $book, &$reservation, $inputs) {
 $affectedRows = $book->decrementInventory($inputs['quantity']);
 if ($affectedRows !== 1) {
 throw new PreconditionException('not_enough_book_inventory');
 }
 
 $reservation->user_id = $user->id;
 $reservation->book_id = $book->id;
 $reservation->quantity = $inputs['quantity'];
 $reservation->reservation_code = $reservation->generateReservationCode();
 $reservation->save();
 });
 
 return $reservation;
 }
  28. 28. Service public function book(User $user, array $inputs)
 {
 $book = Book::where('asin', $inputs['asin'])->first();
 if (empty($book)) {
 throw new PreconditionException('book_not_found');
 }
 if ($book->inventory < $inputs['quantity']) {
 throw new PreconditionException('not_enough_book_inventory');
 }
 
 $reservation = new Reservation();
 DB::transaction(function () use ($user, $book, &$reservation, $inputs) {
 $affectedRows = $book->decrementInventory($inputs['quantity']);
 if ($affectedRows !== 1) {
 throw new PreconditionException('not_enough_book_inventory');
 }
 
 $reservation->user_id = $user->id;
 $reservation->book_id = $book->id;
 $reservation->quantity = $inputs['quantity'];
 $reservation->reservation_code = $reservation->generateReservationCode();
 $reservation->save();
 });
 
 return $reservation;
 } 必要なパラメータ
  29. 29. Service public function book(User $user, array $inputs)
 {
 $book = Book::where('asin', $inputs['asin'])->first();
 if (empty($book)) {
 throw new PreconditionException('book_not_found');
 }
 if ($book->inventory < $inputs['quantity']) {
 throw new PreconditionException('not_enough_book_inventory');
 }
 
 $reservation = new Reservation();
 DB::transaction(function () use ($user, $book, &$reservation, $inputs) {
 $affectedRows = $book->decrementInventory($inputs['quantity']);
 if ($affectedRows !== 1) {
 throw new PreconditionException('not_enough_book_inventory');
 }
 
 $reservation->user_id = $user->id;
 $reservation->book_id = $book->id;
 $reservation->quantity = $inputs['quantity'];
 $reservation->reservation_code = $reservation->generateReservationCode();
 $reservation->save();
 });
 
 return $reservation;
 } 事前条件の検証
  30. 30. Service public function book(User $user, array $inputs)
 {
 $book = Book::where('asin', $inputs['asin'])->first();
 if (empty($book)) {
 throw new PreconditionException('book_not_found');
 }
 if ($book->inventory < $inputs['quantity']) {
 throw new PreconditionException('not_enough_book_inventory');
 }
 
 $reservation = new Reservation();
 DB::transaction(function () use ($user, $book, &$reservation, $inputs) {
 $affectedRows = $book->decrementInventory($inputs['quantity']);
 if ($affectedRows !== 1) {
 throw new PreconditionException('not_enough_book_inventory');
 }
 
 $reservation->user_id = $user->id;
 $reservation->book_id = $book->id;
 $reservation->quantity = $inputs['quantity'];
 $reservation->reservation_code = $reservation->generateReservationCode();
 $reservation->save();
 });
 
 return $reservation;
 } ビジネスロジックの実行
  31. 31. レイヤの依存、処理の流れ (c) 2015 Masashi Shinbara @shin1x1 Routing Controller Eloquent(ORM) Service
  32. 32. レイヤの依存、処理の流れ (c) 2015 Masashi Shinbara @shin1x1 Routing Controller Eloquent(ORM) Service 流れを一方向に固定
  33. 33. レイヤをまたぐのはアリ (c) 2015 Masashi Shinbara @shin1x1 Routing Controller Eloquent(ORM) Service
  34. 34. レイヤをまたぐのはアリ (c) 2015 Masashi Shinbara @shin1x1 Routing Controller Eloquent(ORM) Service 方向は変えない
  35. 35. サービスを中心に考える (c) 2015 Masashi Shinbara @shin1x1 •サービス(ビジネスドメイン)が中心 •事前条件検証とビジネスロジック •HTTPの関心事は持ち込まない
 必要なもの引数で渡す(scalar, array, object)
  36. 36. ドメインをサービスで表現 (c) 2015 Masashi Shinbara @shin1x1 •クラス名、メソッド名はドメインの用語で
 (ユビキタス言語) •ユースケースをメソッドに実装 •ex) 書籍の予約
 => ReservationService#book()
  37. 37. サービスから作る (c) 2015 Masashi Shinbara @shin1x1 • サービスとテストを先に実装 • サービスの最初の利用者は、テスト • サービスを Web に結ぶのが、コントローラ • サービスは、バッチ処理等からも使える
  38. 38. ドメインごとに名前空間を分ける [package] + [AcmeReservation] + [Controller] + [Service] + [Model] + [Validation] [AcmeUser] + [Controller] + [Service] + [Model] + [Validation] PSR-4
  39. 39. 結果 (c) 2015 Masashi Shinbara @shin1x1 • レイヤの役割に専念できる • 流れが一方向なので、依存の方向が明確に • サービス(ドメイン)に集中 • サービスをどう分割していくかが課題
  40. 40. まとめ (c) 2015 Masashi Shinbara @shin1x1 • MVCだけじゃない • 自分でレイヤ構造を考える • レイヤの責務と処理の流れ
  41. 41. @shin1x1 (c) 2015 Masashi Shinbara @shin1x1

×