0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

The Clean Architecture - 忠実な実装例

0
Last updated at Posted at 2026-04-14

概要

Clean Coder Blog の同心円図 及び class 図を忠実に再現してみた実装例です。

The Clean Architecture

⚠️ 実装の重要ポイント

  1. Gatewaysの配置: 図に従い、Gateways(具象)は Green Layer に配置
  2. Red Layerの独立: Interactorが外部と通信するためのインターフェース(UserDataPort)を Red Layer で定義
  3. 依存性の逆転 (DIP): Green Layer(Gateway)が Blue Layer(DB)の具体的な型に依存しないよう、外部から処理を注入
  4. UIとPresenterの関係: UIは最外周(Blue)に位置し、Presenter(Green)からの通知を受け取って描画に専念

忠実な再現コード (Dart)

// =============================================================================
// 1. Entities (Yellow Layer) - Enterprise Business Rules
// =============================================================================
class UserEntity {
  final String id;
  final String name;
  UserEntity({required this.id, required this.name});
}

// =============================================================================
// 2. Use Cases (Red Layer) - Application Business Rules
// =============================================================================

/// [図の Use Case Input Port]
abstract interface class UseCaseInputPort {
  void execute();
}

/// [図の Use Case Output Port]
abstract interface class UseCaseOutputPort {
  void present(UserEntity entity);
}

/// [Data Port] - Red Layerで定義される境界
/// Interactorはこの抽象を介して外部(DB等)とやり取りする
abstract interface class UserDataPort {
  Future<UserEntity> loadData();
}

/// [図の Use Case Interactor]
class UseCaseInteractor implements UseCaseInputPort {
  final UseCaseOutputPort outputPort;
  final UserDataPort dataPort; // Red Layerの抽象にのみ依存

  UseCaseInteractor({required this.outputPort, required this.dataPort});

  @override
  Future<void> execute() async {
    // 1. DataPort(境界)を通じてデータを取得
    final entity = await dataPort.loadData();
    // 2. OutputPort(境界)を通じて結果を外へ渡す
    outputPort.present(entity);
  }
}

// =============================================================================
// 3. Interface Adapters (Green Layer) - Controllers, Presenters, Gateways
// =============================================================================

/// [図の Controller]
class UserController {
  final UseCaseInputPort inputPort;
  UserController(this.inputPort);
  void handle() => inputPort.execute();
}

/// [図の Presenter]
class UserPresenter implements UseCaseOutputPort {
  // UIが購読するためのコールバック
  void Function(String)? onViewModelUpdate;

  @override
  void present(UserEntity entity) {
    // UI用にデータを整形(ViewModelの作成)
    final viewModel = "Name: ${entity.name}";
    onViewModelUpdate?.call(viewModel);
  }
}

/// [図の Gateways]
/// Red Layerの抽象を実装し、外部(Blue)への橋渡しを行う
class UserGateway implements UserDataPort {
  // Blue Layerの具体的クラスに依存しないよう、関数として注入を受ける
  final Future<String> Function() fetchRawData;

  UserGateway(this.fetchRawData);

  @override
  Future<UserEntity> loadData() async {
    final raw = await fetchRawData();
    // 外部データをEntityに変換(Adapterとしての役割)
    return UserEntity(id: "1", name: raw);
  }
}

// =============================================================================
// 4. Frameworks & Drivers (Blue Layer) - UI, DB, Devices
// =============================================================================

/// [図の DB / External Interfaces]
class ExternalDatabase {
  Future<String> fetchRaw() async => "Data from PostgreSQL";
}

/// [図の UI]
class UserInterface {
  final UserController controller;
  final UserPresenter presenter;

  UserInterface(this.controller, this.presenter) {
    // Presenterからの通知を待ち受ける
    presenter.onViewModelUpdate = (vm) => _render(vm);
  }

  void _render(String vm) => print("UI Display: $vm");
  void onUserClick() => controller.handle();
}

// =============================================================================
// 5. Main Setup (Flow of Control)
// =============================================================================
void main() {
  // --- Blue Layer ---
  final db = ExternalDatabase();

  // --- Green Layer ---
  final presenter = UserPresenter();
  final gateway = UserGateway(db.fetchRaw); // Blueの処理を注入

  // --- Red Layer ---
  final interactor = UseCaseInteractor(
    outputPort: presenter,
    dataPort: gateway,
  );

  // --- Assembler (Finalizing Blue & Green) ---
  final controller = UserController(interactor);
  final ui = UserInterface(controller, presenter);

  // 実行開始 (UI -> Controller -> Interactor -> Gateway -> Presenter -> UI)
  ui.onUserClick();
}


ドキュメント補足:層の関係性まとめ

層 (Layer) 対応クラス 役割
Entities UserEntity ビジネスの核心となるデータ構造。
Use Cases Interactor, Ports アプリケーション固有のルール。Gatewaysの名前を直接使わずDataPortとして定義。
Interface Adapters Controller, Presenter, Gateway 変換担当。Redのインターフェースを実装し、外側と内側を繋ぐ。
Frameworks & Drivers UI, Database 詳細担当。フレームワーク、DB、表示デバイスの具体的な実装。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?