Convert sequential Monos to Flux - java

I have a web service where I want to retrieve the elements of a tree up to the root node.
I have a Webflux interface which returns a Mono on each call:
public interface WebService {
Mono<Node> fetchNode(String nodeId);
}
public class Node {
public String id;
public String parentId; // null, if parent node
}
Let's assume there is a tree
1
2 3
4 5
I want to create the following method:
public interface ParentNodeResolver {
Flux<Node> getNodeChain(String nodeId);
}
which would give me on getNodeChain(5) a Flux with the nodes for 5, 3 and 1 and then completes.
Unfortunately, I don't quite understand how I can combine Monos sequentially, but without blocking them. With Flux.generate(), I think I need to block on each mono to check whether it has a next element. Other methods which I've found seem to combine only a fixed number of Monos, but not in this recursive fashion.
Here is a sample code which would simulate the network request with some delay.
public class MonoChaining {
ExecutorService executorService = Executors.newFixedThreadPool(5);
#Test
void name() {
var nodeChain = generateNodeChainFlux("5")
.collectList()
.block();
assertThat(nodeChain).isNotEmpty();
}
private Flux<Node> generateNodeChainFlux(String nodeId) {
//TODO
return Flux.empty();
}
public Mono<Node> getSingleNode(String nodeId) {
var future =
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000); // Simulate delay
if ("5".equals(nodeId)) {
return new Node("5", "3");
} else if ("3".equals(nodeId)) {
return new Node("3", "1");
} else if ("1".equals(nodeId)) {
return new Node("1", null);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}, executorService);
return Mono.fromFuture(future);
}
public static class Node {
public String id;
public String parentId;
public Node(String id, String parentId) {
this.id = id;
this.parentId = parentId;
}
}
}
Is there a way to retrieve this?
Thanks!

The operator you are looking for is Mono#expand. It is used for recursively expanding sequences. Read more here.
In your case:
private Flux<Node> generateNodeChainFlux(String nodeId) {
return getSingleNode(nodeId).expand(node -> getSingleNode(node.parentId));
}

Using recursion with flatMap to get parent node and concat to append the current node to resulting flux might work. Try the code below:
public Flux<Node> getNodeChain(String nodeId) {
return fetchNode(nodeId).flatMapMany(node -> {
if (node.parent != null) {
Flux<Node> nodeChain = getNodeChain(node.parent);
return Flux.concat(Flux.just(node), nodeChain);
}
return Flux.just(node);
});
}
Here I'm using flatMapMany to convert Mono to Flux.

Related

How to manipulate a list attribute inside Mono as a flux?

I am new to project reactor and I am trying to manipulate some fields in a list as Flux inside a Mono.
So I have Mono<Basket> with an attribute called lines which is a List<Line>.
Then, for every line I have to call two external services to get some additional info.
Here is my code:
Mono<Basket> basketMono = //this doesn't work cause I map it to a Flux
Mono.just(basket)
.flatMapIterable(Basket::getLines)
.parallel(...)
.runOn(...)
.map((line) -> {
line.setInfo1(externalService1.getInfo());
line.setInfo2(externalService2.getInfo());
return line;
});
My main problem here is that I don't know how to set this additional info to the lines and keep the original object so the method that holds this code can return the Mono<Basket> with all the additional info setted.
I am struggling with this. Is this approach right? Some help would be more than appreciate.
A simple solution is you dont flatmap the lines, because it will create a new publisher with n element (and type line). Within flatmap, you can start another publisher, which goes to services, set data and then you can return the original object.
Mono<Basket> basketMono = Mono.just(basket)
.flatMap(b ->
Flux.fromIterable(b.items)
.flatMap(this::callService1)
.flatMap(this::callService2)
.then(Mono.just(b))
);
I suppose your external service calls are reactive, something like this:
Mono<Item> callService1(Item item) {
return mockService1().zipWith(Mono.just(item))
.map(it -> {
var result = it.getT2();
result.setInfo1(it.getT1());
return result;
});
}
Mono<String> mockService1() {
return Mono.just("some data " + ThreadLocalRandom.current().nextInt(100)).delayElement(Duration.ofMillis(100));
}
Note that flatMap will automatically subscribe to the inner publisher.
Also I've created a simple example, you can test:
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class Example {
class Item {
String info1;
String info2;
public void setInfo1(String info1) {
this.info1 = info1;
}
public void setInfo2(String info2) {
this.info2 = info2;
}
public String getInfo1() {
return info1;
}
public String getInfo2() {
return info2;
}
}
class Basket {
String user;
List<Item> items;
public Basket(String user, List<Item> items) {
this.user = user;
this.items = items;
}
}
Mono<String> mockService1() {
return Mono.just("some data " + ThreadLocalRandom.current().nextInt(100)).delayElement(Duration.ofMillis(100));
}
Mono<String> mockService2() {
return Mono.just("some other data " + ThreadLocalRandom.current().nextInt(1000)).delayElement(Duration.ofMillis(100));
}
Mono<Item> callService1(Item item) {
return mockService1().zipWith(Mono.just(item))
.map(it -> {
var result = it.getT2();
result.setInfo1(it.getT1());
return result;
});
}
Mono<Item> callService2(Item item) {
return mockService2().zipWith(Mono.just(item))
.map(it -> {
var result = it.getT2();
result.setInfo1(it.getT1());
return result;
});
}
#Test
public void testBasket() {
var basket = new Basket("first", List.of(new Item(), new Item(), new Item()));
Mono<Basket> basketMono = Mono.just(basket)
.flatMap(b ->
Flux.fromIterable(b.items)
.flatMap(this::callService1)
.flatMap(this::callService2)
.then(Mono.just(b))
);
StepVerifier.create(basketMono)
.expectNextMatches(b -> b.items.get(0).info1 != null)
.verifyComplete();
}
}

