Skip to content

Error handling in QBit

Richard Hightower edited this page Jun 8, 2015 · 2 revisions

Let's say you have a simple Java class as follows:

    @RequestMapping("/my")
    public static class MyService {

        public boolean methodThatThrowsError() {
            puts("Method that throws error");
            throw new IllegalStateException("ERROR");
        }


        public void methodThatThrowsError2(Callback<Boolean> callback) {
            puts("Method that throws error");
            throw new IllegalStateException("ERROR");
        }

        public boolean regularMethod() {
            puts("Regular method");
            return true;
        }
    }

You want to handle errors from the above. You can. First, in order to handle errors, the service method must have a return or a callback.

The above would equate to this client proxy interface.

    public static interface IMyService {
        void methodThatThrowsError(Callback<Boolean> callback);
        void regularMethod(Callback<Boolean> callback);
        void methodThatThrowsError2(Callback<Boolean> callback);

    }

The error handling should work the same if you are using a remote websocket proxy, a service bundle or a service as this example shows.

Create a client proxy using a service builder.

        final ServiceQueue serviceQueue = ServiceBuilder.serviceBuilder().build(new MyService())
                .startServiceQueue().startCallBackHandler();

        final IMyService client = serviceQueue
                .createProxyWithAutoFlush(IMyService.class, 10, TimeUnit.MILLISECONDS);

Now invoke it.

        client.methodThatThrowsError(new Callback<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) {

                //Never called
                puts("Called back 1.. callback is here", aBoolean);
            }
        });
        puts("Called back 1");

The accept method is never called because the method throws an exception.

Since the method throws an exception the callback is never called.

If you want to register an exception handler, you could do this.

        /* Now to actually handle the error. */
        client.methodThatThrowsError(new Callback<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) {

                //Never called
                puts("Called back 1.. callback is here", aBoolean);
            }

            @Override
            public void onError(Throwable error) {
                //THIS GETS CALLED!
                puts("This gets called!", error);
            }

        });

The moral of the story is that if you want to handle the error, you must register a callback. In order to register a callback, you must have a method that has a callback as the first param or a method

Since this is not very attractive, you could use a reactor like so:

        final Reactor reactor = new Reactor(Timer.timer(), 10_000, TimeUnit.MILLISECONDS);
        final CallbackBuilder callbackBuilder = CallbackBuilder.callbackBuilder(reactor);


        callbackBuilder.setCallback(Boolean.class,
                aBoolean ->
                {
                    puts("Return from method", aBoolean);
                });

        callbackBuilder.setOnError(throwable
                -> {
            puts("I customized the way I am handling errors");
        });


        client.regularMethod(callbackBuilder.build());
        client.methodThatThrowsError(callbackBuilder.build());
        client.methodThatThrowsError2(callbackBuilder.build());

A reactor can handle multiple callbacks and timeouts, and errors. But you have to periodically call reactor process which is no problem if you are running a QBit service. You use a reactor inside of a services' @QueueCallbackHandler handler for limit, empty and idle.

I just added another version of callback builder that works without a reactor so you can do this:

        final CallbackBuilder callbackBuilder2 = CallbackBuilder.callbackBuilder();


        callbackBuilder2.setCallback(Boolean.class,
                aBoolean ->
                {
                    puts("Return from method 2", aBoolean);
                });

        callbackBuilder2.setOnError(throwable
                -> {
            puts("I customized the way I am handling errors 2");
        });


        client.regularMethod(callbackBuilder2.build());
        client.methodThatThrowsError(callbackBuilder2.build());
        client.methodThatThrowsError2(callbackBuilder2.build());

To register a new callback just call setCallback before build. This will build a new callback with the error handling that you registered with the builder. If you want to customize the error handling just call setOnError before the call.

        client.methodThatThrowsError(
                CallbackBuilder.callbackBuilder().setCallback(Boolean.class, aBoolean -> puts("Custom callback handler", aBoolean))
                        .setOnError(throwable -> puts("Custom error handler", throwable)).build()
        );

in progress

package io.advantageous.qbit.example.errors;

import io.advantageous.boon.core.Sys;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.reactive.Callback;
import io.advantageous.qbit.reactive.CallbackBuilder;
import io.advantageous.qbit.reactive.Reactor;
import io.advantageous.qbit.service.ServiceBuilder;
import io.advantageous.qbit.service.ServiceQueue;
import io.advantageous.qbit.util.Timer;

import java.util.concurrent.TimeUnit;

import static io.advantageous.boon.core.IO.puts;

public class ErrorHandling {

    @RequestMapping("/my")
    public static class MyService {

