'Java Spring Reactive, returning one Mono<..> from many multiple requests

[Java, Spring Reactive, MongoDB] I'm currently trying to learn Reactive programming by doing and I found a challenge.

I have db object CategoryDB which looks like this:

@NoArgsConstructor
@Getter
@Setter
@Document(collection = DBConstraints.CATEGORY_COLLECTION_NAME)
class CategoryDB {

    @Id
    private String id;
    private String name;
    private String details = "";

    @Version
    private Long version;

    private String parentCategoryId;
    private Set<String> childCategoriesIds = new HashSet<>();

}

In a service layer I want to use model object Category.

@Getter
@Builder
public class Category {

    private String id;
    private String name;
    private String details;
    private Long version;
    private Category parentCategory;

    @Builder.Default
    private Set<Category> childCategories = new HashSet<>();

}

I want to create Service with method Mono<Category getById(String id). In this case I want to fetch just one level of childCategories and direct parent Category. By default repository deliver Mono findById(..) and Flux findAllById(..) which I could use, but I'm not sure what would be the best way to receive expected result. I would be grateful for either working example or directions where can I find solution for this problem.



Solution 1:[1]

I've spent some time to figure out solution for this problem, but as I'm learning I don't know if it's good way of solving problems.

Added some methods to Category:

@Getter
@Builder
public class Category {

    private String id;
    private String name;
    private String details;
    private Long version;
    private Category parentCategory;

    @Builder.Default
    private Set<Category> childCategories = new HashSet<>();

    public void addChildCategory(Category childCategory) {
        childCategory.updateParentCategory(this);
        this.childCategories.add(childCategory);
    }

    public void updateParentCategory(Category parentCategory) {
        this.parentCategory = parentCategory;
    }

}

Function inside service would look like this:

@Override
public Mono<Category> findById(String id) {
    return categoryRepository.findById(id).flatMap(
            categoryDB -> {
                Category category = CategoryDBMapper.INSTANCE.toDomain(categoryDB);
                Mono<CategoryDB> parentCategoryMono;
                if(!categoryDB.getParentCategoryId().isBlank()){
                    parentCategoryMono = categoryRepository.findById(categoryDB.getParentCategoryId());
                }
                else {
                    parentCategoryMono = Mono.empty();
                }
                Mono<List<CategoryDB>> childCategoriesMono = categoryRepository.findAllById(categoryDB.getChildCategoriesIds()).collectList();
                return Mono.zip(parentCategoryMono, childCategoriesMono, (parentCategoryDB, childCategoriesDB) -> {
                    Category parentCategory = CategoryDBMapper.INSTANCE.toDomain(parentCategoryDB);
                    category.updateParentCategory(parentCategory);
                    childCategoriesDB.forEach(childCategoryDB -> {
                        Category childCategory = CategoryDBMapper.INSTANCE.toDomain(childCategoryDB);
                        category.addChildCategory(childCategory);
                    });
                    return category;
                });
            }
    );
}

Where mapper is used for just basic properties: @Mapper interface CategoryDBMapper {

    CategoryDBMapper INSTANCE = Mappers.getMapper(CategoryDBMapper.class);

    @Mappings({
            @Mapping(target = "parentCategoryId", source = "parentCategory.id"),
            @Mapping(target = "childCategoriesIds", ignore = true)
    })
    CategoryDB toDb(Category category);

    @Mappings({
            @Mapping(target = "parentCategory", ignore = true),
            @Mapping(target = "childCategories", ignore = true)
    })
    Category toDomain(CategoryDB categoryDB);

}

As I said I don't know if it's correct way of solving the problem, but it seem to work. I would be grateful for review and directions.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Robert Radzik