kitak's blog

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

昭和天皇物語2を読んだ

読んだ。

昭和天皇のお妃選び

下記の藩閥政治の話と絡むのだけど、妃選びが政争の具になるのを避けるために、貞明皇后自ら、積極的に妃を選ぼうとする話が描かれる。具体的には、女学院に乗り込んでいって、よさそうな女学生を探す。
なんとも行動力がある話だけれども、実際にこういうことやってたのかなぁ。この記事を書きながら、貞明皇后Wikipedia のページを読んでいたのだけど、宮中の古いしきたりを破壊したエピソードが多数なので、やりかねないな、と思った。

藩閥政治から政党政治

山縣有朋が皇太子妃を長州の息のかかった人物にしようと画策していて、あー、大正時代でもまだ薩摩と長州のこういう権力争いってやってたんだなぁ、という気持ちになった。
米騒動による寺内正毅の退陣と、その後に政党出身の原敬が首相に選ばれる(元老による推薦)過程が描かれていて、昭和天皇立太子からお妃選びのあたりがちょうど藩閥政治から政党政治へと移り変わっていく時期なのだな、と結びつけることができた。

東郷平八郎

この作品は東郷平八郎の心理描写が面白い。日露戦争の映画やドラマで無口でクールな描かれ方をされ、亡くなった後は神として祀られる東郷が、授業をする学者の発言にいちいち心を乱されたり、「明治が遠く感じる」と弱音を吐いていて、なんとも人間くさい。
また、2巻の時代に起きていた第一次世界大戦について、「テクノロジーの発達によって大量殺戮が可能になり、かつての戦争(日露戦争)に存在していた人間の尊厳が喪失した」と語っている場面も興味深かった。

Webpack を利用した複数の Lambda 関数の管理

こんなかんじでやってみたらどうでしょ、という話。

AWS Lambda を中心にいわゆるサーバーレスのアプリケーションを構築するには、複数の Lambda 関数を作成することになります。
普通のウェブアプリケーションであれば、ひとつのリポジトリでコードを管理して、起動時に実行環境でアプリケーションのコードをまとめて読み込みますが、Lambda 関数を組み合わせてアプリケーションを構築する場合は、アプリケーションの機能ごとに実行環境が分かれることになり、それに応じてコードも分割しなければいけない難しさがあります(その制約を受け入れるメリットのひとつは、リクエスト数が突然跳ねた場合にスケールすることです)。

各 Lambda 関数は「一つのことだけうまくやる」ことを意識して書きますが、設定や汎用的なロジック、API クライアントなど Lambda 関数に跨った共通のコードをどうやって扱うか、という課題があります。
関数単位や共通のロジックで複数のリポジトリに分けるアプローチは様々な作業が煩雑になる(開発時のリポジトリの行き来、イシューやバージョンの管理、各リポジトリの更新...)ので、なるだけ単一のリポジトリでいきたいです。

ひとつのやり方として、Webpack で各 Lambda 関数の index.js をバンドルファイルとして生成するのがいいんじゃないかと思ってます。

Webpack の設定はこんなかんじです。Webpack のバージョンは 4.5.0 です。

const path = require('path');

module.exports = {
    mode: 'none',
    target: 'node',
    entry: {
        foo: path.resolve(__dirname, './src/foo.js'),
        bar: path.resolve(__dirname, './src/bar.js'),
    },
    output: {
        filename: '[name]/index.js',
        path: path.resolve(__dirname, 'dist'),
        libraryTarget: 'commonjs2',
    }
};

Lambda 関数ごとに entry を定義する。 Lambda で動かすために特筆することとして、target オプションを node にするのと、output の libraryTarget オプションを commonjs2 にしていることです。
共通のロジックは別ファイルに分けて、各 entry のファイルでインポートします。

