96 votes

aws api gateway & lambda: plusieurs terminaux / fonctions par rapport à un seul terminal

J'ai une api AWS qui assure le proxy des fonctions lamba. J'utilise actuellement différents terminaux avec des fonctions lambda distinctes:

 api.com/getData --> getData
api.com/addData --> addData
api.com/signUp --> signUp
 

Le processus de gestion de tous les points de terminaison et de toutes les fonctions devient fastidieux. Existe-t-il un inconvénient lorsque j'utilise un seul point de terminaison pour une fonction lambda qui décide quoi faire en fonction de la chaîne de requête?

 api.com/exec&func=getData --> exec --> if(params.func === 'getData') { ... }
 

110voto

Dave Maple Points 4422

Il est parfaitement valable pour mapper plusieurs méthodes en une seule fonction lambda et beaucoup de gens sont à l'aide de cette méthodologie, aujourd'hui, par opposition à la création d'une passerelle api de ressources et d'une fonction lambda pour chaque méthode discrète.

Vous pouvez envisager l'utilisation de proxy toutes les demandes pour une seule fonction. Jetez un oeil à la documentation suivante sur la création d'une Passerelle API => Lambda proxy de l'intégration: http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html

Leur exemple est grand ici. Une demande similaire à la suivante:

POST /testStage/hello/world?name=me HTTP/1.1
Host: gy415nuibc.execute-api.us-east-1.amazonaws.com
Content-Type: application/json
headerName: headerValue

{
    "a": 1
}

Sera le vent de l'envoi de l'événement suivant les données de votre AWS Lambda fonction:

{
  "message": "Hello me!",
  "input": {
    "resource": "/{proxy+}",
    "path": "/hello/world",
    "httpMethod": "POST",
    "headers": {
      "Accept": "*/*",
      "Accept-Encoding": "gzip, deflate",
      "cache-control": "no-cache",
      "CloudFront-Forwarded-Proto": "https",
      "CloudFront-Is-Desktop-Viewer": "true",
      "CloudFront-Is-Mobile-Viewer": "false",
      "CloudFront-Is-SmartTV-Viewer": "false",
      "CloudFront-Is-Tablet-Viewer": "false",
      "CloudFront-Viewer-Country": "US",
      "Content-Type": "application/json",
      "headerName": "headerValue",
      "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com",
      "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f",
      "User-Agent": "PostmanRuntime/2.4.5",
      "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)",
      "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==",
      "X-Forwarded-For": "54.240.196.186, 54.182.214.83",
      "X-Forwarded-Port": "443",
      "X-Forwarded-Proto": "https"
    },
    "queryStringParameters": {
      "name": "me"
    },
    "pathParameters": {
      "proxy": "hello/world"
    },
    "stageVariables": {
      "stageVariableName": "stageVariableValue"
    },
    "requestContext": {
      "accountId": "12345678912",
      "resourceId": "roq9wj",
      "stage": "testStage",
      "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33",
      "identity": {
        "cognitoIdentityPoolId": null,
        "accountId": null,
        "cognitoIdentityId": null,
        "caller": null,
        "apiKey": null,
        "sourceIp": "192.168.196.186",
        "cognitoAuthenticationType": null,
        "cognitoAuthenticationProvider": null,
        "userArn": null,
        "userAgent": "PostmanRuntime/2.4.5",
        "user": null
      },
      "resourcePath": "/{proxy+}",
      "httpMethod": "POST",
      "apiId": "gy415nuibc"
    },
    "body": "{\r\n\t\"a\": 1\r\n}",
    "isBase64Encoded": false
  }
}

Vous avez maintenant accès à tous les en-têtes, url params, corps etc. et vous pourriez l'utiliser pour traiter les demandes de manière différente dans une seule fonction Lambda (essentiellement la mise en œuvre de votre propre routage).

Comme une opinion, je vois quelques avantages et les inconvénients de cette approche. Beaucoup d'entre eux dépendent de vos cas d'utilisation:

  • Déploiement: si chaque fonction lambda est discret, alors vous pouvez les déployer de manière indépendante, ce qui peut réduire le risque de changement de code (microservices stratégie). A l'inverse vous pouvez trouver que le besoin de déployer des fonctions séparément ajoute de la complexité et de l'est lourde.
  • Auto Description: Passerelle API est une interface extrêmement intuitive de voir la mise en page de votre Réparateur points de terminaison -- les noms et les verbes sont tous visibles en un coup d'œil. La mise en œuvre de votre propre routage peut se faire au détriment de cette visibilité.
  • Lambda et le dimensionnement des limites: Si vous proxy tous -- puis vous vous retrouverez à devoir choisir une taille de l'instance, le délai d'attente etc. qui pourra accueillir tous vos Reposant points de terminaison. Si vous créez des fonctions distinctes, alors vous pouvez plus choisir avec soin l'empreinte mémoire, délai d'attente, deadletter comportement etc. qui répond le mieux aux besoins spécifiques de l'invocation.

