📌

オニオンアーキテクチャのディレクトリ構成例とか依存関係とか (written with ChatGPT)

2023/04/25に公開

オニオンアーキテクチャでは、以下の4つの層があります。

ドメイン層 < アプリケーション層 < インターフェース層 < インフラストラクチャ層
  1. ドメイン層(Domain Layer):アプリケーションのビジネスルールと、エンティティ(モデル)を定義する層。
  2. アプリケーション層(Application Layer):ドメイン層のビジネスロジックを使用して、具体的なユースケースを実現する層。
  3. インターフェース層(Interfaces Layer):アプリケーションと外部システムとの間のデータ変換や通信を行うアダプターを定義する層。
  4. インフラ層(Infrastructure Layer):インフラストラクチャリソース(データベース、メッセージキュー、APIクライアントなど)を提供する層。

これらの層が互いに利用できるかどうかは以下の通りです:

  • ドメイン層:他の層に依存せず、独立しています。
  • アプリケーション層:ドメイン層の機能を利用できます。
  • インターフェース層:通常、他の層の機能を直接利用しませんが、アプリケーション層やドメイン層のインターフェースを実装することがあります。
  • インフラ層:インターフェース層の実装を提供するため、インターフェース層の機能を利用できます。

オニオンアーキテクチャでは、外側の層から内側の層への依存関係が許可されていますが、内側の層から外側の層への依存関係は避けるべきです。これにより、アプリケーションの構造が明確になり、変更や拡張が容易になります。
(依存関係 =どの層でどの層を使ってよいかの関係)

具体的には:

  • ドメイン層は他の層に依存しないため、他の層の機能を利用できません。
  • アプリケーション層はドメイン層とインターフェース層の機能を利用できます。
  • インターフェース層は通常、他の層の機能を直接利用しませんが、必要に応じてアプリケーション層やドメイン層のインターフェースを実装します。
  • インフラ層はインターフェース層の機能を利用できますが、アプリケーション層やドメイン層の機能を直接利用することは避けるべきです。ただし、インフラ層がアプリケーション層やドメイン層のインターフェースを実装する場合があります。

これらの層間の依存関係と利用可能性を理解することで、アプリケーションの構造が整理され、コードの可読性や保守性が向上します。また、これによりアプリケーションの変更や拡張が容易になり、新たな機能の追加や既存機能の改善がスムーズに行えるようになります。

オニオンアーキテクチャを適用する際は、各層が互いにどのように依存し、どの層の機能が利用できるのかを理解し、適切な設計を行うことが重要です。


interfaces層はインフラ層をすっきりさせるために用いる?


はい、その通りです。interfaces層は、インフラ層をすっきりさせるために用いることができます。オニオンアーキテクチャでは、アプリケーションの各層が独立した責任を持つことを目的としています。interfaces層を利用することで、アプリケーションの内部と外部システムとの間のコミュニケーションを明確に分離し、インフラ層の役割を限定することができます。

具体的には、interfaces層は以下の役割を担います。

  1. APIエンドポイントの定義と実装
  2. 外部サービスのアダプター
  3. データ変換

これに対して、インフラ層は以下の役割を担います。

  1. データベース接続とリポジトリ実装
  2. キャッシング、メッセージキュー、ストレージなどのインフラストラクチャリソースの管理
  3. 認証やロギングなどのクロスカッティング機能の実装

interfaces層を用いることで、インフラ層はアプリケーションの外部リソースやサービスとの具体的な実装に集中できます。これにより、各層の責任が明確になり、アプリケーション全体のスケーラビリティと保守性が向上します。ただし、プロジェクトの要件や規模に応じて、アーキテクチャを適切に調整することが重要です。

具体的な実装例として、以下にs3クライアントとs3アダプターのコードを示します。

project_root/
  ├─ cmd/
  │   └─ yourapp/
  │       └─ main.go
  ├─ internal/
  │   ├─ application/
  │   ├─ domain/
  │   ├─ infrastructure/
  │   │   └─ aws/
  │   │       └─ s3/
  │   │           └─ client.go
  │   └─ interfaces/
  │       └─ adapters/
  │           └─ aws/
  │               └─ s3/
  │                   └─ adapter.go
  └─ pkg/
      └─ utils/

infrastructure/aws/s3/client.go:

package s3

import (
    // import necessary packages
)

type S3Client struct {
    // define S3 client properties
}

func NewS3Client() *S3Client {
    // initialize and return a new S3 client
}

func (c *S3Client) UploadCSV(bucket, key string, data []byte) error {
    // implement low-level code to upload CSV data to S3
}

func (c *S3Client) DownloadCSV(bucket, key string) ([]byte, error) {
    // implement low-level code to download CSV data from S3
}

interfaces/adapters/aws/s3/adapter.go:

package s3

import (
    "project_root/internal/infrastructure/aws/s3"
    // import necessary packages
)

type S3Adapter struct {
    s3Client *s3.S3Client
}

func NewS3Adapter() *S3Adapter {
    // initialize and return a new S3 adapter
}

func (a *S3Adapter) UploadCSVToS3(bucket, key string, csvData [][]string) error {
    // convert CSV data to a byte array
    data := convertCSVDataToBytes(csvData)

    // call the S3 client to upload the data
    return a.s3Client.UploadCSV(bucket, key, data)
}

func (a *S3Adapter) DownloadCSVFromS3(bucket, key string) ([][]string, error) {
    // call the S3 client to download the data
    data, err := a.s3Client.DownloadCSV(bucket, key)
    if err != nil {
        return nil, err
    }

    // convert the byte array to CSV data
    csvData := convertBytesToCSVData(data)

    return csvData, nil
}

この例では、infrastructure層のs3クライアントが低レベルなs3操作を実行し、interfaces層のs3アダプターがアプリケーション内で扱われるデータ形式との変換を担当しています。これにより、アプリケーション全体からs3との通信を簡単かつ効果的に管理できます。

Discussion