Java fallback pattern
Asked Answered
W

3

13

I'm trying to find a nice way of implementing a service which relies on a third-party library class. I also have a 'default' implementation to use as fallback in case the library is unavailable or can not provide an answer.

public interface Service {

    public Object compute1();

    public Object compute2();
}

public class DefaultService implements Service {

    @Override
    public Object compute1() {
       // ...
    }

    @Override
    public Object compute2() {
        // ...
    }
}

The actual implementation of the service would be something like:

public class ServiceImpl implements Service {
    Service defaultService = new DefaultService();
    ThirdPartyService thirdPartyService = new ThirdPartyService();

    @Override
    public Object compute1() {
        try {
            Object obj = thirdPartyService.customCompute1();
            return obj != null ? obj : defaultService.compute1();
        } 
        catch (Exception e) {
            return defaultService.compute1();
        }
    }

    @Override
    public Object compute2() {
        try {
            Object obj = thirdPartyService.customCompute2();
            return obj != null ? obj : defaultService.compute2();
        } 
        catch (Exception e) {
            return defaultService.compute2();
        }
    }
}

The current implementation seems to duplicate things a bit in the way that only the actual calls to the services are different, but the try/catch and the default mechanism are pretty much the same. Also, if another method was added in the service, the implementation would look almost alike.

Is there a design pattern that might apply here (proxy, strategy) to make the code look better and make further additions less copy-paste?

Worm answered 26/4, 2015 at 10:59 Comment(1)
What version of Java are you using?Combination
M
4

You can extract the common logic into a separate method using method references, like:

public class ServiceImpl implements Service {
    Service defaultService = new DefaultService();
    ThirdPartyService thirdPartyService = new ThirdPartyService();

    @Override
    public Object compute1() {
        return run(thirdPartyService::customCompute1, defaultService::compute1);
    }

    @Override
    public Object compute2() {
        return run(thirdPartyService::customCompute2, defaultService::compute2);
    }

    private static <T> T run(Supplier<T> action, Supplier<T> fallback) {
        try {
            T result = action.get();
            return result != null ? result : fallback.get();
        } catch(Exception e) {
            return fallback.get();
        }
    }
}
Manyplies answered 26/4, 2015 at 11:27 Comment(3)
I doubt this solution can nicely be applied if the methods take arguments. You would have to overload run for at least Function and BiFunction, and then you might need a TriFunction but it doesn't scale very well. Moreover, each new method of Service needs a corresponding implementation in ServiceImpl, which does not scale well either and could also be a source of bugs and maintenance problems.Ligature
Agreed with @Didier, while this is a solution for this specific case, it is not a very good general solution. Method handles are cool, but probably not that well suited here.Aggregation
If fallback.get() throws an exception in the try block, it gets repeated in the catch block.Muirhead
A
4

A proxy can probably help you here. The example below is untested, but should give you an idea of what you could put in place:

public class FallbackService implements InvocationHandler {

    private final Service primaryService;
    private final Service fallbackService;

    private FallbackService(Service primaryService, Service fallbackService) {
        this.primaryService = primaryService;
        this.fallbackService = fallbackService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Object result = method.invoke(primaryService, args);
            if (result != null) return result;
        } catch (Exception ignore) {}
        return method.invoke(fallbackService, args);
    }

    public static Service createFallbackService(Service primaryService, Service fallbackService) {
        return (Service) Proxy.newProxyInstance(
                Service.class.getClassLoader(),
                new Class[] { Service.class },
                new FallbackService(primaryService, fallbackService)
        );
    }
}
Aggregation answered 26/4, 2015 at 11:24 Comment(0)
M
4

You can extract the common logic into a separate method using method references, like:

public class ServiceImpl implements Service {
    Service defaultService = new DefaultService();
    ThirdPartyService thirdPartyService = new ThirdPartyService();

    @Override
    public Object compute1() {
        return run(thirdPartyService::customCompute1, defaultService::compute1);
    }

    @Override
    public Object compute2() {
        return run(thirdPartyService::customCompute2, defaultService::compute2);
    }

    private static <T> T run(Supplier<T> action, Supplier<T> fallback) {
        try {
            T result = action.get();
            return result != null ? result : fallback.get();
        } catch(Exception e) {
            return fallback.get();
        }
    }
}
Manyplies answered 26/4, 2015 at 11:27 Comment(3)
I doubt this solution can nicely be applied if the methods take arguments. You would have to overload run for at least Function and BiFunction, and then you might need a TriFunction but it doesn't scale very well. Moreover, each new method of Service needs a corresponding implementation in ServiceImpl, which does not scale well either and could also be a source of bugs and maintenance problems.Ligature
Agreed with @Didier, while this is a solution for this specific case, it is not a very good general solution. Method handles are cool, but probably not that well suited here.Aggregation
If fallback.get() throws an exception in the try block, it gets repeated in the catch block.Muirhead
S
3

One of the best libraries for that is Netflix' Hystrix. I am not sure if you need such a heavy lifting. It will give you thread pool, timeouts, fallbacks, monitoring, runtime configuration changes, short circuit, etc.

Basically it's a library to defend your code from failures of its dependencies.

Shaker answered 26/4, 2015 at 11:11 Comment(2)
Hystrix looks great! I'm evaluating it right now. It provides much much more than what the OP was asking for, but that can be a good thing...Aggregation
Thank you for mentioning Hystrix. Was unaware of it, but it does way more than what I need and we have a rather strict policy about adding libs. Will check it out thoughWorm

© 2022 - 2024 — McMap. All rights reserved.