リポジトリを利用する「アプリケーションサービス」
それでは、リポジトリを利用するコード例(Java/C#)から見ていきましょう。以下では、ユーザー管理のアプリケーションサービス(14章)にて、「ユーザー」の集約をデータベースに登録しています。アプリケーションサービスはUIや外部サービスのユースケースから呼び出されます。
// ■ユーザーIDに関するアプリケーションサービス public class IdentityApplicationService { // ▼リポジトリ(SpringのDIにて設定) @Autowired private UserRepository userRepository; // ▼ユーザーを登録する(トランザクション宣言をアノテーション指定) @Transactional public User registerUser(RegisterUserCommand aCommand) { Tenant tenant = this.existingTenant(aCommand.getTenantId()); // ユーザーの集約を生成(テナント集約のファクトリから生成) User user = tenant.registerUser( aCommand.getInvitationIdentifier(), aCommand.getUsername(), ~中略~ ) // ユーザーをリポジトリに登録 this.userRepository().add(user); return user; }
上記のコードにて、ファクトリが集約を生成しリポジトリへ登録する流れが理解できたと思います。
リポジトリのモジュール構成
続けて、リポジトリのモジュール(パッケージ/名前空間)構成を整理してみましょう。
上図の通り、リポジトリのインターフェイスは、集約と同じドメイン層のパッケージに配置します。そしてリポジトリの実装クラスはインフラ層に配置します。4章「アーキテクチャ」で紹介したDIP原則に従うことで、ドメイン層への影響なく、リポジトリの交換が可能になります。
データベースのトランザクション管理
なお、リポジトリではデータベースのトランザクション管理は行いません。そのため、アプリケーションサービス側にてトランザクションを管理する必要があります。トランザクションは、明示的にメソッドで呼び出す場合もあれば、宣言的に管理する場合もあります。上記コードでは「@Transactional」アノテーションにて宣言的にトランザクションを制御しています。
リポジトリの設計 ~「コレクション指向」と「永続指向」~
続いて、リポジトリの設計について見ていきましょう。IDDDでは2つの設計パターンを紹介しています。ひとつはRDBやインメモリで格納する「コレクション指向リポジトリ」で、もうひとつはNoSQLに格納する「永続指向リポジトリ」です。
「コレクション指向のリポジトリ」では集約の登録時に「追加メソッド(Add)」を用います。リポジトリは、登録された集約の情報をRDBに書き込みます。IDDDではO/Rマッパー(Hibernate等)の仕組みでオブジェクトの変更を検知し、複数のテーブルに反映します。
これに対して「永続指向のリポジトリ」では集約の作成/変更時に「保存メソッド(Save)」を呼び出します。リポジトリでは、保存メソッドが呼ばれたタイミングで、集約のキーを元に、NoSQLに保存します。集約の階層構造を1つのデータ(ドキュメント)として保存します。そのため、差分的な変更ではなく、集約全体を一括で更新します。
コレクション指向のリポジトリ
さらに、「コレクション指向」のリポジトリについて細かく見ていきましょう。コレクション指向のリポジトリとは、コレクションクラス(java.util.HashSet)のように集約の追加/削除/取得ができます。
インターフェイスの設計
コレクション指向のコードサンプル(Java/C#)を見てみましょう。次のコードにある通り、Add(追加)、Remove(削除)、Get(取得)といった基本的な操作が備わっていることがわかります。
// ■ユーザー集約を管理するリポジトリのインターフェイス public interface UserRepository { // ▼リポジトリに追加 public void add(User aUser); // ▼リポジトリから削除 public void remove(User aUser); // ▼名前でユーザー集約を取得 public User userWithUsername( TenantId aTenantId, String aUsername); ~中略~ }
コレクション指向の実装クラス
次に、実装コード(Java)を見てみましょう。O/Rマッパーを利用している「HibernateUserRepository」クラスです。Hibernateを利用して保存や削除を呼び出しています。また取得においても、クエリを組み立てて条件に合った集約を取り出していることがわかります。
// ■ユーザー集約を管理するリポジトリの実装クラス public class HibernateUserRepository extends AbstractHibernateSession implements UserRepository { // ▼リポジトリに追加 @Override public void add(User aUser) { try { this.session().saveOrUpdate(aUser); } catch (ConstraintViolationException e) { throw new IllegalStateException("User is not unique.", e); } } // ▼リポジトリから削除 @Override public void remove(User aUser) { this.session().delete(aUser); } // ▼名前でユーザー集約を取得 @Override public User userWithUsername( TenantId aTenantId, String aUsername) { Query query = this.session().createQuery( "from com.saasovation.identityaccess.domain.model.identity.User as _obj_ " + "where _obj_.tenantId = ? " + "and _obj_.username = ?"); query.setParameter(0, aTenantId); query.setParameter(1, aUsername, Hibernate.STRING); return (User) query.uniqueResult(); } ~中略~ }