先行開発!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/5oJeSrwztPg

# サンプルコード
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. 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(); } }
  2. 8.

    8

  3. 10.

    10

  4. 11.

    11

  5. 14.
  6. 15.
  7. 16.
  8. 17.
  9. 21.
  10. 25.

    25

  11. 35.
  12. 37.

    37

  13. 39.
  14. 44.

    44

  15. 48.

    48

  16. 54.

    54

  17. 55.

    55

  18. 56.

    56

  19. 57.

    57

  20. 58.

    58

  21. 59.

    59

  22. 60.

    60

  23. 61.

    61

  24. 62.

    62

  25. 63.

    63

  26. 64.

    64

  27. 66.

    66

  28. 67.

    67

  29. 68.

    68

  30. 69.

    69

  31. 71.

    71

  32. 74.

    74

  33. 75.

    75

  34. 80.

    80

  35. 81.

    81

  36. 84.

    84

  37. 88.

    88

  38. 95.

    95

  39. 115.

    115 Controller Presenter Use Case Interactor Use Case Input Port

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

    124

  41. 125.

    125

  42. 126.

    126

  43. 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(); } } }
  44. 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(); } } }
  45. 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(); } } }
  46. 130.

    130

  47. 131.

    131

  48. 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; } }
  49. 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; } }
  50. 135.

    135

  51. 136.

    136

  52. 138.

    138

  53. 139.

    139

  54. 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); } }
  55. 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); } }
  56. 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); } }
  57. 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); } }
  58. 144.

    144

  59. 145.

    145

  60. 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); }
  61. 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); }
  62. 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
  63. 149.

    149

  64. 150.

    150

  65. 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
  66. 152.

    152

  67. 153.

    153

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

    157

  72. 158.

    158

  73. 159.

    159 public class UserAddOutputData implements OutputData { private String createdId;

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

    160

  75. 161.

    161

  76. 163.

    163

  77. 164.

    164

  78. 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); } }
  79. 166.

    166

  80. 167.

    167

  81. 168.

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

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

    171

  84. 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(); } } }
  85. 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(); } } }
  86. 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(); } } }
  87. 176.
  88. 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); } }
  89. 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); } }
  90. 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); } }
  91. 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); }
  92. 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); }
  93. 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; } }
  94. 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); } }
  95. 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); } }
  96. 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); } }
  97. 190.

    190 public class StubUserAddInteractor implements UserAddUseCase { @Override public UserAddOutputData

    handle(UserAddInputData inputData) { return new UserAddOutputData("test"); } }
  98. 192.

    192 public class StubUserAddInteractor implements UserAddUseCase { @Override public UserAddOutputData

    handle(UserAddInputData inputData) { throw new RuntimeException(); } }
  99. 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 を採用していないときとかに
  100. 195.
  101. 196.
  102. 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 ?????????????; } }
  103. 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.
  104. 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.
  105. 201.

    201 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; }
  106. 202.

    202 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; @Inject private UserGetDetailUseCase userGetDetailUseCase; }
  107. 203.

    203 public class UserController extends Controller { @Inject private UserAddUseCase

    userAddUseCase; @Inject private UserGetListUseCase userGetListUseCase; @Inject private UserGetDetailUseCase userGetDetailUseCase; @Inject private UserUpdateUseCase userUpdateUseCase; }
  108. 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; }
  109. 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
  110. 206.

    206

  111. 211.
  112. 212.

    212 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor Create Output Data Create Output Boundary
  113. 213.

    213 Create Controller Create Input Data Create Input Boundary Create

    Use Case Interactor Create Output Data Create Output Boundary Create Presenter
  114. 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
  115. 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
  116. 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); } } 素直に戻り値戻してます
  117. 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
  118. 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)); } }
  119. 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)); } }
  120. 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
  121. 245.

    245

  122. 246.

    246

  123. 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 設定
  124. 248.

    248 public class StubUserGetListInteractor implements UserGetListUseCase { @Inject private JsonsLoader

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

    262

  127. 264.

    264

  128. 265.

    265

  129. 266.

    266

  130. 267.

    267

  131. 268.

    268

  132. 269.

    269

  133. 270.

    270

  134. 273.

    273

  135. 274.

    274

  136. 275.

    275