DTOパターン(データ転送オブジェクト)
1. 概要
このチュートリアルでは、 DTOパターン、それが何であるか、そしてそれをいつどのように使用するかについて説明します。 最後に、それを適切に使用する方法を理解します。
2. パターン
DTOまたはデータ転送オブジェクトは、メソッド呼び出しの数を減らすためにプロセス間でデータを運ぶオブジェクトです。このパターンは、MartinFowlerの著書EAAで最初に紹介されました。
Fowlerは、このパターンの主な目的は、1回の呼び出しで複数のパラメーターをバッチ処理することにより、サーバーへのラウンドトリップを減らすことであると説明しました。 これにより、このようなリモート操作でのネットワークオーバーヘッドが削減されます。
もう1つの利点は、シリアル化のロジック(オブジェクトの構造とデータを保存および転送できる特定の形式に変換するメカニズム)のカプセル化です。 これは、シリアル化のニュアンスに単一の変更点を提供します。 また、ドメインモデルをプレゼンテーション層から切り離し、両方を独立して変更できるようにします。
3. それの使い方?
DTOは通常、POJOsとして作成されます。 それらは、ビジネスロジックを含まないフラットなデータ構造です。ストレージ、アクセサー、そして最終的にはシリアル化または解析に関連するメソッドのみが含まれます。
データは、ドメインモデルからDTOに、通常はプレゼンテーションまたはファサードレイヤーのマッパーコンポーネントを介してマッピングされます。
以下の画像は、コンポーネント間の相互作用を示しています。
4. いつ使用しますか?
DTOは、リモート呼び出しの数を減らすのに役立つため、リモート呼び出しを使用するシステムで役立ちます。
DTOは、ドメインモデルが多くの異なるオブジェクトで構成されており、プレゼンテーションモデルがすべてのデータを一度に必要とする場合にも役立ちます。また、クライアントとサーバー間のラウンドトリップを減らすこともできます。
DTOを使用すると、ドメインモデルからさまざまなビューを構築できます。同じドメインの他の表現を作成できますが、ドメインの設計に影響を与えることなく、クライアントのニーズに合わせて最適化できます。 このような柔軟性は、複雑な問題を解決するための強力なツールです。
5. 使用事例
パターンの実装を示すために、2つのメインドメインモデル(この場合は UserとRole)を備えた単純なアプリケーションを使用します。 パターンに焦点を当てるために、機能の2つの例、ユーザーの取得と新しいユーザーの作成を見てみましょう。
5.1. DTOとドメイン
以下は、両方のモデルの定義です。
public class User {
private String id;
private String name;
private String password;
private List<Role> roles;
public User(String name, String password, List<Role> roles) {
this.name = Objects.requireNonNull(name);
this.password = this.encrypt(password);
this.roles = Objects.requireNonNull(roles);
}
// Getters and Setters
String encrypt(String password) {
// encryption logic
}
}
public class Role {
private String id;
private String name;
// Constructors, getters and setters
}
次に、DTOを見て、ドメインモデルと比較できるようにします。
現時点では、DTOがAPIクライアントとの間で送受信されるモデルを表していることに注意することが重要です。
したがって、小さな違いは、サーバーに送信された要求をまとめるか、クライアントの応答を最適化することです。
public class UserDTO {
private String name;
private List<String> roles;
// standard getters and setters
}
上記のDTOは、セキュリティ上の理由から、たとえばパスワードを非表示にして、関連情報のみをクライアントに提供します。
次のDTOは、ユーザーの作成に必要なすべてのデータをグループ化し、単一のリクエストでサーバーに送信します。これにより、APIとのやり取りが最適化されます。
public class UserCreationDTO {
private String name;
private String password;
private List<String> roles;
// standard getters and setters
}
5.2. 両側を接続する
次に、両方のクラスを結び付けるレイヤーは、マッパーコンポーネントを使用して、データを一方の側からもう一方の側に、またはその逆に渡します。
これは通常、プレゼンテーション層で発生します。
@RestController
@RequestMapping("/users")
class UserController {
private UserService userService;
private RoleService roleService;
private Mapper mapper;
// Constructor
@GetMapping
@ResponseBody
public List<UserDTO> getUsers() {
return userService.getAll()
.stream()
.map(mapper::toDto)
.collect(toList());
}
@PostMapping
@ResponseBody
public UserIdDTO create(@RequestBody UserCreationDTO userDTO) {
User user = mapper.toUser(userDTO);
userDTO.getRoles()
.stream()
.map(role -> roleService.getOrCreate(role))
.forEach(user::addRole);
userService.save(user);
return new UserIdDTO(user.getId());
}
}
最後に、データを転送するマッパーコンポーネントがあり、DTOとドメインモデルの両方がお互いを知る必要がないことを確認します:
@Component
class Mapper {
public UserDTO toDto(User user) {
String name = user.getName();
List<String> roles = user
.getRoles()
.stream()
.map(Role::getName)
.collect(toList());
return new UserDTO(name, roles);
}
public User toUser(UserCreationDTO userDTO) {
return new User(userDTO.getName(), userDTO.getPassword(), new ArrayList<>());
}
}
6. よくある間違い
DTOパターンは単純なデザインパターンですが、この手法を実装するアプリケーションではいくつかの間違いを犯す可能性があります。
最初の間違いは、機会ごとに異なるDTOを作成することです。 これにより、維持する必要のあるクラスとマッパーの数が増えます。 それらを簡潔に保ち、1つ追加するか既存のものを再利用するかのトレードオフを評価するようにしてください。
また、多くのシナリオで単一のクラスを使用しようとしないようにします。 この方法は、多くの属性が頻繁に使用されない大きな契約につながる可能性があります。
もう1つのよくある間違いは、これらのクラスにビジネスロジックを追加することですが、これは発生しないはずです。 このパターンの目的は、データ転送と契約の構造を最適化することです。 したがって、すべてのビジネスロジックはドメイン層に存在する必要があります。
最後に、いわゆる
このアプローチを支持する最も一般的な議論の1つは、ドメインモデルのカプセル化です。 しかし、ここでの問題は、ドメインモデルを永続性モデルと結合させることです。 それらを分離することにより、ドメインモデルを公開するリスクはほとんどなくなります。
他のパターンも同様の結果になりますが、通常、 CQRS 、データマッパー、CommandQuerySeparationなどのより複雑なシナリオで使用されます。
7. 結論
この記事では、 DTOパターンの定義、それが存在する理由、およびその実装方法について説明しました。
また、その実装とそれらを回避する方法に関連する一般的な間違いのいくつかを見ました。
いつものように、例のソースコードはGitHubでから入手できます。