プログラミング初心者がアーキテクトっぽく語る

見苦しい記事も多数あるとは思いますが訂正しつつブログと共に成長していければと思います

Clean Architectureで作る流れ

簡単なユースケースをClean Architectureで実装する流れをメモします。

まだポエムの段階です。 勉強目的ですので、「そもそも簡単なユースケースをClean Architectureで作る意味があるのか?」という疑問は忘れてください。


1. お絵かき(簡易設計)

  • 「入力 > ユースケースで実施すること > 出力」を簡単に絵に書く
  • すぐにユースケースを書き始めたいが、入力/出力を全く考慮していないと後々、ViewImpl実装中に足りないものが色々と出てきて、手戻りが多く発生して辛い思いをするので先に大雑把に検討しておく

2. UseCaseクラス、Gatewayクラス、Entityを作る

UseCaseクラス

  • Delivery非依存なロジック
  • 入力はInputDataで受け取る
    • InputDataはUseCaseのpublic methodにmethod injectionする
  • 出力はまだ返さない
    • OutputDataは後で作る
  • フィールドは使わない方がThreadSafe

UseCaseのTDDの流れ

  • 最初は周辺機能に関わるprivate methodの試験が多い
  • 次第に1つのpubic methodの試験に置き換わる
  • 濃いビジネスロジックがEntityとして切り出される
  • 入力をInputDataに置き換える

GatewayのTDD

  • Mockでテストする過程でData Access Interfaceが作成される


3. Presenterクラス、OutputDataクラス、ViewModelクラスを作る

OutputDataクラス

  • 出力仕様変更時にPresenterで影響が止まるよう、OutputDataには料理しやすい柔らかいデータを多めに入れておく
  • 単体試験は不要

ViewModelクラス

  • 出力するデータを出力する形式で格納
  • ViewableModelのArrayListにする
  • primitiveな型を使う
    • PythonのDictはやめた方がよい(Renameが難しい)
  • 単体試験は不要

Presenterクラス

  • UseCaseクラスからOutputDataを受け取る
    • presentメソッドにmethod injectionされる
  • presentメソッドでOutputDataをViewModelに変換してフィールドに保持する
    • UseCaseクラスがpresentメソッドを呼び出す
    • フィールドがThreadSafeでない気がする
  • getViewModel methodでViewModelを返す
    • Controllerが呼び出す
class FooPresenter:
  def __init__(self):
    self.view_model = {}

  def present(output_data):
    self.view_model = build_view_model(output_data)

  def get_view_model():
    return self.view_model

PresenterのTDD

  • presentメソッドを実行してOutputDataをViewModelに変換できるか確認
    • 1つのTestでpresentメソッドとgetViewModelメソッドの両方をテストする


4. UseCaseクラスとPresenterクラスを連携する

UseCaseクラス

  • 結果をOutputDataにまとめてPresenter#presentを呼び出す
class FooUseCase:
  def do_usecase(input_data, presenter):
    output_data = do_something_with_gateway_and_entity(input_data)
    presenter.present(output_data)

UseCaseのTDD

  • PresenterSpyの中のOutputDataで結果をチェックするように既存のテストを変更する
    • Spy用にOutputBoundaryのInterfaceが出来る


5. Viewを作る

ViewImpleクラス

  • ViewModelを元にViewImpl#generateViewで結果を出力する
    • ViewModelはmethod injectionでViewImpleへ渡す
  • ViewをInterfaceにする
  • platform依存なAPIが出ていてTDDが難しい部分


6. Controllerクラスを作る

Controllerクラス

  • constructor injectionでUseCase、Presenter、ViewImplを受け取る
  • handleメソッドでUseCase#doUsecase()、Presenter#getViewModel()、View#generateIvew()を呼び出す
class FooController:
  def __init__(self, usecase, presenter, view):
    self.usecase = usecase
    self.presenter = presenter
    self.view = view

  def handle(input_data):
    self.usecase.do_usecase(input_data, presenter)
    view_model = self.presenter.get_view_model()
    view.generate_view(view_model)

ControllerのTDD

  • UseCaseSpy、PresenterSpy、ViewSpyを使ってhandle()でUseCaseのpublic methodとPresenter#getViewModelが呼ばれたことを試験
  • 同様にhandle()でView#generateViewが呼ばれたことをSpyで試験