Kiến thức

Serverless Series (Golang) – Bài 2 – Build REST API with AWS API Gateway 10 tháng 03, 2022 – 226 lượt xem Golang DevOps AWS Đầu mục bài viết Bài viết liên quan Khoá học hay

Tác giả: Huỳnh Minh Quân – Người chỉ dẫn khóa học AWS: Learn AWS the Hard Way.

Xem các bài viết khác trong Series Serverless


Giới thiệu

Chào mừng các bạn tới với loạt bài Serverless, trong bài trước chúng ta đã nói về kiến ​​trúc Serverless là gì, AWS Lambda là gì và nó có vai trò như thế nào trong mẫu hình Serverless. Trong bài viết này, chúng ta sẽ mày mò về thành phần thứ 2 để chúng ta xây dựng mẫu hình Serverless trên môi trường AWS Cloud, đấy là API Gateway. Chúng tôi sẽ sử dụng API Gateway liên kết với Lambda để xây dựng API REST theo mẫu hình Serverless.

Hình minh họa về API REST nhưng chúng tôi sẽ xây dựng.

Cổng API

Như đã nhắc đến trong bài trước, hàm AWS Lambda của chúng ta sẽ chẳng thể chạy tự động nhưng nó sẽ được thực thi bởi 1 sự kiện. Thì API Gateway là 1 trong những serivce sẽ phát ra sự kiện để thực thi hàm Lambda, chi tiết hơn API Gateway sẽ phát ra 1 sự kiện đến hàm Lambda lúc có 1 đề xuất http từ khách hàng gọi nó, vì thế nó rất thích hợp để xây dựng các API REST.

Trong mẫu hình Serverless, API Gateway sẽ hoạt động như 1 điểm vào cho tất cả các hàm Lambda của chúng ta, nó sẽ giao cho và hướng 1 đề xuất http tới đúng hàm Lambda nhưng chúng ta muốn.

Kế bên việc vào vai trò là điểm vào cho các hàm Lambda, API Gateway còn có các công dụng nổi trội sau:

  • Caching: Chúng ta có thể cache các kết quả nhưng Lambda trả về => giảm số lần API Gateway gọi hàm Lambda bên dưới, giúp chúng ta giảm tiền và giảm thời kì phản hồi của 1 đề xuất.
  • Cấu hình CORS.
  • Các công đoạn khai triển: API Gateway cung ứng tạo và điều hành các bạn dạng không giống nhau của API, vì thế chúng ta có thể phân thành nhiều môi trường (dev, staging, production).
  • Phân phối giám sát và gỡ lỗi tại lớp đề xuất http
  • Phân phối tạo tài liệu dễ ợt, chả hạn như xuất API trong tài liệu nhưng Swagger có thể đọc

Chúng ta chỉ nói về lý thuyết nhiều tương tự, tiếp theo chúng ta sẽ mở đầu xây dựng API REST.

Xây dựng API REST

Chúng tôi sẽ tạo 1 API REST khai triển CRUD dễ dàng, bao gồm danh sách sách, nhận 1 sách, tạo sách, cập nhật sách và xóa sách. Trong chương này, chúng ta chỉ tương tác với dữ liệu giả được gán cứng cho biến, chúng ta chưa tương tác với cơ sở dữ liệu.

Khởi tạo hàm Lambda

Bước trước tiên là tạo 1 hàm lambda trả về 1 danh sách các sách.

image.png

Mình sẽ sử dụng terraform để tạo các hàm lambda, nếu bạn nào mới làm quen với terraform thì xem bài trước để biết cách tạo bằng AWS Web Console, không những thế nếu bạn muốn mày mò về Terraform thì mình cũng đã viết 1 loạt bài về Terraform, các bạn có thể đọc để biết thêm. Bạn tải code trong repo github này https://github.com/hoalongnatsu/serverless-series.git, nhảy vào folder bai-2 / terraform-start. Chạy các lệnh sau terraform init -> terraform apply -auto-approve. Sau lúc chạy, bạn sẽ thấy trên lambda 1 hàm có tên books_list được tạo trên AWS.

image.png

Hiện giờ chúng ta sẽ viết mã cho danh sách tác dụng, tạo 1 folder có tên là danh sách, mở nó và tạo 1 tệp có tên main.go với đoạn mã sau.

package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/lambda"
)

type Books struct {
	Id     int    `json:"id"`
	Name   string `json:"name"`
	Author string `json:"author"`
}

func list() (string, error) {
	books := []Books{
		{Id: 1, Name: "NodeJS", Author: "NodeJS"},
		{Id: 2, Name: "Golang", Author: "Golang"},
	}

	res, _ := json.Marshal(&books)
	return string(res), nil
}

func main() {
	lambda.Start(list)
}

Sau lúc viết mã, chúng ta chạy lệnh sau để xây dựng mã golang thành tệp nhì phân và tải nó lên hàm lambda. Tải xuống các gói go mod init list && go get và xây dựng mã:

go build -o main main.go
zip list.zip main
rm -rf main

Để dễ ợt tạo sau này, bạn tạo 1 tệp có tên build.shhãy sao chép đoạn mã trên vào. Folder của chúng ta hiện giờ sẽ trông như thế này:

.
├── build.sh
├── go.mod
├── go.sum
├── list.zip
└── main.go

Sau lúc xây dựng tệp list.zipchúng tôi sẽ cập nhật hàm lambda books_list.

$ aws lambda update-function-code --function-name books_list --zip-file fileb://list.zip --region us-west-2

Lúc tải lên mã, chúng tôi rà soát xem liệu hàm lambda của chúng tôi có đang chạy chuẩn xác hay ko.

$ aws lambda invoke --function-name books_list response.json --region us-west-2
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

$ cat response.json ; echo
"[{"id":1,"name":"NodeJS","author":"NodeJS"},{"id":2,"name":"Golang","author":"Golang"}]"

Nếu bạn thấy kết quả ở trên, thì hàm lambda của chúng ta đang chạy chuẩn xác. Tiếp theo, thay vì thực thi tác dụng bằng CLI, chúng ta sẽ sử dụng API Gateway để thực thi nó.

Khởi tạo API Gateway

Mở Web Console và tìm API Gateway.

image.png

Chọn API REST ko riêng tây.

image.png

Ở phần giao thức, chúng tôi chọn REST, chọn New API, nơi tên API bạn nhập là được, đối với tôi, tôi nhập BOOKS. Loại điểm cuối chúng tôi chọn Khu vực.

image.png

Sau lúc nhập tất cả, chúng ta bấm Tạo API và chúng ta sẽ tới trang có giao diện khách hàng như sau.

image.png

Hiện giờ chúng ta sẽ xác định API của chúng ta. Nhấp vào Hành động, chọn Tạo khoáng sản.

image.png

Trong đấy Tên khoáng sản và Đường dẫn khoáng sản, chúng tôi nhập sách. Nhấp vào Tạo khoáng sản.

image.png

Sau lúc tạo xong, bạn sẽ thấy Khoáng sản có đường dẫn bổ sung ở đâu /books.

