What is the right way to implement sync and async methods in a library?

家住魔仙堡 提交于 2019-12-04 06:45:13

If you are creating a new thread even in case of synchronous operation(when it is actually not required), it will lead to performance hit. You are basically creating new Thread(read as wasting resources) without even getting any benefits of it. That being said I think better way would be to wrap the HTTP part in a different class. That way you would be re-using the code for HTTP access in both synchronous and asynchronous case.

class HTTPAccess{
    private RestTemplate restTemplate;
    private DataKey key;

    public HTTPAccess(DataKey key,RestTemplate restTemplate){
        this.key = key;
        this.restTemplate = restTemplate;

    }


    public DataResponse performRequest() {
        DataResponse dataResponse = null;        
        try {
            String url = createURL();
            response = restTemplate.getForObject(url, String.class);

            // it is a successful response
            dataResponse = new DataResponse(response, DataErrorEnum.NONE, DataStatusEnum.SUCCESS);
        } catch (RestClientException ex) {
            PotoLogging.logErrors(ex, DataErrorEnum.SERVER_DOWN, key);
            dataResponse = new DataResponse(null, DataErrorEnum.SERVER_DOWN, DataStatusEnum.ERROR);
        } catch (Exception ex) {
            PotoLogging.logErrors(ex, DataErrorEnum.CLIENT_ERROR, key);
            dataResponse = new DataResponse(null, DataErrorEnum.CLIENT_ERROR, DataStatusEnum.ERROR);
        }

        return dataResponse;
    }

    // create a URL by using key object
    private String createURL() {
        String url = somecode;
        return url;
    }

}

Now for the Client implementation, simply use this class.

public class DataClient implements Client {

    private ExecutorService executor = Executors.newFixedThreadPool(10);
    private RestTemplate restTemplate;
    private void initRestClient(DataKey key){
    if(restTemplate == null)
        restTemplate = new RestTemplate(clientHttpRequestFactory(key));
    }

    private ClientHttpRequestFactory clientHttpRequestFactory(DataKey key) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(key.getTimeout());
        factory.setConnectTimeout(key.getTimeout());
        //if you need to set otherparams this is the place we can do it extracting from DataKey obj
        return factory;
    }

    // for synchronous call
    @Override
    public DataResponse executeSynchronous(DataKey key) {
        initRestClient(key);
        DataResponse dataResponse = new HTTPAccess(key).performRequest();
        return dataResponse;
    }

    //for asynchronous call
    @Override
    public Future<DataResponse> executeAsynchronous(final DataKey key) {
        return executor.submit(new Callable<DataResponse>() {
            @Override
            public DataResponse call() throws Exception {
                return executeSynchronous(key);
            }
        });
    }
}

This way your HTTP implementation is completely separate and in future if you need to change the way to receive DataResponse (maybe from DB call ), then you have to change the HTTPAccess class only and other part will not be affected.

For synchronous call, executing in a separate thread is definitely not a good idea. You are incurring extra costs and resources for a Thread along with the cost of context switch of threads in this case.

If there are lots of synchronous calls then you will be unnecessarily blocking the threads for asynchronous calls as your executor is of fixed size threads. The total throughput of the system will be less in that case.

For example: If there are 10 clients calling each of the synchronous and asynchronous calls, in your implementation only threads will be actually working. However, if you were to utilize the client threads also and not make synchronous call as asynchronous and wait then all the 20 calls will be processed at the same time.

wzurita

I think this is better:

@Override
public DataResponse executeSynchronous(DataKey key) {
    Task task = new Task(key, restTemplate);
    return task.call();
}

It performs the same job, is clear, shorter, and has no overhead.

Notice that his also cleans up the duplicate Exception handling you currently have.

If the timeout is a must, an option is to use the underlying timeouts for the RestTemplate class, as explained in Spring RestTemplate timeout

Then the timeout will cause a RestClientException that you or the library client can handle.

I wouldn't bother with that Task class. Just make your synchronous method do all the work and call it asynchronously from the asynchronous method.

public class DataClient implements Client {

    private RestTemplate restTemplate = new RestTemplate();
    private ExecutorService executor = Executors.newFixedThreadPool(10);

    // for synchronous call
    @Override
    public DataResponse executeSynchronous(DataKey key) {
        DataResponse dataResponse = null;
        String response = null;

        try {
            String url = createURL();
            response = restTemplate.getForObject(url, String.class);

            // it is a successful response
            dataResponse = new DataResponse(response, DataErrorEnum.NONE, DataStatusEnum.SUCCESS);
        } catch (RestClientException ex) {
            PotoLogging.logErrors(ex, DataErrorEnum.SERVER_DOWN, key);
            dataResponse = new DataResponse(null, DataErrorEnum.SERVER_DOWN, DataStatusEnum.ERROR);
        } catch (Exception ex) {
            PotoLogging.logErrors(ex, DataErrorEnum.CLIENT_ERROR, key);
            dataResponse = new DataResponse(null, DataErrorEnum.CLIENT_ERROR, DataStatusEnum.ERROR);
        }

        return dataResponse;
    }

    //for asynchronous call
    @Override
    public Future<DataResponse> executeAsynchronous(final DataKey key) {
        return executor.submit(new Callable<DataResponse>() {
            @Override
            public DataResponse call() throws Exception {
                return executeSynchronous(key);
            }
        });
    }
}

The above code of executing synchronous task via async is same as having everything as async. If this is the requirement then I would suggest you to use google guava's ListenableFuture. I am not an advocate but it has methods to manage task timeout, well written callbacks to handle onSuccess, onFailure scenarios. https://code.google.com/p/guava-libraries/wiki/ListenableFutureExplained

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!