Search code examples
javaquarkusmutinyhibernate-reactive

Quarkus: How to change a method from imperative to reactive


I build a small Rest API to understand more about the Quarkus framework. Now I would like to start using the framework with its reactive API but I'm struggling to understand some concepts. Currently the project is using RESTEasy Reactive with Jackson, Hibernate Reactive with Panache and the Postgresql Reactive Client.

This are my classes

@Table(name = "cat_role")
@Entity
public class Role extends PanacheEntityBase {
    private static final long serialVersionUID = -2246110460374253942L;

    @Id
    @Column(name = "id", nullable = false, updatable = false)
    @GeneratedValue
    public UUID id;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "name", nullable = false, length = 18)
    public UserRole name;

    public enum UserRole {
        Administrador, Asesor_Empresarial, Asesor_Academico, Alumno
    }
    
}

Now in my service (imperative way) I do the following:

Role.class

    public static Boolean existsRoleSeed(){
        return Role.count() > 0;
    }

RoleService.class

    @Transactional
    public void seedRoles() {
        if (!Role.existsRoleSeed()) {
            for(Role.UserRole userRole: Role.UserRole.values()){
                Role role = Role.builder()
                        .name(userRole)
                        .build();
                
                role.persist();
            }
        }
    }

This will obviously register all roles from the UserRole enum in the database and it is working correctly. What I am trying to achieve is to replicate this method but using the reactive form. These are the changes that I have made in the code

Role.class

    public static Uni<Boolean> existsRoleSeed() {
        return Role.count().map(x -> x > 0);
    }

RoleService.class

    @ReactiveTransactional
    public void seedRoles() {
        Role.existsRoleSeed()
                .map(exists -> {
                    if (!exists) {
                        Multi.createFrom()
                                .iterable(Arrays
                                        .stream(Role.UserRole.values())
                                        .map(userRole -> Role.builder()
                                                .name(userRole)
                                                .build())
                                        .collect(Collectors.toList()))
                                .map(role -> role.persistAndFlush())
                                .subscribe().with(item -> LOGGER.info("Something happened"), failure -> LOGGER.info("Something bad happened"));
                    }
                    return null;
                }).subscribe().with(o -> {
                });
    }

When I run the application, it does not give any error, the logs show that something happened, the database creates the table, however it does not insert anything. I have tried it in different ways, however, I have not succeeded in making it work as I hope.


Solution

  • Based on the @Haroon answer and the comment of @Clement I did the following

    • Removed the @ReactiveTransactional as my method returns void and is not on the REST boundary
    • As I removed the annotation I need to use the Panache.withTransaction method
    • And finally in the method I subscribed to the multi

    One note is that I changed the transformToUniAndMerge from the @Haroon answer to transformToUniAndConcatenate to maintain the order of the roles.

    public void seedRoles() {
            Role.existsRoleSeed()
                    .onItem().transformToMulti(exists -> {
                        if (!exists) {
                            return Multi.createFrom().items(Role.UserRole.values());
                        } else {
                            return Multi.createFrom().nothing();
                        }
                    })
                    .map(userRole -> Role.builder().name(userRole).build())
                    .onItem().transformToUniAndConcatenate(role -> Panache.withTransaction(role::persist))
                    .subscribe().with(subscription -> LOGGER.infov("Persisting: {0}", subscription));
        }