How to get Flux-Results into a set of a Mono-Bean - spring-webflux

I have the following scenario: I have a ProductFamily that has a set of Products.
I load the ProduktFamily from the database and then I want to load its Products and insert them into ProduktFamily.products.
Easy in Spring MVC (because of JPA) but with Webflux I am struggeling.
I tried this, but it does not work. ProduktFamily.products-set is empty.
Service:
public Flux<ProduktfamilyRS> getAllProduktFamilies() {
return produktfamilyDatabaseFacade.findAll()
.map(produktfamilyEntity2 -> dtoMapper.produktfamilyEntityToProduktfamilyRS(produktfamilyEntity2)) // <-- Mapsctruct Mapper
.concatMap(produktfamilyRS -> loadProducts(produktfamilyRS));
}
private Mono<ProduktfamilieRS> loadProducts(ProduktfamilieRS produktfamilieRS) {
Flux<ProduktEntity> byProduktfamilieId = produktDatabaseFacade.findByProduktfamilieId(produktfamilieRS.getId());
Flux<ProduktRS> produktRSFlux = byProduktfamilieId.map(produktEntity -> dtoMapper.produktEntityToProduktRS(produktEntity));
return Mono.just(produktfamilieRS).map(produktfamilieRS1 -> {
produktRSFlux.all(produktRS -> produktfamilieRS1.getProdukte().add(produktRS));
produktRSFlux.subscribe();
return produktfamilieRS1;
});
}
ProduktfamilyDatabaseFacade:
public Flux<ProduktfamilyEntity> findAll() {
return produktfamilyRepository.findAll();
}
ProduktfamilyDatabaseFacade:
public Flux<ProduktEntity> findByProduktfamilyId(Long produktfamilyId) {
return produktRepository.findAllByProduktfamilyId(produktfamilyeId)
.doOnNext(produktEntity -> log.info("Found Produkt '" + produktEntity.getName() + "' für Produktfamilie"));
}
Is there an way to start from the Mono and then iterate through the Flux to add every Product to the Productfamily and then return the ProductfamilyRS?
Thank you

Ok got it working, but do not know if its a good way. Can somebody review?
public Flux<ProduktfamilieRS> getAllProduktFamilien() {
return produktfamilieDatabaseFacade.findAll()
.map(produktfamilieEntity2 -> dtoMapper.produktfamilieEntityToProduktfamilieRS(produktfamilieEntity2))
.concatMap(this::loadProdukteFlat);
}
private Mono<ProduktfamilieRS> loadProdukteFlat(ProduktfamilieRS produktfamilieRS) {
return produktDatabaseFacade.findByProduktfamilieId(produktfamilieRS.getId())
.doOnNext(produktEntity -> produktfamilieRS.getProdukte().add(dtoMapper.produktEntityToProduktRS(produktEntity)))
.then(Mono.just(produktfamilieRS));
}

Related

Exclude specific products from Product Indexer in Shopware 6

We have four specific products with a massive amount of variants. When running the Product Indexer we run out of memory because of these products.
So we want to exclude these specific products from the Product Indexer Job.
My first approach was to use the ProductIndexerEvent, but the event is dispatched at the end of the handle() method :
(vendor/shopware/core/Content/Product/DataAbstractionLayer/ProductIndexer.php:187),
which is probably too late.
What is the best approach to implement that behaviour?
I would advise against excluding products from being indexed. There's business logic relying on the data being indexed.
If you're confident in what you're doing and know about the consequences, you could decorate the ProductIndexer service.
<service id="Foo\MyPlugin\ProductIndexerDecorator" decorates="Shopware\Core\Content\Product\DataAbstractionLayer\ProductIndexer">
<argument type="service" id="Foo\MyPlugin\ProductIndexerDecorator.inner"/>
</service>
In the decorator you would have to deconstruct the original event, filter the WriteResult instances by excluded IDs and then pass the reconstructed event to the decorated service.
class ProductIndexerDecorator extends EntityIndexer
{
const FILTERED_IDS = ['9b180c61ddef4dad89e9f3b9fa13f3be'];
private EntityIndexer $decorated;
public function __construct(EntityIndexer $decorated)
{
$this->decorated = $decorated;
}
public function getDecorated(): EntityIndexer
{
return $this->decorated;
}
public function getName(): string
{
return $this->getDecorated()->getName();
}
public function iterate($offset): ?EntityIndexingMessage
{
return $this->getDecorated()->iterate($offset);
}
public function update(EntityWrittenContainerEvent $event): ?EntityIndexingMessage
{
$originalEvents = clone $event->getEvents();
if (!$originalEvents) {
return $this->getDecorated()->update($event);
}
$event->getEvents()->clear();
/** #var EntityWrittenEvent $writtenEvent */
foreach ($originalEvents as $writtenEvent) {
if ($writtenEvent->getEntityName() !== 'product') {
$event->getEvents()->add($writtenEvent);
continue;
}
$results = [];
foreach ($writtenEvent->getWriteResults() as $result) {
if (\in_array($result->getPrimaryKey(), self::FILTERED_IDS, true)) {
continue;
}
$results[] = $result;
}
$event->getEvents()->add(new EntityWrittenEvent('product', $results, $event->getContext()));
}
return $this->getDecorated()->update($event);
}
public function handle(EntityIndexingMessage $message): void
{
$data = array_diff($message->getData(), self::FILTERED_IDS);
$newMessage = new ProductIndexingMessage($data, $message->getOffset(), $message->getContext(), $message->forceQueue());
$this->getDecorated()->handle($newMessage);
}
public function getTotal(): int
{
return $this->getDecorated()->getTotal();
}
public function getOptions(): array
{
return $this->getDecorated()->getOptions();
}
}