image.png

Danh sách API

Hiện giờ chúng ta sẽ tạo 1 phương thức trong Resource này, nhấn vào Actions, chọn Create Method.

image.png

Sau đấy, nó sẽ hiển thị 1 hộp thả xuống cho bạn, chọn GET và nhấn nút rà soát.

image.png

image.png

Nó sẽ hiển thị UI như sau, bạn chọn như hình bên dưới và tại ô nhập Công dụng Lambda, nhập books_list.

image.png

Và nhấp vào Lưu. Sẽ có 1 modal nói rằng nó sẽ tạo quyền để API Gateway có thể thực thi hàm Lambda, chúng ta chọn OK.

image.png

Tiếp theo, chúng tôi sẽ khai triển API REST của mình, trong đấy Actions, chọn Deploy API.

image.png

Chúng tôi chọn Quá trình mới, nơi Tên sàn diễn nhập vào công đoạn (này, bạn có thể nhập bất kỳ thứ gì).

image.png

Và nhấp vào Khai triển. Và chúng ta sẽ có giao diện khách hàng sau.

image.png

Mở dàn, chọn phương thức GET, chúng ta sẽ thấy URL của API tiến hành get list book cho chúng ta.

image.png

Được rồi, vậy là chúng tôi đã khai triển API REST trước tiên của mình 😁. Bạn sao chép URL và đề xuất nó.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
{"message": "Internal server error"}

Và chúng ta sẽ thấy rằng có 1 lỗi 😂. Lý do là để Lambda liên kết với API Gateway, hàm Lambda phải trả về đúng định dạng được chỉ định bởi API Gateway. Trong đoạn mã trên, chúng tôi trả về kết quả định dạng ko chuẩn xác.

...
func list() (string, error) {
    books := []Books{
        {Id: 1, Name: "NodeJS", Author: "NodeJS"},
        {Id: 2, Name: "Golang", Author: "Golang"},
    }

    res, _ := json.Marshal(&books)
    return string(res), nil // response not valid format of API Gateway
}
...

Định dạng chuẩn xác của phản hồi nhưng hàm Lambda trả về API Gateway sẽ như sau:

type Response struct {
    StatusCode int `json:"statusCode"`
    Body string `json:"body"`
}

Nó sẽ bao gồm 1 trường Mã tình trạng số và 1 trường định dạng nội dung chuỗi. Tôi cập nhật lại tệp main.go

package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
	StatusCode int    `json:"statusCode"`
	Body       string `json:"body"`
}

type Books struct {
	Id     int    `json:"id"`
	Name   string `json:"name"`
	Author string `json:"author"`
}

func list() (Response, error) {
	books := []Books{
		{Id: 1, Name: "NodeJS", Author: "NodeJS"},
		{Id: 2, Name: "Golang", Author: "Golang"},
	}

	res, _ := json.Marshal(&books)
	return Response{
		StatusCode: 200,
		Body:       string(res),
	}, nil
}

func main() {
	lambda.Start(list)
}

Tạo mã và tải lại mã lên AWS.

$ sh build.sh
updating: main (deflated 46%

$ aws lambda update-function-code --function-name books_list --zip-file fileb://list.zip --region us-west-2

Hiện giờ chúng ta gọi lại API sách danh sách, chúng ta sẽ thấy kết quả trả về nhưng ko có bất cứ lỗi nào.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{"id":1,"name":"NodeJS","author":"NodeJS"},{"id":2,"name":"Golang","author":"Golang"}]

Được rồi, hiện giờ API REST trước tiên của chúng tôi đã thực thụ được khai triển thành công. Để làm đúng, AWS cũng hỗ trợ cho chúng tôi Golang SDK để chúng tôi có thể tránh những lỗi này và viết mã mau lẹ hơn. Thay vì phải tạo Phản hồi cấu trúc theo cách thủ công, chúng tôi sử dụng SDK sau, tải xuống gói go get github.com/aws/aws-lambda-go/eventscập nhật lại main.gochúng tôi sử dụng cấu trúc tích hợp sẵn APIGatewayProxyResponse.

package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

type Books struct {
	Id     int    `json:"id"`
	Name   string `json:"name"`
	Author string `json:"author"`
}

func list() (events.APIGatewayProxyResponse, error) {
	books := []Books{
		{Id: 1, Name: "NodeJS", Author: "NodeJS"},
		{Id: 2, Name: "Golang", Author: "Golang"},
	}

	res, _ := json.Marshal(&books)
	return events.APIGatewayProxyResponse{
		StatusCode: 200,
        Headers: map[string]string{
			"Content-Type": "application/json",
		},
		Body:       string(res),
	}, nil
}

func main() {
	lambda.Start(list)
}

Hình minh họa Serverless ngày nay của tôi.

image.png

API nhận 1

Tiếp theo, chúng tôi sẽ tiến hành API lấy 1 cuốn sách theo id. Hàm xử lý của lambda lúc liên kết với API sẽ có các thông số trước tiên được truyền vào là APIGatewayProxyRequestnó sẽ chứa trị giá đề xuất của khách hàng, chúng tôi sẽ xác định đường dẫn của API lấy 1 là / books / {id}, trong đấy id là param và nó sẽ nằm trong trường PathParameters thuộc về APIGatewayProxyRequest. Chúng tôi tạo 1 folder có tên getOnemở nó và tạo 1 tệp main.go với mã sau:

package main

