オレオレORM「Moco」についてのお気持ち

去年辺りからチマチマとMocoというオレオレORMを作っているので技術的な話ではなくお気持ち的な話を書いてみたくなった。
技術的な話は気が向いたら別の記事にしようかなと思ってるけどまだ気分ではないかな。

Mocoの思想

JavaでDB操作をする時は自分でゴリゴリSQLを書く薄いORMが好んで使われる。DomaとかSpring JDBCとか使ったことないけどMyBatisもそうなのかな?
自分でSQLを書くことには僕は抵抗がないしDomaとかは個人的に結構好きだったりする。ただ、遊びでピャッっとWebアプリ作ろうかなになった時、リレーションをJavaの世界で組み立てるのが結構面倒だったりする。複数のCustomerに各Customerが複数のOrderを持っているケースでJavaのオブジェクト煮詰めていくのが結構面倒くさいな~と思ったりしてた。
その時に出会ったのがActiveRecordのpreloadで、ActiveRecordでは以下のように書くことができる。

customers = Customer.preload(:orders).to_a
orders = customers[0].orders

preloadに関連を書くだけで良い!なんだこれは便利すぎる…。あとSQL書かなくてよいの意外と楽だぞ!?けど補完があまり効かないしそこはJavaのほうが気持ちが良いんだよな…Javaならもっとうまくできるんじゃないのか?となったのが開発着手までの流れです。
なので「リレーションをうまく扱いたいね」と「ちゃんと補完バリバリできる方がいいよね」という思想がベースにあります。

Mocoという名前

Mocoという謎の名前ですが、これはセブンイレブンの「しろもこ」シリーズから取っています。なのでモジュール名もシュークリームっぽい感じにしてます。僕がすごい好きなイラストレーターがFANBOXで定期的にもこシリーズの話を書いていたのでそこから取りました。
ちなみに今まで食べた中ではティラミスもこが一番好きでした。

どうやって気持ちよく書くか

Javaの話に戻って、ActiveRecordっぽいものをJavaでやろうとしたライブラリはいくつかあるようですが、Stringで頑張ろうとしている辛いものばかりでした。MocoではPluggable Annotation Processing APIを使ってコンパイル時にクラスを生成することを選択しました。
POJOだけユーザーが定義して、そこからいい感じ操作をするためのクラスを二種類生成します。
1つ目はPOJOの複数形名のクラスで、カラムを表す定数や後述するEntityListを作るstaticメソッドが生えているTableClassと呼んでいるクラス。
2つ目はEntityListと呼んでいるクエリを作って結果を得るためのクラスです。

これらを使うことで以下のように書くことができます。

// このクラスはユーザー定義
@Table(name = "customers")
public class Customer {
    @Column(name = "id", generatedValue = true, unique = true)
    private int id;
    @Column(name = "name")
    private String name;
    @Column(name = "city")
    private String city;
    @Column(name = "company_id")
    private int companyId;

    @BelongsTo(key = "company_id", foreignKey = "id")
    private Company company;
    @HasMany(key = "id", foreignKey = "customer_id")
    private List<Order> orders = new ArrayList<>();
    // getter...
}

// CustomersとCustmerListはコンパイル時に生成される
// Tokyoに住んでいる顧客
Customers.all().where(Customers.CITY.eq("Tokyo")).toList();

// preloadで関連を一緒に引いてマッピングする
// noteを買ったOrder一覧
 List<Order> noteOrders = LineItems.all()
        .where(LineItems.NAME.eq("note"))
        .preload(LineItems.LINE_ITEM_TO_ORDER)
        .stream()
        .map(LineItem::getOrder)
        .collect(Collectors.toList());

今後やりたいこと

今は一段のpreloadしかできないですが、近いうちにActiveRecordのpreload(hoge: :fuga)のような多段preloadをできるようにしたいと考えています。
どう書けば気持ち良いんだろうね…。

こう書けたほうが気持ち良いはず!などあればIssueやPRを送っていただけるとうれしいです。

github.com

また、試してみたいだけの方はSQLiteで試せるサンプルリポジトリがあるのでこちらで遊んでみてください。

github.com