YoungCarrdinal
YoungCarrdinal

Reputation: 60

Combine two Mono's together where the second mono is subscribed to the first one

I have recently been learning reactive programming using the reactor libraries in Java with the Spring framework and for the most part I have been able to get to grips with it. However, I've found myself in the same situation a couple of times and would like some advice on where I am going wrong.

The gist of what I'm struggling with is often I want to do something with a mono such as finding some complimentary data and then add it back into the original mono. The zip function appears to me as the ideal candidate but then I end up subscribing to the original mono twice which is not my intention.

Here's a contrived example of the type of situation I've been trying to solve as I am unable to share my companies code. It assumes we are using a reactive data library and have a logger set up and the Person class is immutable but has with<FieldName> methods.

public Mono<Person> getPersonWithFamilyMembers(Integer id){
    log.info("Finding person with id {}", id);

    personRepository.findById(id)
        .switchIfEmpty(Mono.error(NotFoundException::new))
        .doOnNext(person -> log.info("Found person: {}", person))
        .as(this::fetchAndAddFamilyMembers)
        .doOnSuccess(person -> log.info("Successfully found person with family members"));
}

private Mono<Person> fetchAndAddFamilyMembers(Mono<Person> personMono){
    Mono<List<Person>> familyMembersMono = personMono
        .map(Person::getFamilyId)
        .flatMapMany(PersonRepository::findByFamilyId)
        .collectList();

    return personMono.zipWith(familyMembersMono, Person::withFamilyMembers);
}

The output I see when running code like this is:

INFO | Finding person with id 1
INFO | Found person: Person(id=1, familyId=1, familyMembers=[])
INFO | Found person: Person(id=1, familyId=1, familyMembers=[])
INFO | Successfully found person with family members

This does make sense as the original person mono has been subscribed to in two places, where I map it into the familyMembersMono and when I zip them together, but I don't want to make unnecessary calls to the repository if I can avoid it.

Does anybody have a suggestion for a better way of handling this sort of behaviour?

Upvotes: 0

Views: 2466

Answers (1)

Panda
Panda

Reputation: 887

In general, you don't "add data" to a Mono, but the data within. With that in mind, use flatMap instead of as:

public Mono<Person> getPersonWithFamilyMembers(Integer id){
    log.info("Finding person with id {}", id);

    return personRepository.findById(id)
        .switchIfEmpty(Mono.error(NotFoundException::new))
        .doOnNext(person -> log.info("Found person: {}", person))
        .flatMap(this::fetchAndAddFamilyMembers)
        .doOnSuccess(person -> log.info("Successfully found person with family members"));
}

private Mono<Person> fetchAndAddFamilyMembers(Person person){ // this accepts Person, not Mono<Person>
    return personRepository.findByFamilyId(person.getFamilyId())
        .collectList()
        .map(person::withFamilyMembers);
}

Upvotes: 1

Related Questions