具体例から学ぶ!作り込みたいWeb制作のためのJavaScriptライブラリdat.GUIのススメ

(こちらの記事はクライアントさんであるJTさんの許可を得て事例を掲載しました。この場を借りてお礼を申し上げます。ありがとうございます!)

どうもみなさまおはようございます。あるいはこんにちは。あるいはこんばんは。 Tech KAYAC Advent Calendar 2019最終日、25日目の記事を担当します、フロントエンドエンジニアのたがみです。普段何してるかは18日目の記事で話しているので省略します。

先日は自分の趣味爆発させた記事を投稿してしまいましたが(趣味爆発にも関わらず投稿を許可してくださったみなさまありがとうございます)、今年の締めはちゃんと技術の記事を書こうと思い、今回は去年と同じく私の今の推しライブラリについての布教活動をしていきたいと思います。

今年私が色々な案件をやっていて「これは色々と役立てたな〜」と感じたのはdat.GUIでした。元々は同僚さんが使ってるのをみて「あ、なんか便利そう」と思って使い始めたのですが、使ってみて「これは知っておいたらWEB制作でもっと良いものができるようになるぞ?!」と個人的に感じました。なので今回はもっとより良いWEBを作りたい皆さんに向けてその良さを語っていきたいと思います。

この記事の対象読者

  • dat.GUI知らないけど技術の話に興味がある方
  • dat.GUI知ってるけど具体的にどう使うのか知りたい方
  • Web制作を少しでも効率よくスマートにやりたいと感じている方
  • 表現面でもクオリティ面でも限界突破したWebサイトを俺は作りたいんだと内心闘志を燃やしている方

dat.GUIって何?

dat.GUIの画面

github.com

  • JavaScriptで使えるパラメーター調整ライブラリです
  • GUI(グラフィカルユーザーインターフェース)と名がついている通り、このライブラリを読み込んでパラメータの設定を行うだけでスライダーや数値入力ができるフォーム画面ができあがります(画像参照)。
  • パラメーターの値の変化に応じて処理を組み込むことができるため、フォームで設定したパラメータの値に応じて画面を変化させることができます
  • もちろんこういったGUIツールは自作しようと思えばできると思いますが、時間をそこに費やすほど余裕がない。。。!という時にこのライブラリの存在を思い出すと幸せになれるかもしれません

dat.GUIのどこがいいの?

私が使ってみて感じたこのライブラリの良いところは以下の点です

  • 一度dat.GUIの設定をしてしまえば、いちいちコード上で値を変化させて画面の状態を確認…といった手動の操作をしなくて済む
  • dat.GUIの設定をしておくことで、画面の状態の確認をエンジニアだけではなくデザイナーやディレクターさんなど他の職種の人とも共有しやすくなる
  • プロトタイプなどを作る時に、デザインだけでは想像できないインタラクティブな表現のイメージを直感的に伝えることができる

言葉で説明すると伝わりにくいと思うので、こちらのサンプルを見てみましょう。

http://workshop.chromeexperiments.com/examples/gui/

sample

上記のページのパラメータを操作して貰えば分かるように、入力した値に応じて表示されている文字の形や演出が変化しています。このように、「この効果を選んだ時、画面はどのように見えるだろう?」という確認が画面上でスムーズに行うようにできるのが、このライブラリを導入する一番のメリットです。

もっと簡単に言えば、インスタやTwitterなどで画像を投稿する時、用意されてるフィルター選んだら画像の加工結果がすぐに表示されますよね?あれと同じことがこのツールを通じて実現することができるのです。

導入してみる

早速使い方を見ていきましょう。

インストール

cdn経由でもnpm経由でも導入することが可能です。

 // cdnの場合、以下のコードを挿入
<script type=“text/javascript” src=“https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js”></script>

 // npmの場合
npm install --save dat.gui

基本的な導入の仕方 dat.GUIのインスタンスを作成し、GUIに表示させたいパラメータの設定を追加していく、というのが基本的な使い方です。

const gui = new dat.GUI();

// パラメーターの設定。初期値を指定
class Parameters {
    constructor() {
        this.message = 'sample'
        this.angle = 0
        this.isVisible = true
        ...
    }
};

// パラメーターのインスタンスを作成し、GUIに追加
const param = new Parameters();
gui.add(param, 'message');
...

使える入力項目とその設定

