Why Resilience Patterns

Cascading Failure

In a monolith, a slow method slows one feature. In microservices, a slow service can take down everything that calls it. If payment-service hangs for 30 seconds, every order-service thread waits on it; soon order-service cannot answer anyone, then the gateway runs out of connections, and the whole site is down because of one service.

payment-service: slow (30 s per call)
        ^ waiting...
 order-service:   200/200 threads blocked  -> new requests queue up
        ^ waiting...
 api-gateway:     connections pile up      -> users see spinning pages
        ^
 users click "Place order" again and again -> even more load

The Toolbox

PatternQuestion it answersShopEase use
Timeout / Time limiterHow long do I wait?Give up on product-service after 2 s
RetryWas it a short glitch?Retry GET product 3 times with backoff
Circuit breakerIs it broken right now? Stop calling!Stop calling payment-service for 10 s after many failures
Rate limiterAm I calling too fast?Max 20 calls/s to the bank API (its contract limit)
BulkheadHow many calls at the same time?Max 10 concurrent payment calls, so other features keep threads
FallbackWhat do I return instead?Cached product, "payment pending" status

Resilience4j is the standard library for this in Spring Boot 3 (Netflix Hystrix is dead since 2018). Add it to order-service:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>  <!-- the annotations work through AOP -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>