Webpack のようなモジュールバンドラを使う副次的な効果として、アップロード制限の回避があります。モジュールバンドラ無しで Lambda 関数をデプロイする場合、node_modules も含めて zip を作成することになるので、いくつかパッケージを依存に追加した程度でファイルサイズが膨れ上がり、アップロードできなくなります(S3経由でデプロイする必要がある)。
モジュールバンドラを使えば、node_modules から必要な内容だけ取り出して、ひとまとまりのファイルが生成されます。関数がよほど複雑にならなければ、制限に達することはなさそうです(達した場合は、関数分割をすべきタイミングかもしれません)。

Webpack でビルドしたファイルは、デプロイのために zip にする必要があります。
Webpack には zip を生成するサードパーティプラグインがありますが、今回は Webpack に過度に依存することを避けるため、あくまで JavaScript のモジュールバンドラとしての役割のみに徹することにします。
postbuild のタスクで以下のスクリプトを走らせて entry 毎に zip ファイルを生成します。

#!/usr/bin/env node
const path = require('path');
const {promisify} = require('util');
const {exec} = require('child_process');
const globby = require('globby');

const execAsync = promisify(exec);

(async () => {
    const paths = await globby(['dist/*'], {
        onlyDirectories: true,
    });
    // Lambda 関数の数が極端に多い場合は、一定数ずつ作成したほうがよさそう
    await Promise.all(paths.map((path) => {
        console.log(`Create zip in ${path}...`);
        return execAsync(`zip -r Lambda-Deployment.zip * -x *.zip`, {
            cwd: path,
        });
    }));
})();

あとは、生成された zip をウェブのコンソールでアップロードするか、CLI を経由してデプロイします。

API Gateway カスタムオーソライザーを使って、Firebase で認証する

組み合わせただけの話なのですが、個人用メモ。

ちょっと前に「AWSによるサーバーレスアーキテクチャ」を読んだり、手元で色々試してました。本では、認証に Auth0 というサービスを使っているんですが、本が書かれた頃から Auth0 の仕様が大きく変わっています。サンプルを直すのがつらそうだったのと、その章で説明したい内容はあくまでカスタムオーソライザーの設定で、正直、認証サービスはなんでもよさそうだったので、馴染み深い Firebase Auth を代わりに使ってみました。

AWSによるサーバーレスアーキテクチャ

AWSによるサーバーレスアーキテクチャ

Firebase の ID トークンの作成と確認

Firebase はクライアントサイドで認証が完結しますが、バックエンドの API サーバーでログインしているユーザーを知りたい場合があります。Firebase ではクライアントで ID トークンの発行をおこない、サーバーでこれを検証することで実現できます。手順は以下のようなかんじです。

  1. ログイン後に ID トークンを作成する(クライアント)
  2. API リクエストにトークンを付与する(クライアント)
  3. トークンを検証してユーザーの情報を取得する(サーバー)

各手順で使う Firebase の APIID トークンを確認する  |  Firebase の説明がわかりやすいです。1, 2 を簡単にコードで示すと以下のようになります。

firebase.auth().currentUser.getIdToken(true)
  .then((idToken) => {
    return fetch('https://XXX.amazonaws.com/dev/get-profile', {
      mode: 'cors',
      headers: {
        'Authorization': 'Bearer ' + idToken,
      }
    });
  }).then((response) => {
    return response.text();
  }).then((body) => {
     console.log(body);
  }).catch((error) => {
     console.error(error);
  });

3 は次で示します。

カスタムオーソライザーに設定する Lambda のコード

トークンの検証とポリシーの生成をおこなう Lambda 関数を用意します。あとはこの Lambda 関数を API Gateway のリクエストの認証に設定すれば終わりです。

const admin = require('firebase-admin');
const serviceAccount = require('./XXX.json'); // Firebase の管理画面からインストールできる鍵ファイル

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://XXX.firebaseio.com'
});

