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

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

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. Advance Development
    Clean architecture with Java
    Clean
    Masanobu Naruse

    View Slide

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

    View Slide

  3. 3
    キャッチコピー

    View Slide

  4. 4
    APIはできてないし

    View Slide

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

    View Slide

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

    View Slide

  7. 7
    よろしく!

    View Slide

  8. 8

    View Slide

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

    View Slide

  10. 10

    View Slide

  11. 11

    View Slide

  12. 12
    10月 11月 12月 1月

    View Slide

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

    View Slide

  14. 14
    DB
    API

    View Slide

  15. 15
    DB
    API

    View Slide

  16. 16
    DB
    API

    View Slide

  17. 17
    理想

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  21. 21
    現実

    View Slide

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

    View Slide

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

    View Slide

  24. 24
    もうひとつの問題

    View Slide

  25. 25

    View Slide

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

    View Slide

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

    View Slide

  28. 28
    できましたー

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  33. 33
    試行錯誤

    View Slide

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

    View Slide

  35. 35
    現実

    View Slide

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

    View Slide

  37. 37

    View Slide

  38. 38
    ありました

    View Slide

  39. 39
    Goal

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. 44

    View Slide

  45. 45
    この図の前に

    View Slide

  46. 46
    まずはこの図

    View Slide

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

    View Slide

  48. 48

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  54. 54

    View Slide

  55. 55

    View Slide

  56. 56

    View Slide

  57. 57

    View Slide

  58. 58

    View Slide

  59. 59

    View Slide

  60. 60

    View Slide

  61. 61

    View Slide

  62. 62

    View Slide

  63. 63

    View Slide

  64. 64

    View Slide

  65. 65
    In Software

    View Slide

  66. 66

    View Slide

  67. 67

    View Slide

  68. 68

    View Slide

  69. 69

    View Slide

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

    View Slide

  71. 71

    View Slide

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

    View Slide

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

    View Slide

  74. 74

    View Slide

  75. 75

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  80. 80

    View Slide

  81. 81

    View Slide

  82. 82
    Enterprise Business Rules
    Entities

    View Slide

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

    View Slide

  84. 84

    View Slide

  85. 85
    Application Business Rules
    Use Cases

    View Slide

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

    View Slide

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

    View Slide

  88. 88

    View Slide

  89. 89
    Interface Adapters

    View Slide

  90. 90
    Controllers Presenters Gateways
    Interface Adapters

    View Slide

  91. 91
    Controllers Presenters Gateways
    Interface Adapters

    View Slide

  92. 92
    Controllers Presenters Gateways
    Interface Adapters

    View Slide

  93. 93
    Controllers Presenters Gateways
    Interface Adapters

    View Slide

  94. 94
    Controllers Presenters Gateways
    Interface Adapters
    Mock

    View Slide

  95. 95

    View Slide

  96. 96
    Controller
    Presenter

    View Slide

  97. 97
    Controller
    Presenter

    View Slide

  98. 98
    Controller
    Presenter

    View Slide

  99. 99
    Controller
    Presenter

    View Slide

  100. 100
    Controller
    Presenter
    Use Case
    Interactor

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  107. 107
    Frameworks & Drivers

    View Slide

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

    View Slide

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

    View Slide

  110. 110
    依存の方向

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  124. 124

    View Slide

  125. 125

    View Slide

  126. 126

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  130. 130

    View Slide

  131. 131

    View Slide

  132. 132
    DS : Data Structure

    View Slide

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

    View Slide

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

    View Slide

  135. 135

    View Slide

  136. 136

    View Slide

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

    View Slide

  138. 138

    View Slide

  139. 139

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  144. 144

    View Slide

  145. 145

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  149. 149

    View Slide

  150. 150

    View Slide

  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

    View Slide

  152. 152

    View Slide

  153. 153

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  157. 157

    View Slide

  158. 158

    View Slide

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

    View Slide

  160. 160

    View Slide

  161. 161

    View Slide

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

    View Slide

  163. 163

    View Slide

  164. 164

    View Slide

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

    View Slide

  166. 166

    View Slide

  167. 167

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  171. 171

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  188. 188
    先行開発?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  194. 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 を採用していないときとかに

    View Slide

  195. 195
    Good ?

    View Slide

  196. 196
    Good ?

    View Slide

  197. 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 ?????????????;
    }
    }

    View Slide

  198. 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.

    View Slide

  199. 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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  206. 206

    View Slide

  207. 207
    Create Controller

    View Slide

  208. 208
    Create Controller
    Create Input Data

    View Slide

  209. 209
    Create Controller
    Create Input Data
    Create Input Boundary

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  220. 220
    Presenter
    捨てました

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  226. 226
    Message Bus

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  230. 230
    MessageBus

    View Slide

  231. 231
    MessageBus
    UserGetList
    InputData

    View Slide

  232. 232
    MessageBus
    UserGetList
    InputData
    UserGetList
    OutputData

    View Slide

  233. 233
    MessageBus
    UserGetList
    InputData

    View Slide

  234. 234
    MessageBus
    UserGetList
    InputData
    UserGetList
    OutputData

    View Slide

  235. 235
    MessageBus
    UserGetList
    InputData
    UserGetList
    OutputData
    UserGetList
    Interactor

    View Slide

  236. 236
    MessageBus
    UserGetList
    InputData
    UserGetList
    OutputData
    StubUserGetList
    Interactor

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  245. 245

    View Slide

  246. 246

    View Slide

  247. 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 設定

    View Slide

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

    View Slide

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

    View Slide

  250. 250
    実際の開発の流れ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  257. 257
    API 用意できたよ!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  261. 261
    終わりに

    View Slide

  262. 262

    View Slide

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

    View Slide

  264. 264

    View Slide

  265. 265

    View Slide

  266. 266

    View Slide

  267. 267

    View Slide

  268. 268

    View Slide

  269. 269

    View Slide

  270. 270

    View Slide

  271. 271
    Freedom

    View Slide

  272. 272
    Expedition

    View Slide

  273. 273

    View Slide

  274. 274

    View Slide

  275. 275

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide