kitak blog

Kみたいなエンジニアになりたいブログ

Vue.js で特定のプロパティを変更の追跡の対象外にする

きっかけ

とある案件で、とあるサードパーティSDK を利用していた。その SDK が提供するコンストラクタから生成したインスタンスを Vue.js のインスタンスのデータとして扱おうとしたら、Security Error のような例外が投げられて困った。 (回避するために、インスタンスのデータとして扱わないように試みたのだけど、致し方ない理由で諦めた)

原因

Vue.js では、データの変更を追跡するためにプロパティを getter/setter に上書きする。ココらへんの話は リアクティブの探求 — Vue.js が詳しい。この上書きの処理はプロパティがオブジェクトの場合は掘り下げて再帰的に行われる。おそらく問題の起きたインスタンスでは、セキュリティの観点からこういった処理を防ぐ機構が入っていたのだと推測。すごいね。

解決

これまた公式ドキュメントなのだけど、Object.freeze で追跡の対象外にできることを知ったので以下のようなコードを書いた( Vue インスタンス — Vue.js )。

const foo = new ThirdpartyFoo();

const container = {
  foo,
};

const vm = new Vue({
  data() {
    return {
      container: Object.freeze(contianer),
    },
  },
});

vm.container.foo;

はじめ foo 自体を Object.freeze したのだけど、foo のメソッド呼び出しに伴うプロパティの変更ができなくなってしまった。そこで、容れ物のオブジェクトを用意して、元のオブジェクトを包むことにした。こうすることで、foo のプロパティの getter/setter への上書きは防ぎつつ、これまで通り foo のメソッドを呼び出すことができる。

これまで特に意識せず、様々なオブジェクトを Vue のインスタンスのデータとして扱っていたのだけど、変更を追跡する必要がない、オブジェクトのプロパティの階層が深い( getter/setter への上書きのコストが高い )オブジェクトは Object.freeze で追跡の対象外にしてよいかもしれない。

個人サイトを nuxt で書き換え、netlify で公開するようにした

経歴や職歴が書かれた個人サイト https://www.kitak.info に久しぶりに手をいれた。

元々、サイトジェネレーターの hugo を使ってサイトを生成し、VPS で公開していたのだけど、nuxt で書き換え、netlify で公開するようにした。以下、それぞれに関する個人メモ。

nuxt

プリレンダリングの機能があることを知って、前々からサイトジェネレーターとして使ってみたい気持ちがあった。

記事を markdown で書いていたので、プリレンダリング時に markdown から HTML を生成した。既存の markdownレンダリングする Vue のライブラリは、slots や mounted hook を利用したクライアントサイドで実行する制約のあるものがほとんどだったので、実装は単純なのだけど、こんなかんじ( https://github.com/kitak/www.kitak.info-nuxt/blob/6af6c36755e75f98e641f614a18109f70f53a1d9/components/MdArticle.vue )の markdown-js をラップした Vue コンポーネントを用意した。別にクライアントサイドでレンダリングしてもよいのだけど、事前にレンダリングしておいたほうが表示が早いだろうという気持ちの問題。

/ にリクエストがきたときに /about と同じ内容を表示したかった(内部リダイレクトしたかった)。nuxt では pages ディレクトリの構造 = ルーティングの設定なので、リダイレクト先のコンポーネントをそのまま返すようなコンポーネントを用意すればよい( https://github.com/kitak/www.kitak.info-nuxt/blob/6af6c36755e75f98e641f614a18109f70f53a1d9/pages/index.vue )。

netlify

作成したサイトを配信するために netlify を利用することにした。静的ファイルの配信に特化したサービス。総じてよかった。今回、自分が利用した機能については無料プランの枠で収まっている。

  • 管理画面が使いやすい
  • CDN で配信してくれる
  • 独自ドメインSSL証明書を Let’s Encrypt で取得してくれる
  • パス毎にカスタムヘッダーの指定ができる
  • (使っていないけど)SPA の Prerendering をしてくれる
  • (使っていないけど)特定のパスだけ api に proxy できる。origin が同じになるので CORS でハマることがなくなる

唯一イケてなかったのは、netlify のビルドタスクを実行する環境の Node のバージョンが古いこと。async/await が使えず、プリレンダリングに失敗してしまった。これは、しょうがないので、dist もバージョン管理対象にした。

アレハンドロ・ホドロフスキー監督 エンドレス・ポエトリーを見た

見た。アレハンドロ・ホドロフスキー監督の自叙伝的小説「リアリティのダンス」の映画の続編。

www.youtube.com

臨死体験のような自叙伝

この映画を見て、ホドロフスキー監督が死ぬ前に臨死体験として見る映像はこのようなものなのではないか、という気がした。臨死体験の中には、これまでの人生が映画のように映され、自身が観客となって追体験するというものがある。この種の臨死体験とは、自分の自叙伝を死の直前に自分だけが読むようなものなのかもしれない。マジック・リアリズムの演出も、時折、老いた自分が現れて若き日の自分に語りかける様子も臨死体験と考えれば、なんとなくしっくりくるのである。

最後に両親と和解するシーンは、過去を自分の都合の良いように美化しているようにも見える。批判的な書き方をしたが、人間の脳とはそのようなものではないかと思う。つらい出来事をそのまま受け入れれば、心を病んでしまう。誰もが、どこかでそういった出来事を忘却したり、美化しているのではないか。ましてや、死の直前に自分の人生を受容するためにそれを行うのは、当たり前のことではないだろうか。

自分の人生を生きること

全体を通して、主人公は自分の人生を生きることを追求する。親の言いなりの日々を過ごし、親という他人の人生を生きていた主人公。詩と出会い、詩人になりたいと思った日から、自分の人生を歩みはじめる。サンティアゴの芸術家たちとの交流を通して、徐々に自分の人生を生きはじめるが、それでも、親の期待に応えなければいけないのではないか、という気持ちが心のどこかにひっかかる。物語の最後の最後まで引きずるその気持ちも、両親と別れる際に向き合い、両親が息子の課題を他人の課題として切り捨てた時点で消え去る。この親子の関係、家族が再構築されるクライマックスは、この物語の最大の見せ場としてふさわしく、心を揺さぶられる。

人生の意味と生の肯定

生きることの虚しさや意味に苦しむ若き日の自分の前に、老いた自分が現れる。そして、生きる意味を問われ、ただ「生きろ」とだけ繰り返し語り続ける。

宇宙の長い時間の流れの中で、人の生命は海の中で浮かび上がる一粒の泡のように、ほんの一瞬だけ存在するちっぽけなものかもしれない。それを虚しいと捉えることもできるが、何もないところから生まれてきたという事実に目を向け肯定的に捉えれば、虚無から何かを形作る力があると考えることができる。一般的な人生の意味などはなく、生きる過程で、その力を行使して生きる意味を作り上げ、自分自身にそれを与えていかなければならない。「生きろ」という言葉の繰り返しには、そのようなメッセージがあるのではないかと思う。

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

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

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

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

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

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

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