dat.GUIで使える入力項目には以下のものがあります。それぞれ addメソッドで指定します。

  • 文字指定(文字列入力)
  • 数値調整(スライダー+数値入力)
  • 効果のオン/オフ切り替え(チェックボックス)
  • 状態の選択(セレクトボックス)
  • イベントの発生(クリックでイベントを発火できる)
  • 色指定(カラーピッカー)

項目の指定の仕方は、パラメーターに設定した初期値の型、ないしはaddで指定した変数によって判断されます。

See the Pen Sample_dat.GUI_1 / param by Mio (@mio_U_M) on CodePen.

ただし、カラーピッカーだけは別で、addColorメソッドで指定します。 16進数カラーコードやRGB、HSVなどで指定が可能です。

See the Pen Sample_dat.GUI_2 / param_color by Mio (@mio_U_M) on CodePen.

値を変更した際に特定の処理を加えたい

guiにaddする際に、メソッドチェーンでonChangeを記述しておけば、値を変更した後の特定の処理を発火させることが可能です。

See the Pen Sample_dat.GUI_3 / onChange by Mio (@mio_U_M) on CodePen.

上記のやり方さえ覚えておけば一通りのGUIの設定は可能です。 もちろん上記以外にも様々な細かい設定を行うことができます。詳しくはAPIのドキュメントやチュートリアルで確認してみてください。

dat.gui/API.md at master · dataarts/dat.gui · GitHub

https://workshop.chromeexperiments.com/examples/gui/

具体的にどう使う? ー AI HOME SCREEN SCANを事例に見てみる

前述の説明でなんとなーく使い方のイメージがついたかとは思います。ただ、具体的にどうやって案件に導入するかを知りたい。。。!と思われた方もいるのではないでしょうか? 百聞は一見に如かず。ということで、実際に私の関わった案件、AI HOME SCREEN SCANでのdat.GUI活用例をお見せしたいと思います。

サイトはこちら↓ (※SP限定)

www.ploom.jp

このコンテンツはカヤックが独自で開発したAIエンジン「AISS」を用いたAI診断コンテンツです(詳しくはこちらに記載しております)。

クライアントであるJTさんはPloom商品(加熱式たばこ)を販売しており、今回このAIエンジンを用いてホーム画面を解析し、結果に基づいて性格分析、ならびにPloom商品のアクセサリーをレコメンドするコンテンツとなっております。

※注意:このAI診断コンテンツ、およびPloomブランドサイトへのアクセスには「20歳以上の喫煙者」の確認が必要となっています!

活用例その1:メインビジュアルを作り込むためのプロトタイプツールに使う

今回この案件でのデザイン的な課題として以下の二点が上がってきました。

  • キービジュアルの方向性として「AIが選出した結果の多様性をだすために有機的なグラデーション表現」で行くことになり、そのグラデーションの調整をデザイナーさんと一緒に詰めていく必要が生じた
  • 「診断結果のページでユーザーがアップロードした画像に応じてグラデーションの色合いを変えよう!」という話になり、結果ページにおける動物アイコンが映えるグラデーションの色を探っていく必要が生じた

デザイナーさんが上げてくれたグラデーションの初期イメージ
デザイナーさんが上げてくれたグラデーションの初期イメージ

そこで今回、サイト制作と並行してグラデーションのイメージやスピード感、色合いなどの検証専用のdat.GUI入りプロトタイプを作成し、デザイナーさんとグラデーションのイメージを詰めていくことにしました。

グラデーションの確認はこのツールを通して行い、スピード感や色合いなどを確認していきました。 (GIF画像なので画質荒くてすみません)

プロトタイプツールでグラデーションの色合いを切り替えている様子
プロトタイプツールでグラデーションの色合いを切り替えている様子

(仕組み的な話をすると、今回グラデーション自体はGLSLで制作したのですが、JavaScriptからGLSLに渡すuniformの項目をdat.GUIのonChangeで操作できるようにつなげています)

時間の経過ごとのグラデーションの状態確認
時間の経過ごとのグラデーションの状態も見れるようになっている

プロトタイプに導入したGUIの入力項目は、デザイナーさんの確認したい要件に合わせて適宜調整し、機能を付け足して行ったりしました。たとえば上記の「time」という項目はデザイナーさんから「グラデーションがいい感じの瞬間をキャプチャしたいんだけど、なんとかできたりしないかな。。。?」という相談から、グラデーションの時間経過を操作できるように入れ込んだ項目です。

このプロトタイプでdat.GUIを導入した結果、デザイナーさん側でグラデーションの色合いや画面上での見え方調整を直接GUIを操作することができるようになり、デザインイメージの再現に貢献することができました。

