Teeda で Service を使う際の方針

TeedaでService を使う場合で、以下のどちらの案が良いか迷っています。

案1:Serviceメソッドの引数が増えた場合に、Pageクラスを引数にする方法

【特徴】

  • DxoはPageとServiceの両方で使うことができる
    • 更新系は、Serviceメソッド内でDxoを呼び出してEntityを取得する
    • 参照系は、Serviceメソッドの戻り値に対してDxoする
  • Serviceメソッドの引数にEntityは用いない


【メリット】

  • Serviceメソッドの引数がPageに統一されてシンプル


【デメリット】

  • 更新系の場合、Serviceメソッド内でDxoしてEntityを取得することになる。一方、参照系の場合のServiceメソッドの戻り値をEntityとするとPageクラス内でDxoすることになる。このような場合だと、1つ1つの処理はシンプルであるが、Dxoによるデータ変換処理がPageとServiceの2箇所で使われることになる。このため、コードに属人性がでてしまう。(プログラマーごとにソースコードがばらばらになり易い。)

  <<属人性が出るコードの例>>
   参照系でServiceの戻り値が次の3通りの選択肢が出てしまう。
   1.Serviceの戻り値をドメインモデルにする
   2.Serviceの戻り値をプレゼンテーションモデルにする
   3.Serviceの戻り値はvoidで、引数で受け取ったPageのプロパティにプレゼンテーションモデルをセットする
  ※ 上記の選択肢は1番としたいところだが、制約が無いためバラつきが出てしまう


案2:Serviceメソッドの引数が増えた場合に、PageクラスもしくはEntityを引数にする方法

【特徴】

  • Serviceメソッドの引数に、参照系はPageを渡し、更新系はEntityを渡す
  • Dxo処理はPageクラス内で行う。(ServiceでDxoしない)


【メリット】

  • Dxoをする箇所がPageだけになるので、クラス図的な観点でシンプル

  (Serviceメソッド内でDxoしない。)

  • Dxoする場所はPageと決めてしまうことで、Serviceメソッドの戻り値は必ずドメインモデルとなる


【デメリット】

  • Entityを引数に持つServiceメソッドとPageを引数に持つServiceメソッドが1つのServiceクラスに混在してしまう。




直感的にコーディングしやすいのが案1、アーキテクチャ的にシンプルなのが案2といったところでしょうか。うーん、迷います。

上記の内容のイメージが沸かない人は、サンプルコードを書いてみたので、ぜひ、見てみてください。

※ ただし、Pageクラスの画面項目のプロパティやsetter/getterは省略しています。


案1:Serviceメソッドの引数が増えた場合に、Pageクラスを引数にする方法

○「1.Serviceの戻り値をドメインモデルにする」の場合

class HogeRegisterPage {
  @Binding
  public HogeService hogeService;

  public String doRegister() {
    hogeService.register(this);
    return null;
  }
}

class HogeSearchPage {
  @Binding
  public HogeService hogeService;

  @Binding
  public HogeDxo hogeDxo;

  List<HogeDto> hogeItems;
  
  public String prerender() {
    List<Hoge> hoges = hogeService.findHoges(this);
    hogeItems = hogeDxo.convert(hoges);
    return null;
  }
}

// ServiceにDxoがDIされているので、ついつい、照会系の戻り値にプレゼンテーションモデルを
// 採用してしまうリスクがある
class HogeService {
  @Binding
  public HogeDao hogeDao;
  
  @Binding
  public HogeDxo hogeDxo;
    
  public List<Hoge> findHoges(HogeSearchPage page) {
    HogeConditionDto hogeConditionDto = new HogeConditionDto();
    ...    	// page.getFuga() などのgetterを使って検索条件をDtoにセット
    return hogeDao.findHoges(hogeConditionDto);
  }

  public void register(HogeRegisterPage page) {
    hogeDao.insert(hogeDxo.convert(page));
  }
}


○「2.Serviceの戻り値をプレゼンテーションモデルにする」の場合

class HogeRegisterPage {
  @Binding
  public HogeService hogeService;

  public String doRegister() {
    hogeService.register(this);
    return null;
  }
}

class HogeSearchPage {
  @Binding
  public HogeService hogeService;

  List<HogeDto> hogeItems;
  
  public String prerender() {
    hogeItems = hogeService.findHoges(this);
    return null;
  }
}

// Serviceの戻り値は、ドメインモデルであるべし、と考える人には受け入れがたいコード
class HogeService {
  @Binding
  public HogeDao hogeDao;

  @Binding
  public HogeDxo hogeDxo;
    
  public List<HogeDto> findHoges(HogeSearchPage page) {
    HogeConditionDto hogeConditionDto = new HogeConditionDto();
    ...    	// page.getFuga() などのgetterを使って検索条件をDtoにセット
    return hogeDxo.convert(hogeDao.findHoges(hogeConditionDto));
  }

  public void register(HogeRegisterPage page) {
    hogeDao.insert(hogeDxo.convert(page));
  }
}

○「3.Serviceの戻り値はvoidで、引数で受け取ったPageのプロパティにプレゼンテーションモデルをセットする」の場合

class HogeRegisterPage {
  @Binding
  public HogeService hogeService;

  public String doRegister() {
    hogeService.register(this);
    return null;
  }
}

class HogeSearchPage {
  @Binding
  public HogeService hogeService;

  List<HogeDto> hogeItems;
  
  public String prerender() {
    hogeService.findHoges(this);
    return null;
  }
}

class HogeService {
  @Binding
  public HogeDao hogeDao;

  @Binding
  public HogeDxo hogeDxo;
    
  // 一見シンプルだが、出力が分かりにくい
  //(どのpageのプロパティに値がセットされたが分からない)
  // メソッドの責務が増加すると単体テストが困難になる
  public void findHoges(HogeSearchPage page) {
    HogeConditionDto hogeConditionDto = new HogeConditionDto();
    ...    	// page.getFuga() などのgetterを使って検索条件をDtoにセット
    page.setHogeItems(hogeDxo.convert(hogeDao.findHoges(hogeConditionDto)));
  }

  public void register(HogeRegisterPage page) {
    hogeDao.insert(hogeDxo.convert(page));
  }
}

案2:Serviceメソッドの引数が増えた場合に、PageクラスもしくはEntityを引数にする方法

class HogeRegisterPage {
  @Binding
  public HogeService hogeService;

  @Binding
  public HogeDxo hogeDxo;

  public String doRegister() {
    hogeService.register(hogeDxo.convert(this));
    return null;
  }
}

class HogeSearchPage {
  @Binding
  public HogeService hogeService;

  @Binding
  public HogeDxo hogeDxo;

  List<HogeDto> hogeItems;
  
  public String prerender() {
    List<Hoge> hoges = hogeService.findHoges(this);
    hogeItems = hogeDxo.convert(hoges);
    return null;
  }
}

// Serviceメソッドの引数がPageとEntity が混在するのがイケてない。
// Serviceメソッドの引数に使うPageはプレゼンテーションモデル
// ではなくDtoの代替と見なす必要がある。
class HogeService {
  @Binding
  public HogeDao hogeDao;
  
  public List<Hoge> findHoges(HogeSearchPage page) {
    HogeConditionDto hogeConditionDto = new HogeConditionDto();
    ...    	
    return hogeDao.findHoges(hogeConditionDto);
  }

  public void register(Hoge hoge) {
    hogeDao.insert(hoge);
  }
}