Slide 1

Slide 1 text

Advance Development Clean architecture with Java Clean Masanobu Naruse

Slide 2

Slide 2 text

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(); } }

Slide 3

Slide 3 text

3 キャッチコピー

Slide 4

Slide 4 text

4 APIはできてないし

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

7 よろしく!

Slide 8

Slide 8 text

8

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

10

Slide 11

Slide 11 text

11

Slide 12

Slide 12 text

12 10月 11月 12月 1月

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

14 DB API

Slide 15

Slide 15 text

15 DB API

Slide 16

Slide 16 text

16 DB API

Slide 17

Slide 17 text

17 理想

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

21 現実

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

24 もうひとつの問題

Slide 25

Slide 25 text

25

Slide 26

Slide 26 text

26 こんな感じでお願い

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

28 できましたー

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

30 やっぱ こっちで !?

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

32 デザインに必要なもの

Slide 33

Slide 33 text

33 試行錯誤

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

35 現実

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

37

Slide 38

Slide 38 text

38 ありました

Slide 39

Slide 39 text

39 Goal

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

44

Slide 45

Slide 45 text

45 この図の前に

Slide 46

Slide 46 text

46 まずはこの図

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

48

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

53 たとえばテレビゲーム

Slide 54

Slide 54 text

54

Slide 55

Slide 55 text

55

Slide 56

Slide 56 text

56

Slide 57

Slide 57 text

57

Slide 58

Slide 58 text

58

Slide 59

Slide 59 text

59

Slide 60

Slide 60 text

60

Slide 61

Slide 61 text

61

Slide 62

Slide 62 text

62

Slide 63

Slide 63 text

63

Slide 64

Slide 64 text

64

Slide 65

Slide 65 text

65 In Software

Slide 66

Slide 66 text

66

Slide 67

Slide 67 text

67

Slide 68

Slide 68 text

68

Slide 69

Slide 69 text

69

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

71

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

74

Slide 75

Slide 75 text

75

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

80

Slide 81

Slide 81 text

81

Slide 82

Slide 82 text

82 Enterprise Business Rules Entities

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

84

Slide 85

Slide 85 text

85 Application Business Rules Use Cases

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

88

Slide 89

Slide 89 text

89 Interface Adapters

Slide 90

Slide 90 text

90 Controllers Presenters Gateways Interface Adapters

Slide 91

Slide 91 text

91 Controllers Presenters Gateways Interface Adapters

Slide 92

Slide 92 text

92 Controllers Presenters Gateways Interface Adapters

Slide 93

Slide 93 text

93 Controllers Presenters Gateways Interface Adapters

Slide 94

Slide 94 text

94 Controllers Presenters Gateways Interface Adapters Mock

Slide 95

Slide 95 text

95

Slide 96

Slide 96 text

96 Controller Presenter

Slide 97

Slide 97 text

97 Controller Presenter

Slide 98

Slide 98 text

98 Controller Presenter

Slide 99

Slide 99 text

99 Controller Presenter

Slide 100

Slide 100 text

100 Controller Presenter Use Case Interactor

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

102 Controller Presenter Use Case Interactor Use Case Input Port Use Case Output Port < I > < I >

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

107 Frameworks & Drivers

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

110 依存の方向

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

114 Controller Presenter Use Case Interactor Use Case Input Port Use Case Output Port < I > < I >

Slide 115

Slide 115 text

115 Controller Presenter Use Case Interactor Use Case Input Port Use Case Output Port < I > < I > もっと細かく

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

121 サンプル ユーザ作成機能

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

124

Slide 125

Slide 125 text

125

Slide 126

Slide 126 text

126

Slide 127

Slide 127 text

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(); } } }

Slide 128

Slide 128 text

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(); } } }

Slide 129

Slide 129 text

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(); } } }

Slide 130

Slide 130 text

130

Slide 131

Slide 131 text

131

Slide 132

Slide 132 text

132 DS : Data Structure

Slide 133

Slide 133 text

133 DS : Data Structure public class UserAddInputData implements InputData { 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; } }

Slide 134

Slide 134 text

