先行開発!Javaでクリーンアーキテクチャ / Clean architecture with java

E37b4344ef4bfd0fc4826c04971e54fb?s=47 nrs
May 18, 2019

先行開発!Javaでクリーンアーキテクチャ / Clean architecture with java

# 解説
Blog: https://nrslib.com/clean-architecture-with-java/
YouTube: https://youtu.be/BvzjpAe3d4g

# サンプルコード
https://github.com/nrslib/play-clean-java

# アジェンダ
* クリーンアーキテクチャの概要
* 実装例
* 課題と解決

# URL
HomePage: https://nrslib.com
Twitter: https://twitter.com/nrslib

E37b4344ef4bfd0fc4826c04971e54fb?s=128

nrs

May 18, 2019
Tweet

Transcript

  1. Advance Development Clean architecture with Java Clean Masanobu Naruse

  2. public class SelfIntroduction { public static void main(String[] args) {

    System.out.println("--------------------------"); System.out.println(" Introduction "); System.out.println("--------------------------"); String name = "Masanobu Naruse"; String[] job = {"Programmer", "Developer Evangelist"}; String company = "GMO Internet, Inc."; String[] team = {"Hosting Product Team", "Developer Relation Team"}; String twitter = "@nrslib"; String url = "https://nrslib.com"; Introduction introduction = new Introduction( name, job, company, team, twitter, url ); introduction.run(); } }
  3. 3 キャッチコピー

  4. 4 APIはできてないし

  5. 5 データベースは構築どころか 選定も済んでないけど

  6. 6 納期だけは決まってるんで

  7. 7 よろしく!

  8. 8

  9. 9 新規開発なら よくあること

  10. 10

  11. 11

  12. 12 10月 11月 12月 1月

  13. 13 フロント API 10月 11月 12月 1月

  14. 14 DB API

  15. 15 DB API

  16. 16 DB API

  17. 17 理想

  18. 18 フロント API 10月 11月 12月 1月

  19. 19 フロント API 10月 11月 12月 1月

  20. 20 フロント API 10月 11月 12月 1月

  21. 21 現実

  22. 22 フロント API 10月 11月 12月 1月

  23. 23 フロント API 10月 11月 12月 1月 もろもろの事情により 理想どおりに ならないことはある

  24. 24 もうひとつの問題

  25. 25

  26. 26 こんな感じでお願い

  27. 27 こんな感じでお願い わかりましたー

  28. 28 できましたー

  29. 29 うーん イメージ違うなぁ

  30. 30 やっぱ こっちで !?

  31. 31 やっぱ こっちで !? 想像だけで 最終形にたどり着くのは 難しい

  32. 32 デザインに必要なもの

  33. 33 試行錯誤

  34. 34 試行錯誤のために フロントの プロトタイプを

  35. 35 現実

  36. 36 現実 クリティカルパスを避け 先行して開発する方法は ないか

  37. 37

  38. 38 ありました

  39. 39 Goal

  40. 40 クリーンアーキテクチャの概要 メリット・デメリットの把握

  41. 41 クリーンアーキテクチャの概要 メリット・デメリットの把握 取捨選択できるようになる

  42. 42 Agenda 1. クリーンアーキテクチャ概要 2. 実装例 3. 課題と解決

  43. 43 Agenda 1. クリーンアーキテクチャ概要 2. 実装例 3. 課題と解決

  44. 44

  45. 45 この図の前に

  46. 46 まずはこの図

  47. 47 まずはこの図 ヘキサゴナルアーキテクチャ

  48. 48

  49. 49 ビジネスを中心に見立て それ以外を交換可能なものとする

  50. 50 ビジネスを中心に見立て それ以外を交換可能なものとする プラガプル

  51. 51 別名 ポートアンドアダプター

  52. 52 別名 ポートアンドアダプター ポート アダプター

  53. 53 たとえばテレビゲーム

  54. 54

  55. 55

  56. 56

  57. 57

  58. 58

  59. 59

  60. 60

  61. 61

  62. 62

  63. 63

  64. 64

  65. 65 In Software

  66. 66

  67. 67

  68. 68

  69. 69

  70. 70 アプリケーションは 入力デバイスのことを知らないし データストアがなにかも知らない

  71. 71

  72. 72 アプリケーションを中心に見据え ポート越しに通信を行い プラガプルを実現

  73. 73 アプリケーションを中心に見据え ポート越しに通信を行い プラガプルを実現 ビジネスロジックを点在させない

  74. 74

  75. 75

  76. 76 ビジネスロジックを中心に見据える

  77. 77 ビジネスロジックを中心に見据える ヘキサゴナルアーキテクチャ と同じ

  78. 78 ヘキサゴナルの外側の具体的な実装について 詳細に記載されているのが クリーンアーキテクチャ

  79. 79 ヘキサゴナルの外側の具体的な実装について 詳細に記載されているのが クリーンアーキテクチャ ※オニオンは内側

  80. 80

  81. 81

  82. 82 Enterprise Business Rules Entities

  83. 83 Enterprise Business Rules Entities いわゆるドメインモデルを実装した ドメインオブジェクト ドメインの概念を表現する

  84. 84

  85. 85 Application Business Rules Use Cases

  86. 86 Application Business Rules Use Cases アプリケーションレイヤー

  87. 87 Application Business Rules Use Cases アプリケーションレイヤー アプリケーションの目的である ドメインにおける問題を解決するため ドメインオブジェクトを束ねあげ

    ユースケースを実現する
  88. 88

  89. 89 Interface Adapters

  90. 90 Controllers Presenters Gateways Interface Adapters

  91. 91 Controllers Presenters Gateways Interface Adapters

  92. 92 Controllers Presenters Gateways Interface Adapters

  93. 93 Controllers Presenters Gateways Interface Adapters

  94. 94 Controllers Presenters Gateways Interface Adapters Mock

  95. 95

  96. 96 Controller Presenter

  97. 97 Controller Presenter

  98. 98 Controller Presenter

  99. 99 Controller Presenter

  100. 100 Controller Presenter Use Case Interactor

  101. 101 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port
  102. 102 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port < I > < I >
  103. 103 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port < I > < I >
  104. 104 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port < I > < I >
  105. 105 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port < I > < I >
  106. 106 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port < I > < I >
  107. 107 Frameworks & Drivers

  108. 108 Frameworks & Drivers 詳細なコード ギークなコード ビジネスロジックが これに依存しないようにする

  109. 109 Frameworks & Drivers 詳細なコード ギークなコード ビジネスロジックが これに依存しないようにする DIP

  110. 110 依存の方向

  111. 111 依存の方向は内向き 内側の変更は外に影響する 内側は外側を知らない 依存の方向

  112. 112 依存の方向は内向き 内側の変更は外に影響する 内側は外側を知らない ドメインロジックで Web とか DB とか UI

    とかを 扱わない 依存の方向
  113. 113 Agenda 1. クリーンアーキテクチャ概要 2. 実装例 3. 課題と解決

  114. 114 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port < I > < I >
  115. 115 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port < I > < I > もっと細かく
  116. 116 図 22-2 「Clean Architecture 達人に学ぶソフトウェアの構造と設計」(Robert C. Martin)より

  117. 117 図 22-2 「Clean Architecture 達人に学ぶソフトウェアの構造と設計」(Robert C. Martin)より

  118. 118 図 22-2 「Clean Architecture 達人に学ぶソフトウェアの構造と設計」(Robert C. Martin)より

  119. 119 図 22-2 「Clean Architecture 達人に学ぶソフトウェアの構造と設計」(Robert C. Martin)より 上下は逆で 用語も少し違うけど

    同じ
  120. 120 図 22-2 「Clean Architecture 達人に学ぶソフトウェアの構造と設計」(Robert C. Martin)より 上下は逆で 用語も少し違うけど

    同じ これに従って実装
  121. 121 サンプル ユーザ作成機能

  122. 122 1.コードを確認 2.処理の流れを確認

  123. 123 1.コードを確認 2.処理の流れを確認

  124. 124

  125. 125

  126. 126

  127. 127 public class UserController { @Inject private UserAddUseCase addUseCase; public

    void createUser(String name, String roleId){ UserRole role = convertRole(roleId); UserAddInputData inputData = new UserAddInputData(name, role); addUseCase.handle(inputData); } private UserRole convertRole(String roleId) { switch (roleId) { case "admin": return UserRole.ADMIN; case "member": return UserRole.MEMBER; default: throw new RuntimeException(); } } }
  128. 128 アプリケーションが要求するデータに 入力を変換 public class UserController { @Inject private UserAddUseCase

    addUseCase; public void createUser(String name, String roleId){ UserRole role = convertRole(roleId); UserAddInputData inputData = new UserAddInputData(name, role); addUseCase.handle(inputData); } private UserRole convertRole(String roleId) { switch (roleId) { case "admin": return UserRole.ADMIN; case "member": return UserRole.MEMBER; default: throw new RuntimeException(); } } }
  129. 129 アプリケーションが要求するデータに 入力を変換 public class UserController { @Inject private UserAddUseCase

    addUseCase; public void createUser(String name, String roleId){ UserRole role = convertRole(roleId); UserAddInputData inputData = new UserAddInputData(name, role); addUseCase.handle(inputData); } private UserRole convertRole(String roleId) { switch (roleId) { case "admin": return UserRole.ADMIN; case "member": return UserRole.MEMBER; default: throw new RuntimeException(); } } }
  130. 130

  131. 131

  132. 132 DS : Data Structure

  133. 133 DS : Data Structure public class UserAddInputData implements InputData<UserAddOutputData>

    { private final String userName; private final UserRole role; public UserAddInputData(String userName, UserRole role) { this.userName = userName; this.role = role; } public String getUserName() { return userName; } public UserRole getRole() { return role; } }
  134. 134 DTO (POJO) DS : Data Structure public class UserAddInputData

    implements InputData<UserAddOutputData> { private final String userName; private final UserRole role; public UserAddInputData(String userName, UserRole role) { this.userName = userName; this.role = role; } public String getUserName() { return userName; } public UserRole getRole() { return role; } }
  135. 135

  136. 136

  137. 137 public interface UserAddUseCase { void handle(UserAddInputData inputData); }

  138. 138

  139. 139

  140. 140 @Transactional public class UserAddInteractor implements UserAddUseCase { @Inject private

    UserRepository userRepository; @Inject private UserAddPresenter userAddPresenter; @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  141. 141 @Transactional public class UserAddInteractor implements UserAddUseCase { @Inject private

    UserRepository userRepository; @Inject private UserAddPresenter userAddPresenter; @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  142. 142 @Transactional public class UserAddInteractor implements UserAddUseCase { @Inject private

    UserRepository userRepository; @Inject private UserAddPresenter userAddPresenter; @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  143. 143 @Transactional public class UserAddInteractor implements UserAddUseCase { @Inject private

    UserRepository userRepository; @Inject private UserAddPresenter userAddPresenter; @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  144. 144

  145. 145

  146. 146 public interface UserRepository { public void save(User user); public

    void remove(User user); public List<User> findAll(); public Optional<User> find(UserId id); }
  147. 147 public interface UserRepository { public void save(User user); public

    void remove(User user); public List<User> findAll(); public Optional<User> find(UserId id); }
  148. 148 public interface UserRepository { public void save(User user); public

    void remove(User user); public List<User> findAll(); public Optional<User> find(UserId id); } Gateway
  149. 149

  150. 150

  151. 151 public class EBeanUserRepository implements UserRepository { @Override public void

    save(User user) { UserId id = user.getId(); models.User userData = Optional .ofNullable(models.User.find.byId(id.getValue())) .orElseGet(() ->createNew(id, user.getRole())); userData.setName(user.getName().getValue()); models.User.db().save(userData); } // ... private models.User createNew(UserId id, UserRole role) { models.User instance = new models.User(); instance.setId(id.getValue()); instance.setRole(convert(role)); return instance; } } サンプルは EBean 実装は SQL 直打ちでも Entity の再構築が できれば なんでも OK
  152. 152

  153. 153

  154. 154 public class User { private UserId id; private UserName

    name; private UserRole role; public User(UserId id, UserName name, UserRole role) { this.id = id; this.name = name; this.role = role; } public UserId getId() { return id; } public UserName getName() { return name; } public UserRole getRole() { return role; } public void changeName(UserName name) { this.name = name; } }
  155. 155 public class User { private UserId id; private UserName

    name; private UserRole role; public User(UserId id, UserName name, UserRole role) { this.id = id; this.name = name; this.role = role; } public UserId getId() { return id; } public UserName getName() { return name; } public UserRole getRole() { return role; } public void changeName(UserName name) { this.name = name; } } public class UserName { private String value; public UserName(String value) { if (value.length() < 3) throw new RuntimeException(); if (value.length() > 10) throw new RuntimeException(); this.value = value; } public String getValue() { return value; } }
  156. 156 public class User { private UserId id; private UserName

    name; private UserRole role; public User(UserId id, UserName name, UserRole role) { this.id = id; this.name = name; this.role = role; } public UserId getId() { return id; } public UserName getName() { return name; } public UserRole getRole() { return role; } public void changeName(UserName name) { this.name = name; } } public class UserName { private String value; public UserName(String value) { if (value.length() < 3) throw new RuntimeException(); if (value.length() > 10) throw new RuntimeException(); this.value = value; } public String getValue() { return value; } } エンティティはドメインオブジェクト ふるまいが少ないので少し紛らわしいけど データモデルとは異なる
  157. 157

  158. 158

  159. 159 public class UserAddOutputData implements OutputData { private String createdId;

    public UserAddOutputData(String createdId) { this.createdId = createdId; } public String getCreatedId() { return createdId; } }
  160. 160

  161. 161

  162. 162 public interface UserAddPresenter{ public void output(UserAddOutputData outputData); }

  163. 163

  164. 164

  165. 165 public class ConsoleUserAddPresenter implements UserAddPresenter { private ConsoleView consoleView

    = new ConsoleView(); public void output(UserAddOutputData outputData) { String userUuid = outputData.getCreatedId(); ConsoleViewModel viewModel = new ConsoleViewModel(userUuid); consoleView.showCreatedUserId(viewModel); } }
  166. 166

  167. 167

  168. 168 public class ConsoleViewModel { private String uuid; public ConsoleViewModel(String

    uuid) { this.uuid = uuid; } public String getUuid() { return uuid; } }
  169. 169 public class ConsoleViewModel { private String uuid; public ConsoleViewModel(String

    uuid) { this.uuid = uuid; } public String getUuid() { return uuid; } } public class ConsoleView { public void showCreatedUserId(ConsoleViewModel viewModel) { System.out.println("User created"); System.out.println("UserId: " + viewModel.getUuid() ); } }
  170. 170 1.コードを確認 2.処理の流れを確認

  171. 171

  172. 172 Flow of Control public class UserController { @Inject private

    UserAddUseCase addUseCase; public void createUser(String name, String roleId){ UserRole role = convertRole(roleId); UserAddInputData inputData = new UserAddInputData(name, role); addUseCase.handle(inputData); } private UserRole convertRole(String roleId) { switch (roleId) { case "admin": return UserRole.ADMIN; case "member": return UserRole.MEMBER; default: throw new RuntimeException(); } } }
  173. 173 Flow of Control Create public class UserController { @Inject

    private UserAddUseCase addUseCase; public void createUser(String name, String roleId){ UserRole role = convertRole(roleId); UserAddInputData inputData = new UserAddInputData(name, role); addUseCase.handle(inputData); } private UserRole convertRole(String roleId) { switch (roleId) { case "admin": return UserRole.ADMIN; case "member": return UserRole.MEMBER; default: throw new RuntimeException(); } } }
  174. 174 Flow of Control Call public class UserController { @Inject

    private UserAddUseCase addUseCase; public void createUser(String name, String roleId){ UserRole role = convertRole(roleId); UserAddInputData inputData = new UserAddInputData(name, role); addUseCase.handle(inputData); } private UserRole convertRole(String roleId) { switch (roleId) { case "admin": return UserRole.ADMIN; case "member": return UserRole.MEMBER; default: throw new RuntimeException(); } } }
  175. 175 Flow of Control public interface UserAddUseCase { void handle(UserAddInputData

    inputData); }
  176. 176 Flow of Control Delegate public interface UserAddUseCase { void

    handle(UserAddInputData inputData); }
  177. 177 Flow of Control @Transactional public class UserAddInteractor implements UserAddUseCase

    { // ... @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  178. 178 Flow of Control Create @Transactional public class UserAddInteractor implements

    UserAddUseCase { // ... @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  179. 179 Flow of Control Call @Transactional public class UserAddInteractor implements

    UserAddUseCase { // ... @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  180. 180 Flow of Control public interface UserRepository { public void

    save(User user); public void remove(User user); public List<User> findAll(); public Optional<User> find(UserId id); }
  181. 181 Flow of Control Delegate public interface UserRepository { public

    void save(User user); public void remove(User user); public List<User> findAll(); public Optional<User> find(UserId id); }
  182. 182 Flow of Control Access public class EBeanUserRepository implements UserRepository

    { @Override public void save(User user) { UserId id = user.getId(); models.User userData = Optional .ofNullable(models.User.find.byId(id.getValue())) .orElseGet(() ->createNew(id, user.getRole())); userData.setName(user.getName().getValue()); models.User.db().save(userData); } // ... private models.User createNew(UserId id, UserRole role) { models.User instance = new models.User(); instance.setId(id.getValue()); instance.setRole(convert(role)); return instance; } }
  183. 183 Flow of Control @Transactional public class UserAddInteractor implements UserAddUseCase

    { // ... @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  184. 184 Flow of Control Create @Transactional public class UserAddInteractor implements

    UserAddUseCase { // ... @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  185. 185 Flow of Control Call @Transactional public class UserAddInteractor implements

    UserAddUseCase { // ... @Override public void handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); UserAddOutputData outputData = new UserAddOutputData(uuid); userAddPresenter.output(outputData); } }
  186. 186 Flow of Control public interface UserAddPresenter{ public void output(UserAddOutputData

    outputData); }
  187. 187 Flow of Control Delegate public interface UserAddPresenter{ public void

    output(UserAddOutputData outputData); }
  188. 188 先行開発?

  189. 189 とりあえずフロント組みたい

  190. 190 public class StubUserAddInteractor implements UserAddUseCase { @Override public UserAddOutputData

    handle(UserAddInputData inputData) { return new UserAddOutputData("test"); } }
  191. 191 例外のときの動作確認したい

  192. 192 public class StubUserAddInteractor implements UserAddUseCase { @Override public UserAddOutputData

    handle(UserAddInputData inputData) { throw new RuntimeException(); } }
  193. 193 データベースがまだ選定中だけど ロジックを組みたい

  194. 194 public class InMemoryUserRepository implements UserRepository { private Map<String, User>

    db = new HashMap<>(); @Override public void save (User user) { User cloned = clone(user); db.put(cloned.getId().getValue(), cloned); } private User clone(User original) { return new User( original.getId(), original.getName(), original.getRole() ); } } EBean はもともとインメモリで動作するので EBean を採用していないときとかに
  195. 195 Good ?

  196. 196 Good ?

  197. 197 public class UserController extends Controller { public Result addInputSubmit(){

    Form<UserAddForm> requestForm = userAddForm.bindFromRequest(); if(requestForm.hasErrors()){ return badRequest( views.html.user.add.input.render(new UserAddInputViewModel(), requestForm)); } UserAddForm form = requestForm.get(); UserAddInputData inputData = new UserAddInputData(form.name, userRoleConverter.convert(form.roleId)); userAddUsecase.handle(inputData); return ?????????????; } }
  198. 198 public class UserController extends Controller { public Result addInputSubmit(){

    Form<UserAddForm> requestForm = userAddForm.bindFromRequest(); if(requestForm.hasErrors()){ return badRequest( views.html.user.add.input.render(new UserAddInputViewModel(), requestForm)); } UserAddForm form = requestForm.get(); UserAddInputData inputData = new UserAddInputData(form.name, userRoleConverter.convert(form.roleId)); userAddUsecase.handle(inputData); return ?????????????; } } MVC Framework needs a response.
  199. 199 public class UserController extends Controller { public Result addInputSubmit(){

    Form<UserAddForm> requestForm = userAddForm.bindFromRequest(); if(requestForm.hasErrors()){ return badRequest( views.html.user.add.input.render(new UserAddInputViewModel(), requestForm)); } UserAddForm form = requestForm.get(); UserAddInputData inputData = new UserAddInputData(form.name, userRoleConverter.convert(form.roleId)); userAddUsecase.handle(inputData); return ?????????????; } } MVC Framework needs a response. Presenter does not work well.
  200. 200 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; }
  201. 201 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; }
  202. 202 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; @Inject private UserGetDetailUseCase userGetDetailUseCase; }
  203. 203 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; @Inject private UserGetDetailUseCase userGetDetailUseCase; @Inject private UserUpdateUseCase userUpdateUseCase; }
  204. 204 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; @Inject private UserGetDetailUseCase userGetDetailUseCase; @Inject private UserUpdateUseCase userUpdateUseCase; @Inject private UserDeleteUseCase userDeleteUseCase; }
  205. 205 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; @Inject private UserGetDetailUseCase userGetDetailUseCase; @Inject private UserUpdateUseCase userUpdateUseCase; @Inject private UserDeleteUseCase userDeleteUseCase; } @Inject Hell
  206. 206

  207. 207 Create Controller

  208. 208 Create Controller Create Input Data

  209. 209 Create Controller Create Input Data Create Input Boundary

  210. 210 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor
  211. 211 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor Create Output Data
  212. 212 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor Create Output Data Create Output Boundary
  213. 213 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor Create Output Data Create Output Boundary Create Presenter
  214. 214 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor Create Output Data Create Output Boundary Create Presenter Create View Model
  215. 215 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor Create Output Data Create Output Boundary Create Presenter Create View Model Bored
  216. 216 Agenda 1. クリーンアーキテクチャ概要 2. 実装例 3. 課題と解決

  217. 217 課題 1. MVC Framework で Presenter 使えない問題 2. コントローラのフィールド増えすぎ問題

    3. 定義するもの多すぎ問題
  218. 218 課題 1. MVC Framework で Presenter 使えない問題 2. コントローラのフィールド増えすぎ問題

    3. 定義するもの多すぎ問題
  219. 219 いろいろ考えたんですが

  220. 220 Presenter 捨てました

  221. 221 @Transactional public class UserAddInteractor implements UserAddUseCase { @Inject private

    UserRepository userRepository; @Override public UserAddOutputData handle(UserAddInputData inputData) { String uuid = UUID.randomUUID().toString(); User user = new User( new UserId(uuid), new UserName(inputData.getUserName()), inputData.getRole() ); userRepository.save(user); return new UserAddOutputData(uuid); } } 素直に戻り値戻してます
  222. 222 Interactor がコールしてデータを渡すか 戻り値でデータを渡すかの違い

  223. 223 Interactor がコールしてデータを渡すか 戻り値でデータを渡すかの違い Controller がビューのために 若干 Fat になるが 最も重要なドメインロジックは守られる

  224. 224 課題 1. MVC Framework で Presenter 使えない問題 2. コントローラのフィールド増えすぎ問題

    3. 定義するもの多すぎ問題
  225. 225 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; @Inject private UserGetDetailUseCase userGetDetailUseCase; @Inject private UserUpdateUseCase userUpdateUseCase; @Inject private UserDeleteUseCase userDeleteUseCase; } @Inject Hell
  226. 226 Message Bus

  227. 227 Command オブジェクト を作る

  228. 228 Command オブジェクト を作る そのコマンドを 実行した結果を 期待している

  229. 229 Command オブジェクト を作る そのコマンドを 実行した結果を 期待している 処理の実体はなんでもよい

  230. 230 MessageBus

  231. 231 MessageBus UserGetList InputData

  232. 232 MessageBus UserGetList InputData UserGetList OutputData

  233. 233 MessageBus UserGetList InputData

  234. 234 MessageBus UserGetList InputData UserGetList OutputData

  235. 235 MessageBus UserGetList InputData UserGetList OutputData UserGetList Interactor

  236. 236 MessageBus UserGetList InputData UserGetList OutputData StubUserGetList Interactor

  237. 237 MessageBus UserGetList InputData UserGetList OutputData UserGetList Interactor UserAdd OutputData

    UserAdd Interactor UserAdd InputData
  238. 238 public class UserController extends Controller { @Inject private UseCaseBus

    bus; @Inject public UserController(FormFactory formFactory){ userAddForm = formFactory.form(UserAddForm.class); userUpdateForm = formFactory.form(UserUpdateForm.class); } public Result index() { UserGetListInputData inputData = new UserGetListInputData(); UserGetListOutputData outputData = bus.handle(inputData); List<UserViewModel> userViewModels = outputData.getUsers().stream() .map(user -> new UserViewModel(user.getId(), user.getName())) .collect(Collectors.toList()); UserGetListViewModel viewModel = new UserGetListViewModel(userViewModels); return ok(index.render(viewModel)); } }
  239. 239 public class UserController extends Controller { @Inject private UseCaseBus

    bus; @Inject public UserController(FormFactory formFactory){ userAddForm = formFactory.form(UserAddForm.class); userUpdateForm = formFactory.form(UserUpdateForm.class); } public Result index() { UserGetListInputData inputData = new UserGetListInputData(); UserGetListOutputData outputData = bus.handle(inputData); List<UserViewModel> userViewModels = outputData.getUsers().stream() .map(user -> new UserViewModel(user.getId(), user.getName())) .collect(Collectors.toList()); UserGetListViewModel viewModel = new UserGetListViewModel(userViewModels); return ok(index.render(viewModel)); } }
  240. 240 課題 1. MVC Framework で Presenter 使えない問題 2. コントローラのフィールド増えすぎ問題

    3. 定義するもの多すぎ問題
  241. 241 書く時に努力すべきとは言うものの

  242. 242 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor Create Output Data Create Output Boundary Create Presenter Create View Model Bored
  243. 243 退屈なコードはプログラムに任せよう

  244. 244 必要なモジュールとテスト用のモジュール 仮のテストデータを一括生成し DI の設定も同時に追加

  245. 245

  246. 246

  247. 247 Debug 用 DI 設定 public class ProductDependencyConfig implements DependencyConfig

    { private HashMap<Class<? extends InputData>, Class<? extends UseCase<? extends I = new HashMap<Class<? extends InputData>, Class<? extends UseCase<? ext { put(UserAddInputData.class, UserAddInteractor.class); put(UserDeleteInputData.class, UserDeleteInteractor.class); put(UserGetDetailInputData.class, UserGetDetailInteractor.class); put(UserGetListInputData.class, UserGetListInteractor.class); put(UserUpdateInputData.class, UserUpdateInteractor.class); } }; // ... } public class DebugDependencyConfig implements DependencyConfig { private HashMap<Class<? extends InputData>, Class<? extends UseCase<? extends I = new HashMap<Class<? extends InputData>, Class<? extends UseCase<? ext { put(UserAddInputData.class, StubUserAddInteractor.class); put(UserDeleteInputData.class, StubUserDeleteInteractor.class); put(UserGetDetailInputData.class, StubUserGetDetailInteractor.class); put(UserGetListInputData.class, StubUserGetListInteractor.class); put(UserUpdateInputData.class, StubUserUpdateInteractor.class); } }; // ... } Product 用 DI 設定
  248. 248 public class StubUserGetListInteractor implements UserGetListUseCase { @Inject private JsonsLoader

    jsonsLoader; @Override public UserGetListOutputData handle(UserGetListInputData inputData) { return jsonsLoader.generate(UserGetListOutputData.class); } } Debug 用の Stub モジュール
  249. 249 public class StubUserGetListInteractor implements UserGetListUseCase { @Inject private JsonsLoader

    jsonsLoader; @Override public UserGetListOutputData handle(UserGetListInputData inputData) { return jsonsLoader.generate(UserGetListOutputData.class); } } Debug 用の Stub モジュール { "users" : [ { "id" : "1", "name" : "testuser-1", "role" : 0 }, { "id" : "2", "name" : "testuser-2", "role" : 1 }, { "id" : "3", "name" : "testuser-3", "role" : 1 } ] } 同名の json 形式ファイルから データを読み出して OutputData を構築
  250. 250 実際の開発の流れ

  251. 251 こんな感じでお願い

  252. 252 こんな感じでお願い わかりましたー

  253. 253 画面から 必要なデータを検討

  254. 254 ツールでスキャフォールディング

  255. 255 パターン多くて難しいなー テストデータ用意するのも大変 フロントのプログラミング

  256. 256 パターン多くて難しいなー テストデータ用意するのも大変 だけどデバッグモードなら データ自由に書き換えれるや フロントのプログラミング { "users" : [

    { "id" : "1", "name" : "testuser-1", "role" : 0 } ] }
  257. 257 API 用意できたよ!

  258. 258 API 用意できたよ! お、待ってました

  259. 259 API 用意できたよ! お、待ってました 先行開発 クリーンアーキテクチャ

  260. 260 実プロダクトを模したサンプルコード https://github.com/nrslib/play-clean-java

  261. 261 終わりに

  262. 262

  263. 263 アーキテクチャの役目は?

  264. 264

  265. 265

  266. 266

  267. 267

  268. 268

  269. 269

  270. 270

  271. 271 Freedom

  272. 272 Expedition

  273. 273

  274. 274

  275. 275

  276. 276 依存関係逆転の原則 抽象は詳細に依存してはならない 詳細が抽象に依存すべきである

  277. 277 技術的な決定ではなく ビジネスを中心に システムを組み立てよう

  278. 278 Auther nrs HomePage https://nrslib.com Twitter @nrslib Sample Code https://github.com/nrslib/play-clean-java