SwiftUIのプロジェクトにオニオンアーキテクチャを導入してみる
オニオンアーキテクチャ
-
ドメイン層(Domain)
ビジネスルールやエンティティなど、システムの核となる知識とロジックを保持。最も内側の層です。 -
ドメインサービス(Domain Service)
複数のエンティティや値オブジェクトをまたぐビジネスロジックを定義・実装。 -
アプリケーション層(Application / UseCase)
ドメイン層の操作を組み合わせてシステムの利用シナリオを実現します。 -
インフラ層(Infrastructure)
データベースや外部APIとの通信、リポジトリの具体的実装などの技術的詳細を扱います。最も外側に位置します。 -
UI層(Presentation)
ユーザーインターフェース部分。UXを提供し、外部の入力を受け付けてシステムに渡します。
[ UI層 ] ← ユーザーとのやりとり、最外側
↑
[ Infrastructure層 ] ← 技術的詳細の実装
↑
[ Application層 ] ← ユースケース表現
↑
[ Domain Service ]
↑
[ Domain層 ] ← ビジネスルールの中心、変更に強い
原則
依存関係の原則
- UIは外側の層なので、内側の層(UseCaseやDomain)に依存できる。
- 逆に、内側の層がUIに依存することは許されない。内側は外側の変更に影響されないよう保護される。
具体的なアクセス例
-
UI層からUseCase利用
UIはユーザー操作によりユースケース(例:Todoの追加・取得)を呼び出し、その結果を受け取って画面表示を更新する。 -
UI層からDomainのEntityにアクセス
ユースケースを通じて受け取ったEntityやDTOをUIが直接表示・操作できるが、ビジネスロジックの変更はUseCaseやDomain層で行う。
なので基本的にUIから扱えるのは UseCase のみ。取得したEntityを表示するなどはできるが、操作はできない。
ディレクトリ構成
MyTodoApp/
│
├── Domain/ // ドメイン層(ビジネスロジック・エンティティ)
│ ├── Entities/ // TodoItemなどのエンティティ
│ ├── Interfaces/ // リポジトリやサービスのプロトコル(インターフェース)
│ └── Services/ // ドメインサービス(ビジネスルール)
│
├── UseCase/ // ユースケース層(ビジネスロジックの実装)
│ └── TodoUseCase.swift // Todoに関するUseCaseの実装
│
├── Infrastructure/ // インフラ層(具体的なデータ保存/API等の実装)
│ └── Repository/ // リポジトリ具体実装(例:InMemoryTodoRepository、CoreData対応など)
│
├── Presentation/ // プレゼンテーション層(UI関連)
│ ├── Views/ // SwiftUIのView群
│ │ └── ContentView.swift
│ ├── ViewModels/ // ViewModel群(存在すれば)
│ └── Resources/ // 画像・ローカライズファイル等
│
└── Util/ // ユーティリティや拡張機能、共通コード
└── Extensions/ // Swift拡張など
こんな感じでやってみる。
Domain
システムの中心に位置し、ビジネスルールや業務ロジックそのものを表現・管理する非常に重要な層。ビジネスの核心となる知識やルールの集まり。
-
ドメインモデル(Entities, Value Objects)
業務の概念や実態を表現する「エンティティ」や「値オブジェクト」が含まれます。
例:顧客、注文、Todoアイテムなど。
単なるデータの集まりではなく、関連するビジネスルールや振る舞いも内部に持ちます。 -
ドメインサービス
複数のドメインモデルにまたがり、単一のエンティティで表現できないビジネスルールを実装します。
例:注文可能か判断するロジックや支払処理など。
インフラやUIに依存しない形で純粋なビジネスルールを提供。 -
ドメインイベント(必要に応じて)
ドメイン内での状態変化を表すイベント。
他のドメイン層や外部システムへの通知に用いる場合もあります。
Domain Service 層
プロトコルなどの抽象層。複数のエンティティや値オブジェクトをまたぐビジネスロジックを定義・実装。
Application 層
UseCase などを配置する。
-
役割
アプリケーション固有のユースケースやビジネスフローを実装し、ドメイン層の機能(ビジネスルール)を利用して具体的な処理を提供する。
ユースケースの調整、トランザクション制御、バッチ処理なども担当。 -
依存対象
内側のドメイン層に依存し、DomainモデルやDomainServiceを利用する。
インフラ層には依存しない。 -
例
Todoアプリの「タスク登録」「タスク完了処理」といったユースケースをここに実装。
Infrastructure 層
一番外側に位置する。
-
役割
実際の技術的詳細の実装を担う層。
データベースアクセス、外部API通信、ファイル操作、メッセージングなど、技術的な外部リソースの操作。 -
依存対象
ドメイン層やアプリケーション層のインターフェース(プロトコル)に依存して、具体的な実装を提供する。
内側のドメイン層には依存しない。 -
例
Todoの保存をデータベースに行うリポジトリ実装、API呼び出しクライアント等。
Repository
主にRepositoryの役割は外部データの読み書きをする。この実態はInfrastructure に配置するが、インタフェースはDomain 層におく。
外部データのアクセスは、外部APIへのアクセスやデータベースへのアクセスなどサーバーサイド内部で完結しないものは全て担当し、それ以外のロジックは実装しない。
Repositoryは扱うデータ、つまり Entity ごとに実装する。
https://recruit.gmo.jp/engineer/jisedai/blog/crean_architecture_server_side/
Presentation 層
外側のさらに外界に位置する。
UIなどが対象である。
UIから利用できるもの
UI層からはApplication層やInfrastructure層を利用(依存)することが可能ですが、オニオンアーキテクチャのルールに従って正しい順序や方法でアクセスします。
UI層からの利用について
-
UI層はApplication層に直接依存・利用するのが通常
ユースケースを実装したApplication層のサービスをUIが呼び出し、ビジネスロジックを実行します。 -
UI層はInfrastructure層に直接依存しないのが原則
Infrastructure層はApplication層やDomain層が定めるインターフェースを実装する技術的詳細の層なので、UIは通常Application層を介して間接的に影響を受けます。 -
依存方向は内側に向かうのみ
UI → Application → Domain ← Infrastructure(抽象インターフェース経由)
の一方向依存により、システムの柔軟性と保守性が保たれる。
疑問
なぜUI層から扱うのは Infrastructure 層ではなく、Application層なのか。
[ UI層 ] ← ユーザーとのやりとり、最外側
↑
[ Infrastructure層 ] ← 技術的詳細の実装
↑
[ Application層 ] ← ユースケース表現
↑
[ Domain Service ]
↑
[ Domain層 ] ← ビジネスルールの中心、変更に強
Infrastructure 層は具体的な実装を担う。
Application 層では、例えば Repository のプロトコルだけを持っていて、Infrastructure ではその Repository の中身実装が書かれている。
Application 層ではあくまでRepository のプロトコルを利用しているので依存は内側にのみ向いている。
このApplication層に具体的なRepository を渡すのが UI などから Infrastructure を注入する感じってことよね。
わかったようなわからんような難しい部分。とりあえずやってみる。
