serverless
ServerlessFramework

Serverless Componentsとは何か?サーバーレスなアプリケーションの何を解決するのか

serverless-components.gif

Serverless.inc社より、Serverless Componentsという新しいOSSプロダクトが公開されました。
これがどういったものかというと、Serverless Frameworkは基本的にfunctionとeventは簡単に管理できますが、それ以外のDynamoDBやS3といった必要なCloud上のリソースを定義するのはまだまだ非常に大変です。それはResourcesセクションに生のCloudFormationを定義しないといけなからというのが大きな理由です。

Serverless Componentsはそれを解決します。

componentsという単位でCloud上のリソースを簡単に定義して再利用可能なものにします。これはAWSだけ対応しているのではなく、その他のCloudやNetlifyやGitHubといったSaaSサービスもcomponentという単位で定義出来ることがポイントです。
これは、サーバーレスアプリケーションがこれからはAWS上のものだけではなく、様々なSaaSやFunctionを組み合わせて定義することが必要になり、それは非常に複雑なものになることに対する解決策になるという意味です。

使い方

Serverless ComponentsはCLIベースのアプリケーションとして公開されています。
以下がサンプルアプリケーションの設定例です。serverless.ymlファイルにcomponentsというセクションを定義してその下にcomponentをそれぞれ設定していきます。現状対応しているcomponentsはcomponents registryで定義されているものがすべてになります。

serverless.yml
type: retail-app
version: 0.0.1

components:
  webFrontend:
    type: static-website
    inputs:
      name: retail-frontend
      contentPath: ${self.path}/frontend
      templateValues:
        apiUrl: ${productsApi.url}
      contentIndex: index.html
      contentError: error.html
      hostingRegion: us-east-1
      hostingDomain: retail-${self.serviceId}.example.com
      aliasDomain: www.retail-${self.serviceId}.example.com
  createProduct:
    type: aws-lambda
    inputs:
      memory: 512
      timeout: 10
      handler: products.create
      root: ${self.path}/code
      env:
        productTableName: products-${self.serviceId}
  getProduct:
    type: aws-lambda
    inputs:
      memory: 512
      timeout: 10
      handler: products.get
      root: ${self.path}/code
      env:
        productTableName: products-${self.serviceId}
  listProducts:
    type: aws-lambda
    inputs:
      memory: 512
      timeout: 10
      handler: products.list
      root: ${self.path}/code
      env:
        productTableName: products-${self.serviceId}
  productsApi:
    type: rest-api
    inputs:
      gateway: aws-apigateway
      routes:
        /products: # routes begin with a slash
          post: # HTTP method names are used to attach handlers
            function: ${createProduct}
            cors: true

          # sub-routes can be declared hierarchically
          /{id}: # path parameters use curly braces
            get:
              function: ${getProduct}
              cors: true # CORS can be allowed with this flag

        # multi-segment routes can be declared all at once
        /catalog/{...categories}: # catch-all path parameters use ellipses
          get:
            function: ${listProducts}
            cors: true
  productsDb:
    type: aws-dynamodb
    inputs:
      region: us-east-1
      tables:
        - name: products-${self.serviceId}
          hashKey: id
          indexes:
            - name: ProductIdIndex
              type: global
              hashKey: id
          schema:
            id: number
            name: string
            description: string
            price: number
          options:
            timestamps: true

そして、以下のコマンドで操作を行います。

$ components deploy # 定義したcomponentsのデプロイ
$ components info # デプロイ済みのcomponents情報の確認
$ components remove # デプロイ済みのcomponentsの削除

サンプルアプリケーション

まずはをサンプルアプリケーション立ち上げてcomponentsを体験してみましょう。

以下のコマンドでインストールしましょう。

$ npm install --global serverless-components

そして環境変数にAWSのアクセスキーを設定してください

export AWS_ACCESS_KEY_ID=my_access_key_id
export AWS_SECRET_ACCESS_KEY=my_secret_access_key

componentsのサンプルアプリケーションをgit cloneします

$ git clone https://github.com/serverless/components.git
$ cd components/examples/retail-app

最後にデプロイします。

