Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

nrs

May 18, 2019
Tweet

More Decks by nrs

Other Decks in Programming

Transcript

  1. 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(); } }
  2. 8

  3. 10

  4. 11

  5. 25

  6. 37

  7. 44

  8. 48

  9. 54

  10. 55

  11. 56

  12. 57

  13. 58

  14. 59

  15. 60

  16. 61

  17. 62

  18. 63

  19. 64

  20. 66

  21. 67

  22. 68

  23. 69

  24. 71

  25. 74

  26. 75

  27. 80

  28. 81

  29. 84

  30. 88

  31. 95

  32. 115 Controller Presenter Use Case Interactor Use Case Input Port

    Use Case Output Port < I > < I > もっと細かく
  33. 124

  34. 125

  35. 126

  36. 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(); } } }
  37. 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(); } } }
  38. 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(); } } }
  39. 130

  40. 131

  41. 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; } }
  42. 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; } }
  43. 135

  44. 136

  45. 138

  46. 139

  47. 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); } }
  48. 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); } }
  49. 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); } }
  50. 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); } }
  51. 144

  52. 145

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

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

    void remove(User user); public List<User> findAll(); public Optional<User> find(UserId id); }
  55. 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
  56. 149

  57. 150

  58. 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
  59. 152

  60. 153

  61. 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; } }
  62. 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; } }
  63. 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; } } エンティティはドメインオブジェクト ふるまいが少ないので少し紛らわしいけど データモデルとは異なる
  64. 157

  65. 158

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

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

  68. 161

  69. 163

  70. 164

  71. 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); } }
  72. 166

  73. 167

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

    uuid) { this.uuid = uuid; } public String getUuid() { return uuid; } }
  75. 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() ); } }
  76. 171

  77. 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(); } } }
  78. 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(); } } }
  79. 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(); } } }
  80. 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); } }
  81. 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); } }
  82. 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); } }
  83. 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); }
  84. 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); }
  85. 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; } }
  86. 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); } }
  87. 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); } }
  88. 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); } }
  89. 190 public class StubUserAddInteractor implements UserAddUseCase { @Override public UserAddOutputData

    handle(UserAddInputData inputData) { return new UserAddOutputData("test"); } }
  90. 192 public class StubUserAddInteractor implements UserAddUseCase { @Override public UserAddOutputData

    handle(UserAddInputData inputData) { throw new RuntimeException(); } }
  91. 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 を採用していないときとかに
  92. 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 ?????????????; } }
  93. 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.
  94. 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.
  95. 201 public class UserController extends Controller { @Inject private UserAddUseCase

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

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

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; @Inject private UserGetDetailUseCase userGetDetailUseCase; @Inject private UserUpdateUseCase userUpdateUseCase; }
  98. 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; }
  99. 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
  100. 206

  101. 212 Create Controller Create Input Data Create Input Boundary Create

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

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

    Use Case Interactor Create Output Data Create Output Boundary Create Presenter Create View Model
  104. 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
  105. 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); } } 素直に戻り値戻してます
  106. 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
  107. 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)); } }
  108. 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)); } }
  109. 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
  110. 245

  111. 246

  112. 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 設定
  113. 248 public class StubUserGetListInteractor implements UserGetListUseCase { @Inject private JsonsLoader

    jsonsLoader; @Override public UserGetListOutputData handle(UserGetListInputData inputData) { return jsonsLoader.generate(UserGetListOutputData.class); } } Debug 用の Stub モジュール
  114. 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 を構築
  115. 262

  116. 264

  117. 265

  118. 266

  119. 267

  120. 268

  121. 269

  122. 270

  123. 273

  124. 274

  125. 275