summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Fankhauser hiddenalpha.ch2024-04-12 11:59:49 +0200
committerAndreas Fankhauser hiddenalpha.ch2024-04-12 12:00:44 +0200
commit49eb68fcf6e5ed1ff841a8bf5bf5978a4d67f412 (patch)
treea8aac45acf36b0117016df73851d698a176100a6
parent98cac05a8c3c26eff81d89e9980d376cda00f194 (diff)
downloadUnspecifiedGarbage-49eb68fcf6e5ed1ff841a8bf5bf5978a4d67f412.zip
UnspecifiedGarbage-49eb68fcf6e5ed1ff841a8bf5bf5978a4d67f412.tar.gz
Add article draft
-rw-r--r--doc/article-drafts/vertx-promise-errorhandling/vertx-promise-errorhandling.txt199
1 files changed, 199 insertions, 0 deletions
diff --git a/doc/article-drafts/vertx-promise-errorhandling/vertx-promise-errorhandling.txt b/doc/article-drafts/vertx-promise-errorhandling/vertx-promise-errorhandling.txt
new file mode 100644
index 0000000..430740e
--- /dev/null
+++ b/doc/article-drafts/vertx-promise-errorhandling/vertx-promise-errorhandling.txt
@@ -0,0 +1,199 @@
+
+<hdr>How to do error handling with java vertx Proimises</hdr>
+
+Example library
+```java
+ public class ExampleLib {
+ public Promise<Person> fetchPerson();
+ public Promise<Phone> fetchPhone(String personId);
+ }
+```
+
+The way to go (HINT still suffers from callback-hell, use method refs):
+
+```java
+ exampleLib.fetchPerson().onComplete( personEv -> {
+ if( personEv.failed() ){
+ log.error("TODO error handling", new UnsupportedOperationException(personEv.cause()));
+ return;
+ }
+ Person person = personEv.result();
+ exampleLib.fetchPhone().onComplete( phoneEv -> {
+ if( phoneEv.failed() ){
+ log.error("TODO error handling", new UnsupportedOperationException(phoneEv.cause()));
+ return;
+ }
+ Phone phone = phoneEv.result();
+ // 'person' and 'phone' are now ready to use.
+ // ....
+ });
+ });
+```
+
+
+<hdr>Why not using onSuccess().onFailure() ?</hdr>
+
+What I've found often in PRODUCTION code is the following:
+
+```java
+ exampleLib.fetchPerson().onSuccess( person -> exampleLib.fetchPhone(person.id, phone -> {
+ log.info("yayy! :) we got {} and {}", person, phone);
+ })).onFailure( ex -> {
+ log.error("whops :(", ex);
+ });
+```
+
+Then I hear stuff like "Yeah is this cool. It is so short and concise. This
+framebloat is great". BUT HERE'S THE CATCH: This code has a major flaw. Do you
+spot it? It is a VERY simple example so we should spot such a major flaw
+immediately. Just imagine a somewhat more realistic example whith tons of more
+code around it and it will become even harder to ever spot what is wrong here.
+
+Couldn't find the flaw yet? To make it more obvious I'll rewrite the code to
+good old synchronous, blocking, stone-age code, removing all that fancy
+framework clutter. This will help us to SEE immediately what the problem is:
+
+```java
+ try{
+ Person person = exampleLib.fetchPerson();
+ try{
+ Phone phone = exampleLib.fetchPhone();
+ }catch( Throwable ex ){
+ return; // HINT: This line should be your eye-catcher.
+ }
+ log.info("yayy! :) we got {} and {}", person, phone);
+ }catch( Throwable ex ){
+ log.error("whops :(", ex);
+ }
+```
+
+Take a look at the inner catch block. Now every serious dev should see something
+terrifying. It is catching EVERY kind of Throwables and JUST COMPLETELY IGNORES
+THEM! This is exactly what the previous code does. The problem is, that even
+experienced developers have a hard time to spot this. I say this, because in
+nearly every pull-request I've to review, I spot this issue dozens of times
+every few lines of code. This is terrifying for hunting bugs later.
+
+
+<hdr>Why not just using global error handler?</hdr>
+
+Lazy devs may argue with DRY, that we should just register a global handler and
+then just ignore all error handling everywher else.
+
+I think it is a good idea to register the global error handler. BUT we have to
+consider the folowing:
+
+- If we're writing a library, we MUST NOT steal the error handler. This is the applications job.
+- We still MUST have error handlers for every individual case.
+
+Why not registering from a lib? Bcause that error handler is GLOBAL to our
+vertx context, and we will make it hard to use our library, because the
+application cannot rely on this feature anymore, as all those fancy
+dependencies are going to fight each other by overriding that handler with
+their own one. Which makes the catch-all error handler pretty useless for the
+application.
+
+So then why not rely on the application setting this handler? Good APIs are
+easy to use right and hard to use wrong. Silently putting this responsibility
+to the application as an option is an example of how to provocate wrong usage
+of the library. So if a library does this, it just degrades the quality of the
+libraries API. And usage of libraries with bad APIs should be if you would like
+to prevent your future yourself unneccessary headache during maintenance later.
+
+So then why we still need individual handlers everywhere, even with a global
+catch-all handler registered? The only thing such a global handler is good for,
+is to make visible that error handling is missing SOMEWHERE. And this is the
+problem. As the stack traces likely will NOT contain the location where you
+have to apply a fix. You still have to guess where it could be. Here an example
+code which needs to be fixed, but we do NOT yet know that it is the code,
+because we only see the exception in the log and do NOT know where it should be
+handled:
+
+```java
+ public Promise<Phone> getOrCreatePhone(Id personId, Phone addIfMissing){
+ var onDone = Promise.<Void>promise();
+ exampleLib.fetchPerson(personId).onSuccess( person -> exampleLib.fetchPhone(person.id).onSuccess( phone -> {
+ onDone.complete(phone);
+ })).onFailure(onDone::fail);
+ return onDone.future();
+ }
+```
+
+Whops this code accidentally misses that it should assign the new phone to the
+person if there is no phone assigned yet. Unluckily we do NOT know this yet, as
+this code is in production and we just found this error in our logs, maybe in
+combination with some complaining customers that they get HTTP response
+timeouts without any further details. With luck we somewhen randomly stumble
+over this error. Notihng indicates a HTTP 500 nor any timeout. Just looks
+unrelated to what we're searching. Exactly as all the other 100'000 usless
+error logs in there.
+
+```
+ foo.bar.HttpNotFoundException: 404
+ at HttpLib.doGet0()
+ at HttpLib.doGet()
+ at HttpLib.get()
+ at EventLoop.runTask()
+ at EventLoop.nextTask()
+ at Thread.run()
+```
+
+So now how would you like to find out where this stack is coming from? There is
+NOT EVEN ONE stacktrace line pointing to any of our application code at all. So
+the only option left is to GUESS where it could come from. And both of the
+message and the stacktrace are pretty useless.
+
+Thereofore: Just add this fu**ing error handling. It is not that hard:
+
+```java
+ public Promise<Phone> getOrCreatePhone(Id personId, Phone addIfMissing){
+ var onDone = Promise.<Void>promise();
+ exampleLib.fetchPerson(personId, personEv -> {
+ if( personEv.failed() ){
+ onDone.fail(new UnsupportedOperationException("TODO error handling", personEv.cause()));
+ return;
+ }
+ Person person = personEv.result();
+ exampleLib.fetchPhone(person.id, phoneEv -> {
+ if( phoneEv.failed() ){
+ onDone.fail(new UnsupportedOperationException("TODO error handling", phoneEv.cause()));
+ return;
+ }
+ onDone.complete(phone);
+ });
+ });
+ return onDone.future();
+ }
+```
+
+Now the stack will look somewhat like below, where the very first line points
+you straight to where the problem has to be fixed.
+
+```
+ UnsupportedOperationException: TODO error handling
+ at getOrCreatePhone$lambda$1() line 42
+ at EventLoop.runTask()
+ at EventLoop.nextTask()
+ at Thread.run()
+ Caused by: foo.bar.HttpNotFoundException: 404
+ at HttpLib.doGet0()
+ at HttpLib.doGet()
+ at HttpLib.get()
+ at EventLoop.runTask()
+ at EventLoop.nextTask()
+ at Thread.run()
+```
+
+For those who now start yelling around "urgh, this code is soo long". Here's your choise:
+
+- Write short code which does NOT TELL YOU WHAT IS HAPPENING and gives bugs
+ many places to play hide-and-seek. Aka happy maintenance nightmare.
+- Write some more code which makes it hard for bugs to hide.
+
+
+Initialized: 2024-04-12
+LastModified: 2024-04-12
+Published: <none>
+Author and Copyright: Andreas Fankhauser
+
+