lamproxとlambda-utilities

AWS Lambdaのアプリケーション開発で利用している自作のフレームワークがそれなりにこなれてきたので、README.mdの日本語訳をのせて紹介します。

Lamprox

AWS Lambdaのlambda-proxy機能を使った開発のためのフレームワークです。

セットアップ

$ npm install lamprox

概念と使用方法

Lamproxは、AWS Lambdaのlambda-proxy機能のための最小限かつ柔軟なフレームワークです。
AWS Lambdaを使用して複数のエンドポイントを構築する場合の、認証、応答処理、およびエラー処理のための処理を、横断的に適用することができます。

Process

Lamproxはハンドラを複数のProcessで定義します。
Processは以下に示すような関数です。

interface Process<T, U, E> {
  (ambience: ProcessAmbience<T, E>): Promise<U>
}

interface ProcessAmbience<T, E> {
  /** Variables that pssed lambda function. */
  lambda: {
    event: APIGatewayEvent
    context: Context
    callback: ProxyCallback
  }
  /** Result that preceding process. */
  result: T
  /** Shared variables accross that processes. */
  environments: E
}

Processor

Processorは複数のプロセスを保持して順番に実行するクラスです。
Processorはbefore、main、after、response、onErrorの各プロセスを保持し、ハンドラとして実行します。

/** Preparing before main process. */
type BeforeProcess<T, E> = Process<void, T, E>
/** Main process for request. */
type MainProcess<T, U, E> = Process<T, U, E>
/** After process. */
type AfterProcess<U, E> = Process<U, U, E>
/** Process that creating proxy result. */
type ResponseProcess<U, E> = Process<U, ProxyResult, E>
/** Process that called when error occured. */
type OnErrorProcess<E> = Process<Error, ProxyResult, E>

interface IProcessor<T, U, E> {
  before: BeforeProcess<T, E>
  main: MainProcess<T, U, E>
  after: AfterProcess<U, E>
  response: ResponseProcess<U, E>
  onError: OnErrorProcess<E>

  toHandler: () => LambdaProxyHandler
}

関数群

一般に、Processorを直接生成する必要はありません。
Lamproxは、ハンドラを作成するためのいくつかの関数を提供します。

lamprox()

単純なラムダプロキシハンドラを作成します。
レスポンスボディを生成するメソッドを書くだけで、lambda-proxyのハンドラを作成できます。

lamprox: <U>(main: MainProcess<void, U, void>) => LambdaProxyHandler

buildHandler()

各種プロセス - befire、after、response、onError - とEnviromentsでラムダ関数を作成します。
Enviromentsはプロセス間で共有される値です。

namespace IProcessor {
  interface Params<T, U, E> {
    main: MainProcess<T, U, E>,
    environments: E,
    before?: BeforeProcess<T, E>
    after?: AfterProcess<U, E>
    response?: ResponseProcess<U, E>
    onError?: OnErrorProcess<E>
  }
}

buildHandler: <T, U, E>(params: IProcessor.Params<T, U, E>) => LambdaProxyHandler

prepareHandlerBuilder()

prepareHandlerBuilder()は、buildHandler関数を生成するための関数です。
多くのラムダ関数があると場合に、共通のプロセスを定義されたbuildHandler関数を生成することができます。

namespace PrepareHandlerBuilder {
  interface Params<T, U, E> {
    before?: BeforeProcess<T, E>
    after?: AfterProcess<U, E>
    response?: ResponseProcess<U, E>
    onError?: OnErrorProcess<E>
  }
}

interface BuildHandler<T, U, E> {
  (params: IProcessor.Params<T, U, E>): LambdaProxyHandler
}

prepareHandlerBuilder: <T, U, E>(preparedParams?: PrepareHandlerBuilder.Params<T, U, E>) => BuildHandler<T, U, E>

ユーティリティ

Lamproxにはnode-lambda-utilitiesが含まれていますが、lambda-proxyのためのいくつかのユーティリティ関数が用意されています。

generateDummyAPIGatewayEvent()

これは、ダミーのAPIGatewayEventを生成するための関数です。
node-lambda-utilitiesのinvokeHandler()と一緒に使用してハンドラをテストすることができます。

generateDummyAPIGatewayEvent: (params?: GenerateDummyAPIGatewayEvent.Params) => APIGatewayEvent

generateProcessAmbience()

