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

Engineer's Way

主にソフトウェア関連について色々書くブログです。

サーバレスアーキテクチャのフレームワークClaudia.jsを実戦で使ってみた

 

f:id:matsnow:20170525010920p:plain:w200

はじめに

今のプロジェクトでAWS Lambdaによるサーバレスアーキテクチャのシステムを開発していますが、フレームワークとしてClaudia.jsというものを選択してみました。

Claudia.jsとは

AWS Lambdaのようなサーバレスアーキテクチャ用のフレームワークとしてはServerless Frameworkが有名ですが、
それ以外にClaudia.jsというフレームワークがあります。 claudiajs.com

以下のような点がServerlessとの大きな違いです。

  • Claudia.jsはAWS Lambda、かつNode.js (JavaScript、TypeScript)に特化している。 特化している分、非常に少ない手間でデプロイまでできる。
  • api-builderやbot-builderなどの追加パッケージを使うことで、簡単にAPIBotを作れる。

なお、サーバレスアーキテクチャということもあり、フレームワークといっても、
フルスタックやMVCなどではなく、むしろビルド・デプロイツールといった色合いが強いです。

Claudia.jsの導入

Claudiaの導入は以下のブログが分かりやすいです。 qiita.com

dev.classmethod.jp

あとは公式のサンプル集を見れば、たいていのものは作れると思います。(手抜き) github.com

実際に使ってみる

Claudia.jsはLambdaやAPIをNode.jsを書いていく上では非常に便利ですが、 実戦投入するに当たって、いくつか考慮する点があります。

1. package.jsonは関数ごとに用意する

公式のサンプルなどでは、ディレクトリは1階層だけで、そこにpackage.jsonが置いていますが、実開発ではディレクトリは複数階層になることが多いと思います。
例えば、以下のようなディレクトリ構成にする場合、api/hello、function/foo、function/hogeの各ディレクトリにpackage.jsonが必要です。

sample  
├── api  
│   └── hello  
│       └── index.ts  
└── function  
    ├── foo  
    │   └── index.ts   
    └── hoge  
        └── index.ts  

その時のpackage.jsonの内容は、例えば「function/foo/package.json」であれば、以下のようになります。
nameがLambda関数の名前、descriptionは説明文、mainはエントリポイントのJSファイルです。

{
  "name": "foo",
  "description": "Lambda関数のサンプル",
  "main": "index.js"
}

なお、node_modulesのディレクトリは下位階層には不要です。 通常のプロジェクトと同じくトップにあればOKです。

2. ロールはあらかじめ作成しておく

Claudia.jsのコマンドでは、とりあえず以下を指定すればLambda関数を作成できます。 sh $ claudia create --region ap-northeast-1 --handler index.handler ただし、この場合、Claudia.jsによってIAMロールが毎回新しく作られてしまいます。 そのため、あらかじめLambda用のIAMロールを作成し、「–role sample-role」のように指定するようにした方が良いです。

3. claudia create –handlerの指定は相対パス

単純なことですが、–handlerで指定するハンドラ関数はディレクトリ構成が影響します。 例えば、src/ の下にindex.jsを入れており、「export handler」としている場合、「–handler src/index.handler」と指定する必要があります。

4. api-builderの中でpromiseを扱うときはpromiseをreturnする

ハマりどころの1つとして、API作成時にPromiseを使う場合、それをreturnしないと、 CloudWatchLogsなどにも何も出力されないまま、APIが終了します。

api.post('/user', function (request) {
    return dynamoDb.put({
        TableName: request.env.tableName,
        Item: {  userid: request.body.userId }
    }).promise();
}); 

公式ドキュメントの「4. Return a Promise out of the API handler」に記載されていますが、 かなりハマりやすい点なので注意が必要です。 https://claudiajs.com/tutorials/external-services.html

まとめ

最初に慣れるまでは若干手間取るかもしれませんが、一度使い方を覚えれば非常に楽に開発が進められる、良いフレームワークだと思います。
とりあえず、今のプロジェクトでは、このままClaudia.jsを使い続けるつもりです。

