タイトルが示唆するように、サービスレイヤーを設計する際のベストプラクティスは何ですか?ドメイン(エンティティ)オブジェクトがサービスレイヤー内に保持されるように、サービスレイヤーは常にDTOを返す必要があることを理解しています。しかし、コントローラーからのサービス層への入力は何でしょうか?
以下の3つの私の提案を提案します。
方法1:このメソッドでは、ドメインオブジェクト(アイテム)がサービスレイヤー内に保持されます。
class Controller
{
@Autowired
private ItemService service;
public ItemDTO createItem(IntemDTO dto)
{
// service layer returns a DTO object and accepts a DTO object
return service.createItem(dto);
}
}
方法2:これは、サービス層がカスタム要求オブジェクトを受け取る場所です。私はこのパターンをAWSで広範囲に見ましたJava SDKとGoogle Cloud Java API
class Controller
{
@Autowired
private ItemService service;
public ItemDTO createItem(CreateItemRequest request)
{
// service layer returns a DTO object and accepts a custom request object
return service.createItem(request);
}
}
方法3:サービス層はDTOを受け入れ、ドメインオブジェクトを返します。私はこの方法のファンではありません。しかし、それは私の職場で広く使用されています。
class Controller
{
@Autowired
private ItemService service;
public ItemDTO createItem(CreateItemRequest request)
{
// service layer returns a DTO object and accepts a DTO object
Item item = service.createItem(request);
return ItemDTO.fromEntity(item);
}
}
上記の3つの方法がすべて正しくないか、最善の方法でない場合は、ベストプラクティスについて教えてください。
から来ました C#
背景ですが、コンセプトは同じです。
このような状況では、アプリケーションレイヤーからサービスレイヤーにパラメーター/状態を渡して、サービスレイヤーから結果を返す必要があるため、関心の分離を追跡する傾向があります。サービス層は、アプリケーション層/コントローラーのRequest
パラメーターを認識する必要はありません。同様に、サービスレイヤーから返されるものを、コントローラーから返されるものと組み合わせるべきではありません。これらは、異なる層、異なる要件、個別の懸念事項です。密結合を避ける必要があります。
上記の例では、次のようにします。
class Controller
{
@Autowired
private ItemService service;
public ItemResponse createItem(CreateItemRequest request)
{
var creatItemDto = GetDTo(request);
var itemDto = service.createItem(createItemDto);
return GetItemResponse(itemDto);
}
}
異なるオブジェクトを変換するために追加のコードを書く必要があるので、これはより多くの作業のように感じるかもしれません。ただし、これにより柔軟性が大幅に向上し、コードの保守がはるかに容易になります。例:CreateItemDto
は、CreateItemRequest
と比較して、追加/計算フィールドを持つ場合があります。このような場合、Request
オブジェクトでこれらのフィールドを公開する必要はありません。 Data Contract
クライアントにそれ以上。同様に、サービス層から返すものに対して、関連するフィールドのみをクライアントに返します。
Dto
とRequest
間の手動マッピングを避けたい場合は、objects
C#にAutoMapper
のようなライブラリがあります。 Javaの世界では、同等のものがあるはずです。多分 ModelMapper が役立ちます。
概念的に言えば、プレゼンテーションレイヤー全体でサービスレイヤーやアプリケーションレイヤーを再利用し、さまざまなアクセスポートを使用できるようにする必要があります(例:コンソールアプリがWebソケットを介してアプリと通信する)。さらに、すべてのドメイン変更がアプリケーションレイヤーより上のレイヤーにバブルアップすることを望まない。
コントローラーは概念的にはプレゼンテーション層に属します。したがって、コントローラーが定義されているのと同じ概念レイヤーで定義されたコントラクトにアプリケーションレイヤーを結合したくないでしょう。また、コントローラーがドメインに依存したり、ドメインが変更。
アプリケーション層のメソッドコントラクト(パラメーターと戻り値の型)が、Javaネイティブ型またはサービス層の境界で定義されている型)で表現されているソリューションが必要です。
Vaughn Vernonから IDDDサンプル を取得すると、彼のアプリケーションサービスメソッドコントラクトがJavaネイティブ型で定義されていることがわかります。彼のアプリケーションサービスコマンドメソッドも彼がCQRSを使用した場合、結果は生成されますが、 query methods がアプリケーション/サービスレイヤーパッケージで定義されたDTOを返すことがわかります。
上記の3つの方法のうち、正しい方法と間違っている方法はどれですか。
#1と#2は非常によく似ており、ItemDto
とCreateItemRequest
がアプリケーションレイヤーパッケージで定義されている限り、依存関係の観点からは正しい可能性がありますが、私は#2を優先します。入力データ型は、それが扱うエンティティの種類ではなく、ユースケースに対して名前が付けられます。entity-naming-focusはCRUDに適しています。そのため、他の入力データ型の適切な名前を見つけるのが難しい場合があります。同じ種類のエンティティで動作するユースケースメソッド。 #2もCQRSを通じて一般化されています(コマンドは通常コマンドバスに送信されます)が、CQRS専用ではありません。 Vaughn Vernonも IDDDサンプル でこのアプローチを使用しています。通常、requestと呼ばれるものは、commandと呼ばれます。
ただし、#3はコントローラー(プレゼンテーションレイヤー)とドメインを結合するため、理想的ではありません。
たとえば、一部のメソッドは4つまたは5つの引数を受け取ります。 Clean CodeのEric Evansによれば、そのような方法は避けなければなりません。
これは従うのに適したガイドラインであり、サンプルを改善できなかったとは言いませんが、DDDでは、ユビキタス言語(UL)に従って名前を付けることと、それを可能な限り厳密に守ることに重点が置かれていることに注意してください。したがって、引数をグループ化するためだけに新しいコンセプトを設計に強制することは、潜在的に有害である可能性があります。皮肉なことに、そうしようとするプロセスは、依然としていくつかの優れた洞察を提供し、ULを強化する可能性のある見落とされた有用なドメインの概念を発見できる場合があります。
PS:ロバートC.マーティンは、ブルーブックで有名なエリックエバンスではなく、クリーンコードを作成しました。