resilience4jを使ったCircuitBreakerパターン

# What is Circuit Breaker?
あるアプリAが外部サービスであるアプリXと連携している際、アプリXがサービスダウンした場合に他サービスへ影響を出さないよう制御する仕組み。
# 状態遷移
![名称未設定.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/159675/3e5a5e01-dd54-f97f-8ea6-1c0e1e39ccea.png)



# 使用するデモ
https://github.com/resilience4j/resilience4j-spring-boot3-demo

# やってみる
health check
（`backendA`のステータスが`"state": "CLOSED"`になっていることが確認できる。）
```bash
curl http://localhost:9080/actuator/health | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   609  100   609    0     0   9755      0 --:--:-- --:--:-- --:--:-- 10684
{
  "status": "UP",
  "components": {
    "circuitBreakers": {
      "status": "UP",
      "details": {
        "backendB": {
          "status": "UP",
          "details": {
            "failureRate": "-1.0%",
            "failureRateThreshold": "50.0%",
            "slowCallRate": "-1.0%",
            "slowCallRateThreshold": "100.0%",
            "bufferedCalls": 0,
            "slowCalls": 0,
            "slowFailedCalls": 0,
            "failedCalls": 0,
            "notPermittedCalls": 0,
            "state": "CLOSED"
          }
        },
        "backendA": {
          "status": "UP",
          "details": {
            "failureRate": "-1.0%",
            "failureRateThreshold": "50.0%",
            "slowCallRate": "-1.0%",
            "slowCallRateThreshold": "100.0%",
            "bufferedCalls": 0,
            "slowCalls": 0,
            "slowFailedCalls": 0,
            "failedCalls": 0,
            "notPermittedCalls": 0,
            "state": "CLOSED"
          }
        }
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}
```

二回失敗させる
```bash
curl http://localhost:9080/backendA/failure | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   168  100   168    0     0    389      0 --:--:-- --:--:-- --:--:--   395
{
  "timestamp": "2023-01-02T01:23:12.760+00:00",
  "path": "/backendA/failure",
  "status": 500,
  "error": "Internal Server Error",
  "requestId": "2c384fce-3"
}
```
```bash
curl http://localhost:9080/backendA/failure | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   168  100   168    0     0    469      0 --:--:-- --:--:-- --:--:--   478
{
  "timestamp": "2023-01-02T01:23:37.328+00:00",
  "path": "/backendA/failure",
  "status": 500,
  "error": "Internal Server Error",
  "requestId": "7868a5ee-4"
}
```

再度health check
（`backendA`のステータスが`"status": "state": "OPEN"`になっていることが確認できる。）
```bash
curl http://localhost:9080/actuator/health | jq 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   617  100   617    0     0  21606      0 --:--:-- --:--:-- --:--:-- 28045
{
  "status": "UP",
  "components": {
    "circuitBreakers": {
      "status": "UP",
      "details": {
        "backendB": {
          "status": "UP",
          "details": {
            "failureRate": "-1.0%",
            "failureRateThreshold": "50.0%",
            "slowCallRate": "-1.0%",
            "slowCallRateThreshold": "100.0%",
            "bufferedCalls": 0,
            "slowCalls": 0,
            "slowFailedCalls": 0,
            "failedCalls": 0,
            "notPermittedCalls": 0,
            "state": "CLOSED"
          }
        },
        "backendA": {
          "status": "CIRCUIT_OPEN",
          "details": {
            "failureRate": "100.0%",
            "failureRateThreshold": "50.0%",
            "slowCallRate": "0.0%",
            "slowCallRateThreshold": "100.0%",
            "bufferedCalls": 5,
            "slowCalls": 0,
            "slowFailedCalls": 0,
            "failedCalls": 5,
            "notPermittedCalls": 1,
            "state": "OPEN"
          }
        }
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}
```

5秒経つと自動でHALF_OPENに移行する
```bash
curl http://localhost:9080/actuator/health | jq 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   627  100   627    0     0  20009      0 --:--:-- --:--:-- --:--:-- 25080
{
  "status": "UP",
  "components": {
    "circuitBreakers": {
      "status": "UP",
      "details": {
        "backendB": {
          "status": "UP",
          "details": {
            "failureRate": "-1.0%",
            "failureRateThreshold": "50.0%",
            "slowCallRate": "-1.0%",
            "slowCallRateThreshold": "100.0%",
            "bufferedCalls": 0,
            "slowCalls": 0,
            "slowFailedCalls": 0,
            "failedCalls": 0,
            "notPermittedCalls": 0,
            "state": "CLOSED"
          }
        },
        "backendA": {
          "status": "CIRCUIT_HALF_OPEN",
          "details": {
            "failureRate": "-1.0%",
            "failureRateThreshold": "50.0%",
            "slowCallRate": "-1.0%",
            "slowCallRateThreshold": "100.0%",
            "bufferedCalls": 2,
            "slowCalls": 0,
            "slowFailedCalls": 0,
            "failedCalls": 0,
            "notPermittedCalls": 0,
            "state": "HALF_OPEN"
          }
        }
      }
    },
    "ping": {
      "status": "UP"
    }
  }
}
```

ブラウザからhttp://localhost:9080/backendA/successにアクセスして成功リクエストを溜めるとCLOSEDに戻る。

# 参考
https://www.docswell.com/s/MasatoshiTada/Z861QK-introduction-to-resilience4j#p1