0

Here is the following flow in my code: User calls create agency endpoint, containing the agency name and a boolean field ->

Router functions receives call, passes to handler ->

Handler converts request body to Mono, passes that mono so a service ->

Service saves agency to DB, generating an ID in the process, then creates a Response object containing the ID wrapped in a Mono, returns Response back to the Handler.

This is the part where I am stuck. Maintaining a reactive approach, I must save the Agency to Db and create the ID within a "doOnNext() part of the Mono. However, I can't return the ID that I get from the DB in order to create the response object.

What is the reactive way to accomplish this? Thanks in advance!

public RouterFunction<ServerResponse> createAgency() {
        return RouterFunctions
                .route(POST("/agency").and(accept(MediaType.APPLICATION_JSON)), handler::createAgency);
    }
@Component
@AllArgsConstructor
public class AgencyHandler {

    private final AgencyServiceImpl service;

    @NonNull
    public Mono<ServerResponse> createAgency(ServerRequest request){
        Mono<CreateAgencyRequest> agency = request.bodyToMono(CreateAgencyRequest.class);

        Mono<CreateAgencyResponse> response = service.createAgency(agency);

        return ServerResponse.created(URI.create("/agency"))
                .contentType(MediaType.APPLICATION_JSON)
                .body(response, CreateAgencyResponse.class);
    }
}
@Service
@AllArgsConstructor
public class AgencyServiceImpl implements AgencyService {
    private final AgencyRepository agencyRepository;
    private final UuidGenerator uuidGenerator;

    public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
        UUID id;
        request.doOnNext(agency -> {
            UUID agencyId = uuidGenerator.getUUID();
            Mono<Agency> newAgency = Mono.just(
                new Agency(
                    agencyId,
                    agency.getFields().getName()
            ));

            //repository.save(newAgency)

            //return Mono.just(new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString())));
        });

        // return request
        return Mono.just(new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString())));
    }
}
Toerktumlare
  • 12,548
  • 3
  • 35
  • 54
Aluxxen
  • 63
  • 6

3 Answers3

1

Something like the following should do the trick:

@Service
@AllArgsConstructor
public class AgencyServiceImpl implements AgencyService {
    private final AgencyRepository agencyRepository;
    private final UuidGenerator uuidGenerator;

    public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
        UUID agencyId = uuidGenerator.getUUID();

        return request.flatMap(createAgencyRequest -> {
            Agency agency = new Agency(agencyId, agency.getFields().getName();
            return repository.save(newAgency);    
        }).map(saved ->
            new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString()));
        )
    }
}

You would create the Agency in the flatMap operation and store it in the database. I assume your repository is also reactive so it should return Mono as well, hence the flatMap operation. Afterwards you just need to map whatever the repository returned (you may want to have some logic here based on the successful operation on the database) to create the CreateAgencyResponse.

João Dias
  • 16,277
  • 6
  • 33
  • 45
  • So the reason this wasn’t clear to me, was I couldn’t figure out how to return the CreateAgencyResponse to the handler, since I couldn’t put a return function inside the “request.flatMap”. But it seems within your code, the map function will return back up to the original return function and go on from there. I guess that’s where I’m confused. I thought the map function only modified the value(s) passed along the mono to the next Consumer or Predicate, etc. I was not under the impression that the map function actually “returned” anything. Can you explain this a little? – Aluxxen Nov 13 '21 at 01:34
  • 1
    Both do return objects. As you might read in the documentation (https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html), `flatMap` is described as "Transform the item emitted by this Mono asynchronously, returning the value emitted by another Mono (possibly changing the value type)." and `map` as "Transform the item emitted by this Mono by applying a synchronous function to it.". The transformation function can be anything really and you don't even need to return the same type. They are exactly that, mappers, and you decide to what they should be mapped. – João Dias Nov 13 '21 at 01:50
  • Interesting. This was where my misunderstanding was and why I missed the solution myself. Thanks so much! – Aluxxen Nov 13 '21 at 01:52
  • 1
    Glad I could help. If you feel like it, consider upvoting my answer and even accepting it as the correct answer so that it can be ”closed” and others can benefit from it and easily understand which could be a possible solution for similar questions ;) Thanks! – João Dias Nov 13 '21 at 01:55
0

ThedoOnNext is not a good option to perform I/O bound operations such as database access. You should use flatmap instead. Also, have a look at map operator for synchronous, 1-to-1 transformations.

The final code should looks like this:

  public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
    return request.map(req -> new Agency(uuidGenerator.getUUID(), req.getFields().getName()))
        .flatMap(agency -> repository.save(agency))
        .map(agency -> new CreateAgencyResponse(new CreateAgencyResponseData(agency.getAgencyId())));
  }
lkatiforis
  • 5,703
  • 2
  • 16
  • 35
-1

doOnNext is for side effects, what you are looking for is flatMap

return request.flatMap(agency -> {
    final UUID agencyId = uuidGenerator.getUUID();
    return repository.save(new Agency(agencyId, agency.getFields().getName()))
        .thenReturn(agencyId);
}).flatMap(id -> ServerResponse.ok()
    .bodyValue(new CreateAgencyResponseData(agencyId.toString()))
    .build());

Wrote this without a compiler to check the code but you should get the gist of it.

Toerktumlare
  • 12,548
  • 3
  • 35
  • 54