import (
	"encoding/json"
	"strconv"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

type Books struct {
	Id     int    `json:"id"`
	Name   string `json:"name"`
	Author string `json:"author"`
}

func getOne(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	books := []Books{
		{Id: 1, Name: "NodeJS", Author: "NodeJS"},
		{Id: 2, Name: "Golang", Author: "Golang"},
	}

	id, err := strconv.Atoi(req.PathParameters["id"])
	if err != nil {
		return events.APIGatewayProxyResponse{
			StatusCode: 400,
			Body:       err.Error(),
		}, nil
	}

	res, err := json.Marshal(books[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(res),
	}, nil
}

func main() {
	lambda.Start(getOne)
}

Tải xuống gói và xây dựng nguồn.

go mod init getone
go get

#!/bin/bash

GOOS=linux go build -o main main.go
zip getOne.zip main
rm -rf main

sh build.sh

Tạo hàm Lambda cho API lấy 1.

$ aws lambda create-function --function-name books_get_one --zip-file fileb://getOne.zip --runtime go1.x --handler main --role arn:aws:iam::ACCOUNT_ID:role/lambda_role --region us-west-2

Với arn:aws:iam::ACCOUNT_ID:role/lambda_roletrị giá ACCOUNT_ID là id nick của bạn.

image.png

Hiện giờ chúng ta sẽ tạo 1 API để lấy 1. Quay lại API Gateway, nhấp vào / books, chọn Actions -> Create Resource.

image.png

Nhập {id} và nhấp vào tạo.

image.png

Và tạo phương thức cho {id}, chọn GET.

image.png

Trường hợp tên hàm chúng ta nhập books_get_one.

image.png

Bấm tạo nó sẽ hỏi quyền tạo, chúng ta OK. Chúng tôi nhấp vào khai triển 1 lần nữa.

image.png

Lần này chúng ta đang trong công đoạn Khai triển, hãy chọn lại dàn trước đấy và nhấn Khai triển.

Và chúng ta sẽ thấy 1 URL API của chúng ta.

image.png

Tiến hành 1 đề xuất với nó.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books/1 ; echo
{"id":1,"name":"NodeJS","author":"NodeJS"}

Được rồi, chúng tôi đã khai triển get one API thành công. Hiện giờ Serverless của chúng ta sẽ trông như thế này.

image.png

API tạo

Tiếp theo là API để tạo 1 cuốn sách mới, vì chúng tôi ko sử dụng API, chúng tôi chỉ cần lấy phần nội dung từ đề xuất của người dùng và nối nó vào mảng ngày nay. Tạo 1 folder có tên createmở nó và tạo 1 tệp có tên main.go với mã sau:

package main

import (
	"encoding/json"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

type Books struct {
	Id     int    `json:"id"`
	Name   string `json:"name"`
	Author string `json:"author"`
}

var books = []Books{
	{Id: 1, Name: "NodeJS", Author: "NodeJS"},
	{Id: 2, Name: "Golang", Author: "Golang"},
}

func create(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	var book Books
	err := json.Unmarshal([]byte(req.Body), &book)
	if err != nil {
		return events.APIGatewayProxyResponse{
			StatusCode: 400,
			Body:       err.Error(),
		}, nil
	}

	books = append(books, book)

	res, err := json.Marshal(&books)
	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(res),
	}, nil
}

func main() {
	lambda.Start(create)
}

Tải xuống gói và xây dựng nguồn.

go mod init create
go get

#!/bin/bash

GOOS=linux go build -o main main.go
zip create.zip main
rm -rf main

sh build.sh

Tạo hàm Lambda để tạo API.

$ aws lambda create-function --function-name books_create --zip-file fileb://create.zip --runtime go1.x --handler main --role arn:aws:iam::ACCOUNT_ID:role/lambda_role --region us-west-2

image.png

Trong API Gateway, chọn Khoáng sản 1 lần nữa, nhấp vào / books, chọn Hành động -> Tạo Khoáng sản, sau đấy trong thực đơn thả xuống, chúng tôi chọn phương thức ĐĂNG.

image.png

Trong hộp nhập hàm, chúng ta nhập books_create.

image.png

Bấm lưu và chọn OK lúc nó đề xuất tạo quyền. Sau đấy, chúng tôi khai triển 1 lần nữa.

image.png

Chọn công đoạn như trước và nhấn Khai triển và bạn sẽ có URL cho API tạo.

image.png

Gửi 1 đề xuất tới nó.

$ curl -sX POST -d '{"Id":3, "name": "Java", "author": "Java"}'  https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{"id":1,"name":"NodeJS","author":"NodeJS"},{"id":2,"name":"Golang","author":"Golang"},{"id":3,"name":"Java","author":"Java"}]

Oke, API tạo của chúng tôi cũng đã được khai triển thành công. Hình minh họa Serverless ngày nay.

image.png

Khởi động ấm – Khởi động nguội

Chúng tôi gọi nó 1 lần nữa, bạn sẽ thấy rằng trị giá được nối vào mảng ngày nay và chứa trị giá nhưng chúng tôi đã gửi lần trước.

$ curl -sX POST -d '{"Id":4, "name": ".NET", "author": ".NET"}'  https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{"id":1,"name":"NodeJS","author":"NodeJS"},{"id":2,"name":"Golang","author":"Golang"},{"id":3,"name":"Java","author":"Java"},{"id":4,"name":".NET","author":".NET"}]

Nhưng mà lúc chúng tôi đợi khoảng 5 phút, bạn gọi lại thì sẽ thấy 2 trị giá trước đấy chúng tôi gửi lên đã bị mất.

$ curl -sX POST -d '{"Id":5, "name": "PHP", "author": "PHP"}'  https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{"id":1,"name":"NodeJS","author":"NodeJS"},{"id":2,"name":"Golang","author":"Golang"},{"id":5,"name":"PHP","author":"PHP"}]

Vì sao vậy? Sau đấy, sự cố này được gọi là Khởi động ấm và Khởi động nguội trong AWS Lambda. Lúc AWS Lambda thực thi 1 hàm lambda, nó sẽ tiến hành các bước sau:

  • Lúc hàm được thực thi lần trước tiên, AWS Lambda sẽ tìm thấy 1 nơi nào đấy trên hệ thống máy ảo bên dưới của nó có đủ khoáng sản để chạy hàm này, sau lúc kiếm được, nó sẽ tạo 1 vùng chứa với môi trường thích hợp cho hàm. hàm của chúng ta, sau lúc vùng chứa được tạo, hàm sẽ được thực thi trong vùng chứa đấy và trả về kết quả cho máy khách, công đoạn này được gọi là khởi động nguội.
  • Và trong lần thực thi hàm tiếp theo, AWS Lambda sẽ rà soát xem trước đấy đã có vùng chứa cho hàm này chưa, nếu có nó sẽ sử dụng vùng chứa cũ để thực thi hàm, công đoạn này được gọi là Warm start.

Nhưng mà nếu trong 1 khoảng thời kì, tác dụng ko còn được kích hoạt, vùng chứa sẽ bị xóa và công đoạn khởi động nguội sẽ được lặp lại. Đây là lý do vì sao hàm Lambda là 1 phần mềm ko tình trạng, nó chẳng thể dùng để lưu trữ dữ liệu nhưng chúng ta phải sử dụng 1 dịch vụ khác để lưu trữ dữ liệu cho chúng ta, chả hạn như AWS RDS hoặc DynamoDB.

image.png

Các API khác như cập nhật và xóa, bạn cũng làm tương tự. Toàn thể mã của chương này nằm trong github repo ở trên.

Kết luận

Thành ra, chúng ta đã mày mò về các bản dựng API Serverless REST với API Gateway và AWS Lambda. Nếu bạn thấy rằng chỉ tạo 1 vài API dễ dàng nhưng ko cần phải tạo quá nhiều thứ, đấy là do tôi làm thủ công ở đây và vì tôi giảng giải từng cái nên sẽ chậm, nhưng mà trong thực tiễn, chúng ta sẽ có 1 phương tiện. để tự động khai triển như terraform, viết luồng CI / CD để tự động khai triển mã, dev cục bộ với AWS Serverless Application Model (SAM), những điều này tôi sẽ nói trong các bài viết sau. Nếu có thắc mắc hoặc cần trả lời thêm, bạn có thể hỏi trong phần bình luận bên dưới. Bài tiếp theo sẽ nói về cách sử dụng Lambda với DynamoDB.


Thông tin thêm

Serverless Series (Golang) – Bài 2 – Build REST API with AWS API Gateway

