web-dev-qa-db-ja.com

リポジトリレイヤー内でビジネスロジックを呼び出すことは許容されますか?

私はDDDを学んでいて、リポジトリレイヤーの内部からドメインモデルロジックを呼び出すのは大丈夫かどうか疑問に思っていますか?ビジネスロジックがリポジトリの一部として存在するわけではなく、リポジトリがドメインモデルのビジネスロジックを呼び出すだけです。

私はDDDについてたくさん読んでいますが、サービス層は通常、ドメインモデルのビジネスロジックを呼び出すものであるようです。リポジトリは、ドメインモデルの取得/永続化のみに関係する、単なるダムストレージの抽象概念です。ただし、Transactionをサービスレイヤーに公開せずにドメインモデルでアトミック更新を実行する必要がある場合は、リポジトリ内から更新を呼び出すことが唯一の方法です。

だから代わりに:

// -----Service------
// some_model = repo.Get(...)
// some_model.Update(...)
// repo.Save(some_model)

チェンジセットをリポに渡して、リポに次のように更新させてもいいですか?

// ------Service------
// repo.Update(some_changeset)

// ------Repository------
// tx.Begin()
// some_model = db.GetByID(...)
// some_model.Apply(some_changeset)
// db.Save(some_model.Serialized)
// tx.Commit()

これが簡単な質問であれば許してください。しかし、これに対する答えをどこかで見つけることができませんでした。

リポジトリはサービスレイヤーの境界付きコンテキストにあるため、答えは「はい」だと思いますが、可能であればコメントや確認をお願いします。

1
user7467314

リポジトリレイヤー内でビジネスロジックを呼び出すことは許容されますか?

DDD警察が来てあなたのドアを蹴飛ばすつもりはありません。

だが...

リポジトリパターンの動機は、「アプリケーションとドメインの設計を永続化テクノロジから切り離すこと」です。したがって、リポジトリインターフェースの背後にドメインロジックを追加すると、目的が損なわれます。

また、ドメインロジックがどこにあるかを推測するのが困難になります。すべてのドメインロジックがドメインモデルにある場合、推測は正しいものだけです。ロジックの一部が代わりにリポジトリ実装または永続ストア自体にある場合、変更が必要なコードの再発見はより困難になります。

しかしながら...

チェンジセットをリポに渡して、リポに更新を任せてもいいですか...

Evansによって説明されたものではありませんが、効果的である可能性のある別のパターンを発見するのは、もうすぐです。

さまざまな要素について注意深く検討すると、変更可能な状態がドメインモデルではなくリポジトリに存在することに気付くでしょう。集約とエンティティについて話しますが、これらは単なる一時的な変更です。変更される実際のオブジェクトはリポジトリ自体です。

これは、「ドメインロジック」が純粋な関数から構築され、リポジトリが可変状態を管理する設計を示唆している可能性があります。

Repository::onChange(id, domainLogic)
    oldValue = this.get(id)
    newValue = domainLogic(oldValue)
    this.replace(oldValue, newValue)

あなたはモナドのようなもので終わります-変​​更可能な状態を管理する不透明なボックスです。

良いニュース?長い間DDDに浸っている多くの人々は、ブルーブックの重要な部分は実装パターンのセクションではないではないと主張しています。 Domain Modeling Made Functional のようなテキストは、「オブジェクト指向」のフォーカスをすべて破棄し、それらのデザインは健全であるように見えます。

どのプロジェクトでも、1つのスタイルを選択してそれを維持するのが最善だと思いますが、Javaで「ベストプラクティス」と見なされていたものに15年以上の改善があった可能性があります前。

これは単一責任の原則に違反していますか?現在、リポジトリはドメインモデルのビジネスロジックを呼び出しています。つまり、オブジェクトストレージだけでなく、他にも責任があります。実行するビジネスプロセスに関する知識があり、発生する可能性のあるビジネスエラーを処理する必要がある

結構です。 「domainLogic」引数は、その実装がドメインモデルにある関数であり、永続性コンポーネントは、署名以外は何も認識しません。関数をリポジトリに渡すのは、実際にはアプリケーション層です。

例はどうですか-配送のユースケースの1つを試してみましょう:ルートへの貨物の割り当て。これが通常のイディオムで大体どのように見えるかです

Application::assignCargoToRoute(itinerary, trackingId) {
    cargo = repository.getById(trackingId)
    cargo.assignToRoute(itinerary)
    repository.save(cargo)
}

これが私が説明した代替案です:

Application::assignCargoToRoute(itinerary, trackingId) {
    // Notice: this returns a _function_
    domainLogic = domainModel.assignToRoute(itinerary)
    repository.onChange(trackingId, domainLogic)
}

「責任」は実際には分かれていることに注意してください。

  • ドメインモデルはロジックについては知っていますが、オーケストレーションや状態については何も知りません
  • アプリケーションはオーケストレーションについては知っていますが、ロジックや状態については何も知りません
  • リポジトリは状態を知っていますが、オーケストレーションやロジックは何もしません

これが根本的なトリックです。ドメインモデルに埋め込まれているのは、次のような純粋な関数の可能性です。

Cargo.State assignCargoToRoute(trackingId, cargoState, itinerary)

ある場合では、cargoStateを「クローズ」し、後で他の引数を受け入れます

Cargo {
    Cargo.State state;
    void assignToRoute(trackingId, itinerary) {
        this.state = PureFunction::assignCargoToRoute(trackingId, this.state, itinerary)
    }
}

もう1つは、コマンドデータを閉じて、状態を延期する方法です。

Function<Cargo.State, Cargo.State) assignoRoute(trackingId, itinerary) {
    return new Function<> (Cargo.State state) {
        return PureFunction::assignCargoToRoute(this.trackingId, state, this.itinerary);
    }
}

2番目のスペルはalienですが、実際には最初のものと同等です。私たちがやったことは、15年前に説明された方法とは少し異なる責任の境界を引くことです。

5
VoiceOfUnreason