Như ở bài viết trước chúng ta đã tạo được một ứng dụng serverless có thể deploy lên hệ sinh thái aws, thực hiện phát triển ở mội trường local. Show Với cấu trúc code như đã viết trong bài trước, mỗi một chức năng sẽ được xử lý bởi một hàm và được trỏ bởi một api endpoind. functions: createCat: handler: handler.createCat events: - http: path: cats method: POST findCatById: handler: handler.findCatById events: - http: path: cats/:id method: GETCấu trúc code theo hướng này được gọi là Microservices Pattern theo serverless framework. Kiểu này có một nhược điểm lớn: Giới hạn kích thước của CloudFormation template (khoảng 200 resources), ứng dụng của chúng ta sẽ có giới hạn về số lượng các chức năng. Ví dụ: Một chức năng createCat chúng ta sẽ cần ít nhất 4 resource: 1 AWS::Lambda::Function + 1 AWS::Lambda::Permission(ApiGateway) + 1 AWS::Lambda::Version + 1 AWS::Logs::LogGroup Mỗi bảng dynamodb chúng ta mất một tài nguyên(dynamodb chỉ định nghĩa bảng không định nghĩa database (??)) Mình sẽ viết một bài viết khác để chia sẻ về các kiểu cấu trúc code cho một dự án theo hướng serverless. Serverless có vẻ còn khá mới, để phát triển một ứng dụng với thời gian nhanh nhất có thể hoặc tất cả các members trong team có thể tham gia phát triền với mô hình serverless mà không cần quá nhiều thời gian tìm hiểu, chúng ta sẽ sử dụng những cấu trúc cũ để giải quyết vấn đề. Monolithic Pattern một function giải quyết tất cả tính năng (khoc2) (tất nhiên hướng này cũng có những nhược điểm nhất định). Do mục đích cuối cùng là xây dựng một ứng dụng cung cấp API chuẩn RESTFul chúng ta sẽ nghĩ tới việc sử dụng express framework để tạo ra một ứng dụng như vậy. Việc chuyển đổi từ một trigger event của Aws Lambda Function sang request theo express các bạn có thể đọc bài viết này https://viblo.asia/p/expressjs-style-flow-for-aws-lambda-vyDZOXa9lwj của tác giải @Kinyakin (bow). Còn trong bài viết này chúng ta sẽ sử dụng package serverless-http để làm việc đó (yaoming). Xây dựng ứng dụng với Express bằng TypescriptCấu trúc thư mụcChúng ta sẽ vẫn dùng thư mục của bài viết trước. Chúng ta sẽ chia thư mục giống thế này. src ----modules // các thư mục tính năng theo đối tượng --------cats // code của module `cats` ------------dto // các Data Transfer Objects của cats module ------------interfaces // các Interface của cats module ------------repositories // các repo của cats module ------------cats.controller.ts // file chứa nội dung router của cats module ----shared // các thư viện dùng chung cho cả app ----App.ts // class mô tả ứng dụng express ----handler.ts // file chứa hàm handler của serverless (từ bài trước) ----localServer.ts // file để thực hiện chạy ứng dụng theo cách `thông thường`, không qua `serverless-offline` index.js // hỗ trợ chạy ứng dụng theo cách thông thường mà không cần build typescriptNhững file khác vẫn giữa như bài viết trước. Hiện thực các file chức năngMình sẽ không nêu các packages sẽ phải thêm, chúng ta thấy cần thư viện nào thì sẽ thêm thư viện đó (khoc2) Main app và helpersrc/App.js file này export ra một instance ứng dụng express. Class App mô tả một ứng dụng express,
src/shared/dynamoDbConnection.ts thực hiện việc kết nối và thư thi trên cơ sở dữ liệu Dynamodb
Cats moduleCác file trong phần này sẽ được tạo trong thư mục src/modules/cats dto/create-cat.dto.ts mô tả class dữ liệu để tạo mới một con mèo, sử dụng package class-validator để định nghĩa và validate dữ liệu. import {IsDefined, MaxLength, MinLength} from 'class-validator'; class CreateCatDto { @MinLength(3, { message: 'name is too short', }) @MaxLength(50, { message: 'name is to long', }) public name: string; @IsDefined({ message: 'mood should be defined', }) public mood: string; constructor(name: string, mood: string) { this.name = name; this.mood = mood; } } export default CreateCatDto;interfaces/cat.interface.ts Mô tả interface một đối tượng m èo interface ICat { id: string; name: string; mood: string; } export default ICat;repositories/cats.repo.ts mô tả class CatsRepo – làm việc với bảng cats
cats.controller.ts mô tả class CatsController – mapping url với phương thức tương ứng
Cấu hình ứng dụng chạy localVì được viết bằng express framework nên chúng ta có thể thực hiện chạy và test như cách thông thường mà không cần sự hỗ trợ của package serverless-offline. Chúng ta đã có một đối tượng App là một instance của express app, chúng ta sẽ tiến hành khởi tạo http server theo express instance này src/localServer.ts require('dotenv').config(); import * as http from 'http'; import App from './App'; const port = normalizePort(process.env.PORT || 3000); App.set('port', port); const server = http.createServer(App); server.listen(port); server.on('error', onError); server.on('listening', onListening); function normalizePort(val: number | string): number | string | boolean { let port: number = (typeof val === 'string') ? parseInt(val, 10) : val; if (isNaN(port)) { return val; } else if (port >= 0) { return port; } else { return false; } } function onError(error: NodeJS.ErrnoException): void { if (error.syscall !== 'listen') { throw error; } const bind = (typeof port === 'string') ? 'Pipe ' + port : 'Port ' + port; switch (error.code) { case 'EACCES': console.error(`${bind} requires elevated privileges`); process.exit(1); break; case 'EADDRINUSE': console.error(`${bind} is already in use`); process.exit(1); break; default: throw error; } } function onListening(): void { const addr = server.address(); const bind = (typeof addr === 'string') ? `pipe ${addr}` : `port ${addr.port}`; console.log(`Listening on ${bind}`); }Tới đây chúng ta đã có thể khởi chạy server bằng cách chạy các lệnh tsc -p tsconfig.json — để compile ts file sang file js node src/localServer.js — khởi động ứng dụng Chúng ta sẽ có một http server chạy trên cổng process.env.PORT || 3000 Việc compile mất khá nhiều thời gian nên chúng ta sẽ dùng thêm một file index.js ở thư mục root của project, sử dụng package ts-node/register để tạo ra môi trường thực thi Typescript. require('ts-node/register'); require('./src/localServer');!!! Vì ứng dụng không chạy bằng serverless, nên chúng ta phải cài đặt dynamodb trên máy local. Có thể dùng docker hoặc cài đặt theo hướng dẫn từ AWS https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html Cấu hình serverless handle function với instance của expressChúng ta sẽ sửa lại file src/handler.ts, các trigger request của Lambda funtion sẽ được convert sang http request (trigger function phải là API Gateway request) bằng package serverless-http import serverless = require('serverless-http'); import App from './App'; module.exports.handler = serverless(App);Và thay đổi nội dung file serverless.yml, chỉ còn 1 function, toàn bộ các http request sẽ được xử lý bởi function này service: serverless-typescript-express provider: name: aws runtime: nodejs6.10 stage: dev plugins: - serverless-webpack - serverless-dynamodb-local - serverless-offline custom: dynamodb: start: port: 8000 inMemory: true migrate: true seed: true # Uncomment only if you already have a DynamoDB running locally noStart: true functions: app: handler: handler.handler events: - http: ANY / - http: 'ANY {proxy+}' resources: Resources: usersTable: Type: AWS::DynamoDB::Table Properties: TableName: cats AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1Như vậy là hoàn thành ứng dụng serverless theo express style. Kết luậnBằng cấu trúc này chúng ta có thể chuyển một ứng dụng express cũ sang dạng serverless, hoặc phát triển một ứng dụng mới theo hướng serverless sử dụng hệ sinh thái Aws một cách dễ dàng. Chúng ta sẽ gặp nhau ở các bài tiếp theo xoay quanh chủ đề serverless. Bài viết được public bởi cùng tác giả tại trang Viblo. |