ビジュアルイメージ
プロトタイプツールのスクリーンショットを用いたビジュアル

また、OGPやキービジュアルのグラデーションをプロトタイプツールから作成するという、実装からデザインへ落とし込むという通常のWeb制作とは真逆のフローになったのも、このツールのおかげです(OGPやこの↑バナーなどのグラデーションは、プロトタイプツールからスクリーンショットを撮って抽出してもらったものなのです)

活用例その2:ページのデザイン確認や診断結果の出し分け確認に使う

また、今回のサイトはSPA(シングルページアプリケーション)形式の診断コンテンツのため、以下の課題もありました。

  • 動物アイコン、診断結果文言、ユーザーの入力項目と解析結果に応じたおすすめ商品…などと、結果画面で表示を出し分ける項目が多かった。そのため、「この解析結果の場合の画面が見たい!」という要望に応えられる環境を作る必要があった
  • SPAなので単純にURLを指定すれば特定のページに飛べる、という状態ではなかった。ただ、デザイン確認のためにいちいちページの初めから辿っていくのはどう考えても手間なので、一発で目的のページを確認できる手段が必要だった

そこでここでもdat.GUIを導入し、出し分け確認用のURLを用意しました。このURLではGUIの項目を変化させれば即座に画面が項目の値に応じた結果を表示できるように設定しています。

表示するページを切り替えている様子
表示するページを切り替えている様子

結果画面の動物アイコンを切り替えている様子
結果画面の動物アイコンを切り替えている様子

このようにdat.GUIを導入した結果、

  • ツールですぐに文言確認や見え方の確認ができるようになったため、文言のミスや、動物アイコンの画像が一部欠けてる!などといった細かいミスをすぐに検出することができた。
  • QA(WEBサイトのテスト)を外部のテスターさんに依頼して実施する際にもこのツールの使い方を共有してテストを実施してもらったため、QAの工数を半分も下げることに成功した(ディレクターさん喜んでいた)

といった作業の効率化、ならびにクオリティ担保に貢献する結果になりました。

やはりGUIが持つ「一度使い方を理解すれば誰でも使用できるようになる」という性質は、案件でスムーズにイメージ共有する上で大きなメリットだと感じました。

最後に

最近のウェブサイトを見ていると、リッチな演出や仕掛けが盛り沢山なウェブサイトや、機能的に色々と詰まったツール的なウェブサイトなどが数多く見られると思います。

そんなサイト達のように、複雑な仕様が求められたり、演出やデザインをより詰めていきたい!という時にぜひこのライブラリを活用してみてください。

一度実装して使い方を共有すれば、エンジニアに限らずデザイナーやディレクターなどの様々な人がwebの完成イメージを共有しやすくなるので、チーム内で「より良いものを作りたい!」という気持ちが高い案件の時には特に、その想いの架け橋になってくれるライブラリだと個人的に感じております。

12月1日から25日間にわたって更新してきました Tech Kayac Advent Calendar 2019もこれが最後の記事です! 技術を軸に色々多方面に展開しまくった記事が揃っていたかと思いますが、みなさまお楽しみいただけましたでしょうか?

また来年もよろしくお願いいたします!みなさま良いクリスマスを&良いお年を!

Lambdaを使ったサーバレス構成の社内アプリのデバッグのためにX-Rayを使ってみた

12月も終盤、2019年も終盤、令和元年も終盤です。みなさんいかがお過ごしですか。ソーシャルゲーム事業部ゲーム技研の谷脇です。

この記事はTech KAYAC Advent Calendar 2019の24日目の記事です。

Migration Trackの方で結構書いたので、こっちは何書こうかなーとなったんですが、あえて小ネタでAWS Lambda使う時のTipsな感じで行きます。

AWS Lambdaのデバッグがしんどい件

僕はサーバレス・コンテナ以前からWebのサーバ開発をやっている人間なもので、動いているやつをダイレクトにソースコードいじってバグを直した経験があります。そこまで野蛮な方法を取らなくても、sshで入ってリクエスト叩きながらログを読んだり、プリントデバッグを仕込むなどの手段を使って、とにかくなんとか直す手段を持っていました。

しかし、コンテナでsshを封じられ、サーバレス環境ではさらに古来からのデバッグ手段を縛られる始末。

例えば、本番で特定のパラメータを入れたときだけ発生するバグは、あらかじめログを詳細に残しておかないと再現することすらままなりません。あと、サーバレスでお手軽にリクエスト単位で見れるログをどうやって吐くの?って問題もありますね。というのをAWS X-Rayを使って解決してみました。