10 tháng 03, 2022 – 226 lượt xem

Golang DevOps AWS

Đầu mục bài viết

Bài viết liên can

Khoá học hay

#Serverless #Series #Golang #Bài #Build #REST #API #AWS #API #Gateway10 #tháng #lượt #xem #Golang #DevOps #AWS #Đầu #mục #bài #viếtBài #viết #liên #quanKhoá #học #hay
[rule_3_plain] #Serverless #Series #Golang #Bài #Build #REST #API #AWS #API #Gateway10 #tháng #lượt #xem #Golang #DevOps #AWS #Đầu #mục #bài #viếtBài #viết #liên #quanKhoá #học #hay
Tác giả: Huỳnh Minh Quân- giảng sư khóa học AWS : Learn AWS the Hard Way.
Xem các bài viết khác thuộc Series Serverless

Giới thiệu
Chào các bạn đến với series về Serverless, ở bài trước chúng ta đã nói về kiến trúc Serverless là gì, AWS Lambda là gì và nó vào vai trò như thế nào trong mẫu hình Serverless. Ở bài này chúng ta sẽ mày mò về thành phần thứ 2 để ta xây dựng mẫu hình Serverless trên môi trường AWS Cloud, là API Gateway. Ta sẽ sử dụng API Gateway liên kết với Lambda để xây dựng 1 REST API theo mẫu hình Serverless.
Hình minh họa REST API nhưng ta sẽ xây dựng.

API Gateway
Như đã nói ở bài trước, AWS Lambda function của ta sẽ chẳng thể tự động chạy, nhưng nó sẽ được thực thi bởi 1 event nào đấy. Thì API Gateway là 1 trong những serivce nhưng sẽ phát ra event để thực thi Lambda function, chi tiết hơn là API Gateway sẽ phát ra 1 event đến Lambda function lúc có 1 http request từ phía khách hàng gọi đến nó, do đấy nó rất phù hợp cho việc xây dựng REST API.
Trong mẫu hình Serverless thì API Gateway sẽ vào vai trò như là 1 entry point cho toàn thể Lambda functions của ta, nó sẽ proxy và điều hướng 1 http request đến đúng Lambda function nhưng ta muốn.
Kế bên việc vào vai trò như 1 entry point cho Lambda function, API Gateway còn có những đặc tính nổi trội sau đây:

Caching: Ta có thể cache lại kết quả nhưng Lambda trả về => giảm số lần nhưng API Gateway gọi đến Lambda function bên dưới, giúp ta giảm tiền và giảm thời kì response của 1 request.
Cấu hình CORS.
Deployment stages: API Gateway cung ứng tạo và điều hành các version không giống nhau của API, do đấy ta có thể chia ra được nhiều môi trường (dev, staging, production).
Phân phối monitor và debug ở tầng http request
Phân phối ra tạo ra document 1 cách dễ ợt, như là export API ra theo dạng docs nhưng Swagger có thể đọc được

Ta chỉ nói lý thuyết nhiêu đây thôi, tiếp theo ta sẽ bắt tay vào xây dựng REST API.
Xây dựng REST API
Ta sẽ làm 1 REST API nhưng tiến hành CRUD dễ dàng, gồm list books, get one books, create book, update book và delete book. Ở chương này ta chỉ tương tác với dữ liệu giả được gán cứng vào biến, ta chưa có tương tác với database nha.
Khởi tạo Lambda function
Bước trước tiên ta sẽ tạo 1 lambda fuction nhưng trả về list books.

Mình sẽ dùng terraform để tạo lambda function, nếu các bạn chưa quen với terraform thì xem bài trước để biết cách tạo bằng AWS Web Console nhé, còn các bạn muốn mày mò về Terraform thì mình cũng có viết series về Terraform, các bạn có thể đọc để biết thêm. Các bạn tải code ở repo github này https://github.com/hoalongnatsu/serverless-series.git, nhảy vào thư mục bai-2/terraform-start. Chạy những câu lệnh sau terraform init -> terraform apply -auto-approve. Sau lúc chạy xong bạn sẽ thấy trên 1 lambda function tên là books_list được tạo ra trên AWS.

Hiện giờ ta sẽ viết code cho function list, tạo 1 thư mục tên list, mở nó ra và tạo 1 file tên là main.go với code sau đây.

package main

import (
“encoding/json”

“github.com/aws/aws-lambda-go/lambda”
)

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

func list() (string, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

res, _ := json.Marshal(&books)
return string(res), nil
}

func main() {
lambda.Start(list)
}

Sau lúc viết code xong, ta chạy câu lệnh sau để build golang code ra file binary và upload nó lên lambda function. Tải package go mod init list && go get và build code:

go build -o main main.go
zip list.zip main
rm -rf main

Để tiện cho sau này build thì bạn tao 1 file tên là build.sh, copy đoạn code trên vào. Thư mục của ta sau khi này sẽ như sau:

.
├── build.sh
├── go.mod
├── go.sum
├── list.zip
└── main.go

Sau lúc build ra được file list.zip, ta sẽ update lại books_list lambda function.

$ aws lambda update-function-code –function-name books_list –zip-file fileb://list.zip –region us-west-2

Lúc upload code xong ta rà soát lại xem lambda function của ta có chạy đúng ko.

$ aws lambda invoke –function-name books_list response.json –region us-west-2
{
“StatusCode”: 200,
“ExecutedVersion”: “$LATEST”
}

$ cat response.json ; echo
“[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”}]”

Nếu bạn thấy kết quả trên thì lambda function của ta đã chạy đúng. Tiếp theo, thay vì thực thi function bằng CLI, ta sẽ dùng API Gateway để thực thi nó.
Khởi tạo API Gateway
Mở Web Console và kiếm API Gateway.

Chọn REST API ko có private.

Chỗ protocol ta chọn REST, chọn New API, chỗ API name bạn nhập gì cũng được, của mình thì mình nhập là BOOKS. Endpoint Type ta chọn Regional.

Nhập xong hết thì ta bấm Create API và ta sẽ qua trang có UI như sau.

Giờ ta sẽ khái niệm API của ta. Bấm vào chỗ Actions, chọn Create Resource.

Chỗ Resource Name và Resource Path, ta nhập vào là books. Nhấn Create Resource.

Sau lúc tạo xong, bạn sẽ thấy chỗ Resource có thêm path là /books.

API List
Giờ ta sẽ tạo method chỗ Resource này, nhấn vào Actions, chọn Create Method.

Sau đấy nó sẽ hiện 1 ô dropdown cho bạn, ta chọn GET và nhấn nút check.

Nó sẽ hiện UI như sau, bạn chọn như hình phía dưới và ở ô nhập vào Lambda Function, bạn nhập vào books_list.

Và nhấn Save. Sẽ có 1 modal mở lên nói là nó sẽ tạo permission để API Gateway có thể thực thi được Lambda function, ta chọn OK.

Tiếp theo ta sẽ deploy REST API của ta, chỗ Actions, chọn Deploy API.