34voto

John Wolf Points 61

J'aurais commenté tout simplement à ajouter un couple de points à Dave d'Érable de grande réponse, mais je n'ai pas assez de points de réputation encore donc je vais ajouter des commentaires ici.

J'ai commencé à la tête en bas le chemin de plusieurs points d'extrémité pointant vers une fonction Lambda qui pourrait traiter chaque point de terminaison différente en accédant à la "ressource" propriété de l'Événement. Après l'avoir essayé j'ai maintenant séparés en deux fonctions distinctes pour les raisons que Dave l'a suggéré plus:

  • Je trouve qu'il est plus facile de passer à travers les journaux et les moniteurs lorsque les fonctions sont séparées.
  • Une nuance qu'en tant que débutant, je n'ai pas décroché jusqu'à la première est que vous pouvez avoir une seule base de code et de déployer exactement le même code que de multiples fonctions Lambda. Cela vous permet d'avoir les avantages de la fonction de séparation et les avantages d'une approche consolidée dans votre base de code.
  • Vous pouvez utiliser AWS CLI pour automatiser des tâches à travers les multiples fonctions pour réduire/éliminer l'inconvénient de la gestion des fonctions distinctes. Par exemple, j'ai un script qui met à jour 10 fonctions avec le même code.

28voto

Kurt Lee Points 111

j'ai été la construction de 5~6 microservices avec la Lambda-Passerelle API, et fait l'objet de plusieurs essais et d'échec et de réussite.

en bref, à partir de mon expérience, il est préférable de déléguer tous les appels d'API pour lambda avec juste un APIGateway mappage de caractères génériques, tels que

/api/{+proxy} -> Lambda

si vous avez déjà utilisé des cadres comme les raisins , vous savez que lors de la prise des Api, des caractéristiques comme
"middleware"
"mondial de la gestion des exceptions"
"cascade de routage"
"la validation des paramètres"

sont vraiment crucial. comme votre API grandit, il est presque impossible de gérer toutes les routes avec de la Passerelle API de cartographie, ni de la Passerelle API appui d'un de ces longs aussi.

de plus, il n'est pas vraiment pratique pour casser lambda pour chacun des points de terminaison pour le développement ou le déploiement.

à partir de votre exemple,

api.com/getData --> getData  
api.com/addData --> addData  
api.com/signUp --> signUp  

imaginez que vous avez données ORM, l'authentification de l'Utilisateur de la logique, de point de vue commun de fichiers (comme des données.erb).. alors comment vas-tu le partager?

vous pourriez peut casser comme,

api/auth/{+proxy} -> AuthServiceLambda  
api/data/{+proxy} -> DataServiceLambda  

mais pas comme "pour chaque point de terminaison". vous pourriez peut-recherche de concept de microservice et les meilleures pratiques sur la façon dont vous pouvez fractionner le service

pour ceux framework web comme les fonctionnalités, la caisse cela nous vient de construire framework web pour lambda car j'avais besoin de ce à mon entreprise.

4voto

Jörg Points 11

Autant que je sache, AWS permet à un seul gestionnaire par une fonction Lambda. C'est pourquoi j'ai créé un peu de "routage" mécanisme de Java Génériques (pour les plus forts, les vérifications de type au moment de la compilation). Dans l'exemple suivant, vous pouvez appeler plusieurs méthodes et passer les différents types d'objets pour le Lambda et à l'arrière via un Lambda gestionnaire:

Lambda de la classe avec le gestionnaire de:

public class GenericLambda implements RequestHandler<LambdaRequest<?>, LambdaResponse<?>> {

@Override
public LambdaResponse<?> handleRequest(LambdaRequest<?> lambdaRequest, Context context) {

    switch (lambdaRequest.getMethod()) {
    case WARMUP:
        context.getLogger().log("Warmup");  
        LambdaResponse<String> lambdaResponseWarmup = new LambdaResponse<String>();
        lambdaResponseWarmup.setResponseStatus(LambdaResponse.ResponseStatus.IN_PROGRESS);
        return lambdaResponseWarmup;
    case CREATE:
        User user = (User)lambdaRequest.getData();
        context.getLogger().log("insert user with name: " + user.getName());  //insert user in db
        LambdaResponse<String> lambdaResponseCreate = new LambdaResponse<String>();
        lambdaResponseCreate.setResponseStatus(LambdaResponse.ResponseStatus.COMPLETE);
        return lambdaResponseCreate;
    case READ:
        context.getLogger().log("read user with id: " + (Integer)lambdaRequest.getData());
        user = new User(); //create user object for test, instead of read from db
        user.setName("name");
        LambdaResponse<User> lambdaResponseRead = new LambdaResponse<User>();
        lambdaResponseRead.setData(user);
        lambdaResponseRead.setResponseStatus(LambdaResponse.ResponseStatus.COMPLETE);
        return lambdaResponseRead;
    default:
        LambdaResponse<String> lambdaResponseIgnore = new LambdaResponse<String>();
        lambdaResponseIgnore.setResponseStatus(LambdaResponse.ResponseStatus.IGNORED);
        return lambdaResponseIgnore;    
    }
}
}

