データアクセスパターン
読了時間: 約15分
はじめに
データアクセスはPrism Architectureの重要な側面であり、ビジネスロジックと永続化の懸念事項を分離しながら、データの保存、取得、操作のメカニズムを提供します。この文書では、Prism Architectureのさまざまなデータアクセスパターン、それらが他の層とどのように相互作用するか、そしていつどのアプローチを使用するかを探ります。
Prism Architectureのデータアクセスパターンは、以下のいくつかの重要な考慮事項のバランスを取るように設計されています:
- ドメインロジックと永続化の詳細の分離
- 異なるアクセスパターン(CRUD操作 vs 複雑なクエリ)に対する最適化
- 従来のREST APIと共に、GraphQLのような最新のAPIのサポート
- パフォーマンス最適化を可能にしながらクリーンアーキテクチャを維持
コアデータアクセスの概念
複数のデータアクセスパターン
Prism Architectureは、異なるデータアクセスシナリオには異なるアプローチが必要であることを認識しています。すべてのデータアクセスを単一のパターンに強制するのではなく、相互補完的なパターンのスイートを提供します:
- リポジトリパターン: 従来のエンティティ中心のデータアクセス
- クエリサービスパターン: 複雑なデータ取得シナリオに最適化
- オペレーションパターン: 特定のGraphQL操作に焦点を当てたもの
これらのパターンは協力して、アーキテクチャの完全性を維持しながら包括的なデータアクセス機能を提供します。
包括的なAPIサポート
Prism Architectureは、GraphQLをファーストクラスの市民として設計されていますが、他のAPIテクノロジーも完全にサポートしています。この柔軟なアプローチにより、特定のニーズに最も適切なデータアクセステクノロジーを選択できます。
REST APIサポート
Prism Architectureは、REST APIの包括的なサポートを提供します:
- 標準リポジトリ実装: 従来のリポジトリ実装はREST APIと完全に連携
- RESTfulリソース編成: リポジトリはRESTリソースを中心に編成可能
- HTTPクライアント抽象化: コア層はHTTPクライアントの詳細を抽象化するインターフェースを定義
- REST固有のフォルダ構造: インフラストラクチャ層には通常、専用のRESTフォルダ構造が含まれます:
REST/
├── Clients/
│ ├── RESTClient.swift
│ └── ...
├── Endpoints/
│ ├── UserEndpoints.swift
│ └── ...
その他のサポートされるデータソース
GraphQLとREST以外にも、Prism Architectureはさまざまなデータソースをサポートしています:
- ローカルストレージ: SQLite、Core Data、Realmなどのテクノロジーを使用したデバイス上の永続化
- ファイルストレージ: ドキュメントベースまたはファイルシステムストレージのニーズ用
- WebSocket API: リアルタイム通信とストリーミングデータ用
- サードパーティSDK: ベンダー固有のAPIやサービスとの統合用
- レガシーSOAPまたはXML-RPCサービス: 古い企業システムとの統合用
ハイブリッドデータアクセスアプローチ
多くのアプリケーションは、複数のデータアクセスアプローチを組み合わせて使用することで恩恵を受けます:
- シンプルなCRUD用のREST: シンプルなエンティティ操作にRESTを使用
- 複雑なクエリ用のGraphQL: 複雑な関係を持つデータ集約的な画面にGraphQLを使用
- オフラインサポート用のローカルストレージ: オフライン機能を可能にするためのデバイス上ストレージの使用
- リアルタイム更新用のWebSocket: REST/GraphQLを補完するリアルタイムデータ用にWebSocketを使用
リポジトリパターン
リポジトリパターンとは?
リポジトリパターンは、ドメインモデルとデータマッピング層の間を仲介する抽象化です。それはデータベースクエリ、API呼び出し、または他のデータアクセスメカニズムの複雑さを隠しながら、ドメインオブジェクトにアクセスするためのコレクションのようなインターフェースを提供します。
目的と特徴
Prism Architectureにおけるリポジトリはいくつかの重要な目的を果たします:
- データアクセスの抽象化: データがどのように保存され、取得されるかの詳細を隠す
- ドメイン中心のインターフェイス: ドメインエンティティと概念の観点で動作する
- 関心の分離: データアクセスロジックをビジネスロジックから分離する
- テスト容易性: テスト用に簡単にモックや代替品を作成できる
リポジトリの主な特徴
- 集約ベースの編成: 通常、集約ルート(Aggregate Root)を中心に編成
- CRUD操作: エンティティのCreate、Read、Update、Delete操作に焦点
- ドメイン整合性: ドメインモデルの整合性と一貫性を維持
- コア層のインターフェイス: コア層でプロトコル/インターフェイスとして定義
- インフラストラクチャでの実装: 具象実装はインフラストラクチャ層にある
リポジトリを使用するタイミング
リポジトリは以下の場合の主要な選択肢です:
- 標準的なエンティティCRUD操作
- 集約境界を維持する必要がある操作
- ドメイン整合性が主要な関心事である場合
- エンティティ状態を変更するトランザクション操作
クエリサービスパターン
クエリサービスパターンとは?
クエリサービスパターンはPrism Architectureにおける特殊なデータアクセスパターンで、特にGraphQLを使用した複雑なデータ取得シナリオに最適化されています。集約境界に焦点を当てるリポジトリとは異なり、クエリサービスは複数の集約にまたがる可能性のある効率的なデータ取得のために設計されています。
目的と特徴
クエリサービスは、従来のリポジトリが最適に処理しない特定のニーズに対応します:
- 複雑なクエリ最適化: 複雑な関連データを効率的に取得
- 集約間取得: 単一の操作で複数の集約にまたがるデータを取得
- UI駆動のデータニーズ: ドメイン境界ではなくUIの要件を中心に編成
- GraphQL互換性: 取得するデータを正確に指定するGraphQLの能力を活用
クエリサービスの主な特徴
- ユースケースまたは機能編成: エンティティではなく、特定のユースケースや機能を中心に編成
- 読み取り最適化: 主にデータ取得に焦点を当て、変更はあまり行わない
- DTO戻り値: ドメインエンティティではなくデータ転送オブジェクト(DTO)を返す
- 平坦化された階層: 特定のUIニーズのためにエンティティ関係を平坦化する場合がある
- GraphQLの活用: 効率的なデータ取得のためのGraphQLの機能を利用
クエリサービスを使用するタイミング
クエリサービスは以下の場合に好ましい選択肢です:
- 複数の集約にまたがる複雑なデータ取得
- 特定のデータ要件を持つ画面や機能
- パフォーマンスが重要な読み取り操作
- GraphQLベースのアプリケーション
- 集約境界がデータアクセスを非効率にする場合
オペレーションパターン
オペレーションパターンとは?
Prism Architectureにおけるオペレーションパターンは、目的に特化したGraphQL操作を提供します。オペレーションは、クエリサービスまたはオーケストレーション層で直接使用できる、実行可能な細かいGraphQLクエリやミューテーションです。
目的と特徴
オペレーションはいくつかの特定の目的を果たします:
- GraphQLロジックのカプセル化: 特定のGraphQLクエリまたはミューテーションを含む
- 細かい機能性: 各オペレーションは通常、1つの特定のタスクを処理する
- 再利用性: 複数のクエリサービスによって組み合わせて再利用できる
- 型安全性: GraphQL操作に型安全なインターフェースを提供
オペレーションの主な特徴
- オペレーション固有の編成: オペレーションタイプ(クエリ vs ミューテーション)によって編成
- 強く型付けされている: GraphQLスキーマから型安全なインターフェースを生成
- 実装の詳細: 変数やフラグメントなどのGraphQL固有の懸念事項を処理
- 構成可能性: より複雑なオペレーションに構成できる
オペレーションを使用するタイミング
オペレーションは通常、次の場合に使用されます:
- クエリサービスのビルディングブロックとして
- シンプルでアトミックなGraphQL操作のため
- GraphQL操作の型安全性が重要な場合
- GraphQLクエリロジックの重複を避けるため
データマッピング
マッパーの役割
データマッパーはドメインエンティティとデータ転送オブジェクトまたは外部データ形式の間を変換します。これらはPrism Architectureのデータアクセス戦略における重要なコンポーネントです。
目的と特徴
マッパーはいくつかの重要な目的を果たします:
- 変換: ドメインエンティティとDTO/外部形式の間の変換
- 境界保護: 外部形式の変更からドメインモデルを保護
- 形式変換: シリアル化と逆シリアル化を処理
- 一貫性: 変換中のデータ整合性を確保
マッピングシナリオの種類
- エンティティからDTO: 外部通信用にドメインエンティティをDTOにマップ
- DTOからエンティティ: 入力データをドメインエンティティに変換
- エンティティから永続化モデル: 保存用にエンティティを変換
- 永続化モデルからエンティティ: 保存されたデータからエンティティを再構築
データアクセスパターン間の関係
これらのパターンは互いに排他的ではなく補完的であり、それぞれが異なるニーズに対応します:
リポジトリとクエリサービスの連携
- リポジトリ: 集約境界を尊重するエンティティCRUD操作を処理
- クエリサービス: 集約間の複雑なデータ取得を最適化
- 責任の分割: 書き込みはリポジトリ、読み取りはクエリサービス(一部のアプリケーションで)
クエリサービスとオペレーションの相互作用
- オペレーション: ビルディングブロック(GraphQLクエリ/ミューテーション)を提供
- クエリサービス: オペレーションを構成し、ユースケース重視のインターフェースを提供
- 階層アプローチ: オペレーションは低レベル、クエリサービスは高レベルの抽象化
異なる層でのデータアクセス
コア層
- リポジトリインターフェイスを定義
- クエリサービスインターフェイスを定義(それらがコア抽象化の一部である場合)
- リポジトリが扱うエンティティ定義を含む
ドメイン層
- コア層で定義されたインターフェイスを通じてリポジトリを使用
- 直接ではなくオーケストレーション層を介してリポジトリを使用
- ドメイン重視の操作を通じてドメイン整合性を維持
- 通常、クエリサービスと直接相互作用しない
オーケストレーション層
- リポジトリ操作を調整
- データ取得のためにクエリサービスを使用
- シンプルなシナリオでは直接オペレーションを使用することも
- 複数のリポジトリにまたがるトランザクションを管理
- ドメイン層とインフラストラクチャ層の間の仲介者として機能
インフラストラクチャ層
- リポジトリインターフェイスを実装
- クエリサービスを実装
- オペレーションを定義および実装
- データマッピングロジックを含む
- データソースとの通信を処理
実装ガイドライン
リポジトリの編成
Prism Architectureのリポジトリは、主に集約を中心に編成されています:
-
集約ベースのリポジトリ:
- 各リポジトリは完全な集約を管理
- リポジトリは集約ルートエンティティとインターフェース
- 集約内のすべてのエンティティはその集約のリポジトリを通じてアクセス
-
独立エンティティリポジトリ:
- どの集約の一部でもない(または自身が集約ルートである)エンティティは専用のリポジトリを持つことができる
- これらのリポジトリはこれらのスタンドアローンエンティティの永続化操作を処理
クエリサービスの編成
リポジトリとは異なり、クエリサービスは集約境界に厳密に従う必要はありません:
-
ユースケースまたは機能ベースの編成:
- クエリサービスは特定のユースケースや機能を中心に編成
- 特定の画面やコンポーネントに必要なデータを効率的に取得するように設計
- 特定のデータアクセスパターンに最適化できる
-
集約間クエリ:
- クエリサービスは単一の操作で複数の集約にまたがるデータを取得可能
- 複雑なデータクエリを効率的に構成するGraphQLの能力を活用
- このアプローチにより、複数の別々のリポジトリ呼び出しの必要性が減少
オペレーションの編成
オペレーションは通常、その目的と主に扱うエンティティによって編成されます:
-
クエリ vs ミューテーションの分離:
- クエリオペレーションとミューテーションオペレーションは分離
- これはその目的の根本的な違いを反映している
-
エンティティ重視のグループ化:
- オペレーションは多くの場合、扱う主要なエンティティでグループ化
- 例えば、UserQueries.swiftはユーザーに関連するすべてのクエリオペレーションを含む可能性がある
特殊なデータアクセスパターン
読み取り専用リポジトリ
アプリケーションを通じて変更されないエンティティやプロジェクションの場合:
- 取得操作に排他的に焦点
- 多くの場合、参照データや歴史的記録に使用
- 読み取りパフォーマンスのために最適化可能
- 効率性のために一部のドメイン制約をバイパスすることも
イベントソースリポジトリ
イベントソーシングを使用するドメインの場合:
- エンティティ状態ではなくイベントストリームを保存および取得
- イベント履歴からエンティティを再構築
- 時間的クエリ(特定の時点でのエンティティ状態)をサポート
- 読み取り操作のためにCQRSとよく組み合わせる
GraphQL最適化クエリサービス
GraphQLを使用するアプリケーションの場合:
- 動的クエリ構成をサポート
- ネストされた関連エンティティの取得を効率的に処理
- GraphQL型とドメインエンティティ間をマップ
- バッチ処理とデータローダーパターンによるN+1クエリ問題に対処
クエリ最適化パターン
効率的なデータアクセスはアプリケーションのパフォーマンスにとって重要です。Prism Architectureはクエリ最適化のためにいくつかのパターンを推奨しています:
プロジェクションクエリ
エンティティ全体ではなく、必要なデータフィールドのみを取得:
- データ転送オーバーヘッドを削減
- オブジェクトマッピングの複雑さを最小化
- 特定のビューモデルやDTOをサポート
- 読み取り専用シナリオでドメイン制約をバイパス可能
バッチロード
単一の操作で複数の関連エンティティを処理:
- N+1クエリ問題を解決
- データベースの往復を削減
- アプリケーション全体のパフォーマンスを向上
- GraphQLシナリオでは特に重要
ページネーション
大きな結果セットを効率的に管理:
- 一貫した結果のためのカーソルベースのページネーション
- シンプルさのためのオフセットベースのページネーション
- パフォーマンスのためのキーセットページネーション
- 合計結果と利用可能なページに関するメタデータを含む
キャッシング
戦略的なキャッシングでパフォーマンスを劇的に向上:
- 頻繁にアクセスされるデータのエンティティキャッシング
- 高コストな操作のクエリ結果キャッシング
- ドメインイベントに合わせたキャッシュ無効化戦略
- マルチレベルキャッシング(メモリ、分散など)
データアクセスコンポーネントのテスト
リポジトリのテスト
-
モックを使用した単体テスト:
- 実際のデータアクセスなしでリポジトリを使用するビジネスロジックをテスト
- ドメインロジックを分離するためにリポジトリインターフェースをモック
- 異なるリポジトリの応答に対する正しい動作に焦点
-
統合テスト:
- テストデータベースに対する実際のリポジトリ実装をテスト
- 正しい永続化と取得を検証
- エラー処理とエッジケースをテスト
クエリサービスのテスト
-
単体テスト:
- マッピングロジックとクエリ構成をテスト
- 基盤となるGraphQLオペレーションをモック
- 結果の正しい変換を検証
-
統合テスト:
- テストGraphQLエンドポイントに対してテスト
- 複雑なクエリシナリオが期待通りに機能することを検証
- エラー処理とパフォーマンスをテスト
オペレーションのテスト
-
単体テスト:
- オペレーション変数が正しく構築されることを検証
- レスポンス解析をテスト
- エラー処理をチェック
-
統合テスト:
- テストGraphQLエンドポイントに対してオペレーションを実行
- レスポンス処理を検証
- さまざまな入力シナリオでテスト
避けるべき一般的なアンチパターン
リポジトリアンチパターン
- リポジトリ汚染: リポジトリに属さないメソッドを追加する
- 漏れた抽象化: リポジトリインターフェースを通じて永続化の詳細を公開する
- 過度の一般化: あまりにも一般的すぎるリポジトリを作成する
- 不十分な抽象化: データソースに対する十分な抽象化を提供しない
クエリサービスアンチパターン
- クエリサービス内のドメインロジック: ドメインルールとデータ取得を混在させる
- 冗長なマッピング: 類似タイプ間の不必要なマッピング
- 画面ごとに1つのクエリサービス: あまりにも細かいクエリサービスを作成しすぎる
- データを変更するクエリサービス: データ変更にクエリサービスを使用する
オペレーションアンチパターン
- モノリシックなオペレーション: あまりにも多くのデータを取得するオペレーション
- オペレーション内のビジネスロジック: ビジネスルールとGraphQLオペレーションを混在させる
- 冗長なオペレーション: わずかな違いで類似したオペレーションを作成する
- 不十分なエラー処理: GraphQLエラーを適切に処理しない
同じアプリケーションでGraphQLとRESTを処理する
多くのアプリケーションはGraphQLとREST APIの両方をサポートする必要があるかもしれません。Prism Architectureは以下を通じてこれをサポートします:
- 共有抽象化: 可能な限り共通のインターフェースを使用し、各APIタイプの実装を持つ
- 適切なAPI選択: 複雑な関連データニーズにはGraphQLを、シンプルなCRUD操作にはRESTを使用
- GraphQL用クエリサービス: データ取得を最適化するGraphQL特化のクエリサービスを作成
- REST用標準リポジトリ: REST APIに従来のリポジトリ実装を使用
- API固有フォルダ: コードを整理するためにGraphQLとREST実装用の別々のフォルダを維持
実世界のシナリオ
シナリオ1: Eコマース商品カタログ
リポジトリアプローチ:
- 商品のCRUD操作用の
ProductRepository
- カテゴリ管理用の
CategoryRepository
- 厳格な集約境界を維持
クエリサービスアプローチ:
- カテゴリ、フィルター、画像を含む商品を効率的に取得するための
ProductCatalogQueryService
- 商品リストと詳細画面向けに最適化
- 必要なデータのみを含む平坦化されたDTOを返す
使用するオペレーション:
- 特定の商品関連GraphQLオペレーションを含む
ProductQueries.swift
- リスト表示、詳細表示、検索結果などのオペレーション
シナリオ2: ソーシャルメディアフィード
リポジトリアプローチ:
- 投稿の作成と更新用の
PostRepository
- ユーザープロフィール管理用の
UserRepository
- コメント管理用の
CommentRepository
クエリサービスアプローチ:
- 投稿、コメント、ユーザー情報を含むフィードの取得に最適化された
FeedQueryService
- 表示準備ができたフィードアイテムを返す
使用するオペレーション:
- 異なるフィードタイプのオペレーションを含む
FeedQueries.swift
- 投稿の作成、いいねなどのアクション用の
PostMutations.swift
結論
Prism Architectureにおけるデータアクセスは、補完的なパターンを通じて現代のアプリケーションの多様なニーズに対応するように設計されています。リポジトリはドメイン境界を尊重するエンティティ重視の操作のためのクリーンな抽象化を提供します。クエリサービスは、特にGraphQLを使用した複雑なデータ取得シナリオを最適化します。オペレーションは細かい、再利用可能なGraphQL機能を提供します。
Prism Architectureは、GraphQL、REST、ローカルストレージ、WebSocketなど、幅広いデータアクセステクノロジーをサポートしています。この柔軟性により、一貫したアーキテクチャパターンを維持しながら、特定のデータアクセスニーズに最も適したテクノロジーを選択できます。
各パターンをいつ使用するか、そしてそれらがどのように協力するかを理解することで、クリーンアーキテクチャを維持しながらパフォーマンスと開発者エクスペリエンスを最適化するデータアクセス層を作成できます。これらのパターンにより、アプリケーションは従来のアーキテクチャアプローチと最新のテクノロジーの両方の強みを活用できます。
これらのパターンは競合する選択肢ではなく、異なるシナリオに特化した補完的なツールであることを覚えておいてください。重要なのは、特定のデータアクセス要件に基づいて適切に使用することです。
次のステップ
- ユースケースとビジネスロジック - リポジトリとクエリサービスがユースケース内でどのように使用されるかを確認
- GraphQL統合 - GraphQLデータアクセスの特殊なパターンを詳しく探索
- インフラストラクチャ実装 - 特定のテクノロジーでこれらのパターンを実装する方法を学ぶ
- ドメインサービス - ドメインサービスがリポジトリとどのように相互作用するかを理解