A Backend-for-Frontend (BFF) service that aggregates downstream APIs with resilience patterns, observability, and graceful degradation. Built with Spring Boot 3, WebFlux, and Resilience4j.
- Aggregation API — Single
/api/dashboardendpoint that combines user and product data from two backend services. - Resilience4j — Circuit breaker and retry per downstream service; configurable via
application.yml. - Fallback responses — When a backend is down or the circuit is open, the BFF returns partial data (empty user or empty products) instead of failing the whole request.
- Reactive stack — Spring WebFlux and
WebClientfor non-blocking I/O and backpressure. - Observability — Spring Boot Actuator, Prometheus metrics, and Grafana-ready dashboards; optional Docker Compose stack for Prometheus + Grafana.
- Tests — Unit tests for services (mocked
WebClient) and integration tests for the BFF controller (@WebFluxTest,WebTestClient). - One-command run —
docker compose up -d --buildruns BFF, mock backends, Prometheus, and Grafana.
┌─────────────┐
│ Client │
└──────┬──────┘
│ GET /api/dashboard
▼
┌──────────────────────────────────────────────────────────────┐
│ BFF (8085) │
│ WebFlux · WebClient · Resilience4j · Actuator · Prometheus │
└───────┬────────────────────────────────────┬─────────────────┘
│ │
│ Circuit breaker + Retry │ Circuit breaker + Retry
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ mock-user-service │ │ mock-product-svc │
│ (8082) │ │ (8084) │
│ GET /api/users/me│ │ GET /api/products│
└───────────────────┘ └───────────────────┘
│ │
└────────────────┬───────────────────┘
│ scrape /actuator/prometheus
▼
┌─────────────────┐ ┌─────────┐
│ Prometheus │────▶│ Grafana │
│ (9090) │ │ (3000) │
└─────────────────┘ └─────────┘
| Layer | Technology |
|---|---|
| Runtime | Java 21 |
| Framework | Spring Boot 3.2, Spring WebFlux |
| Resilience | Resilience4j (circuit breaker, retry) |
| HTTP Client | WebClient (reactive) |
| Metrics | Micrometer, Prometheus |
| Build | Gradle (Kotlin DSL) |
| Containers | Docker, Docker Compose |
- Java 21+ (for local run and tests)
- Docker & Docker Compose (for full stack)
From the project root:
docker compose up -d --buildThis builds and starts:
- mock-user-service (8082)
- mock-product-service (8084)
- bff (8085)
- Prometheus (9090)
- Grafana (3000)
Then open:
- BFF API: http://localhost:8085/api/dashboard
- Prometheus: http://localhost:9090
- Grafana: http://localhost:3000 (login:
admin/admin)
In Grafana, add a Prometheus data source with URL: http://prometheus:9090.
- Start mock services and BFF (each in its own terminal):
./gradlew :mock-user-service:bootRun # port 8082
./gradlew :mock-product-service:bootRun # port 8084
./gradlew :bff:bootRun # port 8085- Call the BFF:
curl http://localhost:8085/api/dashboard- (Optional) Run Prometheus and Grafana via Docker for metrics:
docker compose up -d prometheus grafanaUse prometheus.yml (target localhost:8085) when Prometheus runs on the host; use prometheus-docker.yml (target bff:8085) when everything runs in Docker.
Returns aggregated user and product data.
Response (200 OK):
{
"user": {
"id": 1,
"name": "John",
"email": "john.email@email"
},
"products": [
{ "id": 1, "name": "milk", "price": 20 },
{ "id": 2, "name": "bread", "price": 40 },
{ "id": 3, "name": "coke", "price": 60 }
]
}If a backend is unavailable, the BFF returns the same structure with fallback values (e.g. user with null fields or products as an empty array) and still responds with 200 OK.
- Health: http://localhost:8085/actuator/health
- Prometheus metrics: http://localhost:8085/actuator/prometheus
| Property | Description | Default (BFF) |
|---|---|---|
server.port |
BFF server port | 8085 |
user-service.base-url |
User service base URL | http://localhost:8082 |
product-service.base-url |
Product service base URL | http://localhost:8084 |
Override in Docker with environment variables: USER_SERVICE_BASE_URL, PRODUCT_SERVICE_BASE_URL.
Resilience4j (circuit breaker, retry) is configured in bff/src/main/resources/application.yml under resilience4j.*.
resilient_bff/
├── bff/ # BFF service (WebFlux, Resilience4j, Actuator)
│ ├── src/main/java/bff/
│ ├── src/test/java/bff/
│ └── Dockerfile
├── mock-user-service/ # Mock user API
├── mock-product-service/ # Mock products API
├── docker-compose.yml # BFF + mocks + Prometheus + Grafana
├── prometheus.yml # Prometheus config (host)
├── prometheus-docker.yml # Prometheus config (Docker network)
├── build.gradle.kts
└── settings.gradle.kts
./gradlew :bff:test- UserServiceTest — success and fallback with mocked
WebClientand StepVerifier. - ProductServiceTest — same for product list and empty fallback.
- BffControllerTest —
@WebFluxTestwith mocked services; checks 200 and JSON shape for full and fallback responses.
- Prometheus scrapes BFF at
/actuator/prometheus(jobbff). - Grafana can use the JVM (Micrometer) or Spring Boot dashboards (e.g. import ID
4701or11378) with the Prometheus data source. - Resilience4j metrics (e.g. circuit breaker state) are exposed via the same actuator endpoint.