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.

Laravel でやってみるクリーンアーキテクチャ #phpconfuk

1,113 views

Published on

ドメイン駆動設計やユースケース駆動開発などの文脈でレイヤードアーキテクチャやクリーンアーキテクチャといった言葉をよく聞くようになりました。
「名前は聞いたことあるけど、敷居が高そう......」「本は読んだけど実際に実装するイメージがつかない......」そんなことを感じている方もいらっしゃるのではないでしょうか?
本トークではそんな方に向けて、簡単なアプリケーションの実装例とともに、クリーンアーキテクチャの考え方や実装する上での Laravel の機能についてお話します!

2018/06/29 開催の PHP カンファレンス福岡2019 (https://phpcon.fukuoka.jp/2019/) の発表資料です。

Published in: Software
  • Be the first to comment

Laravel でやってみるクリーンアーキテクチャ #phpconfuk

  1. 1. Laravel PHP Conference Fukuoka 2019 @okashoi WILLGATE, Inc.
  2. 2. • • 🙆 • • #phpconfuk #hall_fu 2
  3. 3. • 2 • 3 • 5 • 10 • 3 • 2 3
  4. 4. • 2 • 3 • 5 • 10 • 3 • 2 4
  5. 5. 🏁 • • • Laravel 
 5
  6. 6. 🙅 • Laravel • 6
  7. 7. • 2 • 3 • 5 • 10 • 3 • 2 7
  8. 8. 8 2017 2018
  9. 9. ”The Clean Architecture - The Clean Code Blog”, https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html 9
  10. 10. • 
 • • 10
  11. 11. • • • ※ PHP 11
  12. 12. “ 
 ” 12
  13. 13. • 2 • 3 • 5 • 10 • 3 • 2 13
  14. 14. 14
  15. 15. 15
  16. 16. 16 🙅
  17. 17. 17
  18. 18. 18
  19. 19. • • • • 19
  20. 20. 20
  21. 21. 21 ↑
  22. 22. 22
  23. 23. package foo; import bar; class Foo { bar.Bar x; function doFoo() { x.process(); } } 23 package bar; class Bar { function process() { // do something... } }
  24. 24. package foo; import bar; class Foo { bar.Bar x; function doFoo() { x.process(); } } 24 package bar; class Bar { function process() { // do something... } } foo bar = foo → bar
  25. 25. package foo; import bar; class Foo { bar.Bar x; function doFoo() { x.process(); } } 25 package bar; class Bar { function process() { // do something... } } foo bar = foo → bar
  26. 26. package foo; import bar; class Foo { bar.Bar x; function doFoo() { x.process(); } } 26 package bar; class Bar { function process() { // do something... } } foo → bar foo → bar
  27. 27. package foo; class Foo { Buz x; function doFoo() { x.process(); } } interface Buz { function process(); } 27 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } }
  28. 28. package foo; class Foo { Buz x; function doFoo() { x.process(); } } interface Buz { function process(); } 28 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } }
  29. 29. package foo; class Foo { Buz x; function doFoo() { x.process(); } } interface Buz { function process(); } 29 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } } foo bar = foo → bar
  30. 30. package foo; class Foo { Buz x; function doFoo() { x.process(); } } interface Buz { function process(); } 30 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } } bar foo = foo ← bar
  31. 31. package foo; class Foo { Buz x; function doFoo() { x.process(); } } interface Buz { function process(); } 31 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } } foo → bar foo ← bar
  32. 32. “ 
 
 ” 32
  33. 33. • 
 ➡ 
 33
  34. 34. 34 UI
  35. 35. • 2 • 3 • 5 • 10 • 3 • 2 35
  36. 36. CleanArchitecture Clean Architecture Kindle No. 3228/6016 36
  37. 37. • • https://github.com/okashoi/laravel-clean- architecture • • 37
  38. 38. 38
  39. 39. • • ID 
 39
  40. 40. readme 40
  41. 41. • • • POPO Plain Old PHP Object ➡ 41
  42. 42. • PHP 42 <?php namespace MyAppComponentsTasksEntities; use DatetimeImmutable; /** * Class Inbox * @package MyAppComponentsTasksEntities */ final class Inbox extends Task { /** * @var EstimatedTime|null */ private $estimatedTime;
  43. 43. • • 43 /** * @test * @expectedException MyAppComponentsTasksEntitiesEstimatedTimeNotSet */ public function 「Inbox」に対して「見積もり時間」が未設定のまま「着手日」を設定できないこと() { $id = Mockery::mock(Id::class); $name = new Name('test'); $inbox = new Inbox($id, $name); $startDate = new StartDate(new DateTimeImmutable('tomorrow')); $inbox->convertToScheduled($startDate); }
  44. 44. 44
  45. 45. 45
  46. 46. • • 
 • 
 46
  47. 47. InputBoundary / OutputBoundary • 
 47 <?php namespace MyAppComponentsTasksUseCasesCreateInbox; /** * Interface InputBoundary * @package MyAppComponentsTasksUseCasesCreateInbox */ interface InputBoundary { /** * @param InputData $input */ public function __invoke(InputData $input): void; }
  48. 48. InputBoundary / OutputBoundary • 
 48 <?php namespace MyAppComponentsTasksUseCasesCreateInbox; /** * Interface NormalOutputBoundary * @package MyAppComponentsTasksUseCasesCreateInbox */ interface NormalOutputBoundary { /** * @param NormalOutputData $output */ public function __invoke(NormalOutputData $output): void; }
  49. 49. Interactor 49 <?php namespace MyAppComponentsTasksUseCasesCreateInbox; use MyAppComponentsTasksEntities{Inbox, Task, Name, Note}; use MyAppComponentsTasksUseCases{IdProvider, TaskRepository}; /** * Class Interactor * @package MyAppComponentsTasksUseCasesCreateInbox */ final class Interactor implements InputBoundary { /** * @var IdProvider */ private $idProvider; /** * @var TaskRepository */ private $taskRepository; /** * @var NormalOutputBoundary */ private $normalOutputBoundary; /** * Interactor constructor. * @param IdProvider $idProvider * @param TaskRepository $taskRepository * @param NormalOutputBoundary $normalOutputBoundary */ public function __construct(IdProvider $idProvider, TaskRepository $t { $this->idProvider = $idProvider; $this->taskRepository = $taskRepository; $this->normalOutputBoundary = $normalOutputBoundary; } /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); } /** * @param InputData $input * @return Task
  50. 50. Interactor 50 <?php namespace MyAppComponentsTasksUseCasesCreateInbox; use MyAppComponentsTasksEntities{Inbox, Task, Name, Note}; use MyAppComponentsTasksUseCases{IdProvider, TaskRepository}; /** * Class Interactor * @package MyAppComponentsTasksUseCasesCreateInbox */ final class Interactor implements InputBoundary { /** * @var IdProvider */ private $idProvider; /** * @var TaskRepository */ private $taskRepository; /** * @var NormalOutputBoundary */ private $normalOutputBoundary; /** * Interactor constructor. * @param IdProvider $idProvider * @param TaskRepository $taskRepository * @param NormalOutputBoundary $normalOutputBoundary */ public function __construct(IdProvider $idProvider, TaskRepository $t { $this->idProvider = $idProvider; $this->taskRepository = $taskRepository; $this->normalOutputBoundary = $normalOutputBoundary; } /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); } /** * @param InputData $input * @return Task InputBoundary
  51. 51. Interactor 51 <?php namespace MyAppComponentsTasksUseCasesCreateInbox; use MyAppComponentsTasksEntities{Inbox, Task, Name, Note}; use MyAppComponentsTasksUseCases{IdProvider, TaskRepository}; /** * Class Interactor * @package MyAppComponentsTasksUseCasesCreateInbox */ final class Interactor implements InputBoundary { /** * @var IdProvider */ private $idProvider; /** * @var TaskRepository */ private $taskRepository; /** * @var NormalOutputBoundary */ private $normalOutputBoundary; /** * Interactor constructor. * @param IdProvider $idProvider * @param TaskRepository $taskRepository * @param NormalOutputBoundary $normalOutputBoundary */ public function __construct(IdProvider $idProvider, TaskRepository $t { $this->idProvider = $idProvider; $this->taskRepository = $taskRepository; $this->normalOutputBoundary = $normalOutputBoundary; } /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); } /** * @param InputData $input * @return Task DI ※ 

  52. 52. Interactor 52 <?php namespace MyAppComponentsTasksUseCasesCreateInbox; use MyAppComponentsTasksEntities{Inbox, Task, Name, Note}; use MyAppComponentsTasksUseCases{IdProvider, TaskRepository}; /** * Class Interactor * @package MyAppComponentsTasksUseCasesCreateInbox */ final class Interactor implements InputBoundary { /** * @var IdProvider */ private $idProvider; /** * @var TaskRepository */ private $taskRepository; /** * @var NormalOutputBoundary */ private $normalOutputBoundary; /** * Interactor constructor. * @param IdProvider $idProvider * @param TaskRepository $taskRepository * @param NormalOutputBoundary $normalOutputBoundary */ public function __construct(IdProvider $idProvider, TaskRepository $t { $this->idProvider = $idProvider; $this->taskRepository = $taskRepository; $this->normalOutputBoundary = $normalOutputBoundary; } /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); } /** * @param InputData $input * @return Task 

  53. 53. Interactor::__invoke() 
 53 /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); }
  54. 54. Interactor::__invoke() 
 54 /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); }
  55. 55. Interactor::__invoke() 
 55 /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); }
  56. 56. Interactor::__invoke() OutputBoundary 
 56 /** * @param InputData $input */ public function __invoke(InputData $input): void { $inbox = $this->produceEntity($input); $this->taskRepository->save($inbox); $normalOutput = $this->produceNormalOutputData($inbox); ($this->normalOutputBoundary)($normalOutput); }
  57. 57. 57
  58. 58. • • Repository • 🙆 • Eloquent 58
  59. 59. Web UI 59
  60. 60. Web UI • • Controller 
 void • 
 60
  61. 61. Controller 61 <?php namespace MyAppWebControllers; use IlluminateHttpRequest; use MyAppComponentsTasksUseCasesCreateInboxInputData; use MyAppComponentsTasksUseCasesCreateInboxInputBoundary; /** * Class CreateInbox * @package MyAppWebControllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } }
  62. 62. Controller 62 <?php namespace MyAppWebControllers; use IlluminateHttpRequest; use MyAppComponentsTasksUseCasesCreateInboxInputData; use MyAppComponentsTasksUseCasesCreateInboxInputBoundary; /** * Class CreateInbox * @package MyAppWebControllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } } Laravel HttpControllers
  63. 63. Controller 63 <?php namespace MyAppWebControllers; use IlluminateHttpRequest; use MyAppComponentsTasksUseCasesCreateInboxInputData; use MyAppComponentsTasksUseCasesCreateInboxInputBoundary; /** * Class CreateInbox * @package MyAppWebControllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } } HTTP
  64. 64. Controller 64 <?php namespace MyAppWebControllers; use IlluminateHttpRequest; use MyAppComponentsTasksUseCasesCreateInboxInputData; use MyAppComponentsTasksUseCasesCreateInboxInputBoundary; /** * Class CreateInbox * @package MyAppWebControllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } } InputBoundary Interactor
  65. 65. Controller 65 <?php namespace MyAppWebControllers; use IlluminateHttpRequest; use MyAppComponentsTasksUseCasesCreateInboxInputData; use MyAppComponentsTasksUseCasesCreateInboxInputBoundary; /** * Class CreateInbox * @package MyAppWebControllers */ final class CreateInbox extends Controller { /** * @param Request $request * @param InputBoundary $interactor */ public function __invoke(Request $request, InputBoundary $interactor) { $validated = $this->validate($request, [ 'name' => 'required|string|max:255', 'note' => 'string|nullable', ]); $input = new InputData($validated['name'], $validated['note'] ?? ''); $interactor($input); } }
  66. 66. Presenter • 66 <?php namespace MyAppWebPresentersCreateInbox; use IlluminateViewFactory; use MyAppWebPresentersPresenter as BasePresenter; use MyAppComponentsTasksUseCasesCreateInboxNormalOutputData; use MyAppComponentsTasksUseCasesCreateInboxNormalOutputBoundary; /** * Class CreateInboxPresenter * @package MyAppWebPresenters */ final class Presenter extends BasePresenter implements NormalOutputBoundary { // 中略 /** * @param NormalOutputData $output */ public function __invoke(NormalOutputData $output): void { $viewModel = new ViewModel( $output->taskId(), $output->taskName(), $output->taskNote(), ); $this->respond($this->view->make('web::tasks.create', compact(['viewModel']))); } }
  67. 67. Presenter • 67 <?php namespace MyAppWebPresentersCreateInbox; use IlluminateViewFactory; use MyAppWebPresentersPresenter as BasePresenter; use MyAppComponentsTasksUseCasesCreateInboxNormalOutputData; use MyAppComponentsTasksUseCasesCreateInboxNormalOutputBoundary; /** * Class CreateInboxPresenter * @package MyAppWebPresenters */ final class Presenter extends BasePresenter implements NormalOutputBoundary { // 中略 /** * @param NormalOutputData $output */ public function __invoke(NormalOutputData $output): void { $viewModel = new ViewModel( $output->taskId(), $output->taskName(), $output->taskNote(), ); $this->respond($this->view->make('web::tasks.create', compact(['viewModel']))); } } Interactor ViewModel
  68. 68. Presenter • 68 <?php namespace MyAppWebPresentersCreateInbox; use IlluminateViewFactory; use MyAppWebPresentersPresenter as BasePresenter; use MyAppComponentsTasksUseCasesCreateInboxNormalOutputData; use MyAppComponentsTasksUseCasesCreateInboxNormalOutputBoundary; /** * Class CreateInboxPresenter * @package MyAppWebPresenters */ final class Presenter extends BasePresenter implements NormalOutputBoundary { // 中略 /** * @param NormalOutputData $output */ public function __invoke(NormalOutputData $output): void { $viewModel = new ViewModel( $output->taskId(), $output->taskName(), $output->taskNote(), ); $this->respond($this->view->make('web::tasks.create', compact(['viewModel']))); } } View ※ Controller 
 void ……🙇
  69. 69. • • ServiceProvider 69
  70. 70. 70 <?php namespace AppProviders; use IlluminateSupportServiceProvider; use MyAppComponentsTasksUseCasesCreateInboxInputBoundary; use MyAppComponentsTasksUseCasesCreateInboxInteractor; use MyAppComponentsTasksUseCasesCreateInboxNormalOutputBoundary; use MyAppComponentsTasksUseCasesIdProvider; use MyAppComponentsTasksUseCasesTaskRepository as TaskRepositoryInterface; use MyAppDatabaseRepositoriesAutoIncrementTaskIdProvider; use MyAppDatabaseRepositoriesTaskRepository; use MyAppWebPresentersCreateInboxPresenter; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { $this->app->bind(IdProvider::class, AutoIncrementTaskIdProvider::class); $this->app->bind(TaskRepositoryInterface::class, TaskRepository::class); $this->app->bind(InputBoundary::class, Interactor::class); $this->app->bind(NormalOutputBoundary::class, Presenter::class); } /** * Bootstrap any application services. * * @return void */ public function boot() { // } }
  71. 71. Laravel • Laravel • DI ServiceContainer • 
 ServiceProvider • 
 71
  72. 72. @Laravel JP Conference 2019 https://speakerdeck.com/mikakane/laravel-package- development 72
  73. 73. 73
  74. 74. 74 packages 

  75. 75. 75 

  76. 76. 76 
 

  77. 77. 77 web routes 
 views
  78. 78. • 2 • 3 • 5 • 10 • 3 • 2 78
  79. 79. 79 UI
  80. 80. package foo; class Foo { Buz x; function doFoo() { x.process(); } } interface Buz { function process(); } 80 package bar; import foo; class Bar implements foo.Buz { function process() { // do something... } }
  81. 81. 🏁 • • • Laravel 
 81
  82. 82. • 2 • 3 • 5 • 10 • 3 • 2 82
  83. 83. / @okashoi @okashoi 83
  84. 84. • Hacker’s GATE 84
  85. 85. • Oysters 85
  86. 86. • Laravel JP Conference 2020 86https://conference2020.laravel.jp/
  87. 87. Ask the Speaker 
 😂 87

×