Abhinaba Chakraborty
Abhinaba Chakraborty

Reputation: 3671

How to refactor nested flatmaps when I need both context and mono response of a library function?

I have a reactive web-application (Spring WebFlux) where I am storing user profile image through a POST API in Azure storage. I need to create a storage bucket for the user if it doesn't exist.

The service layer method looks something like this: (which contains nested flatMaps - which is considered a bad practice in Reactive apps and is a code smell.) I want to refactor out such that there is no nested flatMap. Is there any way to do this?

public Mono<BlockBlobItem> processAndUpload(FilePart filePart) {

    return Mono.subscriberContext().flatMap(context -> {
      String userId = context.get(USER_ID_CONTEXT);
      BlobContainerAsyncClient blobContainerClient = blobServiceAsyncClient.getBlobContainerAsyncClient(userId);

      return blobContainerClient.exists()
          .doOnEach(logOnNext(doesContainerExist -> {
            if (doesContainerExist) {
              LOGGER.info(STORAGE_CONTAINER_EXISTS_MSG);
            } else {
              LOGGER.info(STORAGE_CONTAINER_DOES_NOT_EXIST_MSG);
            }
          }))
          .doOnEach(logOnError(err -> LOGGER.error(CONTAINER_CHECK_FAILURE_MSG, err.getMessage(), err)))
          .flatMap(doesContainerExist -> {
            if (doesContainerExist) {
              return uploadFile(filePart, blobContainerClient, userId);
            } else {
              return blobContainerClient.createWithResponse(null, null)
                  .doOnEach(logOnComplete(response -> LOGGER.info(CONTAINER_CREATION_SUCCESS_MSG)))
                  .doOnEach(logOnError(err -> LOGGER.error(CONTAINER_CREATION_FAILURE_MSG, err.getMessage(), err)))
                  .then(uploadFile(filePart, blobContainerClient, userId));
            }
          });
    });
  }

Upvotes: 1

Views: 958

Answers (1)

Nikolas
Nikolas

Reputation: 44368

I am afraid the nested flatMap is the only way to go as long as you need the both contexts.

If you speak about refactoring, I'd move the lambda expressions (or their right sides at least) out of the method to achieve readability. Also consider use map first to get the userId as long as you don't need the initial context.

public Mono<BlockBlobItem> processAndUpload(FilePart filePart) {

    return Mono.subscriberContext()
        .map(context -> context.get(USER_ID_CONTEXT))
        .flatMap(userId -> {
                var client = blobServiceAsyncClient.getBlobContainerAsyncClient(userId);
                return client.exists()
                    .doOnEach(logOnNext(doesContainerExistLambda))
                    .doOnEach(logOnError(errLambda))
                    .flatMap(doesExist -> existenceHandler(filePart, client, userId));
                }
        );
}

The methods and lambda expressions named doesContainerExistLambda, errLambda, existenceHandler are subjects of change based on your needs and consideration. The point of the code snippet is to highligh what can be moved elsewhere.

Upvotes: 1

Related Questions