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
でも大丈夫だと思います。
module
はcommonjs
形式で出力するように。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.libraryTarget
はcommonjs
形式に。AWS Lambdaは、module.exports =
のcommonjs形式で関数を定義するため。
target
にはnode
を指定。
AWS Lambdaではaws-sdk
とImageMagic
がデフォルトで利用できるため、もし利用する場合は、externals
に設定してビルド後のコードに含まれないように。
テストや関数の分割などの方針について
まだ試行錯誤中ではあるのですが、テストや関数の分割などの現在採用している方針について記載します。
AWSリソースのフェイクの利用
関数内でaws-sdkを利用する場合は少なからずあると思いますが、Docker上にAWSリソースを模したイメージを立てて利用するようにしています。
現在は以下のimageを利用しています。
- trayio/dynamodb-local(https://github.com/trayio/dynamodb-local)
- lphoward/fake-s3(https://github.com/lphoward/fake-s3)
- feathj/docker-fake-sqs(https://github.com/feathj/docker-fake-sqs)
基本的にローカルでのテスト時に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.jsonはatom-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を使ったパターンとかは今後検討する予定。
SwiftyEventsのSwift 2.2への対応 +α
自作のSwiftのライブラリ、SwiftyEventsのSwift 2.2への対応を実施しました。
対応と言っても、
に対応しただけでしたが。
Swift 2.2では非推奨の記述を用いていたとしてもWarningとなるだけで、ソースコードのビルド自体は可能です。
ただ、Swift 3.0では後方互換は切り捨てられるようなので、徐々に対応は必要だと思われます。参考
+αとして、Carthageで管理しているサードパーティーライブラリのXcode 7.3での利用について。
メジャーなライブラリはだいぶ対応が進んできてると思いますが、まだ対応していないライブラリをXcode 7.3で利用する場合。
CarthageにはPrebuildという機能があり、プレビルドされたバイナリがGithubに存在する場合は、自動的にそちらを利用するようになっています。
そのため、プレビルドがXcode 7.3より以前のバージョンでコンパイルされていた場合、Xcodeの新しいバージョンでリビルドするようにという警告が出て、アプリをビルドすることができません。
そのような場合、carthage update --no-use-binaries
のように、--no-use-binaries
フラグをつけて実行することでプレビルドを利用せずに、チェックアウトしたプロジェクトを手元でコンパイルしたバイナリを利用できるようになります。
Swift 2.2で非推奨になった記述を含んでいる場合はビルド時に幾つかのWarningが表示されますが、コンパイル自体は問題なく実行され、アプリケーションに組み込むことが可能となります。
Raspberry Pi で iBeacon を発見するための調査メモ
近いうちに試してみることになるかもしれないのでメモ。
Raspberry Pi で Bluetoothを使えるようにするために
Raspberry Pi を Bluetooth Low Energy (BLE) の Peripheral として動作させるで、BLE の Peripheral として動作させたことはあるので参考に。
RPi Bluetooth LE が詳しい。
あとはこのあたりを参考に。
https://github.com/sandeepmistry/noble#prerequisites
apt-get
で BlueZ をインストールできるが、バージョンが 4.99 で BLE のサポートが完全ではないらしい。
BLE のスキャニングだけならおそらく問題はないと思うけど、使えない場合はビルドが必要。
Node.js でプログラミングするために
sandeepmistry/node-bleacon でいけるっぽい。
スキャニングは Central モジュールの noble に、発信は Peripheral 側の bleno に依存している。
UUID を指定しなくてもスキャニングは可能。…というか実装を見てみたら、全部取得してから通知するかどうかの判断をしている模様。
UUID は配列で複数渡すこともできるから、違う種類の iBeacon が混在していても大丈夫そう。
iBeacon と Raspberry Pi の距離の測定
node-bleacon では MeasuredPower や RSSI の値を取得可能。
ただし、それらの値だけでは距離の算出は難しい。
iBeaconのRSSIでiPhoneの二次元座標をとれたらいいな
屋内測位をやってみました
大雑把な距離なら proximity を使えばいいけど、具体的な距離を出したい場合はキャリブレーションが必要っぽい。
Raspberry Pi では収集だけして、フィンガープリンティングの値の設定と距離の算出をサーバ側でするようにしたら個別にソースをいじらなくてもいいかも。