Linuxでのファイルの正しい配置について

 

今までLinuxを使ってきて、どこに何が置かれているのか、何を置くのかを漠然と理解していたけど、
FHS(FileSystem Hierarchy Standard) という名前でちゃんとしたスタンダードが存在することを、恥ずかしながら今更知りました。

wikipedia:Filesystem_Hierarchy_Standard

詳細はWikiを見てもらうとして、大まかには以下の通り。

パス 内容
/bin シングルユーザモードでも使える一般コマンド
/sbin 一般的なシステム管理コマンド
/etc ホスト固有のシステム設定
/usr 他のホストと共有できる“読み出し専用”データ
/usr/bin 基本的ではない一般コマンド
/usr/sbin 基本的ではないシステム管理コマンド
/usr/lib プログラミングとパッケージのためのライブラリ
/var 可変的なデータファイル。消えても問題ないものしか入れない
/var/cache アプリケーションのキャッシュデータ
/var/lock ロックファイル
/var/log ログファイルとディレクト
/tmp 再起動で消える(消えても良い)一時ファイル置き場

前から知っていれば、これまで作ってきたシステムも、もっといい感じのファイル配置にできたんだろうなあ…。

Exrm(Elixir Release Manager)を使ったリリースでエラーが出た時の対処法

 

f:id:matsnow:20170314012129j:plain:w300

最近Elixirを勉強中だけど、ビルド周りで少しハマったのでメモ。

現象

Exrmを導入した状態で「mix release」コマンドを実行してElixirのアプリをビルドしようとしたところ、「(CaseClauseError) no case clause matching: :eacces」というエラーが発生した。

% MIX_ENV=prod mix release
  :
