Node.js で Request Local なコンテキストを Zone.js で作る (2)

前回の続き Node.js で Request Local なコンテキストを Zone.js で作る - kitak blog

本当に Request Local になってるのかなーと思って、以下のようなコードを書いて、ab (ab -n 1000 -c 100) にかけて確かめた。

import * as express from 'express';
import uuidv1 = require('uuid/v1');
import 'zone.js';

interface MyRequest extends express.Request {
    trxId: string,
}

class TrxIdVerifier {
    verify(trxId: string) {
        if (trxId === Zone.current.get('trxId')) {
            console.log(`[trxId=${trxId}] Accept`)
        } else {
            console.error(`[trxId=${trxId}] Reject`)
        }
    }
}

const app = express();
const wait = (sec: number) => {
    return new Promise((resolve) => {
        setTimeout(resolve, sec);
    });
};

app.use(function (req: MyRequest, res, next) {
    const trxId = uuidv1();
    req.trxId = trxId;
    Zone.current.fork({
        name: 'request',
        properties: {
            trxId: trxId,
        },
    }).run(next);
});

app.get('/', async (req: MyRequest, res) => {
    await wait(Math.random() * 200 + 100);
    const verifier = new TrxIdVerifier();
    verifier.verify(req.trxId)
    res.send('Hello World!');
});

app.listen(3000, () => {
    console.log('listening port on 3000.');
});