134 DTO (POJO) DS : Data Structure public class UserAddInputData implements InputData { 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; } }

Slide 135

Slide 135 text

135

Slide 136

Slide 136 text

136

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

138

Slide 139

Slide 139 text

139

Slide 140

Slide 140 text

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); } }

Slide 141

Slide 141 text

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); } }

Slide 142

Slide 142 text

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); } }

Slide 143

Slide 143 text

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); } }

Slide 144

Slide 144 text

144

Slide 145

Slide 145 text

145

Slide 146

Slide 146 text

146 public interface UserRepository { public void save(User user); public void remove(User user); public List findAll(); public Optional find(UserId id); }

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

149

Slide 150

Slide 150 text

150

Slide 151

Slide 151 text

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

Slide 152

Slide 152 text

152

Slide 153

Slide 153 text

153

Slide 154

Slide 154 text

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; } }

Slide 155

Slide 155 text

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; } }

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

157

Slide 158

Slide 158 text

158

Slide 159

Slide 159 text

159 public class UserAddOutputData implements OutputData { private String createdId; public UserAddOutputData(String createdId) { this.createdId = createdId; } public String getCreatedId() { return createdId; } }

Slide 160

Slide 160 text

160

Slide 161

Slide 161 text

161

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

163

Slide 164

Slide 164 text

164

Slide 165

Slide 165 text

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); } }

Slide 166

Slide 166 text

166

Slide 167

Slide 167 text

167

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

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() ); } }

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

171

Slide 172

Slide 172 text

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(); } } }

Slide 173

Slide 173 text

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(); } } }

Slide 174

Slide 174 text

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(); } } }

Slide 175

Slide 175 text

175 Flow of Control public interface UserAddUseCase { void handle(UserAddInputData inputData); }

Slide 176

Slide 176 text

176 Flow of Control Delegate public interface UserAddUseCase { void handle(UserAddInputData inputData); }

Slide 177

Slide 177 text

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); } }

Slide 178

Slide 178 text

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); } }

Slide 179

Slide 179 text

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); } }

Slide 180

Slide 180 text

180 Flow of Control public interface UserRepository { public void save(User user); public void remove(User user); public List findAll(); public Optional find(UserId id); }

Slide 181

Slide 181 text

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

Slide 182

Slide 182 text

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; } }

Slide 183

Slide 183 text

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); } }

Slide 184

Slide 184 text

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); } }

Slide 185

Slide 185 text

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); } }

Slide 186

Slide 186 text

186 Flow of Control public interface UserAddPresenter{ public void output(UserAddOutputData outputData); }

Slide 187

Slide 187 text

187 Flow of Control Delegate public interface UserAddPresenter{ public void output(UserAddOutputData outputData); }

Slide 188

Slide 188 text

188 先行開発?

Slide 189

Slide 189 text

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

Slide 190

Slide 190 text

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

Slide 191

Slide 191 text

191 例外のときの動作確認したい

Slide 192

Slide 192 text

192 public class StubUserAddInteractor implements UserAddUseCase { @Override public UserAddOutputData handle(UserAddInputData inputData) { throw new RuntimeException(); } }

Slide 193

Slide 193 text

193 データベースがまだ選定中だけど ロジックを組みたい

Slide 194

Slide 194 text