        public boolean methodThatThrowsError() {
            puts("Method that throws error");
            throw new IllegalStateException("ERROR");
        }


        public void methodThatThrowsError2(Callback<Boolean> callback) {
            puts("Method that throws error");
            throw new IllegalStateException("ERROR");
        }

        public boolean regularMethod() {
            puts("Regular method");
            return true;
        }
    }


    public static interface IMyService {
        void methodThatThrowsError(Callback<Boolean> callback);
        void regularMethod(Callback<Boolean> callback);
        void methodThatThrowsError2(Callback<Boolean> callback);

    }


    public static void main(final String... args) {
        final ServiceQueue serviceQueue = ServiceBuilder.serviceBuilder().build(new MyService())
                .startServiceQueue().startCallBackHandler();

        final IMyService client = serviceQueue
                .createProxyWithAutoFlush(IMyService.class, 10, TimeUnit.MILLISECONDS);

        client.methodThatThrowsError(new Callback<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) {

                //Never called
                puts("Called back 1.. callback is here", aBoolean);
            }
        });
        puts("Called back 1");

        /* Since the method throws an exception
         the callback is never called.
         */

        //No way to catch it. No callback.

        client.methodThatThrowsError2(new Callback<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) {
                //Never called
                puts("Called back 2 callback 2", aBoolean);
            }
        });

        puts("Called back 2");


        /* The default error handling will just output
           errors to the log.
           For many services where you don't expect exception
           or services where there is nothing you can do with exceptions
           if they do occur that is exactly what you want.
           If all you are going to do is log it, then
           we handle that already.
           However, sometimes you can recover.
           Sometimes, you want to log additional context, so...
         */

        /* Now to actually handle the error. */
        client.methodThatThrowsError(new Callback<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) {

                //Never called
                puts("Called back 1.. callback is here", aBoolean);
            }

            @Override
            public void onError(Throwable error) {
                //THIS GETS CALLED!
                puts("This gets called!", error);
            }

        });


        /* Now to actually handle the error. */
        client.methodThatThrowsError2(new Callback<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) {
                 //NEVER CALLED
                puts("Called back 1.. callback is here", aBoolean);
            }

            @Override
            public void onError(Throwable error) {
                //THIS GETS CALLED!
                puts("This gets called!", error);
            }

            /**
             * You can also handle timeouts btw.
             */
            @Override
            public void onTimeout() {
                puts("This does not but can you imagine when it would");
            }

        });

        /* The moral of the story is that if you want to handle
         * the error, you must register a callback.
         *
         * In order to register a callback, you must have a method
         * that has a callback as the first param or a method
         * that returns a value.
         */

        /*
         * So far we have been using inner classes.
         * Let show an example on how to do the same with lambda.
         */

        final Reactor reactor = new Reactor(Timer.timer(), 10_000, TimeUnit.MILLISECONDS);
        final CallbackBuilder callbackBuilder = CallbackBuilder.callbackBuilder(reactor);


        callbackBuilder.setCallback(Boolean.class,
                aBoolean ->
                {
                    puts("Return from method", aBoolean);
                });

        callbackBuilder.setOnError(throwable
                -> {
            puts("I customized the way I am handling errors");
        });


        client.regularMethod(callbackBuilder.build());
        client.methodThatThrowsError(callbackBuilder.build());
        client.methodThatThrowsError2(callbackBuilder.build());

        /** A reactor can handle multiple callbacks and timeouts, and errors. */

        for (int index =0; index<10; index++) {
            Sys.sleep(10);
            reactor.process();
        }

        /** You use a reactor inside of a services @QueueCallback handler for limit, empty and idle. */

        Sys.sleep(1000);


        /**
         * I just added this.
         * You can now use callback builder without reactor.
         * Use this when you want to use things outside of a service.
         */

        final CallbackBuilder callbackBuilder2 = CallbackBuilder.callbackBuilder();


        callbackBuilder2.setCallback(Boolean.class,
                aBoolean ->
                {
                    puts("Return from method 2", aBoolean);
                });

        callbackBuilder2.setOnError(throwable
                -> {
            puts("I customized the way I am handling errors 2");
        });


        client.regularMethod(callbackBuilder2.build());
        client.methodThatThrowsError(callbackBuilder2.build());
        client.methodThatThrowsError2(callbackBuilder2.build());


        Sys.sleep(2000);

    }

}

Tutorials

__

Docs

Getting Started

Basics

Concepts

REST

Callbacks and Reactor

Event Bus

Advanced

Integration

QBit case studies

QBit 2 Roadmap

-- Related Projects

Kafka training, Kafka consulting, Cassandra training, Cassandra consulting, Spark training, Spark consulting

Clone this wiki locally