Ta chọn New Stage, chỗ Stage name nhập vào staging (này bạn nhập gì cũng được).

Và bấm Deploy. Và ta sẽ có UI như sau.

Mở staging ra, chọn vào GET method, ta sẽ thấy được URL của API nhưng tiến hành get list books cho ta.

Oke, vậy là ta đã deploy được REST API trước tiên của ta 😁. Bạn copy URL và tiến hành gửi request đến nó.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
{“message”: “Internal server error”}

Và ta sẽ thấy có lỗi xảy ra 😂. Lý do là vì để Lambda liên kết được với API Gateway, thì Lambda function phải trả về đúng format nhưng API Gateway quy định. Ở đoạn code trên ta trả về kết quả chưa đúng format.


func list() (string, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

res, _ := json.Marshal(&books)
return string(res), nil // response not valid format of API Gateway
}

Format đúng của response nhưng Lambda function trả về cho API Gateway sẽ như sau:

type Response struct {
StatusCode int `json:”statusCode”`
Body string `json:”body”`
}

Nó sẽ gồm 1 trường statusCode định dạng số và 1 trường body định dạng là string. Ta cập nhật lại file main.go

package main

import (
“encoding/json”

“github.com/aws/aws-lambda-go/lambda”
)

type Response struct {
StatusCode int `json:”statusCode”`
Body string `json:”body”`
}

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

func list() (Response, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

res, _ := json.Marshal(&books)
return Response{
StatusCode: 200,
Body: string(res),
}, nil
}

func main() {
lambda.Start(list)
}

Build code và upload lại lên AWS.

$ sh build.sh
updating: main (deflated 46%

$ aws lambda update-function-code –function-name books_list –zip-file fileb://list.zip –region us-west-2

Giờ ta gọi lại API list books, ta sẽ thấy được kết quả trả về nhưng ko có lỗi xảy ra.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”}]

Oke, hiện giờ thì REST API trước tiên của ta mới thực thụ được deploy thành công. Để làm đúng thì AWS có cũng cấp cho ta bộ Golang SDK để ta tránh được mấy lỗi trên và viết code mau lẹ hơn. Thay vì phải tự tạo struct Response , thì ta xài SDK như sau, tải package go get github.com/aws/aws-lambda-go/events, update lại main.go, ta sử dụng struct có sẵn là APIGatewayProxyResponse.

package main

import (
“encoding/json”

“github.com/aws/aws-lambda-go/events”
“github.com/aws/aws-lambda-go/lambda”
)

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

func list() (events.APIGatewayProxyResponse, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

res, _ := json.Marshal(&books)
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
“Content-Type”: “application/json”,
},
Body: string(res),
}, nil
}

func main() {
lambda.Start(list)
}

Minh họa Serverless ngày nay của ta.

API get one
Tiếp theo ta sẽ làm API get one book theo id. Handle function của lambda lúc liên kết với API sẽ có params trước tiên được truyền vào là APIGatewayProxyRequest, nó sẽ chứa trị giá request của user, ta sẽ khái niệm đường dẫn của API get one là /books/{id}, trong đấy id là param, và nó sẽ nằm trong trường PathParameters của APIGatewayProxyRequest. Ta tạo 1 thư mục tên là getOne, mở nó ra và tạo 1 file main.go với code như sau:

package main

import (
“encoding/json”
“strconv”

“github.com/aws/aws-lambda-go/events”
“github.com/aws/aws-lambda-go/lambda”
)

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

func getOne(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

id, err := strconv.Atoi(req.PathParameters[“id”])
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 400,
Body: err.Error(),
}, nil
}

res, err := json.Marshal(books[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(res),
}, nil
}

func main() {
lambda.Start(getOne)
}

Tải package và build source.

go mod init getone
go get

#!/bin/bash

GOOS=linux go build -o main main.go
zip getOne.zip main
rm -rf main

sh build.sh

Tạo Lambda function cho API get one.

$ aws lambda create-function –function-name books_get_one –zip-file fileb://getOne.zip –runtime go1.x –handler main –role arn:aws:iam::ACCOUNT_ID:role/lambda_role –region us-west-2

Với arn:aws:iam::ACCOUNT_ID:role/lambda_role, trị giá ACCOUNT_ID là tài khoản id của bạn.

Giờ ta sẽ tạo API cho get one. Quay lại API Gateway, bấm vào /books, chọn Actions -> Create Resource.

Nhập vào {id} và bấm tạo.

Và tạo method cho {id}, chọn GET.

Ở chỗ tên function ta nhập vào books_get_one.

Bấm tạo và nó sẽ hỏi tạo permission, ta OK. Ta bấm deploy lại.

Lần này ta ở Deployment stage chọn lại staging trước đấy và nhấn Deploy.
Và ta sẽ thấy URL API get one của ta.

Tiến hành request đến nó.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books/1 ; echo
{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”}

Oke, ta đã deploy get one API thành công. Hiện giờ Serverless của ta sẽ như sau.

API create
Tiếp theo là API tạo book mới, do ta ko có dùng API nên ta chỉ dễ dàng là lấy body từ request của client rồi append vào mảng ngày nay. Tạo 1 thư mục tên là create, mở nó ra và tạo 1 file tên là main.go với code như sau:

package main

import (
“encoding/json”

“github.com/aws/aws-lambda-go/events”
“github.com/aws/aws-lambda-go/lambda”
)

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

var books = []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

func create(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
var book Books
err := json.Unmarshal([]byte(req.Body), &book)
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 400,
Body: err.Error(),
}, nil
}

books = append(books, book)

res, err := json.Marshal(&books)
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(res),
}, nil
}

func main() {
lambda.Start(create)
}

Tải package và build source.

go mod init create
go get

#!/bin/bash

GOOS=linux go build -o main main.go
zip create.zip main
rm -rf main

sh build.sh

Tạo Lambda function cho API create.

$ aws lambda create-function –function-name books_create –zip-file fileb://create.zip –runtime go1.x –handler main –role arn:aws:iam::ACCOUNT_ID:role/lambda_role –region us-west-2

Ở API Gateway, chọn lại mục Resource, bấm vào /books, chọn Actions -> Create Resource, xong đấy ở chỗ dropdown ta chọn POST method.

Ở ô nhập function, ta nhập vào books_create.

Bấm save và chọn OK lúc nó hỏi tạo permission. Sau đấy ta deploy lại.

Chọn staging như trước đấy và nhấn Deploy, và ta sẽ có URL cho API create.

Gửi request đến nó.

$ curl -sX POST -d ‘{“Id”:3, “name”: “Java”, “author”: “Java”}’ https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”},{“id”:3,”name”:”Java”,”author”:”Java”}]

Oke, API create của ta cũng đã được deploy thành công. Minh họa Serverless ngày nay.

Warm start – Cold start
Ta gọi thêm 1 lần nữa, bạn sẽ thấy là trị giá được append vào mảng ngày nay và có chứa cả trị giá ta gửi lên lần trước.