新マスタデータ管理システムakashic

LambdaとX-Rayを組み合わせてみたのは、ゲームを作るための内製システムakashicです。

techblog.kayac.com

Lambda上のakashicのアプリケーションの特性としては、

  • Go言語のLambda関数をALBでInvokeしている
  • 1時間あたりのAPIリクエストが50回から100回程度。使われるのも営業時間中の4時間程度
  • 1回の関数あたりの実行時間は10秒〜60秒
    • Google Sheets APIの待ち時間が多くを占める
  • Lambdaが起動するAPIは、Google Sheetsのメニューに埋め込んだGoogle App Scriptか、akashic-clientというCLIコマンドから呼び出される

f:id:mackee_w:20191223174820p:plain
akashicのGASからの呼び出しメニュー

と言った感じです。業務に必要な内製ツールではよくあるようなワークロードではないでしょうか。

内製ツールの運用負荷というのを下げるという意味でも、akashicをAWS Lambdaで動かしています。複数人が同時に使っているときなどには、勝手にスケールアウトしますし、永続化層をakashicが持っていないため、Lambdaに適したアプリケーションであると言えます。

実際にこの構成で大体のケースではちゃんと動くのですが、スプレッドシートという自由な入力データを扱い特性から、使用している方からしばしば意図通りに動かないという問い合わせがありました。

よく多い、ちゃんと動かないという問い合わせとしては、

  • APIの実行時間がタイムアウトしたという表示(GAS)
  • 特定のシートを読み込んだときだけエラーが出る

などがあります。一個ずつ見ていきます。

「APIの実行時間がタイムアウトした」

akashicのGoogle Sheets統合の仕組みは、シート上のメニューからGoogle App Scriptの関数を実行し、関数の中でシートのキー名や各種オプションを読み取った上で、ALBにぶら下がっているLambdaにリクエストを投げているという仕組みです。

最初の方では別に問題がなかったのですが、開発が進んで取り込むマスタデータの量が多くなってきたところで、が「APIがタイムアウトしました」というエラーがGASのほうで出てきたようです。

早速Lambdaの実行時間を、ログを見て調べてみました。すると、リクエストを受けてからレスポンスを返しきるまで61秒から59秒ほどかかっていましたが、Lambda上では元気に200を返した気になっているログが出ています。

ここで、「はは〜ん、GAS側に1分でなにかが起こる制限があるんだな〜」と思って、GASのドキュメントを調べてみます。

Quotas for Google Services

GASユーザはよくご存知の通り、GASの実行時間には6分という制限があります。

Script runtime 6 min / execution

ですが、URL Fetchの項目を見てもリクエストやレスポンスのサイズに関する制限があるだけで、「1分的なもの」はありません。

うーむ、と思いましたが、とりあえず余裕を見て50秒ぐらいに納めればだいたいタイム・アウトしなくなるのはわかってたので高速化していきます。

「推測するな、計測せよ」

見出しの言葉の通り、何が時間を食っているのか(いや、推測はできていたのですが)知らないふりをして、計測する手段を導入します。

Lambdaで時間がかかるといえば何個かパターンがあります。

これを可視化するために使ったのが、分散トレーシングサービスのAWS X-Rayです。本来は、多数のマイクロサービスを束ねたようなサービスを一気通貫でログを見たり、リクエストの経路を追いかける目的のサービスですが、外部APIを多く叩くような場合でも有用です。

幸いなことにAWS LambdaはX-Rayとネイティブに連携出来るため、ECSなどで使うときのような別途デーモンを立てたりするような手間ありません。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-x-ray.html

また、Goなので、X-Ray SDK for Goを使えば、だいたい事足ります......。いやそんな簡単なものではなかった。

Webサービスならまだしも、多くのフィルタやアウトプットする君を入れ替えられるakashicの設計では、一部のコンポーネントにうまくcontextが伝搬していないケースがありました。なので、そこそこの書き換えが発生しました。

別のサービスで、同じようなことを倍ぐらいやった、同僚のれもんさんの資料があるので、これを見てください。

開発期間数年のサービスの完成間際にcontext対応をぶち込む / introduce context.Context into long term project - Speaker Deck

なにはともあれ、akashicにX-Rayを導入できました。すると、このような感じでいい感じに...

f:id:mackee_w:20191223174943p:plain
直列にSheets APIリクエストが連なっている...