Flux into Mono List Object - project reactor

I'm using project reactor and I've the next issue:
I've one method that return Mono<CustomerResponse> that contains a CustomerDto list, each client has attributes, one of theirs attributes is a payment list. But this payment list is null.
I've another method that receive client id and returns a Flux payment Flux<PaymentDto> for that client.
This are the model
public class CustomerResponse {
private List<CustomerDto> customers;
}
public class CustomerDto {
private int id;
private String fullname;
private String documentNumber;
private List<PaymentDto> payments;
}
These are the interfaces
public interface CustomerService {
public Mono<CustomerResponse> customerSearch(CustomerRequest request);
}
public interface PaymentService {
public Flux<PaymentDto> getPayments(int clientId);
}
This is my method
public Mono<CustomerResponse> getCustomer(CustomerRequest request) {
return customerService.customerSearch(request).map(resp -> resp.getCustomers())
.flatMap(customerList -> {
List<CustomerDto> newCustomerList = customerList.parallelStream().map(customer -> {
Flux<PaymentDto> paymentFlux =
paymentService.getPayments(customer.getId());
// Here: java.lang.IllegalStateException: block()/blockFirst()/blockLast()
customer.setPayments(paymentFlux.collectList().block());
return customer;
}).collect(Collectors.toList());
return Mono.just(new CustomerResponse(newCustomerList));
});
}
I've the next exception:
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-4
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
I would like to know if there is a non-blocking or optimal way to do it
You can refactor your code like this to avoid blocking call:
public Mono<CustomerResponse> getCustomer(CustomerRequest request) {
Flux<CustomerDto> customerDtoFluxEnriched = customerService.customerSearch(request)
.map(CustomerResponse::getCustomers).flatMapMany(Flux::fromIterable).flatMap(customerDto -> {
Flux<PaymentDto> paymentFlux = paymentService.getPayments(customerDto.getId());
Mono<List<PaymentDto>> paymentListMono = paymentFlux.collectList();
return paymentListMono.map(paymentList -> {
customerDto.setPayments(paymentList);
return customerDto;
});
});
return customerDtoFluxEnriched.collectList().map(customerList -> {
CustomerResponse customerResponse = new CustomerResponse();
customerResponse.setCustomers(customerList);
return customerResponse;
});
}

How can I merge both values using Single.flatMap?

I have this code:
private Single<Invoice> getInvoiceWithItems() {
return getInvoice().flatMap(invoice -> getItems(invoice)); // <--- Here, I need invoice and items
}
private Single<Invoice> getInvoice() { ... }
private Single<List<Item>> getItems(Invoice invoice) { ... }
I want to do something like invoice.setItems(items). I tried passing an extra function parameter to flatMap but it doesn't accept it.
How can I do it?
I found this solution, but I'm not sure if it is the best one:
private Single<Invoice> getInvoiceWithItems() {
return Single.zip(getInvoice(), getInvoice().flatMap(invoice -> getInvoiceItems(invoice)), (invoice, items) -> {
invoice.setItems(items);
return invoice;
});
}
private Single<Invoice> getInvoiceWithItems() {
return getInvoice().flatMap(invoice -> getItems(invoice).map(items -> {
invoice.setItems(items);
return invoice;
}));
}

Reactor Mono - execute parallel tasks