$ curl -sX POST -d ‘{“Id”:4, “name”: “.NET”, “author”: “.NET”}’ https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”},{“id”:3,”name”:”Java”,”author”:”Java”},{“id”:4,”name”:”.NET”,”author”:”.NET”}]

Nhưng mà lúc ta đợi thời kì khoảng chừng 1 5 phút sau, bạn gọi lại là sẽ thấy là 2 trị giá trước đấy ta gửi lên bị mất đi.

$ curl -sX POST -d ‘{“Id”:5, “name”: “PHP”, “author”: “PHP”}’ https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”},{“id”:5,”name”:”PHP”,”author”:”PHP”}]

Vì sao lại tương tự? Thì vấn đề này được gọi là Warm start và Cold start trong AWS Lambda. Lúc 1 AWS Lambda thực thi 1 lambda function, thì nó sẽ làm các bước sau đây:

Lúc function được thực thi lần trước tiên, AWS Lambda sẽ kiếm chỗ nào đấy trên hệ thống máy ảo bên dưới của nó nhưng có đủ resource để chạy function này, sau lúc kiếm được, nó sẽ tạo 1 container với môi trường thích hợp cho function của ta, sau lúc container được tạo xong thì function sẽ được thực thi trong container đấy và trả về kết quả cho client, công đoạn này gọi là Cold start.
Và lần sau lúc function được thực thi, AWS Lambda sẽ rà soát trước đấy có container cho function này chưa, nếu có rồi thì nó sẽ sử dụng container cũ để thực thi function, công đoạn này gọi là Warm start.

Nhưng mà nếu 1 khoảng thời kì nhưng function ko được trigger nữa, container đấy sẽ bị xóa đi, và công đoạn Cold start sẽ được lặp lại. Đây là lý do tại sao Lambda function là 1 stateless application, nó ko có dùng để lưu dữ liệu được, nhưng ta phải dùng 1 service khác để lưu dữ liệu cho ta, như là AWS RDS hoặc DynamoDB.

Các API khác như update và delete thì các bạn làm gần giống nhé. Toàn thể code của chương này nằm ở repo github ở trên nha.
Kết luận
Vậy là ta đã mày mò xong về các xây dựng REST API theo mẫu hình Serverless với API Gateway và AWS Lambda. Nếu các bạn thấy chỉ làm 1 vài API dễ dàng nhưng gì đâu phải tạo nhiều thứ quá thìa là do ở đây mình làm bằng tay thôi với do mình giảng giải từng cái nên nó sẽ chậm, còn làm thực tiễn thì ta sẽ có tool để tự động deploy như terraform, viết luồng CI/CD để tự động deploy code, dev local với AWS Serverless Application Model (SAM), những thứ này mình sẽ nói ở các bài sau. Nếu có thắc mắc hoặc cần giảng giải rõ thêm chỗ nào thì các bạn có thể hỏi dưới phần comment. Bài tiếp theo ta sẽ nói về cách sử dụng Lambda với DynamoDB.
#Serverless #Series #Golang #Bài #Build #REST #API #AWS #API #Gateway10 #tháng #lượt #xem #Golang #DevOps #AWS #Đầu #mục #bài #viếtBài #viết #liên #quanKhoá #học #hay
[rule_2_plain] #Serverless #Series #Golang #Bài #Build #REST #API #AWS #API #Gateway10 #tháng #lượt #xem #Golang #DevOps #AWS #Đầu #mục #bài #viếtBài #viết #liên #quanKhoá #học #hay
[rule_2_plain] #Serverless #Series #Golang #Bài #Build #REST #API #AWS #API #Gateway10 #tháng #lượt #xem #Golang #DevOps #AWS #Đầu #mục #bài #viếtBài #viết #liên #quanKhoá #học #hay
[rule_3_plain]

#Serverless #Series #Golang #Bài #Build #REST #API #AWS #API #Gateway10 #tháng #lượt #xem #Golang #DevOps #AWS #Đầu #mục #bài #viếtBài #viết #liên #quanKhoá #học #hay
Tác giả: Huỳnh Minh Quân- giảng sư khóa học AWS : Learn AWS the Hard Way.
Xem các bài viết khác thuộc Series Serverless

Giới thiệu
Chào các bạn đến với series về Serverless, ở bài trước chúng ta đã nói về kiến trúc Serverless là gì, AWS Lambda là gì và nó vào vai trò như thế nào trong mẫu hình Serverless. Ở bài này chúng ta sẽ mày mò về thành phần thứ 2 để ta xây dựng mẫu hình Serverless trên môi trường AWS Cloud, là API Gateway. Ta sẽ sử dụng API Gateway liên kết với Lambda để xây dựng 1 REST API theo mẫu hình Serverless.
Hình minh họa REST API nhưng ta sẽ xây dựng.

API Gateway
Như đã nói ở bài trước, AWS Lambda function của ta sẽ chẳng thể tự động chạy, nhưng nó sẽ được thực thi bởi 1 event nào đấy. Thì API Gateway là 1 trong những serivce nhưng sẽ phát ra event để thực thi Lambda function, chi tiết hơn là API Gateway sẽ phát ra 1 event đến Lambda function lúc có 1 http request từ phía khách hàng gọi đến nó, do đấy nó rất phù hợp cho việc xây dựng REST API.
Trong mẫu hình Serverless thì API Gateway sẽ vào vai trò như là 1 entry point cho toàn thể Lambda functions của ta, nó sẽ proxy và điều hướng 1 http request đến đúng Lambda function nhưng ta muốn.
Kế bên việc vào vai trò như 1 entry point cho Lambda function, API Gateway còn có những đặc tính nổi trội sau đây:

Caching: Ta có thể cache lại kết quả nhưng Lambda trả về => giảm số lần nhưng API Gateway gọi đến Lambda function bên dưới, giúp ta giảm tiền và giảm thời kì response của 1 request.
Cấu hình CORS.
Deployment stages: API Gateway cung ứng tạo và điều hành các version không giống nhau của API, do đấy ta có thể chia ra được nhiều môi trường (dev, staging, production).
Phân phối monitor và debug ở tầng http request
Phân phối ra tạo ra document 1 cách dễ ợt, như là export API ra theo dạng docs nhưng Swagger có thể đọc được

Ta chỉ nói lý thuyết nhiêu đây thôi, tiếp theo ta sẽ bắt tay vào xây dựng REST API.
Xây dựng REST API
Ta sẽ làm 1 REST API nhưng tiến hành CRUD dễ dàng, gồm list books, get one books, create book, update book và delete book. Ở chương này ta chỉ tương tác với dữ liệu giả được gán cứng vào biến, ta chưa có tương tác với database nha.
Khởi tạo Lambda function
Bước trước tiên ta sẽ tạo 1 lambda fuction nhưng trả về list books.

Mình sẽ dùng terraform để tạo lambda function, nếu các bạn chưa quen với terraform thì xem bài trước để biết cách tạo bằng AWS Web Console nhé, còn các bạn muốn mày mò về Terraform thì mình cũng có viết series về Terraform, các bạn có thể đọc để biết thêm. Các bạn tải code ở repo github này https://github.com/hoalongnatsu/serverless-series.git, nhảy vào thư mục bai-2/terraform-start. Chạy những câu lệnh sau terraform init -> terraform apply -auto-approve. Sau lúc chạy xong bạn sẽ thấy trên 1 lambda function tên là books_list được tạo ra trên AWS.

Hiện giờ ta sẽ viết code cho function list, tạo 1 thư mục tên list, mở nó ra và tạo 1 file tên là main.go với code sau đây.

package main

import (
“encoding/json”

“github.com/aws/aws-lambda-go/lambda”
)

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

func list() (string, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

res, _ := json.Marshal(&books)
return string(res), nil
}

func main() {
lambda.Start(list)
}

Sau lúc viết code xong, ta chạy câu lệnh sau để build golang code ra file binary và upload nó lên lambda function. Tải package go mod init list && go get và build code:

go build -o main main.go
zip list.zip main
rm -rf main

Để tiện cho sau này build thì bạn tao 1 file tên là build.sh, copy đoạn code trên vào. Thư mục của ta sau khi này sẽ như sau:

.
├── build.sh
├── go.mod
├── go.sum
├── list.zip
└── main.go

Sau lúc build ra được file list.zip, ta sẽ update lại books_list lambda function.

$ aws lambda update-function-code –function-name books_list –zip-file fileb://list.zip –region us-west-2

Lúc upload code xong ta rà soát lại xem lambda function của ta có chạy đúng ko.

$ aws lambda invoke –function-name books_list response.json –region us-west-2
{
“StatusCode”: 200,
“ExecutedVersion”: “$LATEST”
}

$ cat response.json ; echo
“[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”}]”

Nếu bạn thấy kết quả trên thì lambda function của ta đã chạy đúng. Tiếp theo, thay vì thực thi function bằng CLI, ta sẽ dùng API Gateway để thực thi nó.
Khởi tạo API Gateway
Mở Web Console và kiếm API Gateway.

Chọn REST API ko có private.

Chỗ protocol ta chọn REST, chọn New API, chỗ API name bạn nhập gì cũng được, của mình thì mình nhập là BOOKS. Endpoint Type ta chọn Regional.

Nhập xong hết thì ta bấm Create API và ta sẽ qua trang có UI như sau.

Giờ ta sẽ khái niệm API của ta. Bấm vào chỗ Actions, chọn Create Resource.

Chỗ Resource Name và Resource Path, ta nhập vào là books. Nhấn Create Resource.

Sau lúc tạo xong, bạn sẽ thấy chỗ Resource có thêm path là /books.

API List
Giờ ta sẽ tạo method chỗ Resource này, nhấn vào Actions, chọn Create Method.

Sau đấy nó sẽ hiện 1 ô dropdown cho bạn, ta chọn GET và nhấn nút check.

Nó sẽ hiện UI như sau, bạn chọn như hình phía dưới và ở ô nhập vào Lambda Function, bạn nhập vào books_list.

Và nhấn Save. Sẽ có 1 modal mở lên nói là nó sẽ tạo permission để API Gateway có thể thực thi được Lambda function, ta chọn OK.

Tiếp theo ta sẽ deploy REST API của ta, chỗ Actions, chọn Deploy API.

Ta chọn New Stage, chỗ Stage name nhập vào staging (này bạn nhập gì cũng được).

Và bấm Deploy. Và ta sẽ có UI như sau.

Mở staging ra, chọn vào GET method, ta sẽ thấy được URL của API nhưng tiến hành get list books cho ta.

Oke, vậy là ta đã deploy được REST API trước tiên của ta 😁. Bạn copy URL và tiến hành gửi request đến nó.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
{“message”: “Internal server error”}

Và ta sẽ thấy có lỗi xảy ra 😂. Lý do là vì để Lambda liên kết được với API Gateway, thì Lambda function phải trả về đúng format nhưng API Gateway quy định. Ở đoạn code trên ta trả về kết quả chưa đúng format.


func list() (string, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

res, _ := json.Marshal(&books)
return string(res), nil // response not valid format of API Gateway
}

Format đúng của response nhưng Lambda function trả về cho API Gateway sẽ như sau:

type Response struct {
StatusCode int `json:”statusCode”`
Body string `json:”body”`
}

Nó sẽ gồm 1 trường statusCode định dạng số và 1 trường body định dạng là string. Ta cập nhật lại file main.go

package main

import (
“encoding/json”

“github.com/aws/aws-lambda-go/lambda”
)

type Response struct {
StatusCode int `json:”statusCode”`
Body string `json:”body”`
}

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

func list() (Response, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

res, _ := json.Marshal(&books)
return Response{
StatusCode: 200,
Body: string(res),
}, nil
}

func main() {
lambda.Start(list)
}

Build code và upload lại lên AWS.

$ sh build.sh
updating: main (deflated 46%

$ aws lambda update-function-code –function-name books_list –zip-file fileb://list.zip –region us-west-2

Giờ ta gọi lại API list books, ta sẽ thấy được kết quả trả về nhưng ko có lỗi xảy ra.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”}]

Oke, hiện giờ thì REST API trước tiên của ta mới thực thụ được deploy thành công. Để làm đúng thì AWS có cũng cấp cho ta bộ Golang SDK để ta tránh được mấy lỗi trên và viết code mau lẹ hơn. Thay vì phải tự tạo struct Response , thì ta xài SDK như sau, tải package go get github.com/aws/aws-lambda-go/events, update lại main.go, ta sử dụng struct có sẵn là APIGatewayProxyResponse.

package main

import (
“encoding/json”

“github.com/aws/aws-lambda-go/events”
“github.com/aws/aws-lambda-go/lambda”
)

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

func list() (events.APIGatewayProxyResponse, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

res, _ := json.Marshal(&books)
return events.APIGatewayProxyResponse{
StatusCode: 200,
Headers: map[string]string{
“Content-Type”: “application/json”,
},
Body: string(res),
}, nil
}

func main() {
lambda.Start(list)
}

Minh họa Serverless ngày nay của ta.

API get one
Tiếp theo ta sẽ làm API get one book theo id. Handle function của lambda lúc liên kết với API sẽ có params trước tiên được truyền vào là APIGatewayProxyRequest, nó sẽ chứa trị giá request của user, ta sẽ khái niệm đường dẫn của API get one là /books/{id}, trong đấy id là param, và nó sẽ nằm trong trường PathParameters của APIGatewayProxyRequest. Ta tạo 1 thư mục tên là getOne, mở nó ra và tạo 1 file main.go với code như sau:

package main

import (
“encoding/json”
“strconv”

“github.com/aws/aws-lambda-go/events”
“github.com/aws/aws-lambda-go/lambda”
)

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

func getOne(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
books := []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

id, err := strconv.Atoi(req.PathParameters[“id”])
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 400,
Body: err.Error(),
}, nil
}

res, err := json.Marshal(books[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(res),
}, nil
}

func main() {
lambda.Start(getOne)
}

Tải package và build source.

go mod init getone
go get

#!/bin/bash

GOOS=linux go build -o main main.go
zip getOne.zip main
rm -rf main

sh build.sh

Tạo Lambda function cho API get one.

$ aws lambda create-function –function-name books_get_one –zip-file fileb://getOne.zip –runtime go1.x –handler main –role arn:aws:iam::ACCOUNT_ID:role/lambda_role –region us-west-2

Với arn:aws:iam::ACCOUNT_ID:role/lambda_role, trị giá ACCOUNT_ID là tài khoản id của bạn.

Giờ ta sẽ tạo API cho get one. Quay lại API Gateway, bấm vào /books, chọn Actions -> Create Resource.

Nhập vào {id} và bấm tạo.

Và tạo method cho {id}, chọn GET.

Ở chỗ tên function ta nhập vào books_get_one.

Bấm tạo và nó sẽ hỏi tạo permission, ta OK. Ta bấm deploy lại.

Lần này ta ở Deployment stage chọn lại staging trước đấy và nhấn Deploy.
Và ta sẽ thấy URL API get one của ta.

Tiến hành request đến nó.

$ curl https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books/1 ; echo
{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”}

Oke, ta đã deploy get one API thành công. Hiện giờ Serverless của ta sẽ như sau.

API create
Tiếp theo là API tạo book mới, do ta ko có dùng API nên ta chỉ dễ dàng là lấy body từ request của client rồi append vào mảng ngày nay. Tạo 1 thư mục tên là create, mở nó ra và tạo 1 file tên là main.go với code như sau:

package main

import (
“encoding/json”

“github.com/aws/aws-lambda-go/events”
“github.com/aws/aws-lambda-go/lambda”
)

type Books struct {
Id int `json:”id”`
Name string `json:”name”`
Author string `json:”author”`
}

var books = []Books{
{Id: 1, Name: “NodeJS”, Author: “NodeJS”},
{Id: 2, Name: “Golang”, Author: “Golang”},
}

func create(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
var book Books
err := json.Unmarshal([]byte(req.Body), &book)
if err != nil {
return events.APIGatewayProxyResponse{
StatusCode: 400,
Body: err.Error(),
}, nil
}

books = append(books, book)

res, err := json.Marshal(&books)
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(res),
}, nil
}

func main() {
lambda.Start(create)
}

Tải package và build source.

go mod init create
go get

#!/bin/bash

GOOS=linux go build -o main main.go
zip create.zip main
rm -rf main

sh build.sh

Tạo Lambda function cho API create.

$ aws lambda create-function –function-name books_create –zip-file fileb://create.zip –runtime go1.x –handler main –role arn:aws:iam::ACCOUNT_ID:role/lambda_role –region us-west-2

Ở API Gateway, chọn lại mục Resource, bấm vào /books, chọn Actions -> Create Resource, xong đấy ở chỗ dropdown ta chọn POST method.

Ở ô nhập function, ta nhập vào books_create.

Bấm save và chọn OK lúc nó hỏi tạo permission. Sau đấy ta deploy lại.

Chọn staging như trước đấy và nhấn Deploy, và ta sẽ có URL cho API create.

Gửi request đến nó.

$ curl -sX POST -d ‘{“Id”:3, “name”: “Java”, “author”: “Java”}’ https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”},{“id”:3,”name”:”Java”,”author”:”Java”}]

Oke, API create của ta cũng đã được deploy thành công. Minh họa Serverless ngày nay.

Warm start – Cold start
Ta gọi thêm 1 lần nữa, bạn sẽ thấy là trị giá được append vào mảng ngày nay và có chứa cả trị giá ta gửi lên lần trước.

$ curl -sX POST -d ‘{“Id”:4, “name”: “.NET”, “author”: “.NET”}’ https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”},{“id”:3,”name”:”Java”,”author”:”Java”},{“id”:4,”name”:”.NET”,”author”:”.NET”}]

Nhưng mà lúc ta đợi thời kì khoảng chừng 1 5 phút sau, bạn gọi lại là sẽ thấy là 2 trị giá trước đấy ta gửi lên bị mất đi.

$ curl -sX POST -d ‘{“Id”:5, “name”: “PHP”, “author”: “PHP”}’ https://ferwqd3ttf.execute-api.us-west-2.amazonaws.com/staging/books ; echo
[{“id”:1,”name”:”NodeJS”,”author”:”NodeJS”},{“id”:2,”name”:”Golang”,”author”:”Golang”},{“id”:5,”name”:”PHP”,”author”:”PHP”}]

Vì sao lại tương tự? Thì vấn đề này được gọi là Warm start và Cold start trong AWS Lambda. Lúc 1 AWS Lambda thực thi 1 lambda function, thì nó sẽ làm các bước sau đây:

Lúc function được thực thi lần trước tiên, AWS Lambda sẽ kiếm chỗ nào đấy trên hệ thống máy ảo bên dưới của nó nhưng có đủ resource để chạy function này, sau lúc kiếm được, nó sẽ tạo 1 container với môi trường thích hợp cho function của ta, sau lúc container được tạo xong thì function sẽ được thực thi trong container đấy và trả về kết quả cho client, công đoạn này gọi là Cold start.
Và lần sau lúc function được thực thi, AWS Lambda sẽ rà soát trước đấy có container cho function này chưa, nếu có rồi thì nó sẽ sử dụng container cũ để thực thi function, công đoạn này gọi là Warm start.

Nhưng mà nếu 1 khoảng thời kì nhưng function ko được trigger nữa, container đấy sẽ bị xóa đi, và công đoạn Cold start sẽ được lặp lại. Đây là lý do tại sao Lambda function là 1 stateless application, nó ko có dùng để lưu dữ liệu được, nhưng ta phải dùng 1 service khác để lưu dữ liệu cho ta, như là AWS RDS hoặc DynamoDB.

Các API khác như update và delete thì các bạn làm gần giống nhé. Toàn thể code của chương này nằm ở repo github ở trên nha.
Kết luận
Vậy là ta đã mày mò xong về các xây dựng REST API theo mẫu hình Serverless với API Gateway và AWS Lambda. Nếu các bạn thấy chỉ làm 1 vài API dễ dàng nhưng gì đâu phải tạo nhiều thứ quá thìa là do ở đây mình làm bằng tay thôi với do mình giảng giải từng cái nên nó sẽ chậm, còn làm thực tiễn thì ta sẽ có tool để tự động deploy như terraform, viết luồng CI/CD để tự động deploy code, dev local với AWS Serverless Application Model (SAM), những thứ này mình sẽ nói ở các bài sau. Nếu có thắc mắc hoặc cần giảng giải rõ thêm chỗ nào thì các bạn có thể hỏi dưới phần comment. Bài tiếp theo ta sẽ nói về cách sử dụng Lambda với DynamoDB.

Related Articles

Trả lời

Email của bạn sẽ không được hiển thị công khai.

Back to top button