Serverless, Typescript 與 Express

Steven Shen
Cubo AI
Published in
9 min readMar 25, 2019

一開始我們也跟很多在建構 API 的公司一樣,採用 Serverless 架構,希望可以透過 Serverless 自動擴展的方便性減少一開始開發時的 effort,這部份 Serverless 的確為我們這樣的新創公司在一開始帶來不少好處,開發快,不用太擔心 DevOps 繁瑣的事情,但是一路走來也踩了不少雷,慢慢地找到一個最好的運作方式。

Javascript => Typescript

我對 Javascript 沒有什麼意見,在新創公司一開始沒多少人力時,我喜歡用 Javascript 從後端寫到前端,他可以讓我在快速開發時因為較少的 Context switch,而有較好的效率。

但就像大多數 Node.js 開發人員一樣,太容易踩雷了,很多問題總是到了 production 後才爆出來,如果是一開始使用者還不多時,趕快修一修重新上線還可以解決,但是等到公司開始成長,使用者人數逐漸增加,每次上 Code 都會有一種心理壓力,心裡面總是會擔心是否哪裡有 Bug 沒測出來。Unit test 可以解決部分壓力,但總還是放不下心,因為畢竟要將 Unit Test 寫到完整得花不少時間。

後來我們將大部分的程式碼轉移到 Typescript,我認為 Typescript 帶來幾個優點:

  1. 需要先作編譯,可以先去除一些如 typo 或者其他預期外的錯誤
  2. 強型態檢查,可以事先檢查變數型態的正確性
  3. 物件導向,透過 Interface / Class 可以將複雜的程式碼有更好的維護方式
  4. 兼容性,Typescript 基本上是 Javascript 的 subset,從 Javascript 轉移過去非常快速,基本上可以說是無痛轉移。

從 Javascript 轉移到 Typescript 我們大概是分做兩階段,第一階段是先轉成 .ts 檔案,並修正部分因 typescript 而發生錯誤的地方,以及部份的語法 (通常是在 module 的處理上會有些不同) 。第一階段確認執行無誤後,第二階段再依據 Typescript 的特性去修改,例如強型態檢查與物件導向設計等等。

在 Serverless 部分我們使用 AWS Lambda 以及 serverless framework,這方面有些修改需要作才能讓 serverless 支援 Typescript:

  1. 安裝 `serverless-webpack` plugin (你會看到另外一個 serverless-plugin-typescript plugin,但如果你是一開始 Node.js 跟 Typescript 混用,我比較不建議使用這個)
  2. 在一個空的目錄下執行 `serverless create — template aws-nodejs-typescript`,這會以 aws-nodejs-typescript 為 template 產生一個空的專案,如果你是要開始一個新的專案可以使用這個方式,但如果你跟我們一樣,要從原有的專案轉去 Typescript,可以將產生出來的 `webpack.config.js` 與 tsconfig.js Copy 到你的專案目錄下
  3. 你的 serverless.yml 必需有底下的設定
plugins:
- serverless-webpack

你的 `webpack.config.js` 應該看起來如下

const path = require('path');
const slsw = require('serverless-webpack');
module.exports = {
mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
entry: slsw.lib.entries,
devtool: 'source-map',
resolve: {
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
},
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, '.webpack'),
filename: '[name].js',
},
target: 'node',
module: {
rules: [
// all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
{ test: /\.tsx?$/, loader: 'ts-loader' },
],
},
};

接下來就可以開始開發 Typescript 了,將檔案命名成 .ts 檔,然後可以透過 sls package 指令來測試是否可以將 typescript 正常打包。

Express

AWS Cloudformation 有個限制,也就是每次 resource 上限不得超過 200 個,不要以為看起來很多,但每個 API Gateway 的 function 會包含其他許多的 dependency ,加一加很容易就超過 200 的限制了,而 Serverless 是用 cloudformation 來作佈署。

網路上有個解法是中間加上一層 Node.js Web Framework 如 Express (或者 Koa),透過 Express 去作 routing,如此一來你在 API Gateway 中的 entry 就會比較少,甚至可以全部只有一個 entry,然後透過 Express 去 routing 就好。

當然,這麼做有優缺點,優點是程式碼跟 Lambda 的相依性比較低,可以隨時抽出來直接執行成 HTTP 伺服器,未來會比較有機會脫離 Lambda 採用其他系統,另外也可以解決 Cloudformation 200 個 resource 限制的問題。缺點是,沒辦法個別 function 去作部屬或者查 log,你每次 deploy 都必須整包去 deploy,所以還是要做好 Unit Test 。

至於程式就跟一般 Express 程式沒什麼兩樣

import serverless from 'serverless-http'
import bodyParser from 'body-parser'
import cors from 'cors'
import express from 'express'
import * as asyncHandler from 'express-async-handler'
const app = express()
app.use(bodyParser.json({ strict: false }))
app.use(cors())
app.get('/users', asyncHandler(get_users))
app.post('/user', asyncHandler(post_user))
export const handler = serverless(app, {
request: function(request, event, context) {
context.callbackWaitsForEmptyEventLoop = false
request.context = event.requestContext
}
})

不過比較特別的是,express 不能直接 export 給 serverless 使用,必須經過 `serverless-http` 包過一層才能使用。

另外如果要使用 async/await 的話,就要將 lambda 的 `callbackWaitsForEmptyEventLoop` 設為 false,這樣才不會在 lambda function return 後就直接結束了,而是等到所有 Promise 都結束才會跟著結束。

另外我也將 `event.requestContext` 指定給 request 物件,目的是要讓每個 function 去取得 Cognito 的 user 資訊。

然後因為會透過 express 去 route 路徑,所以在 serverless.yml 中允許所有可能的 Request 進到 express 中,如:

user:
handler: user.handler
events:
- http:
path: /user/{any+}
method: ANY
cors: true
authorizer: aws_iam
- http:
path: /users
method: ANY
cors: true
authorizer: aws_iam

Unit Tests

該做的單元測試還是要作,Typescript 的測試也不會不一樣,只是多了一些額外的設定就是了,關於 Typescript 如何作 Unit test,可以參考這篇文章:

最後

我們公司是一間新創公司,已經走過了辛苦的低谷,目前已經有不錯的成績,但是我們希望可以繼續往上前進,往更大的市場邁進,面對更大的挑戰,所以很希望跟更多的好手一起努力。

目前我們有在招募好幾個不同的職務,如果你有興趣都歡迎跟我聯繫:
Backend

Frontend

App / Web Designer

還有 AI 演算法工程師

你可以找我或者寄信至 jobs @ getcubo.com

--

--