Search code examples
kotlinproject-reactorarrow-kt

Can you convert this code to monad comprehensions using Arrow FX?


Can you convert this reactive method to an Arrow Fx Project Reactor monad comprehension?

class ApplicationServiceImpl(private val applicationRepository: ApplicationRepository,
                             private val clientRepository: ClientRepository) : ApplicationService {

    override fun findByProjectId(clientId: String, projectId: String): Flux<ApplicationOut> {
        return clientRepository.findById(clientId)
                .switchIfEmpty(ClientDoesntExistException(clientId).toMono())
                .flatMapMany { client ->
                    applicationRepository.findByProjectIdOrderByNameAsc(projectId).map {
                        it.convertToApplicationOut(client.timeZone)
                    }
                }
    }

}

I've tried something like this but it isn't valid.

The first problem I've found is, originally I converted a Mono to a Flux using flatMapMany. If I use FluxK.monad().fx.monad clientRepository.findById(clientId).k().bind() doesn't have a .bind() function available.

If I use MonoK.monad().fx.monad instead I don't know how to convert the output to a Flux:

    override fun findByProjectId(clientId: String, projectId: String): Flux<out ApplicationOut> {

        return FluxK.monad().fx.monad {
            val client = clientRepository.findById(clientId).k().bind()
            if (client != null) {
                !applicationRepository.findByProjectIdOrderByNameAsc(projectId)
                        .map { it.convertToApplicationOut(client.timeZone) }.k()
            } else {
                throw !ClientDoesntExistException(clientId).toMono<ApplicationOut>().k()
            }
        }.fix().flux

UPDATE

Following El Paco's answer I've modified the code to:

        return FluxK.monad().fx.monad {
            val client = !clientRepository.findById(clientId).toFlux().k()
            if (client != null) {
                !applicationRepository.findByProjectIdOrderByNameAsc(projectId)
                        .map { it.convertToApplicationOut(client.timeZone) }.k()
            } else {
                !ClientDoesntExistException(clientId).toFlux<ApplicationOut>().k()
            }
        }.fix().flux

It works fine when clientRepository.findById(clientId) exists. When it doesn't, instead of assigning null to val client, it exits the comprehension (else isn't executed) so I can't launch my exception from inside the comprehension, which I guess is normal.

Consider the following method, in which I need to control two situations where the client or the project don't exist:

    return clientRepository.findById(clientId)
            .switchIfEmpty(ClientDoesntExistException(clientId).toMono())
            .flatMapMany { client ->
                applicationRepository.findByProjectIdOrderByNameAsc(projectId)
                        .switchIfEmpty(ClientDoesntExistException(projectId).toMono())
                        .map { it.convertToApplicationOut(client.timeZone) }
            }

How can I deal with those non-existence cases when using comprehensions, taking into account that the if/else approach doesnt work?

I can use the switchIfEmpty operator outside the comprehension but I wouldn't know the cause (client doesn't exist vs project doesn't exist)

    return FluxK.monad().fx.monad {
        val client = !clientRepository.findById(clientId).toFlux().k()
        !applicationRepository.findByProjectIdOrderByNameAsc(projectId)
              .map { it.convertToApplicationOut(client.timeZone) }.k()        
    }.fix().flux
            .switchIfEmpty(XXX)

Solution

  • You should convert all operations to Flux then, in your case clientRepository.findById(clientId).toFlux().k()

    Also, you don't need to throw that exception to break the chain.

    !ClientDoesntExistException(clientId).toMono<ApplicationOut>().k()