** (CaseClauseError) no case clause matching: :eacces
    lib/exrm/utils/utils.ex:110: ReleaseManager.Utils.relx/5
    lib/mix/tasks/release.ex:339: anonymous fn/4 in Mix.Tasks.Release.do_release/1
    lib/ex_unit/capture_io.ex:146: ExUnit.CaptureIO.do_capture_io/2
    lib/ex_unit/capture_io.ex:119: ExUnit.CaptureIO.do_capture_io/3
    lib/mix/tasks/release.ex:338: Mix.Tasks.Release.do_release/1
    lib/mix/tasks/release.ex:78: Mix.Tasks.Release.do_run/1
    (mix) lib/mix/task.ex:294: Mix.Task.run_task/3
    (mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2

対処法

調べたところ、とりあえずsudoをつけることで解決することが判明。
mix release fails with 'no case clause matching: :eacces' · Issue #401 · bitwalker/exrm · GitHub

% sudo  MIX_ENV=prod mix release
==> The release for sample-app-0.0.1 is ready!
==> You can boot a console running your release with `$ rel/sample-app/bin/sample-app console`

別の対処

Exrmの代わりにdistilleryを使う。
release.initが事前に必要になるが、sudoをわざわざ付ける必要はなくなる。

%  MIX_ENV=prod mix release.init

An example config file has been placed in rel/config.exs, review it,
make edits as needed/desired, and then run `mix release` to build the release

%  MIX_ENV=prod mix release
==> Assembling release..
==> Building release sample-app:0.0.1 using environment prod
==> Including ERTS 8.2.2 from /usr/local/Cellar/erlang/19.2.3/lib/erlang/erts-8.2.2
==> Packaging release..
==> Release successfully built!
    You can run it in one of the following ways:[f:id:matsnow:20170314011357p:plain]
      Interactive: _build/prod/rel/sample-app/bin/sample-app console
      Foreground: _build/prod/rel/sample-app/bin/sample-app foreground
      Daemon: _build/prod/rel/sample-app/bin/sample-app start

BrowserifyからWebpack(バージョン2.2.1)に移行してみた

 

目次

今までのプロジェクトで、ずっとフロントの開発環境としてBrowserifyを使ってきたけど、gulpの記述が悪いのか、
とにかく重たいのでWebpackを試してみることにした。

f:id:matsnow:20170306024225p:plain

1 Webpackの特徴

事前に調べてみた範囲では以下のような特徴があるとのこと。

  1. JSの依存関係を解決できる。
  2. JS以外のファイルもまとめて扱える。
  3. gulpを置き換えることもできる。
  4. ビルド結果を複数にすることが容易。
  5. トランスパイラ機能あり(Sass、TypeScript、Babel)
  6. 全ファイルをjsにするので、cssなどを別扱いにするなら、gulpと組み合わせる方が良い。

2 今回やりたいこと

  • JavaScriptファイルを複数の単位でまとめる。
  • JSONもマージしたい。
  • 難読化、minifyする。
  • ソースマップを作る。
  • watchも機能させる。

3 インストール方法

  1. webpackをnpmインストールする。(以下ではnpm iの代わりにyarn addを使用)
    $ yarn [global] add webpack
    $ yarn [global] add ***-loader
    # ***の部分はbabelやsass、ts、jsonなど。読み込みたいファイルに対応したloaderをインストールする。
    
  2. 開発用サーバをインストールする。(任意)
    $ yarn add webpack-dev-server
    # node 4.7以上が必要。
    
  3. webpack.config.jsを作成する。
  4. コマンドを打つなり、gulpに組み込むなりで動かす。

4 動かし方

4-1 コマンドで動かす方法

  1. webpackを実行する。
    $ webpack
    
  2. watchを実行する。
    $ webpack --progress --colors --watch
    
  3. serverを起動する。
    $ webpack-dev-server --progress —colors
    

4-2 gulpで動かす方法

  1. 「webpack-stream」をnpmインストールする。
  2. 以下のようなgulpタスクを作成する。
    import webpackStream from 'webpack-stream';
    import webpack from 'webpack';
    import webpackConfig from '../webpack.config';  // webpack.config.jsのパスを指定する。
    
    gulp.task('webpack', () => {
    
      webpackConfig.watch = true;  // watchをしたい場合は必要。webpack.config.jsに直接書いてもOK。
    
      return webpackStream(webpackConfig, webpack)
        .on('error', function handleError() { // watch中のエラーで死なないようにする。
          this.emit('end');
        })
        .pipe(gulp.dest(config.sources.dest));
    });
    
  3. gulpを実行する。

5 webpack.config.jsの書き方

5-1 最低限必要な内容

const config = {
  // メインとなるJavaScriptファイル(エントリーポイント)
  entry: './app/js/index.js',

  // ファイルの出力設定
  output: {
    filename: 'bundle.js',
  }
}

5-2 難読化、最適化してみる。

  • webpack.config.jsに以下を追記。
plugins: [
    new webpack.optimize.UglifyJsPlugin({ minimize:true }),
    new webpack.optimize.OccurrenceOrderPlugin()
]

UglifyJsPluginは、gulp-uglifyと同じく難読化とminifyを行ってくれるプラグイン
OccurrenceOrderPluginは、Uglifyされたスクリプト中に出てくるIDの桁数を短くするプラグイン(らしい)。

5-3 ソースマップを使う。

  1. UgilifyJsPluginの引数のオブジェクトに「sourceMap: true」を追加する。
  2. webpack.config.jsに以下を追記。
  devtool: 'inline-source-map'

5-4 作成するファイルを複数に分割する。(browserifyとの大きな違い)

  1. entryをオブジェクトで記述する。
    1. 配列を使うと、マージする順番を指定できる。(GoogleAnalyticsをファイルの最後に追加するなど)
  2. outputのfilenameに[name]をつける。

5-5 共通処理を1つのJSファイルにまとめる。

  1. webpack.optimize.CommonsChunkPluginを使う。

5-6 JSONコンパイル対象にする。(同様に画像やCSSコンパイルできる)

  1. デフォルトで拡張子jsonコンパイル対象なので、気にしなくて良い。
  2. loaderに以下を追加。
  3. { test: /.json$/, loader: ‘json-loader’ },

5-7 CDNの外部ライブラリを使ってみる。

externalsを定義することで、CDNで取得した外部ライブラリを、import無しで使用可能となる。

  externals: {
    '$': 'jQuery',
    'jquery-ui': 'jquery-ui',
  },

5-8 一部のローカルライブラリのimportでエラーが出る場合

今回Amchartsを使おうとしたところ、名前が解決できないというエラーが出てしまった。 webpack.config.jsに以下のようにresolve.aliasを書くと解消できた。

resolve: {
    alias: {
      amcharts3$: path.resolve(__dirname, 'app/js/lib/amcharts/amcharts.js')
    }
  }

6 webpack.config.jsのサンプル

最終的に作って見たwebpack.config.jsはこんな感じ。

const webpack = require('webpack');
const path = require('path');
const env = process.env.NODE_ENV;

const config = {
  // メインとなるJavaScriptファイル(エントリーポイント)
  entry: {
    'bundle': path.resolve(__dirname, '../app/js/index.js'),
    'chart':  path.resolve(__dirname, '../app/js/chart.js'),
  },

  // ファイルの出力設定
  output: {
    filename: '[name].js',
  },

  // amcharts3のimport失敗への対応
  resolve: {
    alias: {
      amcharts3$: path.resolve(__dirname, '../app/js/lib/amcharts/amcharts.js')
    }
  },

  externals: {
    '$': 'jQuery',
    'jquery-ui': 'jquery-ui',
  },

  module: {
    rules: [
      { test: /\.js$/,   loader: 'babel-loader', exclude: /node_modules/ },
      { test: /\.json$/, loader: 'json-loader' },
    ]
  },

  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(env)
    }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name:     'commons',
      filename: 'commons.js',
      minChunks: Infinity
    })
  ]
};