const generatePolicy = (principalId, effect, resource) => {
  const authResponse = {};
  authResponse.principalId = principalId;
  if (effect && resource) {
      var policyDocument = {};
      policyDocument.Version = '2012-10-17';
      policyDocument.Statement = [];
      var statementOne = {};
      statementOne.Action = 'execute-api:Invoke';
      statementOne.Effect = effect;
      statementOne.Resource = resource;
      policyDocument.Statement[0] = statementOne;
      authResponse.policyDocument = policyDocument;
  }
  return authResponse;
};

exports.handler = function(event, context, callback){
  if (!event.authorizationToken) {
    callback('Could not find authToken');
    return;
  }
  const token = event.authorizationToken.split(' ')[1];
  // ID トークンの検証
  admin.auth().verifyIdToken(token)
    .then((decodedToken) => {
      const policy = generatePolicy('user', 'Allow', event.methodArn);
      callback(null, policy);
    }).catch((error) => {
      console.log('Failed idToken verification: ', error);
      callback('Authorization Failed');
    });
};

つらかったこと、いまいちなこと

あんまり本質じゃないですが、CORS まわりでけっこうハマりました。

  • カスタムオーソライザーでポリシーの生成にしくじっても(実行時エラーにはならないが、ポリシーの内容に問題がある場合)、API レスポンスが正常(200)で返ってくる
    • エンドポイントに紐付いた Lambda 関数は実行されない
    • Lambda 関数が実行できなかったらエラーを返すか、CloudWatch Logs にエラーを出力してほしい
    • (自分が気づいていないだけでどこかにエラーが出ているかも)
  • 「Lambda プロキシ統合の使用」を使うと、API Gateway で「CORS の有効化」をしていても、CORS 関係のヘッダーが付与されない。Lambda 関数でレスポンスを返す際に明示的に指定する必要がある
  • CloudWatch Logs が見づらいのと、複数の Lambda 関数で処理を実現している場合のデバッグで、各 Lambda 関数のログを調べるのが大変
    • (追記) これは S3 にログを集約して Athena で分析することになりそう

ハリー・ディーン・スタントン主演 ラッキーを観た

渋谷のアップリンクで観た。

eiga.com

主役のラッキーを演じるのは、去年の7月に91歳で亡くなった名優ハリー・ディーン・スタントン。この作品は彼の最後の主演作である。

自分がハリー・ディーン・スタントンを知ったのは、ツイン・ピークスの劇場版だった。数分程度の登場シーンだったが、放心した顔でタバコを吹かしながら、意味深な発言をする演技はあまりに印象的で、自分の頭にベタッとこびりついてしまった。
その後に見たストレイト・ストーリーでも、これまた最後の数分だけの登場にも関わらず、顔で語る圧倒的な演技で惹きつけたのだった。

映画のテーマは最後の主演作らしく「死」についてである。
誰しも、生きている間に少なからず死について考える。「どうなるか分からないから考えるだけ時間の無駄」と思う時点で考えてしまっている。ハイデガー的に、考えることで生を有意義なものに転換することもできる。人生を夏休みに例えると、死について考えることは夏休みの自由研究のようなものではないかと思う。自分なりの答えを一生を通して創り上げていく。

ラッキーは、夏休み最後の日、8月31日に改めて自由研究に向き合うことになった。
彼は生粋の現実主義者であり、自身の生命の終わりを意識したとき、彼の心の中に死についての黒く暗い観念が生まれ、徐々に膨れ上がっていく。彼と一緒にこの宿題に取り組むのは、一癖も二癖もある街の住人達である。彼らと言葉を交わしながら、時にぶつかり合いながら、ラッキーは自分の答えを創り上げていく。
黒く暗い観念を打ち消した先に彼が見たものは何か。名優として生きた人間の生前葬としてふさわしい作品である。

Vue コンポーネントをクライアントサイドでテンプレートエンジンとして使う

forum.vuejs.org に回答したやつ。

コンポーネントを描画した結果の HTML 文字列を取得したいときってあるの?とか思われそうなんですが、たまにあるんですよね(ウィンドウポップアップでプレビューを静的なHTMLで表示する必要があったり、v-html で表示する内容の一部を差し替える必要があったり )。