194 public class InMemoryUserRepository implements UserRepository { private Map 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 を採用していないときとかに

Slide 195

Slide 195 text

195 Good ?

Slide 196

Slide 196 text

196 Good ?

Slide 197

Slide 197 text

197 public class UserController extends Controller { public Result addInputSubmit(){ Form 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 ?????????????; } }

Slide 198

Slide 198 text

198 public class UserController extends Controller { public Result addInputSubmit(){ Form 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.

Slide 199

Slide 199 text

199 public class UserController extends Controller { public Result addInputSubmit(){ Form 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.

Slide 200

Slide 200 text

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

Slide 201

Slide 201 text

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

Slide 202

Slide 202 text

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

Slide 203

Slide 203 text

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

Slide 204

Slide 204 text

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; }

Slide 205

Slide 205 text

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

Slide 206

Slide 206 text

206

Slide 207

Slide 207 text

207 Create Controller

Slide 208

Slide 208 text

208 Create Controller Create Input Data

Slide 209

Slide 209 text

209 Create Controller Create Input Data Create Input Boundary

Slide 210

Slide 210 text

210 Create Controller Create Input Data Create Input Boundary Create Use Case Interactor

Slide 211

Slide 211 text

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

Slide 212

Slide 212 text

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

Slide 213

Slide 213 text

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

Slide 214

Slide 214 text

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

Slide 215

Slide 215 text

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

Slide 216

Slide 216 text

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

Slide 217

Slide 217 text

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

Slide 218

Slide 218 text

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

Slide 219

Slide 219 text

219 いろいろ考えたんですが

Slide 220

Slide 220 text

220 Presenter 捨てました

Slide 221

Slide 221 text

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); } } 素直に戻り値戻してます

Slide 222

Slide 222 text

222 Interactor がコールしてデータを渡すか 戻り値でデータを渡すかの違い

Slide 223

Slide 223 text

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

Slide 224

Slide 224 text

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

Slide 225

Slide 225 text

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

Slide 226

Slide 226 text

226 Message Bus

Slide 227

Slide 227 text

227 Command オブジェクト を作る

Slide 228

Slide 228 text

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

Slide 229

Slide 229 text

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

Slide 230

Slide 230 text

230 MessageBus

Slide 231

Slide 231 text

231 MessageBus UserGetList InputData

Slide 232

Slide 232 text

232 MessageBus UserGetList InputData UserGetList OutputData

Slide 233

Slide 233 text

233 MessageBus UserGetList InputData

Slide 234

Slide 234 text

234 MessageBus UserGetList InputData UserGetList OutputData

Slide 235

Slide 235 text

235 MessageBus UserGetList InputData UserGetList OutputData UserGetList Interactor

Slide 236

Slide 236 text

236 MessageBus UserGetList InputData UserGetList OutputData StubUserGetList Interactor

Slide 237

Slide 237 text

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

Slide 238

Slide 238 text

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 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)); } }

Slide 239

Slide 239 text

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 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)); } }

Slide 240

Slide 240 text

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

Slide 241

Slide 241 text

241 書く時に努力すべきとは言うものの

Slide 242

Slide 242 text

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

Slide 243

Slide 243 text

243 退屈なコードはプログラムに任せよう

Slide 244

Slide 244 text

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

Slide 245

Slide 245 text

245

Slide 246

Slide 246 text

246

Slide 247

Slide 247 text

247 Debug 用 DI 設定 public class ProductDependencyConfig implements DependencyConfig { private HashMap, Class extends UseCase extends I = new HashMap, 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 UseCase extends I = new HashMap, 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 設定

Slide 248

Slide 248 text

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

Slide 249

Slide 249 text

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 を構築

Slide 250

Slide 250 text

250 実際の開発の流れ

Slide 251

Slide 251 text

251 こんな感じでお願い

Slide 252

Slide 252 text

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

Slide 253

Slide 253 text

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

Slide 254

Slide 254 text

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

Slide 255

Slide 255 text

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

Slide 256

Slide 256 text

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

Slide 257

Slide 257 text

257 API 用意できたよ!

Slide 258

Slide 258 text

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

Slide 259

Slide 259 text

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

Slide 260

Slide 260 text

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

Slide 261

Slide 261 text

261 終わりに

Slide 262

Slide 262 text

262

Slide 263

Slide 263 text

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

Slide 264

Slide 264 text

264

Slide 265

Slide 265 text

265

Slide 266

Slide 266 text

266

Slide 267

Slide 267 text

267

Slide 268

Slide 268 text

268

Slide 269

Slide 269 text

269

Slide 270

Slide 270 text

270

Slide 271

Slide 271 text

271 Freedom

Slide 272

Slide 272 text

272 Expedition

Slide 273

Slide 273 text

273

Slide 274

Slide 274 text

274

Slide 275

Slide 275 text

275

Slide 276

Slide 276 text

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

Slide 277

Slide 277 text

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

Slide 278

Slide 278 text

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