Search code examples
javafunctional-programming

descriptive way of calling various feign APIs


I want to create a construct that would work with pageable feign api calls and dry them from the first page of declared size available to the last one.

To take in account:

  • the feign method calls can differ in arg. count tho last two is always page and it's size
  • data structure returned is similar to the extent of paging information, but core data list received type differs

This is what I did:

method that is a base for draining a particular api call:

    public <T> List<BaseFeignResult<T>> drainFeignPageableCall(
            PagedCall<T> feignCall
    ) {
        BaseFeignResult<T> firstPage = feignCall.call(0, 10);
        List<BaseFeignResult<T>> baseFeignResults = drainFeignPageableCall(feignCall, firstPage, Lists.newArrayList(firstPage), 1);

        return baseFeignResults;
    }

It's overload and continuation:

    <T> List<BaseFeignResult<T>> drainFeignPageableCall(
            PagedCall<T> feignCall,
            BaseFeignResult<T> dataPage,
            List<BaseFeignResult<T>> acc,
            int page
    ) {
        if (dataPage.resp.getBody().getData().size() % 10 > 0)
            return acc;

        BaseFeignResult<T> res = feignCall.call(page, 10);
        acc.add(res);

        return drainFeignPageableCall(feignCall, res, acc, ++page);
    }

And the definitions:

    public static class SingleParamPageableCall<T> implements PagedCall<T> {
        SingleParamPagingApi<T> fun;
        String param;

        public SingleParamPageableCall(SingleParamPagingApi<T> fun, String param) {
            this.fun = fun;
            this.param = param;
        }

        @Override
        public BaseFeignResult<T> call(int p, int s) {
            BaseFeignResult.BaseFeignResultBuilder<T> builder = BaseFeignResult.builder();

            try {
                builder.resp(fun.callFeignApi(param, p, s));
            } catch (RuntimeException e) {
                builder.excp(e);
            }

            return builder.build();
        }
    }

    public interface PagedCall<T> {
        BaseFeignResult<T> call(int p, int s);
    }

    @Builder
    public static class BaseFeignResult<T> {
        private final ResponseEntity<IVDPagedResponseOf<T>> resp;
        private final RuntimeException excp;
    }

    public interface SingleParamPagingApi<T> {
        ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(String arg, int page, int size) throws RuntimeException;
    }

This can be arbitraliry called as:

drainFeignPageableCall(new BaseService.SingleParamPageableCall<GetOrderInfoDto>(ordersFeignClient::getOrdersBySampleIds, "34596"));

and works as expected.

So as you can see, if I want to keep some sort of abstraction above various drain-able per api calls, I need to introduce definitions like SingleParamPagingApi and class implementation of SingleParamPageableCall<T>. so with every other api to be treated this way, I would need to redefine those.

My question here is: how to do this in purely descripive way, or how to reimplement this as a functional programming? to be clear: I would like to have code impl. in which I would describe how to map parameters to the method call (that can and will vary) and return a common data structure with the data being of generic type.

Basically I am looking for the most descriptive way of re-implementing this in Java without defining heavy objects like SingleParamPagingApi<T>, but describing how to mount params called with to API params itself rather.

Thank you!


Solution

  • This simplest way would be to replace your SingleParamPagingApi interface with one that has a method that just takes the page no and size as parameters (PagingApi). And replace SingleParamPageableCall with a class that just takes a PagingApi argument. Then you can create the variants of PagingApi for 1 parameter, 2 parameters etc by immediately binding the method to the argument 0, argument 1 etc, thereby creating a PagingApi instance (the of methods).

    public interface PagingApi1<T, A0> {
        ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(A0 arg0, int page, int size) throws RuntimeException;
    }
    
    public interface PagingApi2<T, A0, A1> {
        ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(A0 arg0, A1 arg1, int page, int size) throws RuntimeException;
    }
    
    public interface PagingApi<T> {
        static <T, A0> PagingApi<T> of(PagingApi1<T, A0> api, A0 arg0) {
            return (p, s) -> api.callFeignApi(arg0, p, s);
        }
    
        static <T, A0, A1> PagingApi<T> of(PagingApi2<T, A0, A1> api, A0 arg0, A1 arg1,) {
            return (p, s) -> api.callFeignApi(arg0, arg1, p, s);
        }
    
        ResponseEntity<IVDPagedResponseOf<T>> callFeignApi(int page, int size) throws RuntimeException;
    }
    
    
    public static class PageableCall<T> implements PagedCall<T> {
        PagingApi<T> fun;
    
        public PageableCall(PagingApi<T> fun) {
            this.fun = fun;
        }
    
        @Override
        public BaseFeignResult<T> call(int p, int s) {
            BaseFeignResult.BaseFeignResultBuilder<T> builder = BaseFeignResult.builder();
    
            try {
                builder.resp(fun.callFeignApi(p, s));
            } catch (RuntimeException e) {
                builder.excp(e);
            }
    
            return builder.build();
        }
    }
    

    You would call it as follows:

    drainFeignPageableCall(
            new PageableCall<GetOrderInfoDto>(
                    PagingApi.of(ordersFeignClient::getOrdersBySampleIds, "34596")
            )
    );
    

    As a further simplifcation, you could probably collapse PagingApi and PagedCall into a single interface.

    I would also suggest replacing the recursive calls in drainFeignPageableCall with a simple for loop. You might think recursion is more "functional" but it's needlessly complex and inefficient here.