How to handle failure in reactive my sql client in quarkus - reactive-programming

In https://quarkus.io/guides/reactive-sql-clients page we have code snippet to execute query changes using transaction :
SqlClientHelper.inTransactionUni(client, tx -> tx
.preparedQuery("INSERT INTO person (firstname,lastname) VALUES ($1,$2) RETURNING id").execute(Tuple.of(person.getFirstName(), person.getLastName()))
.onItem().transformToUni(id -> tx.preparedQuery("INSERT INTO addr (person_id,addrline1) VALUES ($1,$2)")
.execute(Tuple.of(id.iterator().next().getLong("id"), person.getLastName()))).onItem().ignore().andContinueWithNull());
so here SqlClientHelper will begin the transaction,commit and rollback if any failure but is there any way to find out the root cause of the failure and print it in logs ?
In the documentation its not mentioned how we can do that.

You can use Mutiny's onFailure to get the exception class and act on it. See this for more details.

based on the link as given in the accepted Answer this is working for me :
return SqlClientHelper.inTransactionUni(mysqlPool, tx -> {
return tx.query(query).execute().onItem().transformToUni(
id -> tx.query("SELECT TRAN_ID FROM " + tableName + "
ORDER BY TO_DB_TS DESC LIMIT 1").execute())
.onItem().transform(rows ->
rows.iterator().next().getString(0)).onFailure().invoke(f -> {
LOG.error("Error while inserting data to " +
tableName + " table::"+f.getMessage());
});
});

Related

Cannot select and update record in the same transaction

I use webflux + spring data and in the same transaction, I'm trying to fetch data and update in the same transaction. On userPrivilegesRepository::saveAll execution stops. That works on H2 database, but does not work on postgres database.
#Transactional
public Flux<Void> syncUsersWithPrivileges() {
return userDetailsRepository.findUserWithoutPrivileges()
.doOnNext(it -> log.info("Syncing users without privileges: id {}", it.getId()))
.concatMap(this::createUserPrivileges)
.buffer(BATCH_SIZE)
.doOnNext(it -> log.info("Update complete for batch users size {}", it.size()))
.flatMap(it -> Mono.empty());
}
public Mono<UserEntity> createUserPrivileges(UserEntity user) {
return Flux.fromIterable(Privileges.jobSeeker())
.map(it -> UserPrivilegesEntity.builder()
.privilegesId(it)
.userId(user.getId())
.id(user.getId() + "-" + it.name())
.isNew(true).build())
.collectList()
.flatMapMany(userPrivilegesRepository::saveAll)
.then(Mono.just(user));
}
the repository
#Query("select u.* from users as u left join user_privileges as up ON up.user_id = u.id\n"
+ "where up.user_id is null")
Flux<UserEntity> findUserWithoutPrivileges();
If I remove #Transactional annotation everything works fine. Any thoughts on that?

Reactive programming - switch second query if first query returns nothing

I'm trying to implement this scenario using reactive programming(switchIfEmpty(Mono.defer() ->...)):
client needs data from first query ==>
basketReadRepository.findByBasketId()
if data is already present in first query => return that data
if data not present from first execute second query for data ==>
basketWriteRepository.findByBasketId()
if second fails then
switchIfEmpty(Mono.error.....)
I tried like this.....
public Mono updateBasket(UUID basketId) { return
basketReadRepository.findByBasketId(basketId) .doFirst(() ->
log.debug("Processing update basket request) .flatMap(basket ->
Mono.error(new
NotFoundException(ErrorConstants.BASKET_NOT_FOUND_WITH_STATUS_CODE)))
.switchIfEmpty(Mono.defer(() ->
basketWriteRepository.findByBasketId(basketId).cast(Basket.class)
.switchIfEmpty(Mono.error(new
NotFoundException(ErrorConstants.BASKET_NOT_FOUND_WITH_STATUS_CODE)))
.flatMap(existingBasket -> validateAndConvertBasket(patch,
existingBasket)) .flatMap(this::validateBasket)
.flatMap(this::updateBasket) .doOnError(throwable ->
log.error("Error in updating basket : {}", throwable.getMessage()))
.doOnSuccess(basket -> log.debug("Basket updated successfully); }
......but it always come inside second query and check, not inside first one... I can not do switchIfEmpty(Mono.error()...) after first query because then it never come to second query

How can I insert and get last inserted id in mysql with transactions using quarkus reactive mysql client with mutiny api

How would i go about creating a transaction, inserting a row, committing the transaction and getting the last inserted id. So the method should return a Uni<Integer>. I'm new to the mutiny api, I previously used the vertx.io chaining future handlers mechanism, and so it's a bit tough readjusting myself to work with the mutiny api. I have checked the documentation and think something similar to the following snippet should work, but i'm stumped on how to make it work and return Uni<Integer> from the last query instead of Uni<Void> from the tx.commit()
return this.client.begin()
.flatMap(tx -> tx
.preparedQuery("INSERT INTO person (firstname,lastname) VALUES ($1,$2)")
.execute(Tuple.of(person.getFirstName(),person.getLastName()))
.onItem().produceUni(id-> tx.query("SELECT LAST_INSERT_ID()"))
.onItem().produceUni(res -> tx.commit())
.onFailure().recoverWithUni(ex-> tx.rollback())
);
Try this:
return client.begin().onItem().produceUni(tx -> tx
.preparedQuery("INSERT INTO person (firstname,lastname) VALUES ($1,$2)").execute(Tuple.of(person.getFirstName(),person.getLastName()))
.onItem().produceUni(id -> tx.query("SELECT LAST_INSERT_ID()").execute())
.onItem().apply(rows -> rows.iterator().next().getInteger(0))
.onItem().produceUni(item -> tx.commit().on().item().produceUni(v -> Uni.createFrom().item(item)))
.on().failure().recoverWithUni(throwable -> {
return tx.rollback().on().failure().recoverWithItem((Void) null)
.on().item().produceUni(v -> Uni.createFrom().failure(throwable));
})
);
A SqlClientHelper is coming to Quarkus in a future version (hopefully 1.6). You will be able to simplify to:
return SqlClientHelper.inTransactionUni(client, tx -> tx
.preparedQuery("INSERT INTO person (firstname,lastname) VALUES ($1,$2)").execute(Tuple.of(person.getFirstName(),person.getLastName()))
.onItem().produceUni(id -> tx.query("SELECT LAST_INSERT_ID()").execute())
.onItem().apply(rows -> rows.iterator().next().getInteger(0))
);

How to process all events emitted by RX Java regardless of error?

I'm using vertx.io web framework to send a list of items to a downstream HTTP server.
records.records() emits 4 records and I have specifically set the web client to connect to the wrong I.P/port.
Processing... prints 4 times.
Exception outer! prints 3 times.
If I put back the proper I.P/port then Susbscribe outer! prints 4 times.
io.reactivex.Flowable
.fromIterable(records.records())
.flatMap(inRecord -> {
System.out.println("Processing...");
// Do stuff here....
Observable<Buffer> bodyBuffer = Observable.just(Buffer.buffer(...));
Single<HttpResponse<Buffer>> request = client
.post(..., ..., ...)
.rxSendStream(bodyBuffer);
return request.toFlowable();
})
.subscribe(record -> {
System.out.println("Subscribe outer!");
}, ex -> {
System.out.println("Exception outer! " + ex.getMessage());
});
UPDATE:
I now understand that on error RX stops right a way. Is there a way to continue and process all records regardless and get an error for each?
Given this article: https://medium.com/#jagsaund/5-not-so-obvious-things-about-rxjava-c388bd19efbc
I have come up with this... Unless you see something wrong with this?
io.reactivex.Flowable
.fromIterable(records.records())
.flatMap
(inRecord -> {
Observable<Buffer> bodyBuffer = Observable.just(Buffer.buffer(inRecord.toString()));
Single<HttpResponse<Buffer>> request = client
.post("xxxxxx", "xxxxxx", "xxxxxx")
.rxSendStream(bodyBuffer);
// So we can capture how long each request took.
final long startTime = System.currentTimeMillis();
return request.toFlowable()
.doOnNext(response -> {
// Capture total time and print it with the logs. Removed below for brevity.
long processTimeMs = System.currentTimeMillis() - startTime;
int status = response.statusCode();
if(status == 200)
logger.info("Success!");
else
logger.error("Failed!");
}).doOnError(ex -> {
long processTimeMs = System.currentTimeMillis() - startTime;
logger.error("Failed! Exception.", ex);
}).doOnTerminate(() -> {
// Do some extra stuff here...
}).onErrorResumeNext(Flowable.empty()); // This will allow us to continue.
}
).subscribe(); // Don't handle here. We subscribe to the inner events.
Is there a way to continue and process all records regardless and get
an error for each?
According to the doc, the observable should be terminated if it encounters an error. So you can't get each error in onError.
You can use onErrorReturn or onErrorResumeNext() to tell the upstream what to do if it encounters an error (e.g. emit null or Flowable.empty()).

Aggregate resource requests & dispatch responses to each subscriber

I'm fairly new to RxJava and struggling with an use case that seems quite common to me :
Gather multiple requests from different parts of the application, aggregate them, make a single resource call and dispatch the results to each subscriber.
I've tried a lot of different approaches, using subjects, connectable observables, deferred observables... none did the trick so far.
I was quite optimistic about this approach but turns out it fails just like the others :
//(...)
static HashMap<String, String> requests = new HashMap<>();
//(...)
#Test
public void myTest() throws InterruptedException {
TestScheduler scheduler = new TestScheduler();
Observable<String> interval = Observable.interval(10, TimeUnit.MILLISECONDS, scheduler)
.doOnSubscribe(() -> System.out.println("new subscriber!"))
.doOnUnsubscribe(() -> System.out.println("unsubscribed"))
.filter(l -> !requests.isEmpty())
.doOnNext(aLong -> System.out.println(requests.size() + " requests to send"))
.flatMap(aLong -> {
System.out.println("requests " + requests);
return Observable.from(requests.keySet()).take(10).distinct().toList();
})
.doOnNext(strings -> System.out.println("calling aggregate for " + strings + " (from " + requests + ")"))
.flatMap(Observable::from)
.doOnNext(s -> {
System.out.println("----");
System.out.println("removing " + s);
requests.remove(s);
})
.doOnNext(s -> System.out.println("remaining " + requests));
TestSubscriber<String> ts1 = new TestSubscriber<>();
TestSubscriber<String> ts2 = new TestSubscriber<>();
TestSubscriber<String> ts3 = new TestSubscriber<>();
TestSubscriber<String> ts4 = new TestSubscriber<>();
Observable<String> defer = buildObservable(interval, "1");
defer.subscribe(ts1);
Observable<String> defer2 = buildObservable(interval, "2");
defer2.subscribe(ts2);
Observable<String> defer3 = buildObservable(interval, "3");
defer3.subscribe(ts3);
scheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS);
Observable<String> defer4 = buildObservable(interval, "4");
defer4.subscribe(ts4);
scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS);
ts1.awaitTerminalEvent(1, TimeUnit.SECONDS);
ts2.awaitTerminalEvent(1, TimeUnit.SECONDS);
ts3.awaitTerminalEvent(1, TimeUnit.SECONDS);
ts4.awaitTerminalEvent(1, TimeUnit.SECONDS);
ts1.assertValue("1");
ts2.assertValue("2"); //fails (test stops here)
ts3.assertValue("3"); //fails
ts4.assertValue("4"); //fails
}
public Observable<String> buildObservable(Observable<String> interval, String key) {
return Observable.defer(() -> {
System.out.printf("creating observable for key " + key);
return Observable.create(subscriber -> {
requests.put(key, "xxx");
interval.doOnNext(s -> System.out.println("filtering : key/val " + key + "/" + s))
.filter(s1 -> s1.equals(key))
.doOnError(subscriber::onError)
.subscribe(s -> {
System.out.println("intern " + s);
subscriber.onNext(s);
subscriber.onCompleted();
subscriber.unsubscribe();
});
});
}
)
;
}
Output :
creating observable for key 1new subscriber!
creating observable for key 2new subscriber!
creating observable for key 3new subscriber!
3 requests to send
requests {3=xxx, 2=xxx, 1=xxx}
calling aggregate for [3, 2, 1] (from {3=xxx, 2=xxx, 1=xxx})
----
removing 3
remaining {2=xxx, 1=xxx}
filtering : key/val 1/3
----
removing 2
remaining {1=xxx}
filtering : key/val 1/2
----
removing 1
remaining {}
filtering : key/val 1/1
intern 1
creating observable for key 4new subscriber!
1 requests to send
requests {4=xxx}
calling aggregate for [4] (from {4=xxx})
----
removing 4
remaining {}
filtering : key/val 1/4
The test fails at the second assertion (ts2 not receiving "2")
Turns out the pseudo-aggregation works as expected, but the values are not dispatched to the corresponding subscribers (only the first subscriber receives it)
Any idea why?
Also, I feel like I'm missing the obvious here. If you think of a better approach, I'm more than willing to hear about it.
EDIT : Adding some context regarding what I want to achieve.
I have a REST API exposing data via multiple endpoints (eg. user/{userid}). This API also makes it possible to aggregate requests (eg. user/user1 & user/user2) and get the corresponding data in one single http request instead of two.
My goal is to be able to automatically aggregate the requests made from different parts of my application in a given time frame (say 10ms) with a max batch size (say 10), make an aggregate http request, then dispatch the results to the corresponding subscribers.
Something like this :
// NOTE: those calls can be fired from anywhere in the app, and randomly combined. The timing and order is completely unpredictable
//ts : 0ms
api.call(userProfileRequest1).subscribe(this::show);
api.call(userProfileRequest2).subscribe(this::show);
//--> after 10ms, should fire one single http aggregate request with those 2 calls, map the response items & send them to the corresponding subscribers (that will show the right user profile)
//ts : 20ms
api.call(userProfileRequest3).subscribe(this::show);
api.call(userProfileRequest4).subscribe(this::show);
api.call(userProfileRequest5).subscribe(this::show);
api.call(userProfileRequest6).subscribe(this::show);
api.call(userProfileRequest7).subscribe(this::show);
api.call(userProfileRequest8).subscribe(this::show);
api.call(userProfileRequest9).subscribe(this::show);
api.call(userProfileRequest10).subscribe(this::show);
api.call(userProfileRequest11).subscribe(this::show);
api.call(userProfileRequest12).subscribe(this::show);
//--> should fire a single http aggregate request RIGHT AWAY (we hit the max batch size) with the 10 items, map the response items & send them to the corresponding subscribers (that will show the right user profile)
The test code I wrote (with just strings) and pasted at the top of this question is meant to be a proof of concept for my final implementation.
Your Observable is not well constructed
public Observable<String> buildObservable(Observable<String> interval, String key) {
return interval.doOnSubscribe(() -> System.out.printf("creating observable for key " + key))
.doOnSubscribe(() -> requests.put(key, "xxx"))
.doOnNext(s -> System.out.println("filtering : key/val " + key + "/" + s))
.filter(s1 -> s1.equals(key));
}
When you subsribe in a subscriber : it's offen a bad design.
I'm not shure to understand what you want to achieve, but I think my code should be pretty close to yours.
Please note that, for all side effects, I use doMethods (like doOnNext, doOnSubscribe) to show I explicitly show that I want to do a side effect.
I replace your defer call by returning directly the interval : as you want to emit all interval events in your custom observable build in your defer call, returning the interval observable is better.
Please note, that you filtering your interval Observable :
Observable<String> interval = Observable.interval(10, TimeUnit.MILLISECONDS, scheduler)
.filter(l -> !requests.isEmpty()).
// ...
So, as soon you'll put something into requests map, interval will stop emmiting.
I don't understand what you wants to achieve with the request map, but please note that you may want to avoid side effects, and updating this map is clearly a side effect.
Update regarding comments
You may want to use the buffer operator to aggregate request, and then perform request in a bulk way :
PublishSubject<String> subject = PublishSubject.create();
TestScheduler scheduler = new TestScheduler();
Observable<Pair> broker = subject.buffer(100, TimeUnit.MILLISECONDS, 10, scheduler)
.flatMapIterable(list -> list) // you can bulk calls here
.flatMap(id -> Observable.fromCallable(() -> api.call(id)).map(response -> Pair.of(id, response)));
TestSubscriber<Object> ts1 = new TestSubscriber<>();
TestSubscriber<Object> ts2 = new TestSubscriber<>();
TestSubscriber<Object> ts3 = new TestSubscriber<>();
TestSubscriber<Object> ts4 = new TestSubscriber<>();
broker.filter(pair -> pair.id.equals("1")).take(1).map(pair -> pair.response).subscribe(ts1);
broker.filter(pair -> pair.id.equals("2")).take(1).map(pair -> pair.response).subscribe(ts2);
broker.filter(pair -> pair.id.equals("3")).take(1).map(pair -> pair.response).subscribe(ts3);
broker.filter(pair -> pair.id.equals("4")).take(1).map(pair -> pair.response).subscribe(ts4);
subject.onNext("1");
subject.onNext("2");
subject.onNext("3");
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
ts1.assertValue("resp1");
ts2.assertValue("resp2");
ts3.assertValue("resp3");
ts4.assertNotCompleted();
subject.onNext("4");
scheduler.advanceTimeBy(1, TimeUnit.SECONDS);
ts4.assertValue("resp4");
ts4.assertCompleted();
If you want to perform network request collapsin, you may want to check Hystrix : https://github.com/Netflix/Hystrix