以下あくまで私見として捉えてください。僕もDDDを勉強中で解釈が間違っていたりとか異なる意見があると思いますので。 あと書きなぐっているので読みづらくてすみません、あとで余裕があれば改めてブログにでもまとめます。
それで、僕の理解だとリポジトリって対象の集約ごとに提供するメソッドが多少ばらつくんですよね。 例えば書き込みをサポートしないリポジトリとか、IDを複数渡して一度に取ってこれるリポジトリ、そうじゃないリポジトリとか。 やりがちな失敗が、最初にスタンダードなread/writeができるリポジトリを定義してすべてのリポジトリでそれを継承しようとしてしまうことです。 これだとReadしかできないリポジトリが発生したとき、それだけリポジトリ基底クラスを継承しないようにするかReadWriteRepositoryの既定クラスとしてReadRepositoryを置くなどしないといけなくなります。 最悪の選択はReadしか不要なのにWriteのメソッドも実装してしまうことで、これは余計なコードが増えるだけではなく他の開発者が「このリポジトリってwriteしていいんだ」と勘違いする理由になります。
より本質的にアンチパターンであるとした場合の理由を考えると リスコフの置換原則「派生型はその基本型と置換可能でなければならない」に反しているからだと思います。
リポジトリの基底クラスって定義しても基底クラスの型へ代入して使うことはまったくないですよね?
だからdef get(id: ID): Aggregate
のようなメソッドを定義したとしても、別々のリポジトリ間でメソッド名が同じである、以上のメリットがないんじゃないかなと。
(これはコップ本12.2 シンインターフェイスとリッチインターフェイスのようなアプローチをする場合はメリットが出ますが、パフォーマンス上の問題などで結局しないことも多いと思います)
あとこれはアンチパターンの論拠とはならないんですが、Scalaの型システムへよく習熟してない人が無理に型パラメータを活用しようとして とんでもないコードになったり本来の意図を表現できない既定リポジトリクラスになることはよくありました(当方2の2でそうなっています)。
このあたりも、その通りだと思います。コード上はidを一意として認識していたとしてもDB上ではPRIMARY KEYとして張ってなければ永続化の観点で意味ないでしょうし。実際サービス上で使う想定を行なったときに。このget関数なんてほぼ利用がなく、むしろSELECT * FROM XXX WHERE id IN (xxx, xxx, xx) のようなマルチ選択のような利用の方が圧倒的に多いです。
ですので。Repositoryはかとじゅんさんが言うようにmixin でもいいかもしれませんが。業務上でエンジニア全員にmixinのパターンを覚えさせるのも大変です。そういった意味で最低限存在する関数だけを固定しておくのはありかもしれませんね。
蛇足すると、Employee EmployeeProfile みたいなモデルを作るといずれのオブジェクトもIDはEmployee#Id として扱いたい場合があります。こういった場合も。add/update を要求する場合。 EmployeeProfile Repositoryにとっては 新規IDの発行ではなく、既存Employee#Idを用いての INSERT OR UPDATE の処理となるので。update関数のみでカバーできることになりadd関数は不要ですね。