最初、全てが Reject になって、あれー... と思ったのだけど、いまいま Native の async/await を使うと Zone が動かないらしい ( Support for native async/await? · Issue #715 · angular/zone.js · GitHub ) 。ES2016 target にトランスパイルしたら意図通り全て Correct で表示された。

Vue.js の mixin を作る時に意識していること

これは Vue.js #3 Advent Calendar 2017 - Qiita の18日目の記事です。

Vue.js には、mixin という機能をコンポーネント間で再利用するための仕組みがあります。 今日は自分が mixin を作成する際に意識していること、気をつけていることを紹介しようと思います。

例えば、振る舞いは同じだが、見た目が異なるためにそれぞれ別のものとして定義されているコンポーネントがあるとしましょう。徐々にそれらが肥大化してきて、共通のロジックを mixin として切り出すとします。

しかし、単純に共通のロジックをまとめて切り出すことには以下のような問題があります。

  • mixin が xxBase や xxCommon のような役割のぼやけた名前になりがちで、mixin によって何の機能(メソッド)が付与されるか分かりづらい
  • ひとつの mixin に複数の機能が含まれると、個々の機能の再利用がしづらい

例えば、~able という名前を付けるように意識する

ここで、mixin を作る際に自分が意識していることをひとつ紹介したいと思います。それは mixin には ~able という名前を付け、コンポーネントにひとつの機能を付与することに徹する、という意識を持つことです。

例えば、SNS にシェアする機能について考えてみましょう。TwitterFacebook にシェアする機能は、ページのあちこちで必要となるものです。その度に同じようなロジックを記述することは、後々の保守を考えると望ましくありません。このような場合は、「コンポーネントSNS にシェアする機能を付与する」と考え、ContentSharable のような名前の mixin を定義して、コンポーネントで使用するようにします。

const ContentSharable = {
  methods: {
    shareToTwitter: function () {
      // ...
    },
    shareToFacebook: function () {
      // ...
    },
  },
};

new Vue({
  mixins: [ContentSharable],
  // ...
});

どうでしょう。mixin の名前から付与される機能(メソッド)が何であるか、またその逆が明確になります。これはコードの整理を行う際に効く重要なポイントです。もちろん、常に ~able という命名をおこなうことが正しいとは限りませんが、mixin の役割を明確にする、再利用性を高めるひとつのコツであるように思います。mixin を定義する際の参考にしていただけたら幸いです。

Node.js で Request Local なコンテキストを Zone.js で作る

Request Local なコンテキストが欲しい理由

例えば、Request 毎に id を生成して、その id をログに書き出す内容に含めたい。こうすれば、障害や不具合の調査の際に、Request 単位で起きた事象の把握が容易になる。

Node.js での難しさ

ログを出力する箇所が express のレイヤーだけであれば、Request オブジェクトにプロパティを生やせば実現できるが、モデルや他のレイヤーも考慮すると難しくなる。

リクエスト毎に実行コンテキストが作られる場合は、グローバル変数で id を扱えばよいが、Node.js は基本的にひとつのスレッドで複数のリクエストを捌く。そのため、グローバル変数で扱うとリクエストの度に id が上書きされるので、複数のリクエストを同時に捌く際、前のリクエストで出力するログに新しいリクエストの id が含まれることになり、意図しない挙動になる。

標準モジュールの domain や、それを使った request-local というモジュールを使うと、やりたいことは実現できるのだが、domain はかなり前のバージョンから pending deprecation になっている。代わりになる API が用意されれば、deprecated になるので、使うのはためらう。

Zone.js

他に実現する方法はないのか調べたところ、Zone.js というライブラリでできそうだった。Zone.js は Angular が内部で使っているライブラリで、非同期処理に跨ったコンテキストを作成することができる。Zone.js の APIECMAScript の Spec として提案されているので、(stage 0 ではあるが)これの策定が進めば、Node.js でも実装されて domain に置き換わるのかな、と勝手に思っている。Zone.js 自体の説明は AngularとZone.jsとテストの話 - Qiita が詳しい。

コードは以下のようになる。middleware でリクエスト毎にコンテキストを作って、後は任意の箇所で Zone.current を通して、コンテキストにアクセスすればよい。実際には Zone の API の変更に備えて、直接扱うのではなく、RequestLocal のような名前の Zone をラップするモジュールを用意するのが適当だろう。

const express = require('express');
const uuidv1 = require('uuid/v1');
require('zone.js');

class Foo {
    constructor() {
        console.log(`[trxId=${Zone.current.get('trxId')}]: create Foo instance`)
    }
}

const app = express();

app.use(function (req, res, next) {
    Zone.current.fork({
        properties: {
            trxId: uuidv1(),
        },
    }).run(next);
});

app.get('/', (req, res) => {
    new Foo();
    res.send('Hello World!');
});

app.listen(3000, () => {
    console.log('listening port on 3000.');
});

Zone.js はライブラリの仕様を実現するために setTimeout 等の非同期処理をおこなう API を上書きしており、アプリケーション全体に影響がある「つよい」造りになっている。意図しない挙動が起きないか、もう少し個人プロダクトで試す予定。

Worker

今後の Node.js で入る Worker を使って、Request 毎に Worker を生成することで解決できないかな、とも考えている。Worker の生成コストだったり、Worker プールの仕組みを用意する必要も含めて、後日また検証する。

ロジ・コミックスとラッセル幸福論

最近の100分de名著でバートランド・ラッセルの幸福論を取り上げているので読んでいる。という話を友人にしたら、「ラッセルといえば、ロジ・コミックスのイメージだった」という話を聞いたので、合わせて読んでみた。

ラッセルの幸福論はラッセルが50代の頃に書いた本で、幸福論の冒頭に述べているように、彼がこれまでの自分の人生を振り返ったり、周囲の人間を観察した結果、導き出されたパターンや法則がまとめられている。一方でロジ・コミックスは、ラッセルの伝記を通して、論理学と数学の証明論の発展を解説しようとした異色のコミックだ。

ロジ・コミックスに出てくるラッセルは人間的に何か問題があったり、大きい挫折を味わうような描き方がされている。例えば、数学や論理学にのめりこみワークライフバランスを崩した結果、家庭を不幸にしたり、同僚の妻に恋をしたり、苦しんで世に出したプリンキピア・マテマティカウィトゲンシュタインゲーデルのような天才に(建設的にだが)批判されるといったように。

幸福論というと、賢者が述べているなんだか浮世離れしたものというイメージがあったが、ロジ・コミックスを読んだことで、ラッセルの主張の背景にある不幸や挫折、成功、克服といった彼の経験が浮かび上がり、その主張に対する納得感のようなものが高まった気がするのだった。

ロジ・コミックス: ラッセルとめぐる論理哲学入門 (単行本)

ロジ・コミックス: ラッセルとめぐる論理哲学入門 (単行本)

ラッセル幸福論 (岩波文庫)

ラッセル幸福論 (岩波文庫)

MobX の runInAction とは

故あって、React + MobX なプログラムを読んでいたら、

runInAction(() => {
  this.state = "done";
});

といった runInAction の呼び出しを多く見かけて、なんだろこれ、と思ったのでメモ。MobX 1日目で変なこと書いているかもしれません。

Writing async actions | MobX を順に読んでいけば分かるのだけど、

  • MobX では strict mode を有効にすることで Action 外で state を変更しようとしたらエラーにすることができる
  • ↑の制約を設けた場合、Action で呼び出した非同期処理のコールバックがただの無名関数だと Action 外ということになるので、コールバックにはメソッドとして定義したアクションを指定する必要がある
  • とはいえ、コールバックのためだけにメソッドを定義するのもまわりくどいので、無名関数を Action にできるユーティリティ関数が用意されている
  • さらに TypeScript で書いている場合に、↑だとコールバックの引数の型を都度記述する必要があって面倒なので、最後の state の変更だけを無名関数に切り出して、アクション内として呼ぶためのユーティリティ関数が用意されている。それが runInAction

ということだった(なんか問題を解決しようとして、さらに問題を生んでを繰り返していて、複雑...)。Action 内の非同期処理のコールバックで state を変更する場合はとりあえず runInAction 経由でおこなえばとりあえずは動く。

Vuex の場合は副作用の伴う処理は Action, state の変更は Mutation で役割が分かれていたのだけど、MobX の Action は副作用の伴う処理も state の変更のいずれも Action 内でおこなうことができる。ただ、「できる」というだけであって、Vuex のように副作用の伴う処理と state の変更で分離する設計をしてもよい。
個人的には副作用が伴う処理とそうでない処理が明確に分かれていたほうが見通しが良いし、書く時に迷わないので、多少は面倒だけども、メソッドをアクションとして定義して、それをコールバックに指定する形で分けると思う。

getUserMedia でバックカメラを要求する

Andoid Chrome で動くQRコード・バーコードを読み取るアプリを書いていて知った。

navigator.mediaDevices.getUserMedia でカメラを要求すると、デフォルトではフロントのカメラになるのだけど、以下のようにすることでバックカメラを要求することができる。

navigator.mediaDevices.getUserMedia({
  video: {
    facingMode: {
      exact: "environment"
    }
  }
})

参考

THE INCAL を読んだ

読んだ。

DUNE で出会ったアレハンドロ・ホドロフスキーメビウスが再びタッグを組んで作ったフレンチSFコミック。
街の一角のありふれた場面からビッグバンのように爆発して、宇宙を覆い尽くさんばかりに物語が膨らんでいく。読んでいて圧倒される作品。 両性具有の話が小道具として出てくるのだけど、その表現がダ・ヴィンチ・コードと繋がるものがあって興味深かった。

アンカル

アンカル