読者です 読者をやめる 読者になる 読者になる

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系の技術者は自分の有用性を証明できるのであれば、それなりのところから声がかかるのでは。その分、無用になれば切り捨てられる可能性も高まるので、日々精進。

Dockerニュービーが開発環境をDocker対応してみた

プログラミング

Node.jsでアプリケーションを開発するときにベースにしているNodeyard、アップデートとあわせてDocker対応も行ってみました。

アップデート内容

Gulpでのタスク管理の廃止

これまでGulpでビルドタスクなどを定義していたのを、全てnpm run-scriptに移行しました。
どうしてもGulpファイルが秘伝のタレ化してしまうのと、必要なツールがCLIのインタフェースを提供していて直接の利用で問題がないことが理由です。

BrowserifyからWebpackへ移行

Browserifyで行っていたフロントエンドのモジュール管理をWebpackに移行しました。
後述するPostCSSで通常のCSSファイルをインポートできるようになったこともあり、フロントエンドのリソースを一元的に管理したかったためです。
また、Webpackと統合されたwebpack-dev-serverが非常に高機能であることも大きな理由です。

SassからPostCSS + cssnextへ移行

これまでも部分的にcssnextを使っていたりしたのですが、完全にPostCSS + cssnextに移行しました。
cssnextはこれからCSSでサポートが予定されている機能を利用するための、PostCSSのプラグインです。
Sassで利用できた全ての機能を利用できるわけではありませんが、変数やネスト、数式などを利用することができ、概ね問題ありません。
PostCSSのpostcss-importを使うことで、CSSファイルのインポートが可能になるのも移行した理由の一つです。
CSSファイルの形式だけで公開されているサードパーティーCSSフレームワークを、独自に拡張したりすることができるようになります。
また、Basscssのように、PostCSSに対応したフレームワークも増えているようです。

Webサーバのライブリロード

フロントエンドはこれまでbrowser-sync、今回からはwebpack-dev-serverによりライブリロードが可能だったのですが、nodemonを利用してWebサーバのライブリロードも行えるようにしました。
/dist以下のファイルに変更があると、サーバの再起動が実行されます。
セッションがからむような場合は利用できませんが、単純なAPI開発の際は非常に便利になりました。

Docker対応

モックなどデータベースを利用しない場合はローカル開発でも問題ないのですが、やはりアプリケーション開発となるとデータベースやキャッシュを利用することになります。
今までは直接HerokuのDBに接続したりしていたのですが、準備や切り替えなどの作業が煩雑になります。
そこで、DockerでひとまずPostgreSQLとRedisを利用できる開発環境を構築するようにしてみました。

Dockerはほとんど利用したことがなかったのですが、Lessons from Building a Node App in DockerやDockerのリファレンスを参考に。
オフィシャルのnodeイメージを利用することで、ほんの少しDockerfileを記述するだけで大丈夫でした。
PostgreSQLとRedisはイメージをそのまま利用して、ポートもデフォルト。名前解決もされるのでアプリからの接続も簡単。

Docker for Mac Betaを使うと、ファイルの変更検知も問題なく動作してlocalhostでDocker上のサーバにアクセスできるので、ほとんどローカルと同じように感覚で開発できました。

少し戸惑ったのが、イメージのビルド段階でインストールするnpmのライブラリ。

volumes:
  - .:/nodeyard

カレントディレクトリを単純にマウントすると、ホストにはnode_modulesが存在しないため、コンテナのnode_modulesが削除される。

VOLUME ["/nodeyard/node_modules"]

Dockerfileかdocker-compose.ymlでコンテナ起動時にホストにディレクトリを作成するようにすると、コンテナ内部ではnode_modulesに配置されたものはちゃんと存在するけれども、ホスト側からはからのディレクトリしか見えない。
ホストから操作するファイルはイメージに含めるべきでないし、意識する必要もないんだろうけど、Dockerの正しい挙動なのかな??

教えて偉い人。

Typescriptをいろいろ試してみた

プログラミング

