2
2

More than 3 years have passed since last update.

レイヤードアーキテクチャにおける依存管理

Last updated at Posted at 2020-06-14

このページはなにか?

新規プロジェクトにて導入した、ビジネスロジックの管理方法を整理したページです。
「SpringにあるようなWeb API用の巨大DIコンテナまではいらないな」って状況で、関連するクラスをどう構造化、利用するか?がフォーカスポイントです。

前提

本プロジェクトは、エントリーポイントになる単一メソッドに引数を渡すと結果が返ってくる、シンプルな共通ライブラリでした。
エントリーポイントから、複数のエンティティに紐づくビジネスロジックが定義される(いわゆるMVCに近いアーキテクチャ)。

ビジネスロジックに変更が走る場合、エントリーポイントではなくビジネスロジックを抱えるサービスが増えたり変更したりする想定で今回は検討しました。

呼び元が直接サービスのインスタンスを生成、実行するのはなんだか気持ち悪い

エントリーポイントから参照するサービスレイヤーのクラスはせいぜい4とか5なので、直接インスタンスを生成してもいいんだけど...
1. クラスからインスタンスを生成する必要の有無。状態を持たないのに生成するんだっけ?究極staticコンテキストに押し込めてもよいとすら思う。
2. インターフェースの必要性。実装のみでよいのか?いっぽう、お約束としてIFを立てるのは安心感ある。
3. 呼び出し元の呼び出し方には、大別すると3つくらいありそう。インスタンス生成、DI、staticメソッド(ユーティリティ化)。
などなど、依存管理の方法に向き合う必要がありました。

恐れていること、想定リスク、観点

4つくらいの観点で検討しました:
1. 今後の追加機能開発でビジネスロジックがアップデートされるとき、めちゃくちゃ混乱する。
2. DIコンテナのためにファットなライブラリに依存すること。
3. わざわざSpring/DIライブラリ導入したくない...サービスも多くて5個くらいだし。密結合が発生すること。
4. 他の人が読むときにもシンプルに読み込んでいけるか?
過去に、クラスの構成に失敗して目も当てられないような複雑な機能を開発してしまった反省があるので、慎重に選択。

解決策

簡易DIコンテナを用意してみることとしました。
ファクトリメソッド、シングルトン、サービスロケータのあいのこのような構造をもつクラスに仲介してもらうイメージですね。
ファクトリクラス自体は単一インスタンスにして、サービスはこの単一インスタンスから取得するようにしておきます。

また、ビジネスロジックのアップデートはファクトリを介してのみ影響をうけるようにしておきます。
ビジネスロジックが追加されるときは、インスタンス生成メソッドをファクトリに追加して対応できるのではないか、と考えたのでした。

そして、サービスに依存する上位レイヤーのクラスは、サービスをファクトリから生成して利用する。
SpringやJavaEEにあるようなアノテーションベースDIは本プロジェクトではちょっとやりすぎ感あるので今回のプロジェクトでは採用しなかったです。
いろいろ調べた結果、自分で用意しやすくかつ今回のようなコンパクトなプロジェクトに向いてそうなDYDI(Do it Yourself Dependency Injection)というパターンを採用しました。

実装イメージ

ロジックを生成するクラス、ロジックのインターフェース/実装、呼び出し元の3つのクラスで構成していきます。

まずはロジックをもつインスタンスを返す、ファクトリクラスを用意しておきます。

ServiceFactory.java
public class ServiceFactory {

    /**
     * ファクトリ固有のシングルインスタンス。
     */
    private static final ServiceFactory serviceFactory = new ServiceFactory();

    private ServiceFactory() {}

    public static ServiceFactory getInstance() { return serviceFactory; }

    /**
     * ロジックの実体を返却します。
     */
    public TargetLogic getTargetLogic() {
        return new TargetLogicImpl();
    }

このファクトリから返すインターフェースおよびロジックを定義しておきます。
このへんはユースケースに応じて柔軟に定義すればよいのでさらっといきます。

TargetLogic.java
public interface TargetLogic {

    void consume();

}
TargetLogicImpl.java
public interface TargetLogicImpl implements TargetLogic {

    void consume() { 
        // なにか処理 
    }

}

最後に、ロジックの呼び出しをしていきます。

Application.java
public class Application {
    private final TargetLogic targetLogic = ServiceFactory.getInstance().getTargetLogic();
}

これにて、 Application クラスでロジックをファクトリを経由して取得できようになりました。

やってみてどうだったか?

当初の読みどおり、アプリケーションが小さめでプロジェクトの構造がシンプルだったので依存管理の方法がすごいちょうどよかったです。
今後ロジックの変更がある場合も、ファクトリにメソッドを足せば事足りるのでシンプルに整理できそうです。
どんなときにインスタンスを作るべきか?作らないべきか?について思考が深まったのは副次的な効果でした。

一方、プロジェクトがもうちょっと大きかったりレイヤーがより多い場合などは素直にSpringやGuiceのようなライブラリに頼るほうが素直ですね。
正直、機能しづらいプロジェクトのほうが多いかもしれないです。

参考

2
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
2
2