-
ContextFrom an API endpoint implemented in a Quarkus 2.9.1 project with Everything in one methodThis works great (although I welcome any suggestion on how to do it more elegantly). Even when I signal failure throwing a @Path("/myresource")
public class MyResource {
private static final Logger LOG = Logger.getLogger(MyResource.class);
private SomeRestClient restClient;
@GET
public Response doTest() {
try {
CompletableFuture<Response> response = new CompletableFuture<>();
Uni<String> start = Uni.createFrom().item("");
start
.chain(unused-> {
// Do test
return restClient.someTaskAsync();
})
.chain(s -> {
// Simulate error
if(some_condition)
throw new RuntimeException("problem");
})
.chain(s -> {
// Success
response.complete(Response.ok(s).build());
return Uni.createFrom().nullItem();
})
.onFailure().invoke(e -> {
LOG.error("Failed test");
if (!response.isDone())
response.complete(Response.ok(e).status(Status.EXPECTATION_FAILED).build());
})
.subscribe();
// Wait until test completes (possibly with error)
Response r = response.get();
return r;
} catch (InterruptedException e) {
return Response.status(Status.INTERNAL_SERVER_ERROR, "interrupted");
} catch (ExecutionException e) {
return Response.status(Status.INTERNAL_SERVER_ERROR, "execution");
}
}
} Processing split between methodsWhen I have to move a part of the async processing to a different method, the only solution I found was to synchronously wait for the processing step to finish, then artificially wrap the result with an public interface ITest {
public abstract Uni<String> test();
}
public class MyException extends RuntimeException {}
public class Test implements ITest {
private static final Logger LOG = Logger.getLogger(Test.class);
private SomeRestClient restClient;
public Uni<String> test() {
if(null == restClient)
throw new MyException("client");
AtomicReference<Uni<String>> result = new AtomicReference<>();
Uni<String> info = this.restClient.someTaskAsync();
info
.ifNoItem().after(Duration.ofMillis(100)).failWith(() -> {
LOG.error("Timeout");
return new MyException("timeout");
)}
.chain(s -> {
// Success
result.set(Uni.createFrom().item(s));
return Uni.createFrom().nullItem();
})
.onFailure().invoke(e -> {
LOG.error(e);
result.set(Uni.createFrom().failure(e));
})
.await().indefinitely();
return result.get();
}
}
@Path("/myresource")
public class MyResource {
private static final Logger LOG = Logger.getLogger(MyResource.class);
private ITest tester;
@GET
public Response doTest() {
try {
CompletableFuture<Response> response = new CompletableFuture<>();
Uni<String> start = Uni.createFrom().item("");
start
.chain(s -> {
// Do test
return tester.test();
})
.chain(s -> {
// Success
response.complete(Response.ok(s).build());
return Uni.createFrom().nullItem();
})
.onFailure().invoke(e -> {
LOG.error("Failed test");
if (!response.isDone())
response.complete(Response.ok(e).status(Status.EXPECTATION_FAILED).build());
})
.subscribe();
// Wait until test completes (possibly with error)
Response r = response.get();
return r;
} catch (InterruptedException e) {
return Response.status(Status.INTERNAL_SERVER_ERROR, "interrupted");
} catch (ExecutionException e) {
return Response.status(Status.INTERNAL_SERVER_ERROR, "execution");
}
}
} Although this works, in the sense that in
Q1: Why is Mutiny unhappy and how can I get this working clean, without errors? |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 11 replies
-
Some elements from a purely Mutiny perspective, but I don't understand everything here.
|
Beta Was this translation helpful? Give feedback.
-
@jponge Thank you very much for the answers. Unfortunately, although your answers are logical, it just muddies my understanding more. For Q1 I throw an exception at the beginning of the method. But even if I rewrite it to return a failure Uni (see below), Mutiny still drops an exception. public class Test implements ITest {
private static final Logger LOG = Logger.getLogger(Test.class);
private SomeRestClient restClient;
public Uni<String> test() {
return Uni.createFrom.failure(new MyException("client"));
}
} For Q2 in My take (does not compile): public class Test implements ITest {
private static final Logger LOG = Logger.getLogger(Test.class);
private SomeRestClient restClient;
public Uni<Boolean> test() {
if(null == restClient) {
LOG.error("No client");
return Uni.createFrom().failure(new MyException("client"));
}
Uni<String> info = this.restClient.someTaskAsync();
info
.ifNoItem().after(Duration.ofMillis(100)).failWith(() -> {
LOG.error("Timeout");
return new MyException("timeout");
)}
.chain(s -> {
// Success
return Uni.createFrom().item(new Boolean(true));
})
.onFailure().invoke(e -> {
LOG.error(e);
});
return info; // <- does not compile, different type
}
} |
Beta Was this translation helpful? Give feedback.
-
For Q2 where I did not know how to write public class Test implements ITest {
private static final Logger LOG = Logger.getLogger(Test.class);
private SomeRestClient restClient;
public Uni<Boolean> test() {
if(null == restClient) {
LOG.error("No client");
return Uni.createFrom().failure(new MyException("client"));
}
Uni<Boolean> result = Uni.createFrom().nullItem(); // <- This gets returned, handlers are not called
result
.ifNoItem().after(Duration.ofMillis(100)).failWith(() -> {
LOG.error("Timeout");
return new MyException("timeout");
)}
.chain(unused -> {
// Ignore initial dummy placeholder, start the actual processing here
LOG.info("Start processing");
Uni<String> info = this.restClient.someTaskAsync();
return info;
})
.chain(s -> {
// Got result of processing, turn it into what we have to return
LOG.info("Finished processing");
return Uni.createFrom().item(new Boolean(s.equals("evrika")));
})
.onFailure().invoke(e -> {
LOG.error(e);
});
return result;
}
} However, when I call this deeper async processing from @Path("/myresource")
public class MyResource {
private static final Logger LOG = Logger.getLogger(MyResource.class);
private ITest tester;
@GET
public Response doTest() {
AtomicReference<Response> result = new AtomicReference<>();
Uni<String> start = Uni.createFrom().item("");
start
.chain(s -> {
// Do test
LOG.info("Start test");
return tester.test(); // <- Returns immediately, does not cause invocation of handlers in Test.test()
})
.chain(b -> { // <- We get null passed to this handler
// Success
LOG.info("Finished test");
result.set(Response.ok(b).build());
return Uni.createFrom().nullItem();
})
.onFailure().invoke(e -> {
LOG.error("Failed test");
result.set(Response.ok(e).status(Status.EXPECTATION_FAILED).build());
})
.await().indefinitely();
return result;
}
} I am expecting this output:
Instead I am getting this output:
The handlers in @jponge Can you please shed a light on what I am doing wrong? I find this a huge problem with the documentation, such an example is badly needed. |
Beta Was this translation helpful? Give feedback.
-
Empirical finding. If I write it like this, handlers in the deep method public class Test implements ITest {
...
public Uni<Boolean> test() {
...
Uni<Boolean> result = Uni.createFrom().nullItem()
.ifNoItem().after(Duration.ofMillis(100)).failWith(() -> {
...
)}
.chain(unused -> {
...
})
.onFailure().invoke(e -> {
...
});
return result;
}
} |
Beta Was this translation helpful? Give feedback.
-
Do you have any minimal reproducer project? It’s hard to guess what ITest is, etc.
…On Mon, Jun 6, 2022, at 21:41, Levente Farkas wrote:
An explanation would be welcome about why it works like this and it does not work as my previous attempt.
—
Reply to this email directly, view it on GitHub <#933 (reply in thread)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAAGK2LDXH6TNGBTSR7AGXLVNZH43ANCNFSM5W4YP37Q>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Final version that works great: public class Test implements ITest {
private static final Logger LOG = Logger.getLogger(Test.class);
private SomeRestClient restClient;
public Uni<Boolean> test() {
if(null == restClient) {
LOG.error("No client");
return Uni.createFrom().failure(new MyException("client"));
}
Uni<Boolean> result = Uni.createFrom().nullItem()
.ifNoItem().after(Duration.ofMillis(100)).failWith(() -> {
LOG.error("Timeout");
return new MyException("timeout");
)}
.chain(unused -> {
// Ignore initial dummy placeholder, start the actual processing here
LOG.info("Start processing");
Uni<String> info = this.restClient.someTaskAsync();
return info;
})
.chain(s -> {
// Got result of processing, turn it into what we have to return
LOG.info("Finished processing");
return Uni.createFrom().item(new Boolean(s.equals("evrika")));
})
.onFailure().invoke(e -> {
LOG.error(e);
});
return result;
}
} And the Quarkus endpoint that calls it: @Path("/myresource")
public class MyResource {
private static final Logger LOG = Logger.getLogger(MyResource.class);
private ITest tester; // Needs to be set up
@GET
public Uni<Response> doTest() {
Uni<Response> result = Uni.createFrom().nullItem()
.chain(unused -> {
// Do test
LOG.info("Start test");
return tester.test();
})
.chain(b -> {
// Success
LOG.info("Finished test");
return Uni.createFrom().item(Response.ok(b).build()));
})
.onFailure().recoverWithItem(e -> {
LOG.error("Failed test");
return Response.ok(e).status(Status.EXPECTATION_FAILED).build();
});
return result;
}
} |
Beta Was this translation helpful? Give feedback.
Final version that works great: