अनस्प्लैश पर लुकास वानट्को द्वारा फोटो
गो अपनी सरलता के कारण हमेशा मेरी पसंदीदा भाषाओं में से एक रही है। हाल ही में, मैंने यह पता लगाने का निर्णय लिया कि गो में लिखे लैम्ब्डा फ़ंक्शंस के साथ एक सरल बॉयलरप्लेट सर्वर रहित प्रोजेक्ट बनाने में क्या लगता है। मैं टूलींग और डेवलपर अनुभव के बारे में उत्सुक था।
मैं एक REST API बनाना चाहता हूं, जो डेटा लेयर के रूप में पोस्टग्रेज डीबी का उपयोग करता है। मेरी प्रारंभिक आवश्यकताएं निम्नलिखित हैं
आपको गो इंस्टॉल करना होगा, साथ ही एडब्ल्यूएस एसएएम भी इंस्टॉल करना होगा। यदि आप अपने प्रोजेक्ट को AWS पर तैनात करते हैं तो आपको आपके द्वारा बनाए जा रहे संसाधनों के लिए बिल भेजा जा सकता है, इसलिए जब आपको अपने संसाधनों की आवश्यकता न हो तो उन्हें साफ़ करना याद रखें।
डीबी के लिए मैं सुपरबेस का उपयोग करता हूं
कोड इस रेपो में उपलब्ध है
आइए सैम इनिट चलाकर शुरुआत करें। मैंने हेलो वर्ल्ड टेम्प्लेट चुना, दिए गए al.2023 env के साथ जाएं। पहले गो के लिए एक प्रबंधित रनटाइम था, लेकिन आजकल इसे बंद कर दिया गया है।
एपीआई स्कीमा को ओपनएपीआई स्पेक के रूप में परिभाषित करने के कुछ स्पष्ट फायदे हैं। हम इसका उपयोग दस्तावेज़ तैयार करने, क्लाइंट, सर्वर आदि बनाने के लिए कर सकते हैं। मैं इसका उपयोग AWS HttpApi गेटवे के आकार को परिभाषित करने के लिए भी करता हूं।
मेरा स्कीमा सीधा है। एकमात्र दिलचस्प हिस्सा x-amazon-apigateway-integration संपत्ति है, जो लैम्ब्डा एकीकरण के साथ कनेक्शन की अनुमति देता है। सेटअप भाषा-अज्ञेयवादी है.
आप रेपो में स्कीमा फ़ाइल पा सकते हैं
# template.yaml # .... ItemsAPI: Type: AWS::Serverless::HttpApi Properties: StageName: Prod DefinitionBody: 'Fn::Transform': Name: 'AWS::Include' Parameters: Location: './api.yaml' FailOnWarnings: false DBSecret: Type: AWS::SecretsManager::Secret Properties: Name: my-db-secret Description: Postgres config string SecretString: 'host=172.17.0.1 port=5431 user=admin password=root dbname=lambdas sslmode=disable' # ....
जैसा कि ऊपर बताया गया है, यहां जाने के लिए कुछ खास नहीं है। HttpApi गेटवे OpenApi के आधार पर बनाया गया है।
कनेक्शन स्ट्रिंग को संग्रहीत करने का भी एक रहस्य है। मैं तैनाती के बाद इसका मूल्य अपडेट करूंगा
गो के लिए AWS SAM समर्थन बहुत बढ़िया है। मैं CodeUri को लैम्ब्डा हैंडलर वाले फ़ोल्डर में इंगित कर सकता हूं और बिल्ड विधि को go1.x
के रूप में परिभाषित कर सकता हूं।गो में निर्मित लैम्ब्डा फ़ंक्शंस उपलब्ध कराए गए 2023 रनटाइम का उपयोग करते हैं, क्योंकि वे एक एकल स्व-युक्त बाइनरी का उत्पादन करते हैं।
फ़ंक्शन की परिभाषा इस तरह दिखती है:
# template.yaml # .... GetItemFunction: Type: AWS::Serverless::Function Metadata: BuildMethod: go1.x Properties: Tracing: Active CodeUri: lambda_handlers/cmd/get_item/ Handler: bootstrap Runtime: provided.al2023 Architectures: - x86_64 Environment: Variables: DB_SECRET_NAME: !Ref DBSecret API_STAGE: Prod Events: HttpApiEvents: Type: HttpApi Properties: Path: /item/{id} Method: GET ApiId: !Ref ItemsAPI Policies: - AWSLambdaBasicExecutionRole - AWSSecretsManagerGetSecretValuePolicy: SecretArn: !Ref DBSecret # ....
SAM जादू के लिए धन्यवाद, HttpApi गेटवे और लैम्ब्डा फ़ंक्शन के बीच कनेक्शन सभी आवश्यक अनुमतियों के साथ स्थापित किया जाएगा।
ईमानदारी से कहें तो, फ़ोल्डर संरचना संभवतः मुहावरेदार नहीं है। लेकिन मैंने सामान्य गो पैटर्न का पालन करने की कोशिश की
lambda_handlers |--/api |--/cmd |--/internal |--/tools |--go.mod |--go.sum
cmd वास्तविक लैम्ब्डा हैंडलर वाला मुख्य फ़ोल्डर है
आंतरिक हैंडलर के बीच साझा किया गया कोड रखता है
टूल्स परियोजनाओं में उपयोग किए जाने वाले अतिरिक्त टूल को परिभाषित करते हैं
ओपनएपीआई जेनरेटर कॉन्फ़िगरेशन और जेनरेट किए गए मॉडल के लिए एपीआई
लैम्ब्डा हैंडलर के लिए प्रारंभिक बॉयलरप्लेट इस तरह दिखती है:
// ... func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // handler logic } func main() { lambda.Start(handleRequest) }
आम तौर पर, पहला सवाल यह पूछा जाता है कि AWS SDK क्लाइंट, डेटाबेस कनेक्शन और अन्य चीजों का इनिशियलाइज़ेशन कहां रखा जाए, जिन्हें हम कोल्ड स्टार्ट के दौरान निपटाना चाहते हैं।
हमारे पास यहां विकल्प हैं। सबसे पहले AWS दस्तावेज़ीकरण उदाहरण से पैटर्न का पालन करना और init() फ़ंक्शन के अंदर सेवाओं को प्रारंभ करना है। मुझे यह दृष्टिकोण पसंद नहीं है, क्योंकि इससे यूनिट परीक्षणों में हैंडलर का उपयोग करना कठिन हो जाता है।
इस तथ्य के लिए धन्यवाद कि Lambda.Start() विधि एक फ़ंक्शन को इनपुट के रूप में लेती है, मैं इसे कस्टम संरचना में लपेट सकता हूं, और इसे उन सेवाओं के साथ प्रारंभ कर सकता हूं जिनकी मुझे आवश्यकता है। मेरे मामले में, कोड इस तरह दिखता है:
package main // imports ... type GetItemsService interface { GetItem(id int) (*api.Item, error) } type LambdaHandler struct { svc GetItemsService } func InitializeLambdaHandler(svc GetItemsService) *LambdaHandler { return &LambdaHandler{ svc: svc, } } func (h *LambdaHandler) HandleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { strId, err := helpers.GetPathParameter("id", request.PathParameters) if err != nil { return helpers.SendResponse("id is required", 400), nil } id, err := strconv.Atoi(*strId) if err != nil { return helpers.SendResponse("id must be an integer", 400), nil } log.Printf("id: %d", id) result, err := h.svc.GetItem(id) if err != nil { if err.Error() == "Item not found" { return helpers.SendResponse("Item not found", 404), nil } return helpers.SendResponse("error", 500), nil } jsonRes, err := json.Marshal(result) if err != nil { log.Printf("error marshalling response: %s", err.Error()) return helpers.SendResponse("internal server error", 500), nil } return helpers.SendResponse(string(jsonRes), 200), nil } func main() { dbSecretName := os.Getenv("DB_SECRET_NAME") log.Printf("dbSecretName: %s", dbSecretName) cfg, err := awssdkconfig.InitializeSdkConfig() if err != nil { log.Fatal(err) } secretsClient := awssdkconfig.InitializeSecretsManager(cfg) connString, err := secretsClient.GetSecret(dbSecretName) if err != nil { log.Fatal(err) } conn, err := db.InitializeDB(connString) if err != nil { log.Fatal(err) } defer conn.Close() log.Println("successfully connected to db") svc := items.InitializeItemsService(conn) handler := InitializeLambdaHandler(svc) lambda.Start(handler.HandleRequest) }
मुख्य() फ़ंक्शन में (जो कोल्ड स्टार्ट के दौरान चलता है) मैं सीक्रेटमैनेजर से रहस्य प्राप्त करता हूं और फिर डीबी के साथ कनेक्शन शुरू करता हूं। दोनों कार्यात्मकताओं को आंतरिक फ़ोल्डरों के अंदर सामान्य सहायकों के रूप में परिभाषित किया गया है ताकि उन्हें अन्य हैंडलर में पुन: उपयोग किया जा सके। अंत में मेरी आइटम्स सर्विस को निर्मित डीबी कनेक्शन के साथ प्रारंभ किया गया है, और लैम्ब्डा हैंडलर बनाने के लिए उपयोग किया जाता है।
HandleRequest पथ पैरामीटर से आईडी को पार्स करता है, और डीबी से एक आइटम प्राप्त करने के लिए आइटम्स सर्विस को कॉल करता है।
चूंकि फ़ंक्शन सरल है, इसलिए इसमें बहुत अधिक व्यावसायिक तर्क नहीं है। आइटम्ससर्विस विशिष्ट आइटम के लिए बस डीबी को कॉल करता है
package items import ( "database/sql" "errors" api "lambda_handlers/api" "log" ) type ItemsService struct { conn *sql.DB } func InitializeItemsService(conn *sql.DB) *ItemsService { return &ItemsService{ conn: conn, } } func (svc ItemsService) GetItem(id int) (*api.Item, error) { log.Printf("Getting item id %v", id) query := `SELECT * FROM items WHERE id = $1` var item api.Item err := svc.conn.QueryRow(query, id).Scan(&item.Id, &item.Name, &item.Price) log.Printf("Got item id %v", id) if err != nil { log.Printf("Error getting item %v: %v", id, err) if err == sql.ErrNoRows { return nil, errors.New("Item not found") } return nil, err } return &item, nil }
इस बिंदु पर, हमें यहां किसी और चीज़ की आवश्यकता नहीं है।
मेरा लक्ष्य अतिरिक्त टूल का उपयोग करना है, जिसे प्रोजेक्ट निर्भरता से जोड़ा जा सकता है, इसलिए डेवलपर की मशीन पर इंस्टॉल किए गए टूल पर भरोसा करने की कोई आवश्यकता नहीं है।
गो में ऐसा करने का एक तरीका टूल पैकेज में oapi-codegen को रखना है
//go:build tools // build tools package main import ( _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" )
और इसे api_gen.go के अंदर से कॉल करें
//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=cfg.yaml ../../api.yaml package api
इस तरह मैं अलग से oapi-codegen बायनेरिज़ इंस्टॉल किए बिना गो जनरेट चला सकता हूं।
मेरी निर्माण प्रक्रिया के लिए दो चरणों की आवश्यकता होती है: ओपनएपीआई से मॉडल तैयार करना, और स्वयं प्रोजेक्ट बनाना। मैंने AWS SAM को बाद वाले से निपटने दिया।
यहां मेरी मेकफ़ाइल है
.PHONY: build local deploy generate: cd lambda_handlers && go generate ./... build: generate rm -rf .aws-sam sam build local: build sam local start-api --env-vars parameters.json deploy: build sam deploy
मेरे लिए, स्थानीय स्तर पर एपीआई गेटवे का परीक्षण करने का सबसे आसान तरीका सैम लोकल स्टार्ट-एपीआई चलाना है
चूँकि हमारा फ़ंक्शन पर्यावरण चर पर निर्भर करता है, इसलिए मैंने env vars को sam local
सर्वर रहित विकास करते समय, किसी बिंदु पर आप स्थानीय विकास के लिए भी क्लाउड संसाधनों का उपयोग शुरू करना चाहेंगे। मेरे मामले में, मैं डीबी के लिए कनेक्शन स्ट्रिंग को संग्रहीत करने के लिए तुरंत रहस्य प्रबंधक का उपयोग करूंगा। इसका मतलब है, कि मुझे पहले स्टैक को तैनात करने की आवश्यकता है, ताकि मैं इसे स्थानीय विकास में उपयोग कर सकूं।
मैं मेक डिप्लॉय चलाता हूं लेकिन अभी मैं संपूर्ण परिनियोजन की जांच नहीं करता हूं, बस कंसोल से एक गुप्त नाम ले लेता हूं। मुझे कंसोल में रहस्य को अद्यतन करने की भी आवश्यकता है, ताकि इसमें सही कनेक्शन स्ट्रिंग बनी रहे।
परीक्षण के लिए, मैंने सुपाबेस पर एक डीबी बनाया है और इसे कुछ डमी रिकॉर्ड के साथ जोड़ा है
मेक लोकल चलाने के बाद मैं स्थानीय स्तर पर एपीआई का परीक्षण कर सकता हूं
चूंकि गो एक संकलित भाषा है, प्रत्येक परिवर्तन के बाद मुझे प्रोजेक्ट को फिर से बनाना होगा और स्टार्ट-एपीआई को फिर से चलाना होगा। गो कंपाइलर की अद्भुत गति को देखते हुए, यह कोई बड़ी बात नहीं है।
एपीआई गेटवे यूआरएल को तैनाती के बाद कंसोल में प्रिंट किया गया था, और इसे सीधे एडब्ल्यूएस कंसोल से भी लिया जा सकता है।
मैं समापन बिंदु पर कॉल करता हूं, और यह अपेक्षा के अनुरूप काम करता है:
कोल्ड स्टार्ट थोड़ा लंबा है, क्योंकि आरंभीकरण में ~300 एमएस लगते हैं, ज्यादातर इसलिए क्योंकि इसमें रहस्य लेना और डीबी से कनेक्शन स्थापित करना शामिल है। लेकिन ईमानदारी से कहूं तो यह एक अच्छे परिणाम से कहीं अधिक है।
दिया गया प्रोजेक्ट गो में सर्वर रहित REST API बनाने के लिए एक प्रारंभिक बिंदु है। यह स्कीमा के लिए ओपनएपीआई और तैनाती और स्थानीय परीक्षण के प्रबंधन के लिए एडब्ल्यूएस एसएएम का उपयोग करता है।
मैंने सीक्रेट मैनेजर से कनेक्शन स्ट्रिंग प्राप्त करने के लिए एक बाहरी पोस्टग्रेस डीबी और एडब्ल्यूएस एसडीके का उपयोग किया है।
लैम्ब्डा हैंडलर और आइटम सेवा के लिए यूनिट परीक्षण भी हैं
मैंने अधिकांश समय AWS भाग को कॉन्फ़िगर करने में बिताया है, जो सभी भाषाओं के लिए समान होगा। गो कोड बहुत सीधा है (इस सरल उपयोग के मामले के लिए)।
अस्वीकरण: उपलब्ध कराए गए सभी संसाधन आंशिक रूप से इंटरनेट से हैं। यदि आपके कॉपीराइट या अन्य अधिकारों और हितों का कोई उल्लंघन होता है, तो कृपया विस्तृत कारण बताएं और कॉपीराइट या अधिकारों और हितों का प्रमाण प्रदान करें और फिर इसे ईमेल पर भेजें: [email protected] हम इसे आपके लिए यथाशीघ्र संभालेंगे।
Copyright© 2022 湘ICP备2022001581号-3