$ components deploy
Creating Bucket: 'retail-gebw40fi7i.example.com'
Creating Bucket: 'www.retail-gebw40fi7i.example.com'
Creating Role: func-gebw40fi7i-3naoitg1-execution-role
Creating Role: func-gebw40fi7i-d2vuqltm-execution-role
Creating Role: func-gebw40fi7i-rw1u3wpq-execution-role
Created table: 'products-gebw40fi7i'
Seeding 3 items into table products-gebw40fi7i.
Creating Lambda: func-gebw40fi7i-d2vuqltm
Creating Lambda: func-gebw40fi7i-3naoitg1
Creating Lambda: func-gebw40fi7i-rw1u3wpq
Item inserted to table: 'products-gebw40fi7i'
{"id":22,"name":"Model A+","description":"A precision-milled, highly durable enhancement of the Model A for performance applications.","price":8.99,"createdAt":"2018-05-03T10:12:58.142Z"}
Item inserted to table: 'products-gebw40fi7i'
{"id":21,"name":"Model B","description":"A cost-reduced version of our classic offering, providing the highest value.","price":4.99,"createdAt":"2018-05-03T10:12:58.126Z"}
Item inserted to table: 'products-gebw40fi7i'
{"id":20,"name":"Model A","description":"Our standard, highly reliable part.","price":6.99,"createdAt":"2018-05-03T10:12:58.158Z"}
Setting policy for bucket: 'retail-gebw40fi7i.example.com'
Setting website configuration for Bucket: 'retail-gebw40fi7i.example.com'
Creating Role: api-gebw40fi7i-plfr8qus-iam-role-gebw40fi7i-plfr8qus
Setting redirection for Bucket: 'www.retail-gebw40fi7i.example.com'
Set policy and CORS for bucket 'retail-gebw40fi7i.example.com'
Creating API Gateway: "api-gebw40fi7i-plfr8qus"
Creating Site: 'retail-frontend'
Syncing files from '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A' to bucket: 'retail-gebw40fi7i.example.com'
REST API resources:
  POST - https://ob7r8ppmgk.execute-api.us-east-1.amazonaws.com/dev/products
  GET - https://ob7r8ppmgk.execute-api.us-east-1.amazonaws.com/dev/products/{id}
  GET - https://ob7r8ppmgk.execute-api.us-east-1.amazonaws.com/dev/catalog/{...categories}
Static Website resources:
  http://retail-gebw40fi7i.example.com.s3-website-us-east-1.amazonaws.com
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-60x60.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/android-icon-96x96.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-180x180.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/android-icon-48x48.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/android-icon-72x72.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/android-icon-144x144.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-114x114.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-precomposed.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/android-icon-36x36.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-152x152.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/favicon-16x16.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-76x76.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/android-icon-192x192.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-120x120.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-72x72.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-57x57.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/browserconfig.xml' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/apple-icon-144x144.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/favicon-32x32.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/favicon-96x96.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/ms-icon-144x144.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/error.html' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/favicon.ico' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/manifest.json' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/ms-icon-150x150.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/ms-icon-70x70.png' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/fonts/serverless-regular.woff2' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/index.html' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/fonts/serverless-regular.woff' ...
Uploading file: '/var/folders/w4/nnwlqs093jlcg6sn2t2t2_2r0000gn/T/tmp-44738fLC7PSPBze0A/assets/favicons/ms-icon-310x310.png' ...
Objects Found: 0 , Files Found: 31 , Files Deleted: 0

これでWebSiteやREST APIのエンドポイントにアクセスするとちゃんとデプロイされていることが確認されているはずです。

Serverless Frameworkとの統合

将来的にはServerless Frameworkとの統合も計画されています。そうすると以下のようにfunctionsとeventsとcomponentsが上手く定義できるようになるでしょう。

service: my-service

provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1

functions:
  myFunction:
    handler: handler.myFunction
    events:
      - http:
          path: api/public
          method: post
    environment:
      database: ${components.myDatabase}

components:
  myDatabase:
    type: aws-dynamodb
    inputs:
      region: us-east-1
      tables:
        - name: myTable
          hashKey: id
          schema:
            id: number
            foo: string
provider:
  name: aws
  runtime: nodejs6.10
  region: us-east-1

functions:
  ...

components:
  myDatabase:
    type: aws-dynamodb
    inputs:
      ...
  myTwilio:
    type: twilio-webhook
    inputs:
      ...
  myGoogleCloudVisionApi:
    ...
  myAuth0:
    ...

特にFrameworkはAWS以外のproviderのfunctionとeventも定義できます。GCPやAzureのfunctionとeventを定義してそれをAWSやNetlifyといったコンポーネントとも上手くインテグレーションが出来る様になるということです。これはかなりサーバレスというもののやれることや可能性が現実的になるのではなるのでないでしょうか。

関連記事