Chain of Responsibilityパターンで肥大化したServiceクラスを分解する

# 概要

- 業務ロジックの多いServiceクラスはどうしても肥大化しがち
- 一つのクラスが大きくなるとServiceクラス自体もそのテストもメンテがしずらくなる…

→ デザインパターンの「Chain of Responsibility」を使って単一責務のクラスに分解してみる！

# 前提

以下のようなサービスクラスを想定します

```SampleService1.java
@Service
public class SampleService1 {
    public void execute() {
        try {
            System.out.println("APIコール①");
        } catch (Exception ex) {
            System.out.println("APIエラー①");
            return;
        }

        try {
            System.out.println("DB登録①");
        } catch (Exception ex) {
            System.out.println("DBエラー①");
            return;
        }

        try {
            System.out.println("APIコール②");
        } catch (Exception ex) {
            System.out.println("APIエラー②");
            return;
        }

        try {
            System.out.println("DB登録②");
        } catch (Exception ex) {
            System.out.println("DBエラー②");
            return;
        }
    }
}
```

APIコールやDB登録は、ここではログ出力で表しておきます。
それぞれの処理で例外が発生したらそこで処理を終わらせる必要があるようなロジックです。

> 実際の業務でよく見るのは、冪等処理のために「既存データがあればそれを返して終わり」というのをロジックの一番最初にやるパターンです

これをChain of Responsibilityパターンで改善していきます。

# 実装

まず、chainするクラスの元となる抽象クラスを作成します

```java:AbstractChain.java
public abstract class AbstractChain {
    // chainする次のクラスを保持
    protected AbstractChain next = null;
    
    // nextを登録する
    public AbstractChain setNext(AbstractChain next) {
        this.next = next;
        return next;
    }
    
    // 処理順を定義するために、getOrderを実装させる
    public abstract int getOrder();
    
    // 業務ロジック
    // 自身の業務が終わったらnext.chain()を呼び出す
    // 後続の業務が不要な場合はreturnする
    public abstract void chain();
}
```

それでは、APIコールやDB登録の責務をもつクラスを`AbstractChain`を継承して実装してみます

```java:Api1Chain
@Component
public class Api1Chain extends AbstractChain {
    @Override
    public int getOrder() {
        return 1;
    }

    @Override
    public void chain() {
        try {
            System.out.println("APIコール①");
        } catch (Exception ex) {
            System.out.println("APIエラー①");
            return;
        }

        next.chain();
    }
}
```

```java:Api2Chain
@Component
public class Api2Chain extends AbstractChain {
    @Override
    public int getOrder() {
        return 3;
    }

    @Override
    public void chain() {
        try {
            System.out.println("APIコール②");
        } catch (Exception ex) {
            System.out.println("APIエラー②");
            return;
        }

        next.chain();
    }
}
```

```java:Db1Chain
@Component
public class Db1Chain extends AbstractChain {
    @Override
    public int getOrder() {
        return 2;
    }

    @Override
    public void chain() {
        try {
            System.out.println("DB登録①");
        } catch (Exception ex) {
            System.out.println("DBエラー①");
            return;
        }

        next.chain();
    }
}
```

```java:Db2Chain
@Component
public class Db2Chain extends AbstractChain {
    @Override
    public int getOrder() {
        return 4;
    }

    @Override
    public void chain() {
        try {
            System.out.println("DB登録②");
        } catch (Exception ex) {
            System.out.println("DBエラー②");
            return;
        }
    }
}
```

> chainの最後になるクラスはnext.chainを実行しないか、実行しても安全に終了する仕組みは必要そう

最後に`AbstractChain`をServiceクラスから呼び出すように依存を注入します。

```java:SampleService2
@Service
public class SampleService2 {
    private final AbstractChain chainer;

    public SampleService2(List<AbstractChain> chainList) {
        this.chainer = chainList.stream()
                // orderの逆順ソート（reduceで最終的に一番小さいものを取得したいので）
                .sorted((c1, c2) -> c2.getOrder() - c1.getOrder())
                // 順々にnextを設定
                .reduce((c1, c2) -> {
                    c2.setNext(c1);
                    return c2;
                })
                .get();
    }

    public void execute() {
        chainer.chain();
    }
}
```

# メリットは？

個人的に思う一番のメリットというか、Chain of Responsibilityパターンで改善したなと思うことは
単体テストがスッキリすることです。

最初の状態だと、2 ** 4 = 16ケースのテストの実装が必要になりますが、
改修した状態だと、2 * 4 = 8ケースの実装で済みます。

ロジックが追加になった場合もクラスを一つ追加するだけで済みます！