- ドメインクラスのインスタンスを持ち回ると、セッション外等で面倒なことになる
- だからといってDTOへの変換をするのはだるい(無駄なクラス、バインディングの手間、等)
- 同一の項目定義で異なるテーブルに対応づける構成が直感的ではない(継承等で可能ではあるが)
- 複数パターンのバリデーションルールを使い分けられない(2.xでプラグインはあったが)
こういうGORM的APIがあると、DDD的にも通常利用でも非常に便利な気がする。 ポイントはドメインクラスに勝手にGORM的APIをはやすのではなく、明示的にRepositoryトレイトを実装させる、だけ。
// 普通のPOJO
// src/groovy配下でOK。Javaでも大丈夫。
class Book {
String title
}
// grails-app/domain
trait BookRepository implements Repository<Book> {
static constraints = { .... }
static mapping = { table 'book' }
static findByMyRule() { .... }
}
// 基本はリポジトリ経由でDB等のGORM的操作をする
BookRepository.list()
BookRepository.where { title == "HOGE" }.get()
BookRepository.add new Book(title: "新しい本")
BookRepository.validate book
// 同一のBookに対して複数のDBテーブルを定義できる
class OldBookRepository implements Repository<Book> {
static constraints = { .... }
static mapping = { table 'old_book' }
static findByMyRule() { .... }
}
// 動的トレイト適用で一時的に従来GORMのようにも使える
def book = Book.get(1) as BookRepository
book.title = "変更"
book.validate()
book.save(flush: true)
// このように定義すれば、従来GORMとまったく同一になる
class Book2 implements Repository<Book> {
String title
static constraints = { .... }
static mapping = { table 'book' }
static findByMyRule() { .... }
}
// バリデーションのための制約は差し替え/上書きできる、とうれしい
// コマンドオブジェクトとして簡単に使い分けられるし、ビジネスロジック上複数のバリデーションルールを使い分けることも多い。
// (DDLやGSP生成はあくまでデフォルトバリデーションをベースとする)
// デフォルト制約のように定義しておいて、第2引数にクロージャを渡しても良い
BookRepository.validate(book) { title matches: /ADHOC/ }
(book as BookRepository).validate { title matches: /ADHOC/ }
コア自体をこのように変更しつつ、設定ファイルでgrails.domain.autoImplementTrait
みたいな項目を追加して、デフォルトtrue
にすることで従来通りのドメインクラスを書けるようにして互換性を保持しつつ、false
にすると明示的にトレイトをimplements
しないとダメになって、上記のような使い方ができるようになる、という感じになると大変うれしい。
// コンバート用トレイト
trait Repository<T, R> implements GormEntity<T> {
static R of(T) {
// ...
}
T get() {
// ...
}
void add(T, params = [:]) {
// ...
}
T asType() {
// 強制型変換で変換可能にする
}
boolean validate(T, Closure constraints) {
// ....
}
}
// src/groovy配下のPOJO
class Book {
String title
}
// grails-app/domain
trait BookRepository extends Book implements Repository<Book, BookRepository> {
static constraints = { .... }
static mapping = { table 'book' }
static findByMyRule() { .... }
}
Book book = new Book(title: "新しい本")
BookRepository.add book, flush: true
//or BookRepository.of(book).save(flush: true)
Book book2 = BookRepository.get(1).get()
List<Book> books = BookRepository.list()*.get()
BookRepository.validate book { title blank: false }
苦しい。