Статьи

Создайте RESTful API в Go с помощью AWS Lambda

В этом посте мы научимся проектировать, создавать и развертывать RESTful API в Go с использованием AWS Lambda. Прежде чем начать, позвольте мне кратко рассказать о AWS Lambda.

Что такое AWS Lambda?
AWS Lambda — это серверная вычислительная служба, которая запускает наш код в ответ на события и автоматически управляет базовыми вычислительными ресурсами для нас. Мы можем использовать AWS Lambda для расширения других сервисов AWS с помощью собственной логики или для создания собственных внутренних сервисов, которые работают с масштабом, производительностью и безопасностью AWS. AWS Lambda может автоматически запускать код в ответ на несколько событий, таких как HTTP-запросы через Amazon API Gateway, изменения объектов в корзинах Amazon S3, обновления таблиц в Amazon DynamoDB и переходы состояний в пошаговых функциях AWS.

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

Теперь давайте начнем с создания API, которое поможет местному магазину проката фильмов управлять имеющимися фильмами.

Архитектура API

Следующая диаграмма показывает, как API Gateway и Lambda вписываются в архитектуру API:

RESTful API

AWS Lambda расширяет возможности микросервиса. При этом каждая конечная точка запускает отдельную лямбда-функцию. Эти функции независимы друг от друга и могут быть написаны на разных языках, что приводит к масштабированию на уровне функций, упрощению модульного тестирования и слабой связи.

Все запросы от клиентов сначала проходят через шлюз API. Затем он направляет входящий запрос в правую лямбда-функцию соответственно.

Обратите внимание, что одна лямбда-функция может обрабатывать несколько методов HTTP ( GET , POST , PUT , DELETE и т. Д.). Рекомендуется создать несколько функций Lambda для каждой функции, чтобы использовать возможности микросервисов. Однако создание одной лямбда-функции для обработки нескольких конечных точек может быть хорошим упражнением.

Дизайн конечных точек

Теперь, когда архитектура определена, пришло время приступить к реализации функций, описанных на диаграмме выше. Вместо жесткого кодирования кода состояния HTTP вы можете использовать пакет net / http Go и использовать встроенные переменные кода состояния, такие как http.StatusOK , http.StatusCreated , http.StatusBadRequest , http.StatusInternalServerError и так далее.

Метод GET

Первая функция, которую нужно реализовать, — это перечисление фильмов. Вот где метод GET вступает в игру. Давайте начнем с следующих шагов:

Шаг 1: Создайте лямбда-функцию, которая регистрирует обработчик findAll . Этот обработчик преобразует список фильмов в строку, а затем возвращает эту строку, заключенную в переменную APIGatewayProxyResponse, вместе с кодом состояния HTTP 200 . Он также обрабатывает ошибки в случае сбоя преобразования. Реализация обработчика выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main
 
import (
  "encoding/json"
 
  "github.com/aws/aws-lambda-go/events"
  "github.com/aws/aws-lambda-go/lambda"
)
 
var movies = []struct {
  ID int `json:"id"`
  Name string `json:"name"`
}{
    {
      ID: 1,
      Name: "Avengers",
    },
    {
      ID: 2,
      Name: "Ant-Man",
    },
    {
      ID: 3,
      Name: "Thor",
    },
    {
      ID: 4,
      Name: "Hulk",
    }, {
      ID: 5,
      Name: "Doctor Strange",
    },
}
 
func findAll() (events.APIGatewayProxyResponse, error) {
  response, err := json.Marshal(movies)
  if err != nil {
    return events.APIGatewayProxyResponse{}, err
  }
 
  return events.APIGatewayProxyResponse{
    StatusCode: 200,
    Headers: map[string]string{
      "Content-Type": "application/json",
    },
    Body: string(response),
  }, nil
}
 
func main() {
  lambda.Start(findAll)
}

Вместо жесткого кодирования кода состояния HTTP вы можете использовать пакет net / http Go и использовать встроенные переменные кода состояния, такие как http.StatusOK , http.StatusCreated , http.StatusBadRequest , http.StatusInternalServerError и так далее.

Шаг 2. Создайте файл сценария со следующим содержимым для создания пакета развертывания функции Lambda, ZIP- файл, состоящий из вашего кода и любых зависимостей, следующим образом:

01
02
03
04
05
06
07
08
09
10
#!/bin/bash
 
echo "Build the binary"
GOOS=linux GOARCH=amd64 go build -o main main.go
 
