Meilleur modèle de conception pour le chaînage API en JAVA


Je veux invoquer une série d'appels API en Java. L'exigence est que la réponse d'une API soit utilisée dans la demande de l'appel d'API suivant. Je peux y parvenir en utilisant certaines boucles. Mais je veux utiliser un modèle de conception de telle sorte que l'implémentation soit générique. Toute aide?

La chaîne de responsabilité ne répond pas à mon besoin car je ne saurai pas quel est le contexte de ma demande au début.

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);
Author: pkpnd, 2018-05-10

4 answers

Ce qui suit vient à l'esprit:

  1. Pour les appels de fonction qui renvoient un objet: ne renvoyez jamais null.
  2. Pour les appels de fonction qui ne renvoient rien (sinon): return this.
  3. Acceptez les interfaces fonctionnelles dans votre API afin que les utilisateurs puissent personnaliser le comportement
  4. Pour les objets avec état qui exposent l'API comme décrit ci-dessus, fournissez un modèle de constructeur afin que les utilisateurs ne finissent pas par choisir entre les constructeurs
  5. Toutes les méthodes du constructeur décrites doivent être nulles, et retournez donc this
 0
Author: Timir, 2018-05-10 06:52:14

, Vous pouvez créer un ResponseStringBuilder classe qui prend un Function<Response,String> pour obtenir le String de la 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();
    }
}

Qui ferait vos appels

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());

Je ne sais pas si la déclaration de tableau se compile réellement, mais cela devrait fonctionner avec des modifications mineures et vous obtenez l'essentiel.

 0
Author: daniu, 2018-05-10 07:34:20

Vous pouvez utiliser un CompletableFuture pour implémenter des promesses en Java. Le problème est que vous essayez de transmettre deux choses différentes dans le "pipeline": la demande, qui est mutable et (parfois) change, et le résultat, qui est accumulé au cours des appels.

J'ai contourné cela en créant une classe appelée Pipe qui a une requête et l'accumulateur pour les résultats jusqu'à présent. Il a getters pour les deux, et il a quelques méthodes pratiques pour renvoyer un nouvel objet avec les résultats accumulés ou même muter la demande et accumuler en un seul appel. Cela rend le code de l'API enchaînant beaucoup plus propre.

Les méthodes with* après les champs, le constructeur et les getters sont celles qui gèrent l'accumulation et la mutation. La méthode chain met tout cela ensemble:

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();
    }
}

Parce qu'il s'exécute de manière asynchrone, il peut lancer InterruptedException, et il peut également lancer ExecutionException si quelque chose d'autre se casse. Je ne sais pas comment vous voulez gérer que, je viens donc de déclarer chain à jeter.

Si vous vouliez appliquer n opérations dans une boucle, vous devez continuer à réaffecter la promesse, comme suit:

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();

J'ai utilisé Java 10 inférence de type avec var ici, mais les types de promise et result est CompletableFuture<Pipe> et String, respectivement.

(Note: il pourrait être préférable de rendre Request immuable et de passer un nouveau, modifié le long du pipeline plutôt que de le muter. D'autre part, vous pouvez également envelopper un StringBuilder au lieu d'un String, et que les données que vous accumulez soient également mutables. En ce moment, c'est un mélange étrange de mutable et d'immuable, mais cela correspond à ce que faisait votre code.)

 0
Author: David Conrad, 2018-05-10 08:32:54

Merci à tous pour les entrées, j'ai finalement atterri sur une solution qui répond à mon besoin. J'ai utilisé un Singleton pour l'exécution de la demande. Pour chaque type de commande, il y aura un ensemble de requêtes à exécuter dans un ordre particulier. Chaque commande a un ordre particulier de demandes à exécuter que j'ai stockées dans un tableau avec l'ID unique de la demande. Ensuite gardé le tableau dans une carte contre le nom de la commande.

Dans une boucle, j'ai exécuté le tableau et exécuté, après chaque itération, je continue à définir le réponse dans l'objet request et a finalement préparé la réponse de sortie.

    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
Author: anz, 2018-07-27 04:52:02