LambdaRequest classe:

public class LambdaRequest<T> {
private Method method;
private T data;
private int languageID; 

public static enum Method {
    WARMUP, CREATE, READ, UPDATE, DELETE 
}

public LambdaRequest(){
}

public Method getMethod() {
    return method;
}
public void setMethod(Method create) {
    this.method = create;
}
public T getData() {
    return data;
}
public void setData(T data) {
    this.data = data;
}
public int getLanguageID() {
    return languageID;
}
public void setLanguageID(int languageID) {
    this.languageID = languageID;
}
}

LambdaResponse classe:

public class LambdaResponse<T> {

private ResponseStatus responseStatus;
private T data;
private String errorMessage;

public LambdaResponse(){
}

public static enum ResponseStatus {
    IGNORED, IN_PROGRESS, COMPLETE, ERROR, COMPLETE_DUPLICATE
}

public ResponseStatus getResponseStatus() {
    return responseStatus;
}

public void setResponseStatus(ResponseStatus responseStatus) {
    this.responseStatus = responseStatus;
}

public T getData() {
    return data;
}

public void setData(T data) {
    this.data = data;
}

public String getErrorMessage() {
    return errorMessage;
}

public void setErrorMessage(String errorMessage) {
    this.errorMessage = errorMessage;
}

}

Exemple POJO de l'Utilisateur de la classe:

public class User {
private String name;

public User() {
}
public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
}

JUnit test méthode:

    @Test
public void GenericLambda() {
    GenericLambda handler = new GenericLambda();
    Context ctx = createContext();

    //test WARMUP
    LambdaRequest<String> lambdaRequestWarmup = new LambdaRequest<String>();
    lambdaRequestWarmup.setMethod(LambdaRequest.Method.WARMUP);
    LambdaResponse<String> lambdaResponseWarmup = (LambdaResponse<String>) handler.handleRequest(lambdaRequestWarmup, ctx);

    //test READ user
    LambdaRequest<Integer> lambdaRequestRead = new LambdaRequest<Integer>();
    lambdaRequestRead.setData(1); //db id
    lambdaRequestRead.setMethod(LambdaRequest.Method.READ);
    LambdaResponse<User> lambdaResponseRead = (LambdaResponse<User>) handler.handleRequest(lambdaRequestRead, ctx);
    }

ps.: si vous avez deserialisation problèmes (LinkedTreeMap ne peut pas être jeté à l' ...) en vous fonction Lambda (parce que uf les Génériques/Gson), utilisez l'instruction suivante:

YourObject yourObject = (YourObject)convertLambdaRequestData2Object(lambdaRequest, YourObject.class);

Méthode:

private <T> Object convertLambdaRequestData2Object(LambdaRequest<?> lambdaRequest, Class<T> clazz) {

    Gson gson = new Gson();
    String json = gson.toJson(lambdaRequest.getData());
    return gson.fromJson(json, clazz);
}

4voto

Ayan Guha Points 339

La façon dont je le vois, le choix simples vs multiples API est une fonction de considérations suivantes:

  1. Sécurité: je pense que c'est le plus grand défi d'avoir une API unique de structure. Il peut être possible d'avoir différents profils de sécurité pour les différentes parties de l'exigence

  2. Pensez microservice modèle à partir du point de vue des entreprises: L'objectif de l'utilisation de l'API doivent être au service de certaines demandes, donc il doit être bien compris et facile à utiliser. Si les APIs doivent être combinés. Par exemple, si vous avez un client mobile et il nécessite 10 choses à être tiré dans et hors de DB, il est logique d'avoir 10 points de terminaison dans un seul API. Mais cela devrait être dans la raison et doit être vu dans le contexte de l'ensemble de la conception de la solution. Par exemple, si vous concevez un produit de la paie, vous pouvez penser à avoir des modules séparés pour la gestion des congés et des détails de l'utilisateur de la gestion. Même si ils sont souvent utilisés par un seul client, ils doivent encore être différentes API, parce que leurs affaires le sens est différent.

  3. Réutilisabilité: s'Applique à la fois le code et les fonctionnalités de réutilisabilité. Code réutilisabilité est un problème plus facile à résoudre, c'est à dire construire des modules communs pour les besoins communs et les construire comme des bibliothèques. La fonctionnalité de réutilisabilité est plus difficile à résoudre. Dans mon esprit, la plupart des cas peut être résolu par une modification de la façon dont les points de terminaison/fonctions sont prévues, parce que si vous avez besoin de duplication des fonctionnalités qui signifie que votre conception initiale n'est pas assez détaillé.

Je viens de trouver un lien dans une autre SORTE de post qui résume mieux

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X