LoginSignup
3
2

More than 5 years have passed since last update.

Spring, JPAによるドメインモデルの実装

Last updated at Posted at 2017-04-20

最初に

Tsansaction Script撲滅には、ドメインモデル貧血症と戦う必要がある
そのためにはサービスから振る舞いを取り除きドメインはPOJOに押し込める必要がある。
Spring Framework、JPAへの適用を考慮するとこれを難しくしてるのはオブジェクト生成時、つまり、newをしたときに依存性がうまく注入できない点にある。Springは@Configurableを使用した実現方法があるが、AOPを使用するため、Java Agentの設定が必要であったり、Lombokと相性が悪かったりなど適用を躊躇することもあると思われる。

ここではAOPに頼らず、もっと簡易に実施する方法を紹介する。
簡易と記載した意味は、今回紹介する方法はベストだとは意味していない点にある。この方法以外に良い方法があれば、ぜひ紹介していただきたい。

考え方

Domain Model Patternではオブジェクトのデータと振る舞いは同居する。オブジェクトが持つフィールドは、そのオブジェクトのデータに加えて、インフラストラクチャー・レイヤーへアクセスするリポジトリー等のフィールドも存在する。
依存性を注入する責務を持つSpring Frameworkでは、このデータではないフィールドへの実装クラスの依存性注入を行う。たとえば、Spring MVCではControllerのフィールドに@Autowiredアノテーションを記述すると、そのフィールドに依存性が注入される。
ところが、「最初に」の箇所に記載した@Configurableを使用しない場合は、オブジェクトをnewで生成したときには依存性が注入されない。
今回のこの記事はこの生成時の依存性注入をどうコントロールするかを記載する。

実装方法

Spring FrameworkとJPAを使用した場合にインスタンスが生成されるのは以下の2つになる。

  1. 明示的にインスタンスをnewする場合
  2. JPAにより検索でオブジェクトを取得する場合

本記事はこの2つのケースをどう対処するかを記載する。

まず、オブジェクトの依存性注入をどこでもできるメソッドを準備する。

ApplicationContextProvider.java
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
    private static ApplicationContext context;

    public static ApplicationContext getApplicationContext() {
        return context;
    }

    public static AutowireCapableBeanFactory getAutowireCapableBeanFactory() {
        return context.getAutowireCapableBeanFactory();
    }

    public static <T> T pupulateBean(T bean) {
        getAutowireCapableBeanFactory().autowireBean(bean);
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        context = ac;
    }
}

これでApplicationContextProvider.pupulateBean(bean)で依存性注入ができていないインスタンスに依存性を注入ができる。
ただし、これをドメイン・レイヤーに記述すると、ドメイン・レイヤーがSpringに依存することになり、適切ではない。

明示的にインスタンスをnewする場合の対処

明示的にインスタンスをnewする場合と書いたが、ここで書く方法ではドメイン・レイヤーでは直接newをしない方法を紹介する。(結局newしないというところが突っ込むどころで、簡易と書いたのはこれに対する布石であった)
クラスFooがサービスBarServiceを使用する必要がある場合の例を示す。

Foo.java
public class Foo {
    @Autowired
    BarService barService;
}

このクラスFooを生成するためのインターフェイスを作成する。

FooFactory.java
public interface FooFactory {
    default Foo create() {
        return new Foo();
    }
}

このインターフェイスFooFactoryはドメイン・レイヤーに属する。そして、ここでは直接newを実施し、オブジェクトを生成している。
一方で、このFooFactoryはインターフェイスのため、実際にオブジェクトを生成する具象クラスを作成する必要がある。

FooFactoryImpl.java
public class FooFactoryImpl implements FooFactory {
    @Override
    public Foo create() {
        return ApplicationContextProvider.pupulateBean(FooFactory.super.create());
    }
}

クラスFooFactoryImplはインフラストラクチャー・レイヤーに属する。ここで、ApplicationContextProviderが登場する。ApplicationContextProvider.pupulateBeanを経由し、オブジェクトに依存性注入している。

では、実際にこれを使用する例を見てみよう。

BazService.java
public class BazService {
    @Autowired
    FooFactory fooFactory;
    public void doSomething() {
        Foo foo = fooFactory.create();
        foo.getBarService().doAnotherThing();
    }
}

サービスBazServiceは、ドメイン・レイヤーに属する。メソッドdoSomethingでクラスFooのインスタンスを生成しているが、このメソッドcreateは具象クラスFooFactoryImplのものが呼び出されるため、Springの依存性注入が行われた後のインスタンスFooが得られている。

JPAにより検索でオブジェクトを取得する場合の対処

JPAにはイベント・リスナーという機能があり、エンティティーに対する様々なイベントに対して、コールバックを仕掛けることができる。

JpaEventListener.java
public class JpaEventListener {
    @PostLoad
    void onPostLoad(Object o) {
        ApplicationContextProvider.pupulateBean(o);
    }
}

あとは、このイベント・リスナーを対象クラスにアノテーションにより付与するのみである。

Foo.java
@EntityListeners(value = { JpaEventListener.class })
public class Foo {
    @Autowired
    BarService barService;
}

最終コード

最終的なコードは以下である。

ドメイン・レイヤー

Foo.java
@EntityListeners(value = { JpaEventListener.class })
public class Foo {
    @Autowired
    BarService barService;
}
FooFactory.java
public interface FooFactory {
    default Foo create() {
        return new Foo();
    }
}

インフラストラクチャー・レイヤー

FooFactoryImpl.java
public class FooFactoryImpl implements FooFactory {
    @Override
    public Foo create() {
        return ApplicationContextProvider.pupulateBean(FooFactory.super.create());
    }
}
JpaEventListener.java
public class JpaEventListener {
    @PostLoad
    void onPostLoad(Object o) {
        ApplicationContextProvider.pupulateBean(o);
    }
}
ApplicationContextProvider.java
@Component
public class ApplicationContextProvider implements ApplicationContextAware {
    private static ApplicationContext context;

    public static ApplicationContext getApplicationContext() {
        return context;
    }

    public static AutowireCapableBeanFactory getAutowireCapableBeanFactory() {
        return context.getAutowireCapableBeanFactory();
    }

    public static <T> T pupulateBean(T bean) {
        getAutowireCapableBeanFactory().autowireBean(bean);
        return bean;
    }

    @Override
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        context = ac;
    }
}

まとめ

今回は実装を通し、ドメイン・モデルの実装方法を紹介した。
最近のフレームワークは、種類も豊富だが、それぞれの設定が多く、複雑化すると問題判別が難しくなるのは、皆さん経験されていることかと思う。
なるべく実現したいことに対し、簡易な方法を実施するのは良いことのひとつと思うため、今回は簡易なドメイン・モデルの実現方法を紹介した。

※このサイトの掲載内容は私個人の見解であり、必ずしも私が所属する会社、組織、団体の立場、戦略、意見を代表するものではありません。

Hirofumi Arimoto

Java, Scala, JavaScriptや、機械学習に興味あり
実際の開発での知見を可能な限り記事にしたいと思っている

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2