やりかたとしては、以下の手順です。

  1. コンポーネントを登録するオプションオブジェクトを Vue.extend に渡してサブコンストラクタを定義する
  2. サブコンストラクタのインスタンスを生成する(このとき、propspropsData プロパティ経由で指定する。データを変更したい場合はインスタンスのプロパティアクセスで変更する)
  3. $mount メソッドで描画する
  4. $el プロパティで DOM 要素が取得できるので、outerHTML プロパティにアクセスして、HTML 文字列を取得する
const Foo = Vue.extend({
  props: ['name'],
  template: `<p>Hello, {{name}}</p>`
});
const foo = new Foo({
    propsData: {
      name: 'Vue.js'
    }
});
foo.$mount();
const html = foo.$el.outerHTML;
console.log(html);

↑はコンパイラを含む Vue のビルドを使わないとエラーになります。ランタイムオンリービルドを使う場合は、.vue からインポートしたオブジェクトを Vue.extend に渡してください。

SNS を絶つために StayFocusd っていう Chrome 拡張を使い始めた

これ

chrome.google.com

気がついたら、すぐにアドレスバーに「twitter」と入力するので、SNS 中毒を治すために StayFocusd という Chrome 拡張を使い始めた。

指定したドメインの1日あたりのトータル閲覧時間を設定できる。時間を超えたら「仕事してんじゃないの?」っていう煽りページが表示される。正直、イラッとする。
けっこうよくできていて、一度、閲覧が制限された後に、解除しようと制限がリセットされる時間を現在時刻からすぐ後の時間にしようとしても「OK。明日以降はその時間に解除するよ」というメッセージが表示されて防がれる。ぶっちゃけアンインストールすればいいんだけど、「お前さんそこまで自制心ないのか」という心の声が聞こえるので、アンインストールする気持ちが起きない。

なかなかいいツールだな、と思ったんだけど、イケてないところもけっこうある。

  • Chrome 拡張からスクリプトが挿入されるので、それがレンダリングをブロックして、ページが表示されるのが遅くなった
  • サジェストされたドメインamazon.com があって、制限された後に AWS のコンパネが開けなくなってしまった (サブドメを許可リストに追加すればいいんだけど、制限が解除されるまで追加できない)
  • 上と同じで TwitterAPI ドキュメントページが開けなくなってしまった
  • 指定したドメインのトータルの閲覧時間ではなく、ドメイン毎に閲覧時間を設定できるようにしたい
  • 毎日決められた時間に制限をリセットされるのではなく、制限された数時間後に制限を解除する、ような仕様にしてほしい。数時間に一回程度は開きたい

自分で作ってみてもいいんだけど、もし良い Chrome 拡張を知っていたら、教えてください。

13デイズを観た

WOWOW で放送してたので作業しながら観た。

13デイズ [Blu-ray]

13デイズ [Blu-ray]

小学生の頃、金曜ロードショーでやってたのを一度観たんだけど、政治的な駆け引きが当時の自分には難しくてあまり面白く感じなかった。歳を取ってから見ると面白かった。

一番おもしろかったのは、物語の開始直後、航空写真でミサイルが確認され、ケネディ兄弟と主人公のオドネルが初手を迫られるシーンだった。 彼らは、対応策を考えるために、各省の精鋭数名からなる機関を立ち上げる。その判断を下す際のやりとりで以下のような意図や思惑が見えた。

  • 不確実性が高い状況においては、人数を増やせば増やすほど意思決定に時間がかかり、かつ、得られる結論もとんがった部分のない平凡なものでまとまってしまう
  • 大統領の顔色を伺わないように、機関のミーティングには、必要なとき以外、大統領を呼ばない。これは心理的安全の確保に繋がる

その後は、後のケネディ暗殺の黒幕が軍部であることを匂わせるような演出があったり、ケネディ(兄)のカリスマ性に酔って酔って酔いまくってください、というかんじだった(あんまりちゃんと観てなかった)。