ゴールデンウィークを利用して、Typescriptをいろいろ試してみました。
JavaScriptでWebアプリケーションを構築する際のベースとして使っている、Nodeyardと同じような感じで使える、Typeyardを作ってみました。

以下の方針で作っています。

  • クライアントサイド、サーバサイド共にTypescriptで記述する
  • クライアントサイドはWebpackを使う
  • スクランナーを利用せず、npm run scriptでビルドする

テストやソースチェック、devサーバなどひとまず自分が使いたい機能は用意することができました。
その中で気付いたことなどを、雑多になりますが書いていきます。

tscが少し使いづらい

Typescriptのコンパイラであるtscの設定が、少し柔軟性に欠ける。

tsconfig.jsonコンパイル対象のファイルをfilesで、除外するファイルをexcludeで指定するのですが、この2つを組み合わせて使うことができません。また、globなどを使ってパターンで指定することができず、ファイル名かディレクトリ名を直接指定することしかできません。

サーバサイドのコードは、ファイルごとにコンパイルして特定のディレクトリに出力したいのですが、クライアントサイドのコードはWebpackを使ってビルドするため、コンパイル対象に含めたくはありません。
excludeに、node_modulesなどと一緒にクライアントサイドのコードのディレクトリを指定すればコンパイルは問題ないのですが、atom-typescriptがtsconfig.jsonを見て動いているようで、クライアントサイドのコードでコード保管等が動いてくれなくなります。

今回は、tsconfig.jsonatom-typescriptとWebpackが参照するから定義はするけれども、実際にサーバサイドのコードをコンパイルする際は、cliでパラメータを渡すようにしました。
サーバサイド用のconfigファイルを定義してそれを指定する…ということができればよかったのですがそれもできない模様。
/src/app.tsというエントリーファイルを用意して、依存するファイルのみコンパイルされるというような形にしています。

Webpackが便利

今回初めてWebpackを使ってみたのですが、いい感じかも。
アプリケーションが利用するスタイルシートや画像などのリソースも一括して管理できるので、クライアントサイドのビルドタスクをいろいろ定義せず、1つで済ますことができるのは非常に便利。
file-loaderで画像をディレクトリ構造や名前を維持したまま、特定のディレクトリに出力することができるので、expressのテンプレートエンジンから利用するのも問題ない感じ。
全体的な考え方は、Reactととても相性が良いと思います。

Express + Typescript

typingsでexpress、express-serve-static-core、serve-static、node、mimeを--save --ambientオプションでまずはインストール。あとは利用するミドルウェアの型定義を適宜インストール。
今回試した範囲では、特に問題は発生せず。
Expressのオブジェクトを利用する関数を定義するときなどは、型を確認して明示的に指定しないといけないけれど、Expressのオブジェクトやメソッドを利用する場合は、型推論が賢くてほぼES6と同様の書き方で大丈夫っぽい。
エラー生成でhttp-errorsを試してみたけど、最終的なエラーハンドリングを行う箇所で、

app.use((err: Error, req, res, next) => {
  const error = err as HttpErrors.HttpError;

  res.status(error.status || 500);

  const params = {
    error: (process.env.NODE_ENV !== "production") ? error : null,
    message: error.message
  };

  if (/^\/api/.test(req.originalUrl)) {
    res.send(params);
  } else {
    res.render("error", params);
  }
});

のようにダウンキャストすることで利用可能。
コンパイルされたコードには型チェックは含まれなので、問題なく動作します。

特にORM系に動的にプロパティを生やすライブラリが多いのですが、それらをTypescriptでどう扱うのかは要確認。
コンパイルを通すために、何らかの型定義が必要なのかも。

React + Typescript

React自体がコンポーネントを定義して組み立てるという思想なので、各コンポーネントのstateやpropsが型で明示的に示されるのが非常に便利。
atom-typescriptでは、tsxのテンプレートリテラルでpropsの候補まで表示してくれるのではかどります。
reduxを使ったパターンとかは今後検討する予定。