Process関数実行時の引数となるProcessAmbienceを生成するための関数です。
これを使用すると、Processそれぞれをテストすることができます。

generateProcessAmbience: <T, E>(params: GenerateProcessAmbience.Params<T, E>) => ProcessAmbience<T, E>

lambda-utilities

AWS Lambdaのユーティリティ関数と型定義

インストール

$ npm isntall lambda-utilities

使用方法

型定義

lambda-utilities@types/aws-lambdaが提供するLambdaの基本な型定義を含みます。
以下のリストは、@types/aws-lambdaで提供されている型定義の一部です。

  • API Gatewayのイベントと応答
  • API Gateway CustomAuthorizerのイベントと応答
  • SNSイベント
  • S3作成時のイベント
  • Cognito User Poolイベント
  • CloudFormation Custom Resourceイベントとレスポンス
  • Context

lambda-utilities@types/aws-lambdaが提供していないDynamoDB Streamsイベントの型定義を提供します。
さらに、lambda-utilitiesは独自のイベントハンドラ用の汎用インタフェースを提供します。

interface Handler<Event, Callback> extends Function {
  (event: Event, context: Context, callback: Callback): void
}

ユーティリティ

Callback

SinonExpectationインタフェースを実装しているCallbackのモックを提供します。
引数callbackcallsFakeで実行されます。

generateMockCallback: (callback?: Callback) => MockCallback

Context

Contextのモックを提供します。
done、failとsucceedは、SinonExpectationインタフェースを実装しています。 パラメータとして関数を設定すると、その関数はcallsFakeで実行されます。

generateMockContext: (params?: GenerateMockContext.Params) => MockContext

export namespace GenerateMockContext {
  export interface Params {
    callbackWaitsForEmptyEventLoop?: boolean
    functionName?: string
    functionVersion?: string
    invokedFunctionArn?: string
    memoryLimitInMB?: number
    awsRequestId?: string
    logGroupName?: string
    logStreamName?: string,
    identity?: CognitoIdentity,

    getRemainingTimeInMillis?: () => number

    done?: (error: any, result: any) => void,
    fail?: (arg0: any) => void,
    succeed?: (arg0: any, arg1?: any) => void
  }
}

InvokeHandler

Lambdaのハンドラを実行する関数です。
モックと組み合わせてテストで使用できます。

const handler: Handler<TestEvet, TestCallback> = (event, context, callback) => {
  setTimeout(() => {
    callback(undefined, { foo: event.foo * 2 })
  }, 1000)
}

const callback = generateMockCallback((error, result) => {
  callback.once()
  assert.equal(result.foo, 42)
  assert.ok(callback.verify())
  done()
})

invokeHandler(handler, {
  event: { foo: 21 },
  callback: callback
})

serverless-import-swaggerの紹介

新規サービス開発のプロジェクトの中で、Serverless FrameworkSwagger形式のAPI定義をインポートする、AKIRA-MIYAKE/serverless-import-swaggerを開発したので、その紹介を行います。

開発の動機

開発中のサービスは、Angular2のSPAをS3に配置してCloudFrontで配信、データ取得などのためのAPIAWS Lambda + API Gatewayで実装、というサーバレスアーキテクチャを採用しています。
フロントエンドとバックエンドの開発を並行してスムーズに行うことができるように、Swagger形式でAPIを定義しそれに基づいて実装するようにしています。

AWS LambdaへのデプロイやAPI Gatewayとの紐付けをを手作業で行うのは考えられないので、Serverless Frameworkを用いてイベントや権限を定義、CIでの自動デプロイの環境を整えています。
Serverlessでは、このような感じYAML形式で関数を定義します。
基本的にSwaggerで定義したAPIのメソッドがLambda関数に対応します。Swaggerの定義からServerlessの関数定義を作れたら嬉しいですよね。
でもそんなプラグインはないらしい…ということで開発しました。

開発の上での考え方

Serverlessにはプラグインを追加できる仕組みがありますが、独立したCLIツールとしています。作り始めた時点では、Serverlessのバージョンが0.5から1.0に変わったすぐあとぐらいで、変更が生じる可能性が高かったこと、ビルドやデプロイのプロセスに処理を追加するわけではなかったためです。
他には以下の点を実現できるようにしています。

  • providerなどの共通項目を別途定義して読み込めること
  • Swaggerのタグを用いて任意にサービスを分割できること
  • 生成されるサービスにプレフィックスを付与できること
  • 関数名がAPIに基づいて自動生成されること
  • 手動で追加した項目が再度実行時に消えないこと

基本的な使い方

  1. npm install serverless-import-swaggerでインストールします。
  2. Swagger定義ファイルの取り込む対象のメソッドにタグを追加します。
  3. Swagger定義ファイルをプロジェクトに配置します。
  4. providerなどの共通項目を定義したYAMLファイルを用意します。
  5. sisコマンドを実行することでインポートが実行されます。Serverlessのサービスが存在しない場合は自動的に作成されます。

オプションは以下の通りです。

# 共通オプション
-i, --input <path>             インポートするSwagger.yamlのファイルを指定します。 (デフォルトは "./swagger.ya?ml" です)
-c, --common <path>            共通項目を定義したYAMLファイルを指定します。 (デフォルトは "./serverless.common.ya?ml" です)
-o, --out-dir <path>           Sserveressサービスの出力先を指定します。 (デフォルトは "./" です)
-f, --force                    このオプションを追加すると、新しい出力で既存の内容を完全に上書きします。

# タグやサービスのプレフィックスに関するオプション
-A, --api-prefix <prefix>      Swaggerのタグにつけるプレフィックスです。 (デフォルトは "sls" です)
-S, --service-prefix <prefix>  出力されるサービスに付与されるプレフィックスです。 (デフォルトはなしです)

# ベースパスの扱いに関するオプション
-B, --base-path                このオプションを追加すると、httpイベントのパスからベースパスが取り除かれます。

# CORSに関するオプション
-C, --cors                     このオプションを追加すると、httpイベントに`cors=true`が追加されます。
-O, --options-method           このオプションを追加すると、GETメソッドの関数には`cors=true`がそれ以外のメソッドを含むパスにはoptionsメソッドの関数が追加されます。

サービス分割時のベースパスについて

デフォルトでは、Swaggerのタグに基づいてサービスが分割され、APIのパスがそのままインポートされます。
CloudFormationの制約のため、API数が一定規模を超える場合サービスを分割する必要がありますが、API Gatewayでカスタムドメインに複数のAPIに紐づける場合、ベースパスの指定が必要となります。
そのため、Swaggerで/foo/barと定義したAPIがカスタムドメインからアクセスすると/{base-path}/foo/barとなってしまい、定義と異なってしまいます。

-B, --base-pathオプションを追加することで、Serverlessのイベントのパスからベースパスを除去するようになります。
先ほどの例だと、serverless.ymlでは/fooが取り除かれて/barとなり、API Gateway/fooのベースパスでマッピングすることで、Swaggerの定義と一致させることができます。
ただし、この機能を使うと、ベースパスを基準にしたサービスの分割を強制されることとなります。

CORSについて

-C, --corsオプションをつけるとAPI GatewayのデフォルトのCORSのためのOPTIONSメソッドが用意されます。単純なAPIアクセスを行う場合であれば、このオプションだけで問題はないと思います。
ただし、Cookieや独自ヘッダを用いた通信を行う場合、Access-Control-Allow-Originワイルドカードを指定することができないため、対象のレスポンスを返す関数だけでなく、preflight requestで呼び出されるOPTIONSも独自に定義する必要があります。
-O, --options-methodオプションを付与すると、SwaggerのAPIのパスの中にGET以外のメソッドが定義されている場合、自動でそのパスのOPTIONSメソッドに対応する関数を追加します。
追加された関数でOPTIONSのヘッダを独自に定義することで、Cookieや独自ヘッダを用いたCORSを実現することができます。


ベースパスやCORSの部分は現在のプロジェクトやAWSの制約によって用意したものなので、もう少しうまい解決方法があるかもしれませんが…。
開発中のサービスがリリースされ、落ち着いた頃に整理ができればと思います。

AWSでサーバレスアーキテクチャで開発する際のいくつかの制約について

現在開発中の新規サービスは、AWSを利用したサーバレスアーキテクチャを採用しています。
Angular2のSPAをS3に配置してCloudFrontで配信、データ取得などのためのAPIAWS Lambda + API Gatewayで実装する、教科書通りの構成をとっています。
そこまで大規模なサービスではないのですが、現時点でのAPI数は約100弱。それを手作業で管理するのはよろしくないということで、Serverless Frameworkを用いて管理しているのですが、その中でいくつかの制約に遭遇したのでまとめておきます。
なお、Serverless Frameworkはv1.5を利用しています。現在の最新版はv1.9.0なので、Serverlessに関するいくつかの項目は解決されている場合もあります。

AWS Lambda

関数名の長さ

関数名の長さが64文字までの制限があります。参考: CreateFunction
Serverlessは関数名にサービス名とステージを組み合わせて、${service-name}-${stage}-${functionName}という名前でLambda関数を作成します。
プロジェクトではSwaggerでAPIの振る舞いを定義し、独自に開発したAKIRA-MIYAKE/serverless-import-swaggerserverless.ymlを自動的に生成するようにしているのですが、APIのパスやパラメータに基づいて自動生成した関数名がたまにこの制約に引っかかってしまいました。
任意の関数名を決める場合なら問題にはならないのですが、関数名を自動生成する場合は(そういう人がどれくらいいるかは不明ですが)一意に識別可能でかつ可能な限り短い名前を生成することを意識する必要があります。

CloudFormation

リソースの最大数

ServerlessはCloudFormationを利用してAWS LambdaとAPI Gatewayへのデプロイを実行するのですが、その際に関数や関連するリソースが多すぎると、テンプレートで宣言できるリソースの最大数200の制約に引っかかります。参考: AWS CloudFormation の制限
例えばHTTPをイベントソースとする関数だとAPI Gatewyのリソースやメソッドの定義が必要となるため、関数が30に近いあたりからこの制約に引っかかる可能性が出てきます。
そのため、ある程度の規模のAPIを構築する場合、サービスを分割する必要が出てきます。

出力の最大数

CloudFormationには出力の最大数が60までという制約があります。
Serverless v1.5までは作成されたスタックを全て出力される設定となっているため、それ以上の規模のサービスを定義することができません。
v1.6以上では、CloudFormationの出力が削除されているため、この制約に引っかかることはなくなったのですが、代わりに次の問題が生じるようになっています。

LogGroup作成時のエラー

Serverless v1.5では発生しないのですが、v1.6以降で遭遇するエラー。

An error occurred while provisioning your stack: IamPolicyLambdaExecution
     - Template error: LogGroup /aws/lambda/my-service-name-dev-functionName
     doesn't exist.

v1.6からロググループの作成方法が変更になったようなのですが、どうやら#2614 (comment)にあるように、CloudFormationレベルのバグに起因するもののよう。
関数名のプレフィックスの命名に注意すれば回避できるようですが、あまり好ましい解決策ではない。。

API Gateway

カスタムドメインへの複数APIマッピング

CloudFormationの制約で、それなりの規模のAPIの場合、サービスを分割することが必要。
ServerlessのサービスがAPI GatewayAPIに対応するのですが、カスタムドメインを複数のAPIマッピングする場合は一意のベースパスの設定が必要となります。
そのため、複数サービスに分割する際はそれを考慮する必要があります。
今回はSwaggerで定義したAPIのパスの最初のディレクトリで機械的に分割してグルーピング、そしてそのディレクトリはserverless.ymlのAPIのパスからは除外するという形で対応しています。
RESTfulなAPIであればそこまで大きな問題にはならないと思いますが、今回のサービスではログイン中ユーザに依存する情報を取得する際のAPIとして/userを複数定義していたため、論理的にはあまり関連のないAPIが同じサービスに含まれるような形となってしまいました。


サーバレスアーキテクチャを採用したAPIを開発する中で遭遇した、AWSのいくつかの制約についてあげてみました。
それなりの規模のAPIをServeress、もしくはCloudFormationで管理する場合、リソースの最大数が200という制約で、必然的にいくつかのグループに分割する必要が生じるというのが、それなりに大きいかと思います。
AWS CLIを利用して直接リソースを作成すればその制約を受けることはありませんが、今度は依存関係の解決が難しくなってきます。

今回のプロジェクトは、サーバレスアーキテクチャを利用したステートフルなバックエンド開発、その過程でserverless-import-swaggerなどいくつかツールを開発したので、ブログで紹介していきたいと思います。

AWS re:Invent 2016 報告会に行ってきました

公式の情報はAWS re:Invent 2016 で発表された新サービスと機能 | AWSから。

気になった項目と感想を抜粋して書いていきます。

データベース関連サービス

Athena

RedshiftやEMRと違って、データフォーマットの事前変換やデータの取り込みが不要。
CloudWatch LogsをS3にエクスポートして簡単な分析とかなら、ほぼ事前準備や専門知識なしでできてしまいそう。

PostgreSQL for Aurora

PostgreSQL準拠のAurora。
Postgresを採用してた既存システムとかだと、移行がより簡単になるかと。

Developer Tools, Serverless and Mobile

AWS CodeBuild

  • 構築不要でスケーラビリティを備え、ビルドとテストが実行可能なマネージドサービス
  • 分単位の時間課金
  • ソースリポジトリとしてAWS CodeCommit、GitHub、S3
  • CloudPipelineと連携し、CI/CD環境を実現

フルマネージドなコードのビルド・テスト環境。
少し前に見つけて気になったけど、GitHub Enterpriseに対応してなかったので採用はしませんでした。

AWS X-Ray

  • End-to-Endで分散アプリケーション環境へのリクエストのトレースを取得し、デバッグ、分析を行うマネージドサービス
  • アプリケーションにSDKとAgentを実装することでトレースデータを送信

AWSのいろいろなサービスを利用しているアプリケーションで、どこがボトルネックになっているかなどを容易に分析ができるように。
AWS Lambdaのサポートは少し先の模様。

AWS Step Functions

  • ワークフローが複雑で、条件分岐が発生するような処理を容易にデザイン及びステップ管理できるサービス
    • ステートマシーンの提供
  • ワークフローはJSONで定義し、個々のステップはLambda関数、EC2やECS上のアプリケーションで実装可能

AWS LambdaやEC2で特定の処理だけ定義して、その処理を実行する条件をGUIで定義できる機能。
外部のデータの参照や前後の結果とかまで表現できるかは要確認。

AWS Pinpoint

  • バイルアプリケーション向けターゲットプッシュ通知サービス
  • ユーザの行動を把握・分析し、セグメントを作成、ターゲットユーザに対してメッセージ配信
  • 通知と結果の再分析や、A/Bテスト分析に活用することで、効果的なエンゲージメントを可能に

自動的にセグメントを作成してくれるのは魅力的。

AWS Lambda Dead Letter Queue

  • 3回実行しても処理されなかったイベントをSQSのキューもしくはSNSトピックへと送信
  • コードに問題がある場合や、スロットルされる場合もイベントを保存
  • ファンクション単位
  • 全ての非同期呼び出しで利用可能

複数関数をまたぐようなトランザクション処理なんかは制御しやすくなりそう。

Lambda@Edge

  • Lambdaベースの処理をCloudFrontのエッジロケーションで実行し、リアルタイムにヘッダー、URLなどを編集可能
  • 実行タイミングは4種類
    • リクエスト受信時
    • レスポンス返却時
    • キャッシュがない場合
    • オリジンからの応答受信時

今開発中のサービスでLambdaを利用したSSRをやろうとしてるけど、キャッシュの制御なんかに使えそう。
まだリミテッドプレビューのようだけど。

AI系サービスとIoT関連アップデート

Amazon Rekognition

  • ディープラーニングベースの画像認識サービス
    • オブジェクトとシーンの抽出
    • 顔分析
    • 顔比較
    • 顔認識
  • GAリリース

Microsoftが提供しているような画像認識のサービス。
Amazonが用意した学習モデルを利用するからすぐに使い始めるけど、アプリケーションに特化した認識はできない。
転移学習とかができればいいんだけど。

Amazon Polly

  • Text-to-Speech
  • プロ声優による47の音声と24の言語に対応
  • ストリーム再生、ファイルダウンロード
  • ファイルの保存、再利用が可能

SSML(Speech Synthesis Markup Language)という共通のマークアップランゲージで発声を制御できる模様。
少しText-to-Speechのサービスに関係する機械があったけど、共通のマークアップランゲージがあるのは知らなかった。

Amazon Lex

  • 音声とテキストを利用して、会話インタフェースを実現するための新サービス
  • リミテッドプレビュー

Amazon Alexaを実現する仕組みを利用できるサービス。
Botとかに利用できる。
設定されたゴールに必要な変数を定義して、ユーザからその変数を聞き出すための会話を自動的に組み立ててくれるとのこと。

AWS LambdaをTypeScriptで開発する

今のプロジェクトでは、AWS LambdaをメインにすえたServerless Architectureを採用しています。
AWS LambdaはWebのコンソールからソースコードを編集したり、イベントソースを定義したりできるのですが、やはりリポジトリで管理したりデプロイの自動化を行いたくなります。
いくつか管理ツールがあるのですが、今回はServerless Frameworkを利用しています。
関数やイベントソース、関連するリソースをYAMLファイルで定義でき、また構成の自由度も高く使いやすく感じます。
さらに、プラグイン形式で機能を拡張でき例えば、今回のようにTypeScriptを利用する場合でも容易に対応することができます。

ServerlessでTypeScriptを扱う際の基本的な設定などを、serverless-typescript-starterにまとめてみました。

serverless-webpack

プラグインとして、serverless-webpackを利用しています。
このプラグインは、その名の通りServerless FrameworkにWebpackを組み込むもので、デプロイコマンド実行時にビルド後のファイルをデプロイできるようになり、またビルドされた関数をローカル環境で実行することができるようになります。
ビルドタスク自体はWebpackの機能をそのまま使っているため、ts-loaderやbabel-loaderなど任意のローダを利用することで、TypeScriptやES2015で開発を行うことができます。

TypeScriptとWebbpackの設定

AWS LambdaのNodeランタイムは、現時点では4.3.2となっています。
そのため、ES2015のいくつかのシンタックスや機能は利用することができません。
TypeScriptやWebpackを利用する場合は、そのことを考慮した設定を行う必要があります。

{
  "compileOnSave": false,
  "compilerOptions": {
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "target": "es5",
    "lib": ["es6", "dom"],
    "typeRoots": [
      "./node_modules/@types"
    ]
  }
}

tsconfig.jsonではtargetをnode v4.3.2で動作するes5に設定。さらにbabelなどを通す場合はes6でも大丈夫だと思います。
modulecommonjs形式で出力するように。Webpackはデフォルトではcommonjs形式のみでimport/exportを処理できないため。

'use strict';
const webpack = require('webpack');
module.exports = {
  entry: './handler.ts',
  output: {
    libraryTarget: 'commonjs',
    path: '.webpack',
    filename: 'handler.js'
  },
  externals: {
    'aws-sdk': true
  },

  target: 'node',
  resolve: {
    extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', '.ts', '.tsx'],
  },
  module: {
    loaders: [
      { test: /\.(jsx?|tsx?)$/, loaders: ['ts-loader'], exclude: [/node_modules/] }
    ]
  },
  devtool: "#source-map"
};

serverless-webpackでは、webpack.config.jsはServiceごとに配置されることがデフォルトになっています。
output.libraryTargetcommonjs形式に。AWS Lambdaは、module.exports =のcommonjs形式で関数を定義するため。
targetにはnodeを指定。
AWS Lambdaではaws-sdkImageMagicがデフォルトで利用できるため、もし利用する場合は、externalsに設定してビルド後のコードに含まれないように。

テストや関数の分割などの方針について

まだ試行錯誤中ではあるのですが、テストや関数の分割などの現在採用している方針について記載します。

AWSリソースのフェイクの利用

関数内でaws-sdkを利用する場合は少なからずあると思いますが、Docker上にAWSリソースを模したイメージを立てて利用するようにしています。
現在は以下のimageを利用しています。

基本的にローカルでのテスト時にaws-sdkのメソッドを呼び出したときに期待するレスポンスが帰ってくることを確認するためで、イベントソースとしては利用していません。

環境変数の利用

AWS Lambdaにはビルドされたコードがデプロイされるため、当然環境変数は利用することができません。
けれども、環境に応じて値を変更したい場合というのは当然あるため、Webpackを用いてビルド時に環境変数を設定できるようにしています。

new webpack.DefinePlugin({
  'process.env': {
    'ENV': JSON.stringify(process.env.ENV),
    'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
  }
})

webpack.config.jsプラグイン定義の箇所で、必要な環境変数を利用できるようにしています。
今回はDockerイメージを立ち上げるときに環境変数を渡すようにしているため、このような手法をとっていますが、Webpackのプラグインを利用するなどの手法もあると思います。

依存するコンポーネントを受け取る関数に分割

Lambdaで実行される関数をそのままテストすることは難しいため、必要に応じて分割を行います。
特に、aws-sdkを利用するような箇所は、sdkのオブジェクトを受け取るような関数に分割を行っています。

export function uploadFile(s3: AWS.S3, key: String, body: String) {}

例えばS3にファイルをアップロードするような場合、実際の処理は上記のような関数に分割して実装しています。
ユニットテスト時にはフェイクのS3にアクセスするS3オブジェクトを利用したり、sinonのstubを利用して挙動の確認を行います。
AWS Lambda上では起動される関数内でS3オブジェクトを生成し、上記の関数を利用するようにします。
責務を明確にした上で、テスト可能な単位まで関数を分割することで、AWS上での試行錯誤を減らし、安全に開発を行うことができると思います。


まだ、いろいろと検証中で固まっているわけではないのですが、現在のAWS Lambda + TypeScriptの開発環境や考え方について、簡単に紹介してみました。

Vue.js: Revolutionary Front-endでLTしました

Blogに書くのを忘れていました…。

ABEJA主催のInnovation MeetupのVue.jsの勉強会でLTを依頼頂いたので、僭越ながら話をさせていただきました。
…Vue.jsをやったことがないにも関わらず。

そんなわけで、React + Redux + react-routerを実戦投入したさいの経験などをベースに、SPA(Single Page Application)の設計について話しました。

フロントエンドの設計に関する考察

どんなフレームワークを使った場合でも、取り入れられる考え方だと思います。
ただ、ある程度の規模以上のアプリケーションにならないと、StoreやDispatcherを整備するコストが相対的に高くなるかと。

他のLTはVue.jsの事例紹介、Vue 1.0からVue 2.0への移行について、Vue 2.0の機能紹介だったので、バランスは取れていた…と思われます。
全体のレポートはABEJA Meetup「Vue.js: Revolutionary Front-end #1 With Evan You!」レポートに。

転職したのでいろいろ書いてみる

転職して、10月より新しい会社で働くこととなりました。
退職エントリとかそーゆーものでもないですが、自分の備忘録や整理みたいな感じで取り留めなく書いてみます。

Why??

前職の環境では自分の考えるスキルセットを身につけることが難しいと考えたから。
新しい会社ではそのスキルセットを身につけることが効率的にできると考えたから。

自分が得意とする領域はフロントエンド、JavaScript / HTML / CSSをそれなりに書けて、アプリケーションのUIデザインもできるプログラマはあんまりいないと思うので、そのポジションは向こう3年くらいは維持する努力をしたい。

それに加えて

  • それなりの規模のWebサービスのインフラ構成の検討やサーバサイドの実装
  • CI環境の整備
  • チーム開発でのお作法

といった感じのことを身につけようと考えている。

前職、前々職のSIerとは異なり、新しい会社はいわゆるユーザ企業で、その中でも新規サービスの企画開発のチームで働くことになったので、良いのではなかろうかと。
サービス開発の初期段階らしく、ビジネス設計やサービス設計とかのフェーズをスキルのある人と経験できるのも嬉しい。

考えたこと

転職のアクションを起こしてから決まるまで2週間程度。でも、ポジショニングや次に身につけるスキル、それに伴う身の振り方についてはそれなりに考えていたわけで。
そういった中での自分の考えを順不同で書いてきます。ただし自分は「プログラマとしてスキルを高めて、その能力を買ってもらえ適切に活かせる環境で働きたい」という考えがベースにあるので、それなりに偏りはあると思います。

腕の立つプログラマの重要性

昔から出来るプログラマと出来ないプログラマの生産性は10倍以上とか言われていたけど、ここ1、2年はそれがさらに顕著というか、腕の立つプログラマを確保することがクリティカルなレベルになってきてるなと。
おそらく、これまでリソースの制約などで出来なかったことが出来るようになったことが要因の、アプリケーションの複雑性の増大や、ビジネス要件や外部影響によるアプリケーションの変更や機能追加のサイクルの短期化が原因ではないかと。
外部連携がほとんど無くて作ったら終わりのようなシステムは、要件さえしっかり明らかになってれば、最悪その辺から人をかき集めてきて人海戦術でなんとかなると思う。
けれども、複数の外部連携がある中での例外処理とかデータの一貫性を保つためのポリシーとか、変更容易性を考慮した実装とかは、出来る人がいるかいないか。出来ないプログラマをたとえ100人集めたって、出来るもんじゃないよね。

しっかり要件定義とか設計をすればいい??
コードを書けないSEでそれが満足に出来る人を見たことがないし、そもそもそういった事を考えるべきということに気づかない。

要求されるスキルの細分化と高度化

自分はフロントエンドを専門にしてるからというのもあるけど、最近のフロント側、特にSPA(Single Page Application)とサーバ側は考え方自体が違います。SPAはどちらかというとiOSとかAndroidのアプリに近い。
どちらもそれぞれ高度なスキルが必要だし、他にもAWSとかのクラウドサービスも本格的に使うとそれこそ専門家が必要になると思う。
確かに、出来る技術者は得意な領域以外のこともそれなりに出来るとは思うけれど、問題はいろんな領域が変化するスピードが早すぎて、自身のスキルをアップデートする範囲を絞らざるを得ないこと。

ふむ、昔の方法でも実装できればいい??
新しいフレームワークや考え方が出てくるということは、その昔の方法にはなんらかの改善されるべき問題が存在するということ。
それが開発するアプリケーションでは問題にならなかったり、低コストで回避する方法があるのなら昔の方法でも。
ただ、それを判断可能なのはその領域の専門家しかいないよね。

とりあえずSPAを昔のJavaScript、サーバ側で画面を組み立てた後のおまけ的感覚の延長で考えるともれなく死ねます。
JavaScriptやったことないような人をあてるのは、もう最悪。

同じ専門領域の技術者がいること

前職を退職する際は、フロントエンドエンジニアは自分一人。
もちろん、別の領域の技術者と話をすることも刺激的ではあるのだけれど、専門分野のスキルの向上という点についてはちと辛い。
新しい技術を全て確認できるわけではないし、それをどのような場面で利用すべきかということも、一人で経験できる数には限界がある。勉強会への出席でカバー出来るといえば出来るけれども、気軽に議論できる人がいる環境というのは、それだけで価値がある。
いい技術者がいる会社にはいい技術者が集まる、というのはそういう点もあるのではないかと。

遭遇するであろう大抵の問題は既知のもの

プログラミングに限らず、よっぽど先進的なことをやっているのでない限り、遭遇する問題のほとんどは誰かが経験している問題だと思う。
逆にそういった事例が全く出てこないのであれば、自分が何かおかしいことをやっている可能性の方が高いように思う。
そういった問題の解決は誰かの経験をおおいに活用すべきだし、何よりそのような問題が発生する可能性があるということを認識している、ということが重要かと。

例えばプロジェクトマネジメントする人なら、人月の神話くらい読んでいてほしい。
明らかに問題が発生する可能性が高いと記述されるような状況を自ら作っておいて、どうしてプロジェクトがうまく進むだろうと考えるのか。

あとSIerのプロジェクトなら、その会社の有価証券報告書とか、システム化対象業務周辺の法令とかには目を通しておこう。
有価証券報告書ではシステム化対象の業務がどのような位置付けにあるかや、注力しようとしている領域がわかる。法令とかは例え担当者が要件に直接書いていなかったとしても、当然制約条件になる。
それをしないで相手に聞いてばっかりだと、なんでそんな当たり前のこといちいち聞いてくるんだよこのやろーと思われます。本当にクリティカルなことを聞いた時に、真剣に考えてくれる可能性が下がります。

マルチスキルとマルチロール

個人的にはマルチスキルは賛成だけど、マルチロールは反対。
特定の階層や範囲の問題を解決する手段として、一人が複数のスキルを有することはいくつもの視点を持てたり相互にフィードバック出来たりと大変よろしい。
自分もフロントエンドに加えて、iOSアプリやデザインもやっているけど、別の領域の知見はおおいに役に立つ。

一方、マルチロールで問題だと思うのは、解決すべき問題の階層が範囲がバラバラになってしまうこと。
人によるとは思うのだけれども、自分は頭のモードを切り替えるコストが高いようなので、例えば2つのプロジェクトに同時にアサインされているとアウトプットの総量が減ってしまうように感じる。
これがプリセールスや障害対応にまでなるととてもとても。
というか、いつ電話が入ってくるかわからない状況って、全くもってプログラミングに適さない環境だと思います。


とりあえず、新しい会社は自分にとって良い環境であると、今のところ認識しています。
自分のいる環境を変えるのも一つの選択肢だけれども、別の環境に行くのも良いかと。
特にIT系の技術者は自分の有用性を証明できるのであれば、それなりのところから声がかかるのでは。その分、無用になれば切り捨てられる可能性も高まるので、日々精進。