I am new to Reactor framework and trying to utilize it in one of our existing implementations. LocationProfileService and InventoryService both return a Mono and are to executed in parallel and have no dependency on each other (from the MainService). Within LocationProfileService - there are 4 queries issued and the last 2 queries have a dependency on the first query.
What is a better way to write this? I see the calls getting executed sequentially, while some of them should be executed in parallel. What is the right way to do it?
public class LocationProfileService {
static final Cache<String, String> customerIdCache //define Cache
#Override
public Mono<LocationProfileInfo> getProfileInfoByLocationAndCustomer(String customerId, String location) {
//These 2 are not interdependent and can be executed immediately
Mono<String> customerAccountMono = getCustomerArNumber(customerId,location) LocationNumber).subscribeOn(Schedulers.parallel()).switchIfEmpty(Mono.error(new CustomerNotFoundException(location, customerId))).log();
Mono<LocationProfile> locationProfileMono = Mono.fromFuture(//location query).subscribeOn(Schedulers.parallel()).log();
//Should block be called, or is there a better way to do ?
String custAccount = customerAccountMono.block(); // This is needed to execute and the value from this is needed for the next 2 calls
Mono<Customer> customerMono = Mono.fromFuture(//query uses custAccount from earlier step).subscribeOn(Schedulers.parallel()).log();
Mono<Result<LocationPricing>> locationPricingMono = Mono.fromFuture(//query uses custAccount from earlier step).subscribeOn(Schedulers.parallel()).log();
return Mono.zip(locationProfileMono,customerMono,locationPricingMono).flatMap(tuple -> {
LocationProfileInfo locationProfileInfo = new LocationProfileInfo();
//populate values from tuple
return Mono.just(locationProfileInfo);
});
}
private Mono<String> getCustomerAccount(String conversationId, String customerId, String location) {
return CacheMono.lookup((Map)customerIdCache.asMap(),customerId).onCacheMissResume(Mono.fromFuture(//query).subscribeOn(Schedulers.parallel()).map(x -> x.getAccountNumber()));
}
}
public class InventoryService {
#Override
public Mono<InventoryInfo> getInventoryInfo(String inventoryId) {
Mono<Inventory> inventoryMono = Mono.fromFuture(//inventory query).subscribeOn(Schedulers.parallel()).log();
Mono<List<InventorySale>> isMono = Mono.fromFuture(//inventory sale query).subscribeOn(Schedulers.parallel()).log();
return Mono.zip(inventoryMono,isMono).flatMap(tuple -> {
InventoryInfo inventoryInfo = new InventoryInfo();
//populate value from tuple
return Mono.just(inventoryInfo);
});
}
}
public class MainService {
#Autowired
LocationProfileService locationProfileService;
#Autowired
InventoryService inventoryService
public void mainService(String customerId, String location, String inventoryId) {
Mono<LocationProfileInfo> locationProfileMono = locationProfileService.getProfileInfoByLocationAndCustomer(....);
Mono<InventoryInfo> inventoryMono = inventoryService.getInventoryInfo(....);
//is using block fine or is there a better way to do?
Mono.zip(locationProfileMono,inventoryMono).subscribeOn(Schedulers.parallel()).block();
}
}
You don't need to block in order to get the pass that parameter your code is very close to the solution. I wrote the code using the class names that you provided. Just replace all the Mono.just(....) with the call to the correct service.
public Mono<LocationProfileInfo> getProfileInfoByLocationAndCustomer(String customerId, String location) {
Mono<String> customerAccountMono = Mono.just("customerAccount");
Mono<LocationProfile> locationProfileMono = Mono.just(new LocationProfile());
return Mono.zip(customerAccountMono, locationProfileMono)
.flatMap(tuple -> {
Mono<Customer> customerMono = Mono.just(new Customer(tuple.getT1()));
Mono<Result<LocationPricing>> result = Mono.just(new Result<LocationPricing>());
Mono<LocationProfile> locationProfile = Mono.just(tuple.getT2());
return Mono.zip(customerMono, result, locationProfile);
})
.map(LocationProfileInfo::new)
;
}
public static class LocationProfileInfo {
public LocationProfileInfo(Tuple3<Customer, Result<LocationPricing>, LocationProfile> tuple){
//do wathever
}
}
public static class LocationProfile {}
private static class Customer {
public Customer(String cutomerAccount) {
}
}
private static class Result<T> {}
private static class LocationPricing {}
Pleas remember that the first zip is not necessary. I re write it to mach your solution. But I would solve the problem a little bit differently. It would be clearer.
public Mono<LocationProfileInfo> getProfileInfoByLocationAndCustomer(String customerId, String location) {
return Mono.just("customerAccount") //call the service
.flatMap(customerAccount -> {
//declare the call to get the customer
Mono<Customer> customerMono = Mono.just(new Customer(customerAccount));
//declare the call to get the location pricing
Mono<Result<LocationPricing>> result = Mono.just(new Result<LocationPricing>());
//declare the call to get the location profile
Mono<LocationProfile> locationProfileMono = Mono.just(new LocationProfile());
//in the zip call all the services actually are executed
return Mono.zip(customerMono, result, locationProfileMono);
})
.map(LocationProfileInfo::new)
;
}

Best way to iterate two lists and extract few things?

I have two classes as shown below. I need to use these two classes to extract few things.
public final class ProcessMetadata {
private final String clientId;
private final String deviceId;
// .. lot of other fields here
// getters here
}
public final class ProcMetadata {
private final String deviceId;
private final Schema schema;
// .. lot of other fields here
}
Now I have below code where I am iterating above two classes and extracting schema given a clientId.
public Optional<Schema> getSchema(final String clientId) {
for (ProcessMetadata metadata1 : processMetadataList) {
if (metadata1.getClientId().equalsIgnoreCase(clientId)) {
String deviceId = metadata1.getDeviceId();
for (ProcMetadata metadata2 : procMetadataList) {
if (metadata2.getDeviceId().equalsIgnoreCase(deviceId)) {
return Optional.of(metadata2.getSchema());
}
}
}
}
return Optional.absent();
}
Is there any better way of getting what I need by iterating those two above classes in couple of lines instead of what I have? I am using Java 7.
You're doing a quadratic* search operation, which is inneficient. You can do this operation in constant time by first creating (in linear time) a mapping from id->object for each list. This would look something like this:
// do this once, in the constructor or wherever you create these lists
// even better discard the lists and use the mappings everywhere
Map<String, ProcessMetadata> processMetadataByClientId = new HashMap<>();
for (ProcessMetadata process : processMetadataList) {
processMetadataByClientId.put(process.getClientId(), process);
}
Map<String, ProcMetadata> procMetadataByDeviceId = new HashMap<>();
for (ProcMetadata metadata2 : procMetadataList) {
procMetadataByDeviceId.put(proc.getDeviceId(), proc);
}
Then your lookup simply becomes:
public Optional<Schema> getSchema(String clientId) {
ProcessMetadata process = processMetadataByClientId.get(clientId);
if (process != null) {
ProcMetadata proc = procMetadataByDeviceId.get(process.getDeviceId());
if (proc != null) {
return Optional.of(proc.getSchema());
}
}
return Optional.absent();
}
In Java 8 you could write it like this:
public Optional<Schema> getSchema(String clientId) {
return Optional.fromNullable(processMetadataByClientId.get(clientId))
.map(p -> procMetadataByDeviceId.get(p.getDeviceId()))
.map(p -> p.getSchema());
}
* In practice your algorithm is linear assuming client IDs are unique, but it's still technically O(n^2) because you potentially touch every element of the proc list for every element of the process list. A slight tweak to your algorithm can guarentee linear time (again assuming unique IDs):
public Optional<Schema> getSchema(final String clientId) {
for (ProcessMetadata metadata1 : processMetadataList) {
if (metadata1.getClientId().equalsIgnoreCase(clientId)) {
String deviceId = metadata1.getDeviceId();
for (ProcMetadata metadata2 : procMetadataList) {
if (metadata2.getDeviceId().equalsIgnoreCase(deviceId)) {
return Optional.of(metadata2.getSchema());
}
}
// adding a break here ensures the search doesn't become quadratic
break;
}
}
return Optional.absent();
}
Though of course using maps ensures constant-time, which is far better.
I wondered what could be done with Guava, and accidentally wrote this hot mess.
import static com.google.common.collect.Iterables.tryFind
public Optional<Schema> getSchema(final String clientId) {
Optional<String> deviceId = findDeviceIdByClientId(clientId);
return deviceId.isPresent() ? findSchemaByDeviceId(deviceId.get()) : Optional.absent();
}
public Optional<String> findDeviceIdByClientId(String clientId) {
return tryFind(processMetadataList, new ClientIdPredicate(clientId))
.transform(new Function<ProcessMetadata, String>() {
String apply(ProcessMetadata processMetadata) {
return processMetadata.getDeviceId();
}
});
}
public Optional<Schema> findSchemaByDeviceId(String deviceId) {
return tryFind(procMetadataList, new DeviceIdPredicate(deviceId.get())
.transform(new Function<ProcMetadata, Schema>() {
Schema apply(ProcMetadata procMetadata) {
return processMetadata.getSchema();
}
});
}
class DeviceIdPredicate implements Predicate<ProcMetadata> {
private String deviceId;
public DeviceIdPredicate(String deviceId) {
this.deviceId = deviceId;
}
#Override
public boolean apply(ProcMetadata metadata2) {
return metadata2.getDeviceId().equalsIgnoreCase(deviceId)
}
}
class ClientIdPredicate implements Predicate<ProcessMetadata> {
private String clientId;
public ClientIdPredicate(String clientId) {
this.clientId = clientId;
}
#Override
public boolean apply(ProcessMetadata metadata1) {
return metadata1.getClientId().equalsIgnoreCase(clientId);
}
}
Sorry.

Categories

Resources