В этом посте мы научимся проектировать, создавать и развертывать 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:
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 |
Выше команда должна дать ответ, как показано на скриншоте ниже:
Шаг 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 |
Как только функция создана, она выдаст нам вывод, такой же, как показано на скриншоте ниже:
Шаг 6. Возвращаясь к лямбда-консоли AWS, вы должны увидеть, что функция была успешно создана:
Шаг 7. Создайте пример события с пустым JSON, так как функция не ожидает аргументов, и нажмите кнопку « Тест» :
На предыдущем снимке экрана вы заметите, что функция возвращает ожидаемый результат в формате JSON.
Шаг 8: Теперь, когда функция была определена, вам нужно создать новый шлюз API, чтобы запустить его:
Шаг 9: Далее в раскрывающемся списке « Действия» выберите « Создать ресурс» и назовите его « фильмы» :
Шаг 10: Откройте метод GET для этого ресурса / videos , нажав « Создать метод» . Выберите Лямбда-функцию в разделе Тип интеграции и выберите функцию FindAllMovies :
Шаг 11. Для развертывания API выберите Deploy API в раскрывающемся списке Actions . Вам будет предложено создать новый этап развертывания:
Шаг 12. После создания этапа развертывания будет отображен URL-адрес вызова:
Шаг 13: Направьте ваш браузер на указанный URL или используйте современный клиент REST, такой как Postman или Insomnia. Вы можете использовать инструмент cURL , так как он установлен по умолчанию практически во всех операционных системах:
1
|
curl -sX GET https: //51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.' |
Приведенная выше команда вернет список фильмов в формате JSON:
При вызове конечной точки 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 . Следующий скриншот изображает это:
Повторно разверните API и используйте следующую команду cURL для проверки конечной точки:
1
|
curl -sX https: //51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies/1 | jq '.' |
Будет возвращен следующий JSON:
Когда 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 :
Чтобы проверить это, используйте следующую команду 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:
Как видите, новый фильм успешно вставлен. Если вы протестируете это снова, оно должно работать как ожидалось
1
|
curl -sX POST -d '{"id":7, "name": "Iron man"}' https: //51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.' |
Предыдущая команда вернет следующий ответ JSON:
Как видите, он был успешным, и фильм снова был вставлен, как и ожидалось, но что если подождать несколько минут и попытаться вставить третий фильм? Следующая команда будет использована для ее повторного выполнения:
1
|
curl -sX POST -d '{"id":8, "name": "Captain America"}' https: //51cxzthvma.execute-api.us-east-1.amazonaws.com/staging/movies | jq '.' |
Еще раз, новый ответ JSON будет возвращен:
Вы обнаружите, что фильмы с идентификаторами 6 и 7 были удалены; почему это случилось? Это просто. Лямбда-функции не имеют состояния.
Когда функция InsertMovie вызывается впервые (первая вставка), AWS Lambda создает контейнер и развертывает полезную нагрузку функции в контейнер. Затем он остается активным в течение нескольких минут до его завершения ( горячий запуск ), что объясняет, почему прошла вторая вставка. В третьей вставке контейнер уже завершен, и, следовательно, Lambda создает новый контейнер ( холодный старт ) для обработки вставки.
Вот почему предыдущее состояние потеряно. Следующая диаграмма иллюстрирует проблему холодного / теплого старта:
Это объясняет, почему лямбда-функции не должны иметь состояния и почему вы не должны делать никаких предположений о том, что состояние будет сохраняться от одного вызова к другому.
Полный исходный код размещен на github .
См. Оригинальную статью здесь: создание RESTful API в Go с использованием AWS Lambda.
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |