Serverless Framework、Apex、SAMをちょっとだけさわってみて比較

# 背景

Serverless Framework、Apex、SAMなど、Lambdaを管理できるツールはたくさんあります。
どれがどんなものなのかイマイチ分からなかったため、Hello World程度動かしながら比較しました。

# 環境
- Mac OS X
- Serverless Framework
    - 1.35.0
- Apex
    - 1.0.0-rc2
- SAM CLI
    - 0.9.0

# Hello Worldしてみる

## Serverless Framework

### インストール

```bash
$ npm install -g serverless
```

AWSのクレデンシャルはServerless FrameworkでもApexでもSAMでも、~/.aws/credentials を読み込んだりしてくれるとのことです。

### プロジェクト作成

`sls create`コマンドにより、最小限のテンプレートが生成されます。

```bash
$ sls create -t aws-nodejs -p my-service
$ ls my-service
handler.js     serverless.yml
```

### serverless.yml

serverless.yamlに環境やイベントが定義できます。
Lambda以外のリソースはCloudFormationの形式で定義するとのことです。

```serverless.yml
    :
service: my-service # NOTE: update this with your service name
    :
provider:
  name: aws
  runtime: nodejs8.10
    :
# you can overwrite defaults here
#  stage: dev
#  region: us-east-1
    :
# you can add statements to the Lambda function's IAM Role here
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
    :
# you can define service wide environment variables here
#  environment:
#    variable1: value1
    :
# you can add packaging information here
#package:
#  include:
#    - include-me.js
#    - include-me-dir/**
#  exclude:
#    - exclude-me.js
#    - exclude-me-dir/**
    :
functions:
  hello:
    handler: handler.hello
    :
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
#    events:
#      - http:
#          path: users/create
#          method: get
#      - s3: ${env:BUCKET}
    :
#    Define function environment variables here
#    environment:
#      variable2: value2
    :
# you can add CloudFormation resource templates here
#resources:
#  Resources:
    :
```

### サービスと関数のデプロイ

`sls deploy`で、CloudFormationのStackが作成されます。

```bash
$ cd my-service
$ sls deploy -v
$ sls deploy function -f hello
```

### 関数の実行

`sls invoke`で関数を実行できます。

```bash
$ sls invoke -f hello
{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"
}
```

標準入力などからデータを渡すこともできます。

### プロジェクト削除

```bash
$ sls remove
```

全てCloudFormationで構築されているため、きれいに削除されます。
実行時に「ほんとに消していいの?」みたいな確認はありませんでした。

## Apex

### インストール

```bash
$ curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh
```

内部でGitHub APIを利用しているため、GitHub APIの制限による403 Forbiddenで何度かインストールが失敗しました。
その場合、時間を置いて再度インストールを試みましょう。

### プロジェクト作成

`apex init`でテンプレートが生成されます。
Serverless FrameworkやSAMと異なり、カレントディレクトリに生成されるので注意が必要です。

```bash
$ apex init
$ tree
.
├── functions
│   └── hello
│       └── index.js
└── project.json

2 directories, 2 files
```

functions 以下にディレクトリを追加すると、自動で新たな関数として認識してくれます。

### project.json

Serverless FrameworkやSAMと異なりCloudFormationを利用していないため、project.jsonにIAM RoleのARNが記載されます。
publicなリポジトリにコミットするにはちょっと嫌な感じです。

```project.json
{
  "name": "sample",
  "description": "",
  "memory": 128,
  "timeout": 5,
  "role": "arn:aws:iam::xxx:role/sample_lambda_function",
  "environment": {}
}
```

Lambda以外のリソースは `apex infra` というコマンドで扱います。
`apex infra` はTerraformのラッパで、リソースをTerraformのテンプレートで定義することになります。

### 関数のデプロイ

```bash
$ apex deploy
```

めちゃくちゃ簡単です。

### 関数の実行

```bash
$ apex invoke hello
{"hello":"world"}
```

### プロジェクト削除

```bash
$ apex delete
```

Lambda関数は消えていましたが、IAM RoleやCloudWatch Logsのロググループは消えませんでした。。。
何に紐づいているか分からないIAM Roleやロググループが残るのは個人的にはあまり好きでないです。


## SAM

### インストール