WebFlux & formation DTO

Hello recently started studying Webflux.
And sometimes I encounter the tasks that you need to form a simple DTO and return it
Take for example the usual class dto
#Data
#Builder
public static class Dto {
private long id;
private String code1;
private String code2;
}
And a primitive service with two methods...
#Nullable Mono<String> getCode1(long id);
#Nullable String getCode2(long id);
And wrote a method that forms at the output of Mono
private Mono<Dto> fill(long id) {
var dto = Dto.builder()
.id(id)
.build();
//doOnNext
var dtoMono1 = service.getCode1(id)
.map(code -> {
dto.setCode1(code);
return dto;
})
.doOnNext(innerDto -> innerDto.setCode2(service.getCode2(id)));
//map
var dtoMono2 = service.getCode1(id)
.map(code -> {
dto.setCode1(code);
return dto;
})
.map(unused -> service.getCode2(id))
.map(code -> {
dto.setCode1(code);
return dto;
});
//just
var dtoMono3 = Mono.just(dto)
.flatMap(innerDto -> service.getCode1(innerDto.getId()));
//just
var dtoMono4 = Mono.fromCallable(() -> dto)
.subscribeOn(Schedulers.boundedElastic())
.flatMap(innerDto -> service.getCode1(innerDto.getId()));
}
QUESTION:
Is it possible to simply create DTO and use it in the Webflux call
chain ... Or I need to wrap it in mono.just or mono.fromcallable
(what are the pros and cons)
How best to fill in values via doOnNext
or through MAP. An extra line (Return DTO) appears in the case of
MAP and some people also told me if for example NULL comes, doOnNext will
miss it and go further to fill up current dto. But on the other, the MAP is used
to transform the object, and the doOnNext is more for debugging and
logging
Thanks you...
How about using zip operator in such a case?
I hope this example can help you:
private Mono<Dto> fill(long id) {
return Mono.zip(someService.getCode1(id), Mono.just(someService.getCode2(id)))
.map(tuple ->
Dto.builder()
.id(id)
.code1(tuple.getT1())
.code2(tuple.getT2())
.build()
);
}

Java reactor and variable scope

I am trying to get my head around variable propagation using reactor. I have a function as follow where I am trying to pass a variable named requestName from outside the map as follow:
public Mono<ResponseEntity<? extends Object>> myFunction(
final Object request, final String requestName) {
return this.client
.create(request)
.exchangeToMono(
response -> {
final HttpStatus status = response.statusCode();
return response
.bodyToMono(String.class)
.defaultIfEmpty(StringUtils.EMPTY)
.map(
body -> {
if (status.is2xxSuccessful()) {
log.info("{}", requestName);
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest().body(null);
}
});
})
.onErrorResume(ex -> Mono.just(buildErrorFromException(requestName, ex)));
}
or another example would be:
String myvar = "test"
return this.
.login()
.flatMap(
response ->
this.myservice(myvar))
.flatMap(
response2 ->
this.myservice2(myvar))
Is it appropriate ? Or would i need to wrap this function around a Mono.deferContextual and apply a contextView ?
Thanks a lot for your help.

