Senior
There is a backend, there is a UI. The stack is standard: Java, Spring, React, Postgres. Task: Design a REST endpoint that takes data from 3 sources and returns it to the UI, while ensuring maximum throughput and minimal response time (max/min criteria based on our specific conditions, since there is no silver bullet and we need to find a compromise). It is known that the expected peak RPS for this REST endpoint will be 200. Details about sources: 1st source - our database, query takes about 15 seconds. 2nd source - a related REST service, degrades to 2 minutes at 100 RPS, normally responds in 5 seconds. We cannot change this source's behavior. 3rd source - another related REST service, randomly (no pattern found) returns 503, normally responds in 10 seconds. We cannot change this source's behavior.
sobes.tech AI
Answer from AI
For designing a REST endpoint with three data sources, considering throughput and response time requirements, I propose the following approach:
-
Asynchronous parallel requests to sources:
- Requests to the database and two REST services are executed in parallel to minimize overall waiting time.
-
Caching:
- For the source with a long database query (15 seconds), implement caching of results with a reasonable TTL to reduce load during peak requests.
-
Timeouts and fallback:
- For the second source, which degrades under high load, set timeouts (e.g., 5-7 seconds) and use cache or return partial data if the service does not respond.
- For the third source, which occasionally returns 503, implement retries with exponential backoff and fallback to cache or default data.
-
Reactive programming:
- Use Spring WebFlux or CompletableFuture for non-blocking execution of requests and aggregation of results.
-
Data aggregation and response:
- After receiving data from all sources, aggregate them into a single response.
- If any source is unavailable, return partial data with a status indicator.
-
Monitoring and load limiting:
- Implement a circuit breaker (e.g., Resilience4j) to protect against degradation of related services.
- Limit the number of concurrent requests to problematic sources.
Example of using CompletableFuture for parallel requests:
CompletableFuture<Data1> future1 = CompletableFuture.supplyAsync(() -> fetchFromDb());
CompletableFuture<Data2> future2 = CompletableFuture.supplyAsync(() -> fetchFromService2());
CompletableFuture<Data3> future3 = CompletableFuture.supplyAsync(() -> fetchFromService3());
CompletableFuture.allOf(future1, future2, future3).join();
// Process results considering errors and timeouts
Data1 d1 = future1.getNow(null);
Data2 d2 = future2.getNow(null);
Data3 d3 = future3.getNow(null);
// Form the response
return aggregate(d1, d2, d3);
This approach balances response speed and reliability, taking into account the limitations and characteristics of each source.