[Installing AWS SAM CLI on macOS](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install-mac.html) の通りの手順でインストールします。

```bash
$ brew tap aws/tap
$ brew install aws-sam-cli
```

### プロジェクト作成

```bash
$ sam init --runtime nodejs8.10
$ tree sam-app
sam-app
├── README.md
├── hello-world
│   ├── app.js
│   ├── package.json
│   └── tests
│       └── unit
│           └── test-handler.js
└── template.yaml

3 directories, 6 files
```

sam-appディレクトリ以下にテンプレートが生成されます。

### template.yaml

template.yamlは、CloudFormationテンプレートを生成するもとになるようです。
インデントにかなり違和感を感じます。

```template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
    sam-app

    Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
    Function:
        Timeout: 3


Resources:

    HelloWorldFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
            CodeUri: hello-world/
            Handler: app.lambdaHandler
            Runtime: nodejs8.10
            Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
                Variables:
                    PARAM1: VALUE
            Events:
                HelloWorld:
                    Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
                    Properties:
                        Path: /hello
                        Method: get

Outputs:

    HelloWorldApi:
      Description: "API Gateway endpoint URL for Prod stage for Hello World function"
      Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

    HelloWorldFunction:
      Description: "Hello World Lambda Function ARN"
      Value: !GetAtt HelloWorldFunction.Arn

    HelloWorldFunctionIamRole:
      Description: "Implicit IAM Role created for Hello World function"
      Value: !GetAtt HelloWorldFunctionRole.Arn
```

### デプロイの準備

node_modulesなどの依存関係をインストールします。

```bash
$ sam build --use-container
```

コードを保存するS3バケットを作成します。

```bash
$ aws s3 mb s3://bucketname
```

デプロイのため、CloudFormationのテンプレートを生成します。

```bash
$ sam package \
    --output-template-file packaged.yaml \
    --s3-bucket bucketname
```

### 関数のデプロイ

```bash
$ sam deploy \
    --template-file packaged.yaml \
    --stack-name sam-app \
    --capabilities CAPABILITY_IAM \
    --region us-east-1
```

### 関数の実行

マネジメントコンソールからCloudFormationの出力を確認してURLにアクセスします。

```
{"message":"hello world","location":"xxx.xxx.xxx.xxx"}
```

### ローカルでのテスト

チュートリアルに関数のテストについて書かれていたので、試してみました。

```bash
$ sam local start-api
```

この状態で http://localhost:3000/hello を叩くと、ローカルで関数が実行されます。
このタイミングでDockerイメージがダウンロードされて起動するので、コンソールを見ているとカッコいいです。

ホットデプロイされるらしく、開発時は嬉しいかもしれません。

ちなみに、Serverless FrameworkやApexのドキュメントには、ビジネスロジックを分離することで単体テスト可能にしろと書かれています。
特にApexはローカルでテストすることを想定していないようです。

# 分かった特徴

## Serverless Framework
- AWS以外にも対応している
- CloudFormationで全て管理される
- シンプルに使えるが、がっつり使っていくこともできそうな気がする
- なんとなくよく聞くし、評価高い気がする (ソースはない)

## Apex
- AWS Lambdaに特化したフレームワーク
- リソースがあまり管理されておらず、`apex delete`しても一部のリソースは残ってしまう
- Lambda以外のリソースをデプロイするには、Terraformテンプレートを利用する
- テストをローカルで行うことは想定していない
- シンプルだけどがっつり使うと辛いかも (個人的な感想)

## SAM
- AWS公式のフレームワーク
- コードを保存するS3以外のリソースはCloudFormationで管理される
- ローカルでAPIとしてテスト可能
- デプロイまでの手順が多い

# 自分なりの結論

Apexはリソースを管理しきれていないこと、SAMはデプロイまでの手順が多いと感じたことから、この3つの中ではServerless Frameworkが気に入りました。
どうせ勉強するならAWSに限定されないツールがいいという気持ちもあります。~~だったらそもそもそういうツールに限定して比較するべき~~

# 参考
- [AWSでServerlessの環境をCIするための選択肢を調べたメモ](https://qiita.com/yoskeoka/items/7494c029d08076ea8f58)
- [Serverless Framework - Documentation](https://serverless.com/framework/docs/)
- [Apex](http://apex.run/)
- [SAM - Quick Start](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-quick-start.html)