Upgrade Solution to use FluentValidation Ver 10 Exception Issue

Please I need your help to solve FluentValidation issue. I have an old desktop application which I wrote a few years ago. I used FluentValidation Ver 4 and Now I'm trying to upgrade this application to use .Net framework 4.8 and FluentValidation Ver 10, but unfortunately, I couldn't continue because of an exception that I still cannot fix.
I have this customer class:
class Customer : MyClassBase
{
string _CustomerName = string.Empty;
public string CustomerName
{
get { return _CustomerName; }
set
{
if (_CustomerName == value)
return;
_CustomerName = value;
}
}
class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(obj => obj.CustomerName).NotEmpty().WithMessage("{PropertyName} is Empty");
}
}
protected override IValidator GetValidator()
{
return new CustomerValidator();
}
}
This is my base class:
class MyClassBase
{
public MyClassBase()
{
_Validator = GetValidator();
Validate();
}
protected IValidator _Validator = null;
protected IEnumerable<ValidationFailure> _ValidationErrors = null;
protected virtual IValidator GetValidator()
{
return null;
}
public IEnumerable<ValidationFailure> ValidationErrors
{
get { return _ValidationErrors; }
set { }
}
public void Validate()
{
if (_Validator != null)
{
var context = new ValidationContext<Object>(_Validator);
var results = _Validator.Validate(context); **// <======= Exception is here in this line**
_ValidationErrors = results.Errors;
}
}
public virtual bool IsValid
{
get
{
if (_ValidationErrors != null && _ValidationErrors.Count() > 0)
return false;
else
return true;
}
}
}
When I run the application test I get the below exception:
System.InvalidOperationException HResult=0x80131509 Message=Cannot
validate instances of type 'CustomerValidator'. This validator can
only validate instances of type 'Customer'. Source=FluentValidation
StackTrace: at
FluentValidation.ValidationContext1.GetFromNonGenericContext(IValidationContext context) in C:\Projects\FluentValidation\src\FluentValidation\IValidationContext.cs:line 211 at FluentValidation.AbstractValidator1.FluentValidation.IValidator.Validate(IValidationContext
context)
Please, what is the issue here and How can I fix it?
Thank you
Your overall implementation isn't what I'd consider normal usage however the problem is that you're asking FV to validate the validator instance, rather than the customer instance:
var context = new ValidationContext<Object>(_Validator);
var results = _Validator.Validate(context);
It should start working if you change it to:
var context = new ValidationContext<object>(this);
var results = _Validator.Validate(context);
You're stuck with using the object argument for the validation context unless you introduce a generic argument to the base class, or create it using reflection.

How to map a flux with mono?

I have these two requests:
Flux<ProductID> getProductIds() {
return this.webClient.get()
.uri(PRODUCT_ID_URI)
.accept(MediaType.APPLICATION_STREAM_JSON)
.retrieve()
.bodyToFlux(ProductID.class);
}
Mono<Product> getProduct(String id) {
return this.itemServiceWebClient.get()
.uri(uriBuilder -> uriBuilder.path(PRODUCT_URI + "/{id}")
.build(id))
.accept(MediaType.APPLICATION_STREAM_JSON)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Product.class));
}
And with these I want to do the following:
Flux<Product> getProducts() {
return Flux.create(sink -> this.gateway.getProductIds()
.doOnComplete(() -> {
log.info("PRODUCTS COMPLETE");
sink.complete();
})
.flatMap(productId -> this.getProduct(productId.getID()))
.subscribe(product -> {
log.info("NEW PRODUCT: " + product);
sink.next(product);
}));
}
When I call this I get the following output:
PRODUCTS COMPLETE
NEW PRODUCT: ...
NEW PRODUCT: ...
....
Ofcourse the stream is closing before the results are actually there because the the async mono mapping. How can I keep this non-blocking but also making sure that the results arrive before the on complete is called?
Assuming getProducts is a controller method and that you want to add those products in your model to be rendered in a view template, you could solve this problem like this:
#GetMapping("/products")
public String getProducts(Model model) {
Flux<Product> products = this.gateway.getProductIds()
.flatMap(productId -> this.getProduct(productId.getID()));
model.put("products", products);
// return the view name
return "showProducts";
}