概要
アクティブレコードは、元々Martin Fowlerによって名付けられたパターンであり、Ebean 特有のものではなく、多くの永続化フレームワークで共通のパターンです。
一般的に、エンティティ Bean が自身でsave()
およびdelete()
する機能を持つことを意味します。これは、エンティティ Bean が保存と削除のために DAO に渡される従来のDAO
アプローチとは対照的です。
Play フレームワーク
Ebean のアクティブレコードパターンは、Play フレームワークから継承されています。 Ebean におけるアクティブレコードパターンを実装するための元の Model と Finder の実装は Play フレームワークに由来し、そのコードは Ebean に移行されました。
批判
アクティブレコードパターンに対する主な批判は2つあります
- 関心の結合 - 「モデル」と「それがどのように永続化されるか」が密接に結合されている
- テスト容易性 - アクティブレコードは理論的にはテストが難しい
Ebean はebean-mocker
を介したテストメカニズムを提供しており、保存と検索の両方に対するアクティブレコードパターンを完全にテストできることに注意してください。
Robの見解:JPAとアクティブレコード
私の意見では、JPA はアクティブレコードパターンには適していません。これは、Grails/GORM/Hibernate での私の個人的な経験に基づいています。具体的には、GORM は開発者から Hibernate Session の存在を効果的に隠しています。私の経験では、これは単純なケースではうまく機能しますが、より複雑なケースでは、開発者は Session/EntityManager オブジェクトを明示的に知って処理する必要がある状況に陥ります。
アクティブレコードの save() delete() セマンティクスは、JPA の基礎となる attach/detach/flush セマンティクスと一致せず、より複雑なケースでは、その不一致が表面化します(抽象化リーク)。これは通常、コーディングスタイルと思考の両方の変更、および両方のスタイルを含むコードベースという結果になります。
Robの見解:Ebeanとアクティブレコード
Ebean は「セッションレス」ORM であり、特に JPA とは異なり、attach/detach セマンティクスがありません。これにより、アクティブレコード API/抽象化と基礎となる EbeanServer の間に不一致がないため、Ebean はアクティブレコードパターンに適しています。
Ebean を使用した複雑な永続化ケースでは、JDBC バッチメカニズムを完全に制御する必要があります。これらのより複雑なケースでは、アクティブレコードの save() と delete() は期待どおりに機能しますが、トランザクションを介して(プログラムまたは @Transactional を介して)JDBC バッチ制御を調整します。つまり、複雑なケースでスタイルを変更する必要はありません。
テスト
Ebean はebean-mocker
プロジェクトを提供しており、アクティブレコードパターンを使用する場合のテスト要件をサポートしています。永続化と検索の両方で EbeanServer のテストダブルの使用をサポートし、永続化と検索のための優れたすぐに使えるテストダブルとして DelegateEbeanServer を提供します。
DelegateEbeanServer は以下を提供します
- 永続化呼び出しのキャプチャ、保存された Bean のアサートなど
- findById スタブ/モックレスポンス
- 静的 Finder スタブ/モックテストダブル
- SQL キャプチャ
Robの見解:アクティブレコードの利点
アクティブレコードは関心を組み合わせていると考える人もおり、それで会話が終わってしまう場合がありますが、それでも構いません。
テスト容易性を損なわない
個人的には、いわゆる従来の DAO/Inject アプローチとアクティブレコードを比較すると、まずテスト容易性を損なわないことに気づきます。Ebean を使用したアクティブレコードは、DAO/Inject スタイルと同じくらいテスト可能です。
単純なことは単純なまま
次に、アクティブレコードの主な利点は、「単純なこと」が単純なままであることです。つまり、save()、delete()、参照の取得、ID による検索、一意キーによる検索はすべて非常にシンプルで、コードも非常にクリーンです。DAO/Inject コードでは、エンティティ Bean のタイプごとに DAO をインジェクトするのが一般的です。4 つの異なるエンティティ Bean タイプを扱うサービスの場合、4 つの DAO をインジェクトすることになります。特にコンストラクタインジェクションでは、DAO のインジェクションは、比較的醜いコンストラクタと「回避策」につながることがあります。
アクティブレコードを使用すると、単純なことは単純なままです
バランスを考えると、アクティブレコードを選択します
アクティブレコードスタイルと従来の DAO/Inject スタイルのどちらを選択するか尋ねられたら、結果として得られる非常にクリーンでシンプルなコードのため、アクティブレコードスタイルを提案します。テスト容易性を損なうことはなく (ebean-mocker のおかげで)、エンティティ Bean に save() delete() メソッドがあること (関心の混在) も問題ありません。
モデル
エンティティ Bean がio.ebean.Model
オブジェクトを拡張する場合、いくつかの便利なメソッド save()、delete() などを継承します。これらのメソッドが内部的に呼び出されると、デフォルトの EbeanServer が save() と delete() を実行するために使用されます。
Ebean Model は、次の便利なメソッドを提供します
- save() - エンティティを保存します
- update() - エンティティを明示的に更新します
- insert() - エンティティを明示的に挿入します
- delete() - エンティティを削除します
- refresh() - データベースからエンティティを更新します
- markAsDirty() - これは、変更されていない Bean が更新されたときにバージョン プロパティが更新されるようにするために使用されます
- update(String server) - 指定された Ebean サーバーを使用してエンティティを更新します
- insert(String server)- 指定されたサーバーを使用してエンティティを挿入します
- delete(String server)- 指定されたサーバーを使用してエンティティを削除します
例:Model を拡張する
/**
* Extend Model to get the save(), delete() etc
* methods on the bean itself (active record style).
*/
@Entity
@Table(name="customer")
public class Customer extends Model {
@Id
Long id;
String name;
Date registered;
String comments;
...
例:save()
Customer customer = new Customer();
customer.setName("Rob");
customer.save();
Finder
Finder は、便利なクエリメソッドを提供するだけでなく、「ファインダー」コードを整理する方法も提案します。
例:ID による検索
Customer customer = Customer.find.byId(42);
例:参照の取得
Customer customer = Customer.find.ref(42);
例:ID による削除
Customer.find.deleteById(42);
例:条件による検索
List<Customer> customers =
Customer.find
.where().ilike("name", "rob%")
.findList();