oh...。やはりGoogle Sheets APIのレスポンス待ちで時間がかかっているようです。Google Sheets APIはデカいシートを一気にAPIで取ってこようとすると、Google側でタイムアウトしたり、何分も時間が掛かるケースがあったため、このように1つのファイルでも複数回にAPIリクエストを分割して取ってくる仕組みになっています。

どうやらここを並列化すれば良さそうですね。goroutineとchannelでちょちょちょいっと。

f:id:mackee_w:20191223175013p:plain
APIリクエストを並列化した結果

このとおりきれいに並列リクエスト化できましたね。ただ、API Quotaなどでn分間あたりのリクエスト数が決まっているので無闇矢鱈に並列化するのはやめましょう。ちなみに、Sheets API v4の場合は、1プロジェクトあたり100秒間で500リクエスト、1ユーザあたり100秒間で100リクエストという制限があります。注意しましょう。

Usage Limits  |  Sheets API  |  Google Developers

これで50秒以内にだいたい処理が終わるようになったため、タイムアウトが発生することはなくなりました。しかし、並列リクエストなどのチューニングを駆使しても1分を超えることはあるかと思います。そのときはStep FunctionsでLambdaを起動する形に改めようかと思っています。

「特定のシートを読み込んだときだけエラーが出る」

上記の話を見て、だいたいバグだったり入力側でどうにか出来る話が多いのですが、ここで困るのは再現手順です。そこで、以下のようにX-RayのAnnotationとして、リクエスト時のパラメータを埋め込んでみました。

xray.AddAnnotation(ctx, "SchemaRef", exportReq.SchemaRef)
xray.AddAnnotation(ctx, "Tag", exportReq.Tag)

するとこのような感じでリクエストごとのパラメータが、トレース結果内に埋め込まれます。

f:id:mackee_w:20191223175100p:plain

また、お問い合わせIDとして、X-Rayのtrace_idを表示すると、エラーが出たといわれたときに「お問い合わせIDはなんですか」と聞くだけで、どういう状況でエラーが起こったかがわかります。

f:id:mackee_w:20191223175115p:plain

エラーログもtrace_idに紐付いているので、わざわざ同じ状況を確認しなくても、ログからエラー内容を確認することも出来るようになりました。

X-Rayのデメリット

カヤックでは内製システム以外でも、ユーザにレスポンスを返している部分でX-Rayを組み込んでいます。ですが、全てのリクエストに対してログをサンプリングしていると、お金がかかってしまいます。

料金- AWS X-Ray | AWS

なので、一般的には5%や1%程度のリクエストをサンプリングしてトレース記録を行ったり、例外が起こったときのみトレースログを送信するなどの工夫をしています。

ですが、内製システムのような場合ですとリクエスト数もたかが知れているので、全件トレースされてもそこまで料金に影響がありません。実質便利になるだけです。

もう一つのデメリットとして、ローカルで動かす際に、X-Rayデーモンがいないと言われて、エラーログが大量に出る場合があります。それに関しては、以下のようなemitterを噛まして、出力を抑制しています。

    ss, _ := sampling.NewLocalizedStrategyFromJSONBytes(
        []byte(`{ "version": 2, "default": { "rate": 0 } }`),
    )

    xray.Configure(xray.Config{
        SamplingStrategy:       ss,
        ContextMissingStrategy: ctxmissing.NewDefaultLogErrorStrategy(),
        LogLevel:               "error",
        Emitter:                &DiscardEmitter{},
    })
// DiscardEmitter は どこにもログを送信しないemitterです
type DiscardEmitter struct{}

// Emit は本来XRayデーモンに送るメソッドですが何もしません
func (d *DiscardEmitter) Emit(seg *xray.Segment) {}

// RefreshEmitterWithAddress は本来XRayデーモンのアドレスを設定しますが何もしません
func (d *DiscardEmitter) RefreshEmitterWithAddress(raddr *net.UDPAddr) {}

まとめ

  • Lambdaでサーバレスアプリケーションを作る時に、デバッグやトラブルシューティング目的でX-Ray組み込むと便利という話をしました
    • SAM Localを使ってデバッグするという別の方向性もあるかとは思います
    • ですが、Google Sheets APIと組み合わせて使用する以上、実際にデータ出ないと出ない不具合を解決するためにX-Rayが便利でした
  • 内製ツールをLambdaで作っておくと、ほぼ手放し運用ができますし、コストもかからないのでぴったりなユースケースです