echo "Create a ZIP file"
zip deployment.zip main
 
echo "Cleaning up"
rm main

Шаг 3. Выполните следующие команды, чтобы создать пакет развертывания в виде файла .zip :

1
2
$ chmod +x build.sh
$ ./build.sh

Шаг 4. Настройте интерфейс командной строки AWS, используя шаги, упомянутые здесь . После настройки создайте роль AWS с именем FindAllMoviesRole, выполнив шаги, упомянутые здесь, и проверьте, успешно ли она создана:

1
$ aws iam get-role --role-name FindAllMoviesRole

Выше команда должна дать ответ, как показано на скриншоте ниже:

RESTful API

Шаг 5. Затем создайте новую функцию Lambda с помощью интерфейса командной строки AWS следующим образом:

1
2
3
4
5
aws lambda create-function --function-name FindAllMovies \
     --zip-file fileb://deployment.zip \
     --runtime go1.x --handler main \
     --role arn:aws:iam::ACCOUNT_ID:role/FindAllMoviesRole \
     --region us-east-1

Как только функция создана, она выдаст нам вывод, такой же, как показано на скриншоте ниже:

RESTful API

Шаг 6. Возвращаясь к лямбда-консоли AWS, вы должны увидеть, что функция была успешно создана:

RESTful API

Шаг 7. Создайте пример события с пустым JSON, так как функция не ожидает аргументов, и нажмите кнопку « Тест» :

RESTful API

На предыдущем снимке экрана вы заметите, что функция возвращает ожидаемый результат в формате JSON.

Шаг 8: Теперь, когда функция была определена, вам нужно создать новый шлюз API, чтобы запустить его:

RESTful API

Шаг 9: Далее в раскрывающемся списке « Действия» выберите « Создать ресурс» и назовите его « фильмы» :

RESTful API

Шаг 10: Откройте метод GET для этого ресурса / videos , нажав « Создать метод» . Выберите Лямбда-функцию в разделе Тип интеграции и выберите функцию FindAllMovies :

RESTful API

Шаг 11. Для развертывания API выберите Deploy API в раскрывающемся списке Actions . Вам будет предложено создать новый этап развертывания:

RESTful API

Шаг 12. После создания этапа развертывания будет отображен URL-адрес вызова:

RESTful API

Шаг 13: Направьте ваш браузер на указанный URL или используйте современный клиент REST, такой как Postman или Insomnia. Вы можете использовать инструмент cURL , так как он установлен по умолчанию практически во всех операционных системах:

1
curl -sX GET https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.'

Приведенная выше команда вернет список фильмов в формате JSON:

RESTful API

При вызове конечной точки GET запрос будет проходить через шлюз API, который вызовет обработчик findAll . Это возвращает ответ, переданный через шлюз API клиенту в формате JSON.

Теперь, когда функция findAll была развернута, вы можете реализовать функцию findOne для поиска фильма по его идентификатору.

Метод GET с параметрами

Обработчик findOne ожидает аргумент APIGatewayProxyRequest, который содержит входные данные события. Затем он использует метод PathParameters, чтобы получить идентификатор фильма и проверить его.

Если предоставленный идентификатор не является действительным числом, метод Atoi вернет ошибку, и код ошибки 500 будет возвращен клиенту. В противном случае фильм будет выбран на основе индекса и возвращен клиенту со статусом 200 OK, завернутым в APIGatewayProxyResponse :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
...
 
func findOne(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
 
  id, err := strconv.Atoi(req.PathParameters["id"])
 
  if err != nil {
 
    return events.APIGatewayProxyResponse{
 
      StatusCode: 500,
 
      Body:       "ID must be a number",
 
    }, nil
 
  }
 
  response, err := json.Marshal(movies[id-1])
 
  if err != nil {
 
    return events.APIGatewayProxyResponse{
 
      StatusCode: 500,
 
      Body:       err.Error(),
 
    }, nil
 
  }
 
  return events.APIGatewayProxyResponse{
 
    StatusCode: 200,
 
    Headers: map[string]string{
 
      "Content-Type": "application/json",
 
    },
 
    Body: string(response),
 
  }, nil
 
}
 
func main() {
 
  lambda.Start(findOne)
 
}

Аналогично функции FindAllMovies создайте новую лямбда-функцию для поиска фильма:

