Search code examples
spring-bootspring-webfluxproject-reactorreactive

Ensure unique id by checking value in DB which returns mono


There is a requirement to generate a unique id based on a format received from request. Before inserting to DB, need to check if it already present, then generate a new if not present in DB. Currently, using Springboot 3, reactive.

Need some guidance on following code.

Expectations:

  • Create a unique id based on format.
  • Check if record already present in DB
  • Generate a new id if already present
  • Try generating a new id 10 times, otherwise error

How to check record exists in DB and keep generating until filter condition match?


// DB CRUD operation service for Group table
// Group record is unique by groupId and type
@Autowired
final GroupService groupService;

public String newUniqueId(String format, String groupId) {

    // try 10 times to generate a unique id
    return IntStream.range(0, 10)
        .mapToObj(i -> newIdByFormat(format))
        .filter(newIdByFormat -> isIdUnique(newIdByFormat, groupId)) // Need some guidence here
        .findFirst()
        .orElseThrow(() -> new RuntimeException("Could not generate unique id"));
}

private String newIdByFormat(String format) {
    // Map<String, Supplier<String>> map;
    return map.get(format).get();
}

private Predicate<String> isIdUnique(String groupId) {
    // This call returns, Mono<Group> group = groupService.query(id, groupId)

    return id -> groupService.query(id, groupId)
    .filter(Objects::isNull); // How to return boolen value here?
    
   // tried calling .block() here, but fails with exception
   // block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-4
}



public Mono<Group> createGroup(Request) {

    return transformRequest(request)
}

private Mono<Group> transformRequest(request) {
  String uniqueId = newUniqueId(request.format, request.groupId);
  
  return dbService.save(new Group(uniqueId));
}

Thank you for your time.


Solution

  • private Mono<Group> transformRequest(request) {
       // newUniqueId() must return a Mono. 
       // otherwise you start to execute the code right in this method, which is not reactive. 
       // Ex., a non-mono executed even if the result is cancelled. 
       // Reactive code executed only when it actually needed. Like lazily
       Mono<String> uniqueId = newUniqueId(request.format, request.groupId);
    
       return uniqueId.map { id ->
           new Group(id)
       }.flatMap { group ->
          // .save() should be a Mono
          dbService.save(group).thenReturn(group)
       }
    }
    
    public Mono<String> newUniqueId(String format, String groupId) {
        Mono.fromCallable {
            // Callable executed only when it's actually subscribed
            newIdByFormat(format)
        }.flatMap { id ->
            groupService.query(id, groupId).retry(10)
        }         
    }