Лучший шаблон проектирования для создания цепочек API в JAVA - PullRequest
0 голосов
/ 10 мая 2018

Я хочу вызвать серию вызовов API в Java. Требуется, чтобы ответ API был использован в последующем запросе API. Я могу добиться этого, используя определенные петли. Но я хочу использовать шаблон проектирования таким образом, чтобы реализация была общей. Любая помощь?

Цепочка ответственности не отвечает моим потребностям, так как я не буду знать, каков мой контекст запроса в начале.

String out = null;
Response res = execute(req);
out += res.getOut();
req.setXYZ(res.getXYZ);
Response res = execute(req);
out += res.getOut();
req.setABC(res.getABC);
Response res = execute(req);
out += res.getOut();

System.out.println("Final response::"+out);

Ответы [ 4 ]

0 голосов
/ 27 июля 2018

Спасибо всем за вклады, наконец, я нашел одно решение, которое отвечает моим потребностям. Я использовал один синглтон для выполнения запроса. Для каждого типа команды будет набор запросов, которые должны быть выполнены в одном конкретном порядке. У каждой команды есть определенный порядок выполнения запросов, который я сохранил в массиве с уникальным идентификатором запроса. Затем сохранил массив в карте против имени команды.

В цикле я запускал массив и выполнял, после каждой итерации продолжал устанавливать ответ обратно в объект запроса и в конце концов готовил выходной ответ.

    private static Map<RequestAction,String[]> actionMap = new HashMap<RequestAction, String[]>();

    static{
    actionMap.put(RequestAction.COMMAND1,new String[]{WebServiceConstants.ONE,WebServiceConstants.TWO,WebServiceConstants.FOUR,WebServiceConstants.THREE});
    actionMap.put(RequestAction.THREE,new String[]{WebServiceConstants.FIVE,WebServiceConstants.ONE,WebServiceConstants.TWO});}


    public Map<String,Object> execute(ServiceParam param) {

    String[] requestChain = getRequestChain(param);

    Map<String,Object> responseMap = new HashMap<String, Object>();


    for(String reqId : requestChain) {

        prepareForProcessing(param, tempMap,responseMap);

        param.getRequest().setReqId(reqId);

        //processing the request
        tempMap = Service.INSTANCE.process(param);

        //prepare responseMap using tempMap         

        param.setResponse(response);

    }

    return responseMap;
}
0 голосов
/ 10 мая 2018

Вы можете создать ResponseStringBuilder класс, который берет Function<Response,String>, чтобы получить String от Response.

public ResponseStringBuilder {
    private Request request;
    public StringBuilder resultBuilder = new StringBuilder();
    public ResponseBuilder(Request req) {
        this.request = req;
    }
    public ResponseStringBuilder fromExtractor(Function<Request, Response> getResponse, Function<Response,String> extract) {
        Response response = getResponse.apply(request);
        resultBuilder.append(extract.apply(response));
        return this;
    }
    public String getResult() {
         return resultBuilder.toString();
    }
}

Это сделало бы ваши звонки

ResponseStringBuilder builder = new ResponseStringBuilder(req);
@SuppressWarnings("unchecked")
Function<Response,String> extractors = new Function[] {
    Response::getABC, Response::getXYZ 
};
for (Function<Response,String> ext : extractors) {
    builder = builder.fromExtractor(this::execute, ext);
}
System.out.println("final response: " + builder.getResult());

Не уверен, что объявление массива действительно компилируется, но оно должно работать с незначительными изменениями, и вы получите суть.

0 голосов
/ 10 мая 2018

Вы можете использовать CompletableFuture для реализации обещаний в Java. Проблема в том, что вы пытаетесь передать две разные вещи по «конвейеру»: запрос, который является изменчивым и (иногда) изменяющимся, и результат, который накапливается в ходе вызовов.

Я справился с этим, создав класс с именем Pipe, у которого есть запрос и накопитель результатов. У него есть геттеры для обоих, и у него есть несколько удобных методов для возврата нового объекта с накопленными результатами или даже для изменения запроса и накопления в одном вызове. Это делает код API цепочки намного чище.

Методы with* после полей, конструктора и геттеров - это те, которые обрабатывают накопление и мутацию. Метод chain объединяет все это:

import java.util.concurrent.CompletableFuture;

public class Pipe {
    private Request req;
    private String out;

    public Pipe(Request req, String out) {
        this.req = req;
        this.out = out;
    }

    public Request getReq() {
        return req;
    }

    public String getOut() {
        return out;
    }

    public Pipe with(String data) {
        return new Pipe(req, out + data);
    }

    public Pipe withABC(String abc, String data) {
        req.setABC(abc);
        return new Pipe(req, out + data);
    }

    public Pipe withXYZ(String xyz, String data) {
        req.setXYZ(xyz);
        return new Pipe(req, out + data);
    }

    public static void chain(Request req) throws Exception {
        var promise = CompletableFuture.supplyAsync(() -> new Pipe(req, ""))
        .thenApply(pipe -> {
            Response res = execute(pipe.getReq());
            return pipe.withABC(res.getABC(), res.getOut());
        })
        .thenApply(pipe -> {
            Response res = execute(pipe.getReq());
            return pipe.withXYZ(res.getXYZ(), res.getOut());
        })
        .thenApply(pipe -> {
            Response res = execute(pipe.getReq());
            return pipe.with(res.getOut());
        });

        var result = promise.get().getOut();

        System.out.println(result);
    }

    public static Response execute(Request req) {
        return req.getResponse();
    }
}

Поскольку он работает асинхронно, он может генерировать InterruptedException, а также может генерировать ExecutionException, если что-то еще прерывается. Я не знаю, как вы хотите справиться с этим, поэтому я просто объявил chain бросить.

Если вы хотите применить n операций в цикле, вы должны продолжать переназначать обещание обратно, как показано ниже:

var promise = CompletableFuture.supplyAsync(() -> new Pipe(req, ""));

for (...) {
    promise = promise.thenApply(pipe -> {
        Response res = execute(pipe.getReq());
        return pipe.with(res.getOut());
    });
}

var result = promise.get().getOut();

Я использовал здесь вывод типа Java 10 с var, но типы promise и result будут CompletableFuture<Pipe> и String соответственно.

(Примечание: может быть лучше сделать Request неизменным и передать новый, измененный по конвейеру, а не мутировать его. С другой стороны, вы также можете обернуть StringBuilder вместо String и данные, которые вы накапливаете, также должны быть изменяемыми. Сейчас это странное сочетание изменчивости и неизменности, но это соответствует тому, что делал ваш код.)

0 голосов
/ 10 мая 2018

На ум приходит следующее:

  1. Для вызовов функций, которые возвращают объект: никогда не возвращать ноль.
  2. Для вызовов функций, которые ничего не возвращают: return this.
  3. Примите функциональные интерфейсы в вашем API, чтобы пользователи могли настраивать поведение
  4. Для объектов с состоянием, которые предоставляют API, как описано выше, предоставьте шаблон Builder, чтобы пользователи не могли выбирать между конструкторами
  5. Все описанные методы Builder должны быть недействительными и поэтому возвращать this
...