1
2
3
4
5
aws lambda create-function --function-name FindOneMovie \
    --zip-file fileb://deployment.zip \
    --runtime go1.x --handler main \
    --role arn:aws:iam::ACCOUNT_ID:role/FindOneMovieRole \
    --region us-east-1

Вернитесь в консоль API Gateway, создайте новый ресурс, откройте метод GET , а затем свяжите ресурс с функцией FindOneMovie . Обратите внимание на использование заполнителя {id} в пути. Значение id будет доступно через объект APIGatewayProxyResponse . Следующий скриншот изображает это:

RESTful API

Повторно разверните API и используйте следующую команду cURL для проверки конечной точки:

1
curl -sX https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies/1 | jq '.'

Будет возвращен следующий JSON:

RESTful API

Когда URL API вызывается с идентификатором, возвращается фильм, соответствующий этому идентификатору, если он существует.

Метод POST

Теперь вы знаете, как работает метод GET с параметрами пути и без них. Следующим шагом является передача полезной нагрузки JSON в функцию Lambda через шлюз API. Код не требует пояснений. Он преобразует входные данные запроса в структуру фильма, добавляет его в список фильмов и возвращает новый список фильмов в формате JSON:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package main
 
import (
  "encoding/json"
  "strconv"
  "github.com/aws/aws-lambda-go/events"
  "github.com/aws/aws-lambda-go/lambda"
)
 
type Movie struct {
  ID int `json:"id"`
  Name string `json:"name"`
}
 
var movies = []Movie{
  Movie{
    ID: 1,
    Name: "Avengers",
  },
 ...
}
 
func insert(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
  var movie Movie
  err := json.Unmarshal([]byte(req.Body), &movie)
  if err != nil {
    return events.APIGatewayProxyResponse{
      StatusCode: 400,
      Body: "Invalid payload",
    }, nil
  }
 
  movies = append(movies, movie)
 
  response, err := json.Marshal(movies)
  if err != nil {
    return events.APIGatewayProxyResponse{
      StatusCode: 500,
      Body: err.Error(),
    }, nil
  }
 
  return events.APIGatewayProxyResponse{
    StatusCode: 200,
    Headers: map[string]string{
      "Content-Type": "application/json",
    },
    Body: string(response),
  }, nil
}
 
func main() {
  lambda.Start(insert)
}

Затем создайте новую лямбда-функцию для InsertMovie с помощью следующей команды:

1
2
3
4
5
aws lambda create-function --function-name InsertMovie \
     --zip-file fileb://deployment.zip \
     --runtime go1.x --handler main \
     --role arn:aws:iam::ACCOUNT_ID:role/InsertMovieRole \
     --region us-east-1

Затем создайте метод POST для ресурса / movies и свяжите его с функцией InsertMovie :

RESTful API

Чтобы проверить это, используйте следующую команду cURL с глаголом POST и флагом -d , за которым следует строка JSON (с атрибутами id и name):

1
curl -sX POST -d '{"id":6, "name": "Spiderman:Homecoming"}' https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.'

Приведенная выше команда вернет следующий ответ JSON:

RESTful API

Как видите, новый фильм успешно вставлен. Если вы протестируете это снова, оно должно работать как ожидалось

1
curl -sX POST -d '{"id":7, "name": "Iron man"}' https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.'

Предыдущая команда вернет следующий ответ JSON:

RESTful API

Как видите, он был успешным, и фильм снова был вставлен, как и ожидалось, но что если подождать несколько минут и попытаться вставить третий фильм? Следующая команда будет использована для ее повторного выполнения:

1
curl -sX POST -d '{"id":8, "name": "Captain America"}' https://51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.'

Еще раз, новый ответ JSON будет возвращен:

RESTful API

Вы обнаружите, что фильмы с идентификаторами 6 и 7 были удалены; почему это случилось? Это просто. Лямбда-функции не имеют состояния.

Когда функция InsertMovie вызывается впервые (первая вставка), AWS Lambda создает контейнер и развертывает полезную нагрузку функции в контейнер. Затем он остается активным в течение нескольких минут до его завершения ( горячий запуск ), что объясняет, почему прошла вторая вставка. В третьей вставке контейнер уже завершен, и, следовательно, Lambda создает новый контейнер ( холодный старт ) для обработки вставки.

Вот почему предыдущее состояние потеряно. Следующая диаграмма иллюстрирует проблему холодного / теплого старта:

RESTful API

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

Полный исходный код размещен на github .

См. Оригинальную статью здесь: создание RESTful API в Go с использованием AWS Lambda.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.