if (env === 'production') {
  config.plugins.push(new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false
    }
  }));
} else {
  config.plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      sourcemap: true, minimize: true
    })
  );
  config.devtool = 'source-map';
}

module.exports
 = config;

AmchartsのTips

 

f:id:matsnow:20170306032523p:plain

最近のプロジェクトでAmcharts(https://www.amcharts.com/)を使っていたが、
ドキュメントの内容がイマイチ、かつ日本語の情報が少なく苦労したので、備忘録を兼ねて記載しておく。

    "graph": {
        "alphaField": "fillAlphas", // グラフの透過度
        "bulletField": "bullet",   // バレットタイプの追加
        "bulletSizeField": "bulletSize", // バレットサイズの追加
    },
    "categoryField": "category", // 行タイトル
    "colorField": "fillColors", // グラフの色
    "startField": "start", // ガント線の開始時間 or 日時
    "endField": "end", // ガント線の終了時間 or 日時
    "durationField": "duration", // ガント線の長さ (2時間など)
    "dataProvider": [ {
        "category": "John",
        "segments": [ {
            "start": 7,
            "duration": 2,
            "fillColors": "#46615e",
            "task": "Task #1"
        }, {
  • グラフのマージンに関わらない変更はmakeChartをやり直さず、validateNowでやった方が軽い。

  • 「usePrefixes:true」をJSONに指定することで、桁の大きい数字を自動的に小さくできる。

    • 例:1,000,000 -> 1M
  • exportプラグインを使うとき、captureメソッドの引数に「backgroundColor: (色コード)」を渡すと、Annotateモードや画像ダウンロード時の背景色を変えられる。

gulpで「CALL_AND_RETRY_LAST Allocation failed」というエラーが出た時の対処

 

gulpで「CALL_AND_RETRY_LAST Allocation failed」というエラーが発生

gulp-concatでファイルを結合していたら、以下のようなエラーが発生してしまった。

<--- Last few GCs --->

  120788 ms: Mark-sweep 1379.7 (1455.9) -> 1379.7 (1455.9) MB, 1211.9 / 0 ms [allocation failure] [GC in old space requested].
  121826 ms: Mark-sweep 1379.7 (1455.9) -> 1379.7 (1455.9) MB, 1038.5 / 0 ms [allocation failure] [GC in old space requested].
  122882 ms: Mark-sweep 1379.7 (1455.9) -> 1379.7 (1455.9) MB, 1055.0 / 0 ms [last resort gc].
  123942 ms: Mark-sweep 1379.7 (1455.9) -> 1379.7 (1455.9) MB, 1060.2 / 0 ms [last resort gc].


<--- JS stacktrace --->

==== JS stack trace =========================================

Security context: 0x3b31c8a5a91 <JS Object>
    1: /* anonymous */(aka /* anonymous */) [0x3b31c8041b9 <undefined>:~2655] [pc=0x2ffd9b29ee8e] (this=0x3b31c8041b9 <undefined>)
    2: /* anonymous */(aka /* anonymous */) [0x3b31c8041b9 <undefined>:~2134] [pc=0x2ffd9b2972ac] (this=0x3b31c8041b9 <undefined>)
    3: /* anonymous */(aka /* anonymous */) [0x3b31c8041b9 <undefined>:~2599] [pc=0x2ffd9b29b423] (this=0x3b31c8041b9 <undefined>,allow_...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
zsh: abort      gulp build:local

エラーの原因

原因は「out of memory」というエラーの通り、ヒープメモリが不足していたことだった。
参考にさせて頂いたサイトによると、Node.jsは512MBがデフォルトとのこと。
Node.js の out of memory エラー回避方法 : まだプログラマーですが何か?

対処方法

gulpを実行するときに、--max_old_space_sizeのオプションを付けてメモリサイズを大きくすればOK
ただ、手打ちはもちろん、シェルのaliasを設定するのもどうかなと思ったので、
package.jsonに以下のようにスクリプト定義を追加することで対処。

"scripts": {
   "dev": "./node_modules/.bin/gulp --max_old_space_size=8192 dev",
}

gulpの場合、オプションの単語がハイフンではなくアンダースコアで繋がれているのが微妙なハマりポイントかも。

  • gulp:–max_old_space_size
  • node:–max-old-space-size

結合されたtableタグをJavaScriptで分割する方法

 

特に何ということは無いのだけども。
ExcelのUIを実現するJSライブラリ」を使っている時、クリックしたりマウスオーバーしたセルの列名が欲しくなることがある。
その時、列名のセルを結合していると列番号から引っ張れないので、こんな感じで分割する一手間が必要になることもあったり。
まあ今回は不要だったけど。

let data = [];
const getter = () => {
  const tr = $("table tr");
  const cells = tr.eq(0).children();
  for( const cell of cells ){
    const text = $(cell).text();
    const colspan = $(cell).attr("colspan");
    const number = colspan ? Number(colspan) : 1;

    for (let i = 0; i < number; i++){
       data.push(text);
    }
  }
};

getter();
$('#result').text(data);

http://codepen.io/anon/pen/WpvamY

Docker for mac v1.13でdocker pullできない問題

  

ある時から、Macでdocker pull をしようとすると、なぜか以下のようなエラーが出るようになってしまった。

% docker pull ubuntu
Using default tag: latest
Error response from daemon: Get https://registry-1.docker.io/v2/:
 dial tcp: lookup registry-1.docker.io on 192.168.65.1:53: server misbehaving

エラーメッセージで検索するとDNSの問題みたいだったので、
/etc/resolv.confにnameserver 8.8.8.8を追加してみたり、
/etc/hosts に「registry-1.docker.io」のIPアドレスを直接書いたり、
dockerを再インストールするなどしてみたが、完全に解決するには至らず。

結果として、v1.13のBeta版をインストールすれば治りました。
dockerの問題だったのだろうか。

(2017/2/14追記)
ブコメによると、以下のissueが該当するようです。情報ありがとうございます!
github.com

はてなブログのトップページでボタンやコメント欄(ついでに広告も)を消す方法

 

続きを読む

高機能でシンプルUIのプロジェクト管理サービスasana

 

続きを読む