Let's say we have a store, a catalog and categories. Each store can have catalogs and each category can have subcategories.
The documents are coming from MongoDB:
@Data
@Document
@NoArgsConstructor
@AllArgsConstructor
public class Catalog {
@Id
private String id;
private Set<String> stores;
@Transient
private List<Category> tree;
}
@Data
@Document
@NoArgsConstructor
@AllArgsConstructor
public class Category {
@Id
private String id;
private List<CatalogMember> catalogs;
private List<CategoryMember> children;
@Transient
private List<Category> subCategories;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CatalogMember {
private String id;
private Long sequence;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CategoryMember {
private String id;
private Long sequence;
}
Now I have other API models to further work with in frontend:
@Getter
@Builder
public class CatalogByStoreIdResponse {
private final String id;
private final List<CategoryResponse> tree;
}
@Getter
@Builder
public class CategoryResponse {
private final String id;
private final List<CategoryResponse> children;
}
Currently I have the following code to retrieve all documents and set Catalog::tree as well as Category::subCategories:
@Slf4j
@Component
@RequiredArgsConstructor
public class CatalogByStoreIdHandler implements HandlerFunction<ServerResponse> {
private final CatalogReactiveRepository catalogReactiveRepository;
private final CategoryReactiveRepository categoryReactiveRepository;
private final CatalogByStoreIdResponseMapper catalogByStoreIdResponseMapper;
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
String storeId = request.pathVariable("storeId");
return ok()
.body(catalogReactiveRepository.findFirstByStoresContains(storeId)
.doOnNext(catalog -> categoryReactiveRepository.findAllByCatalogsIdContainsOrderByCatalogsSequenceAsc(catalog.getId())
.collectList()
.doOnNext(tree -> catalog.setTree(tree))
.flatMapMany(Flux::fromIterable)
.expandDeep(category -> Flux.fromIterable(category.getChildren())
.map(CategoryMember::getId)
.collectList()
.flatMapMany(categoryReactiveRepository::findAllByIdIn)
.collectList()
.doOnNext(subCategories -> category.setSubCategories(subCategories))
.flatMapMany(Flux::fromIterable))
.subscribe())
.map(catalogByStoreIdResponseMapper), CatalogByStoreIdResponse.class);
}
}
This is the essential mapper:
@Component
@RequiredArgsConstructor
public class CatalogByStoreIdResponseMapper implements Function<Catalog, CatalogByStoreIdResponse> {
private final CategoryResponseMapper categoryResponseMapper;
@Override
public CatalogByStoreIdResponse apply(Catalog catalog) {
return CatalogByStoreIdResponse.builder()
.id(catalog.getId())
.tree(Stream.ofNullable(catalog.getTree()) // catalog.getTree() is always null
.flatMap(List::stream)
.map(categoryResponseMapper)
.collect(Collectors.toList()))
.build();
}
}
Now my problem is that the mapping function is getting called before the tree is actually set and thus the tree in my response is empty.
I know that this is the correct behavior in reactive programming, but I need to know how to solve my problem so that my tree field isn't empty.