Percy + CypressでVisual Regression Test

自動テストの中にやっとの思いでe2eテストを組み込んだとしても、「視覚的に正しかったかどうか」という点までキャッチするのはなかなか難しいです。このような観点でテストする一つの手段として「Visual Regression Test」があります。僕自身このテストにはVue Fes Japan 2018の以下の発表で知りました。

ここで紹介されていた「reg-suit」が気になって、自分のローカル環境でも検証してみたこともあります。

github.com

medium.com

今回共有したい内容はこのVisual Regression TestのSaaSであるPercyです。

percy.io

たまたまTwitterの広告で流れてきたのを見たのですが、最近僕が使っているe2eテストフレームワークCypressとも連携できるとのことで気になって試してみました。

参考にしたのは以下のYoutubeの内容で、Percy.ioのCEOである@mikefotinakisとCypress.ioのVPoEである@bahmutovによるCypress + Percyの紹介になります。

www.youtube.com

動画内の以下スライドも非常に参考になります。

slides.com

想定する読者

  • e2eテストなんとなくわかる(Cypressなんとなく知ってる)
  • CIなんとなくわかる(CircleCIなんとなく知ってる)

細かい説明はごっそり省くので、この記事は初心者向けの記事ではありません。また、上記Youtubeが理解できる人はこの記事を読まなくて良いと思います。

概要

細かく一つ一つ説明しているとこの記事がとんでもなく大きくなってしまうので、ざっくりと登場人物と役割を説明します。

テスト対象としては動画内のものと同様、以下のサンプルアプリ(ピザ注文アプリ)を使います。

github.com

f:id:kenev:20190825090330p:plain
angular-pizza-creator

e2eテストのテスト項目としては

  • ユーザーはピザを注文できる

というものがあります。ここで活躍するのがCypressです。画面のボタンをポチポチしながらピザが注文できることをテストします。

このテストで確認することが困難なのが、

  • アプリが正しく表示されているか

という観点です。ここで活躍するのがPercyです。(どのように活躍するのかはこの記事の続きでウォークスルーします)

そしてこれらをGitHubのPRのたびに継続的にテストさせるために活躍するのがCircleCIとなります。

Percyを使うにあたってCypress, GitHub, CircleCIは差し替え可能です

それではPercyを試してみたいと思います!

前提

Percyの準備

Percyの登録

https://percy.ioにアクセスしますして、GitHubアカウントで登録しました。

f:id:kenev:20190825092327p:plain
登録

続いて、Company nameとメールアドレスの登録になるのですが、このCompany nameGitHubでいうOrganizationだったりOwnerみたいな扱いになってそうなので注意が必要です。(Individualにしたら重複エラーになってしまった)

f:id:kenev:20190825092413p:plain

最初のプロジェクトの登録をします。1リポジトリに複数のプロジェクトを登録できるみたいですが、今回はリポジトリの単位=プロジェクトの単位と考えて良さそうです。

f:id:kenev:20190825092700p:plain
初期プロジェクトの登録

これで初回登録のフローが完了しました!

f:id:kenev:20190825092904p:plain

Percyと外部サービス連携

上のキャプチャに表示されている「Add integration」から、外部サービスを連携させることができます。

f:id:kenev:20190825093230p:plain
外部サービス連携

GitHub連携

GitHubとPercyを連携します。GitHub Appをインストールすることになります。

f:id:kenev:20190825093322p:plain
GitHub連携

諸々許可をするとInstalledになります。

f:id:kenev:20190825093417p:plain
GitHub連携完了

連携外部サービスにGitHubが追加されているのがわかります。

f:id:kenev:20190825093524p:plain

Slack連携

Slack連携も簡単に行うことができます。以下の中から何を通知させるか選択できるようですが、とりあえず全部通知させるようにしてみました(通常はUnreviewedだけで十分だと思います)。

  • Unreviewed
    • 画面上で差分が出たもののレビューが必要な場合に通知
  • No changes
    • 画面上で何も差分が無かったものでも通知
  • Auto-approved
    • 自動Approve設定されているブランチでは自動Approveが行われるが、そのタイミングでも通知
  • Approved
    • ビルドがApproveされたときにも通知

f:id:kenev:20190825093734p:plain
Slack連携

以上でGitHubとSlackの連携が終わって以下のようになります。

f:id:kenev:20190825094248p:plain
GitHubとSlack連携完了

CircleCIの設定

PercyとCircleCIを連携します。これに関しては公式ドキュメントでも説明されています。

docs.percy.io

リポジトリ追加

CI連携しておきたいので、CircleCIにリポジトリを追加します。

f:id:kenev:20190825094456p:plain
CircleCIにProject追加

サードパーティ製のorbsを許可

PercyはCircleCIで使えるorbsを提供しています。これを使うためにはCircleCIのSecurity設定からorbsの使用を許可する必要があります。これが設定されていない場合にはCircleCIで以下のようなエラーになります。

f:id:kenev:20190825095030p:plain
ビルド失敗

CircleCIのSettings > Securityの項目で、以下のように設定することでorbsが使えるようになります。ビルドエラーにも書いてありますが、この設定をONにして、CircleCIのJobをRerunしても失敗するので注意!新しいJobを発火させないとビルドエラーは解消されません。

f:id:kenev:20190825095425p:plain
Settins > Security

PERCY_TOKEN環境変数の設定

Visual Regression Testなので、Percyにテスト時の情報を送信する必要があります。そのためにはトークンが必要になるので、PercyのProject Settingsからトークンを取得します。

f:id:kenev:20190825100405p:plain
PERCY_TOKEN

これをCircleCIのProjectのSettings > BUILD SETTINGS > Environment Variablesから設定します。

f:id:kenev:20190825100554p:plain

CI実行

以上の設定が完了したらCircleCIのWorkflowを実行します。全部うまくいけばCIは成功!

f:id:kenev:20190825100640p:plain
CI成功

Percy検証

ここまでで設定が完了したので、いよいよPercyの機能を見てみます。

ベースデータの登録確認

現時点でのPercyのプロジェクトを覗いてみると:

f:id:kenev:20190825100856p:plain

何やら結果が増えているのがわかります。(1のFailは気にしないでください)

この中を見ると、Percyのレビュー画面に遷移します。

f:id:kenev:20190825100945p:plain
レビュー画面

それっぽい画面が表示されているのがわかります!ちょっと最初はわかりづらいかもしれませんが、左が比較元で、右が今回の変更点です。初めてPercyに情報を送ったので、左側には何もないのがわかります。

初回は全てApproveしちゃってOKなので、右上のApprove Allをクリックします。

f:id:kenev:20190825101258p:plain
Approve All

Percy上でもUnreviewedからApprovedになっているのがわかります。

f:id:kenev:20190825102906p:plain

これでベースとなるデータの登録ができたことになります。

画面に変更を加えて検証

ここからが本番!それでは、なんらかの拍子にピザの色をCSSで緑に変更してしまったとしましょう。

f:id:kenev:20190825103505p:plain
ピザの色を緑に変更

通常のe2eテストであればこのバグをキャッチするのは難しいです(不可能ではない)。しかし、Percyを利用していると変更前の画面と、変更後の画面の比較を自動で行ってくれるので、キャッチできるんです!

ということで上の変更内容をGitHubにPushしてPRを作ります。PercyとGitHubの連携も行っているおかげで、PRにPercyが表示されるのがわかります。

f:id:kenev:20190825103850p:plain

ここからPercyに飛ぶと!

f:id:kenev:20190825104128p:plain

なんだか想定と違う。。。?比較元はさっき用意したはずなのに無い。今更ですがそもそも「比較元はどうやって選んでいるのか?」という点が気になります。

公式ドキュメントに専用のページがあります。

docs.percy.io

確かに今回の検証はちょっと特殊で、sample-branchをベースにして、そこからsample-change-pizza-base-colorのbranchを生やしているので、比較元はsample-branch、比較先はsample-change-pizza-base-colorとなります。公式ドキュメントのPull request buildsに該当するから、特に意識せずに動作してほしいところですが、どうもそんなに単純な話じゃないみたいです(僕が設定を間違えている可能性が高いのでこれは別途調査が必要)。

明示的に比較元を設定することができるので、今回はCircleCIからPERCY_TARGET_BRANCH環境変数を設定してテストを再実行することにしました。

f:id:kenev:20190825105407p:plain

再度Percyの結果を見ると:

f:id:kenev:20190825105506p:plain

「おお!!」と言いたくなるようなdiffが生成されています。差分が赤くわかりやすく表示されています。そしてその部分をクリックすると、元の画像とのトグルもできるようになっているんです。

f:id:kenev:20190825105629g:plain

感動します!!

また、Slackにも随時ちゃんと通知が来ているのでSlackユーザーにとってもうれしい限り。

f:id:kenev:20190825110729p:plain

疑問点については指摘をして、修正してもらうorApproveをすることでレビューのプロセスを勧めていくことができます。

Percyをうまく活用することでかなりレビューの手間を省けるのと、品質のレベルを1ランク上げられるのではないかと思います。

まとめ

  • Percy + Cypressを試した
  • SaaSのおかげでVisual Regression Testのハードルがかなり下がったと感じた
  • 画面の表示に関連したレビューのプロセス(スピード)がかなり改善すると感じた

まだまだ氷山の一角

この記事で紹介できたのはまだまだPercyの力のほんの一部です。(僕もまだ触ったばかり)

例えばSDKであれば現時点(2019-08-25)で↓だけ項目が用意されています。

さらに、機能に関しては上で紹介している基本的なものに加えて

  • Ignore regions
    • CSSでPercy実行時だけ表示・非表示にする場所を制御できる
  • Responsive visual testing
    • Percy用のSnapshot取るときに幅を指定してレスポンシブデザインのチェックもできる
  • Cross-browser visual testing
  • Automatic diff matching
    • 同じ差分内容であればPercy側で勝手にグルーピングしてくれて、まとめてApproveできる

があります。

試してみたいことが山のようにありますが、この記事はここまでとします!

Percyを使ってガンガン恩恵を受けていきたい気持ちが高まりました。気になるのは料金体系ですが、どうやらこれはSnapshotの枚数で変わってくるみたいです。「月に何枚のSnapshotを撮るのか」というのはシステムのユースケースと、変更の量(PR)、クロスブラウザだったりレスポンシブをどこまでテストするかなどで変わってくるのでパッとは想像しにくいですね。人がそれをテストした場合のコストと比較してみると恩恵がわかりやすくなりそうです。

Percy以外にもSaaSがあると思いますので、それらとの比較も検証したいですね!

OPAのDecision Logsを使ってログを残す

Open Policy Agent (以下OPA)で様々なPolicyの判断をするにあたって、ログをどのように残すのかが気になってきました。

OPAを初めて聞いた、あるいはあまり知らない場合は以下の記事を参考にしていただければ!

kenfdev.hateblo.jp

ログを残す方法としてOPAではDecision Logsという仕組みがあるのがわかりました。

www.openpolicyagent.org

実際に使ってみたのでどんな感じなのかというのを紹介します。

TL;DR

この記事のサンプルコードのリポジトリを以下に置いてますので、docker-compose upでお試しできます!

github.com

Decision Logsの仕組み

Decision Logsの仕組みはシンプルで、OPAの設定ファイルで指定したLog ServiceにPOSTしてくれます。流れとしては下図の通り。

f:id:kenev:20190623083920p:plain

百聞は一見に如かずなので、実際に簡単なサンプルコードで見てみます!

OPAにはcurlでリクエストして、Log Serviceには超シンプルなNode.jsのサーバーを使います。

f:id:kenev:20190623111521p:plain

Policyの準備

まずはPolicyを作ります。policy.regoというファイルで、下のようなuser == "john"であればtrueになるallowというルールを定義しておきます。

# policy.rego
package foo

default allow = false
allow {
  input.user == "john"
}

Policyが正常に使えるか見てみます。まずはdockerコマンドで立ち上げ。

docker run -p 8181:8181 -v $(pwd):/workspace openpolicyagent/opa run --server /workspace/policy.rego

今回作ったPolicyはpackage foo配下のallowルールになるのでエンドポイントは/v1/data/foo/allowになります。curlで下のようにリクエストしてみると:

$ curl -XPOST -d '{ "input": { "user": "john" } }' http://localhost:8181/v1/data/foo/allow
{"result":true}

結果が{ "result": true }で返ってきているのが確認できます。

Mockサーバーの準備

ログを受け取れるように簡単なAPIサーバーをNode.jsで作ります。server.jsとして下のNode.jsサーバーを作ります。

// server.js
const express = require('express');
const bodyParser = require('body-parser')

const app = express();
const port = 3000;

app.use(bodyParser.json())

app.post('/logs', (request, response) => {
  console.log(JSON.stringify(request.body, null, 2));
  response.sendStatus(200);
});

app.listen(port, err => {
  if (err) {
    return console.log('something bad happened', err);
  }

  console.log(`server is listening on ${port}`);
});

これはポート3000で待ち受けてPOST /logsエンドポイントを公開しているサーバーになります。POST /logsを受け取るとログをconsole.logに出力して、レスポンスは200を返すようにしています。

OPAの設定

Logサービスはできたので、次にOPA側のDecision Logsの設定を加えてログが送信されるようにします。

まず、OPAには設定ファイルを--configオプションとして渡すことができます。以下、公式ドキュメントで詳しく記載されています。

www.openpolicyagent.org

設定ファイルはYAMLで書き、Decision Logsを有効化するには以下のような設定にします。

# config.yml
services:
  logger:
    url: http://logger:3000/

decision_logs:
  service: logger
  reporting:
    min_delay_seconds: 5
    max_delay_seconds: 10

OPAはdecision_logs.serviceに設定したサービスのエンドポイントに下の形でログを送信します。

POST /logs[/<partition_name>] HTTP/1.1
Content-Encoding: gzip
Content-Type: application/json

デフォルトで/logsになるのですが、/logs配下の別の場所にリクエストを飛ばしたければ設定ファイルのdecision_logs.partition_nameに値を設定します。

decision_logs.min_delay_seconds, decision_logs.max_delay_secondsが若干わかりづらいのですが、上の設定であれば、5秒毎にログを送信して、送信失敗した場合は送信間隔が最大10秒になるまではリトライしてくれるという内容になります。

delay_secondsの挙動についてはドキュメントだと曖昧だったのでコードで確認しました

検証

ではOPAとLog用のServiceが立ち上がるようにdocker-compose.ymlを作ります。

version: '3'

services:
  logger:
    build: ./logger
    image: logger
  opa:
    image: openpolicyagent/opa:0.13.0
    ports:
      - 8181:8181
    volumes:
      - .:/workspace
    command: ["run", "--server", "-c", "/workspace/config.yml", "/workspace/policy.rego"]

docker-compose upで起動します。

先程同様にOPAにリクエストしてみましょう!

curl -XPOST -d '{ "input": { "user": "john" } }' http://localhost:8181/v1/data/foo/allow

結果は以下のように返ってくるのがわかります。

{
  "decision_id":"79f24c2d-ce6a-4da1-8d2f-bd0b3c23a690",
  "result":true
}

docker-composeのログを見るとNode.jsのサーバーとOPAもログを出力しているのがわかります。

logger_1  | [
logger_1  |   {
logger_1  |     "labels": {
logger_1  |       "id": "abc47ac3-ab0a-4e4f-9ba2-316af7b2c9b4",
logger_1  |       "version": "0.13.0"
logger_1  |     },
logger_1  |     "decision_id": "79f24c2d-ce6a-4da1-8d2f-bd0b3c23a690",
logger_1  |     "path": "foo/allow",
logger_1  |     "input": {
logger_1  |       "user": "john"
logger_1  |     },
logger_1  |     "result": true,
logger_1  |     "requested_by": "172.18.0.1:59272",
logger_1  |     "timestamp": "2019-08-10T00:30:01.4419929Z",
logger_1  |     "metrics": {
logger_1  |       "timer_rego_module_compile_ns": 18600,
logger_1  |       "timer_rego_module_parse_ns": 32800,
logger_1  |       "timer_rego_query_compile_ns": 214200,
logger_1  |       "timer_rego_query_eval_ns": 90700,
logger_1  |       "timer_rego_query_parse_ns": 1098600,
logger_1  |       "timer_server_handler_ns": 1691000
logger_1  |     }
logger_1  |   }
logger_1  | ]
opa_1     | {"level":"info","msg":"Logs uploaded successfully.","plugin":"decision_logs","time":"2019-08-10T00:30:01Z"}

Decision Logが送信されているのがわかります!これでOPAがどのようなインプットでどのような判断を下したのかというログを残していくことができることがわかりました。

機密データのMasking

それでは、Decision Logsについてもう一歩先に進んでみます。

例えば以下のようなリクエストをOPAに投げたとします。

curl -XPOST -d '{ "input": { "user": "john", "password": "secret" } }' http://localhost:8181/v1/data/foo/allow

このときのDecision Logは以下のようになります。

logger_1  | [
logger_1  |   {
logger_1  |     "labels": {
logger_1  |       "id": "abc47ac3-ab0a-4e4f-9ba2-316af7b2c9b4",
logger_1  |       "version": "0.13.0"
logger_1  |     },
logger_1  |     "decision_id": "64475260-33ec-4d29-b375-afb61a2b2624",
logger_1  |     "path": "foo/allow",
logger_1  |     "input": {
logger_1  |       "password": "secret",
logger_1  |       "user": "john"
logger_1  |     },
logger_1  |     "result": true,
logger_1  |     "requested_by": "172.18.0.1:59276",
logger_1  |     "timestamp": "2019-08-10T00:35:55.2005304Z",
logger_1  |     "metrics": {
...
logger_1  |     }
logger_1  |   }
logger_1  | ]

おもいっきり"password": "secret"が出力されているのが確認できます。

"input": {
  "password": "secret",
  "user": "john"
}

このように機密性の高いデータがinputに含まれている場合に、それが平文でログに残されていくのは穏やかな状況ではないですよね。そういう情報を隠せるようにOPAのDecision LogsにはMaskする機能があります。

仕組みとしては比較的シンプルで、OPA自身がDecision Logを送信する前に(デフォルトでは)data.system.log.maskのルールを評価します。その結果に応じて情報を消します(Maskします)。これもさっそく実装してみます。

mask.regoというファイルを作って、中身を以下のようにします。

# mask.rego
package system.log

mask["/input/password"]

これはsystem.logパッケージのmaskルールを作ったことになり、OPA上のルールとしてはdata.system.log.maskに配置することになります。maskルールの中身(mask[<ここ>])にはJSON Pointerを記述することができて、そこに消したい(Maskしたい)情報へのパスを記述します。上の例であればinput.passwordの内容をログから消したいのでmask["/input/password"]と記述します。

あとはこのmask.regoを起動時に読み込んでおけば大丈夫です。docker-compose.ymlcommandを編集しておきます。

# docker-compose.yml
version: '3'

services:
# ...
  opa:
# ...
    command: ["run", "--server", "-c", "/workspace/config.yml", "/workspace/policy.rego", "/workspace/mask.rego"]

それではもう一度サービスを再起動(docker-compose down && docker-compose up)してみてリクエストしてみます!

curl -XPOST -d '{ "input": { "user": "john", "password": "secret" } }' http://localhost:8181/v1/data/foo/allow

Decision Logsを見るとinputからpasswordが消えているのがわかります!消されたものに関してはerasedの中にログが残るのも確認できます。

logger_1  | [
logger_1  |   {
logger_1  |     "labels": {
logger_1  |       "id": "734fe644-cfa9-4fed-9b87-dd11edbd4c8e",
logger_1  |       "version": "0.13.0"
logger_1  |     },
logger_1  |     "decision_id": "9bbf532b-b7f8-49b9-a8fd-2711c24ece70",
logger_1  |     "path": "foo/allow",
logger_1  |     "input": {
logger_1  |       "user": "john"
logger_1  |     },
logger_1  |     "result": true,
logger_1  |     "erased": [
logger_1  |       "/input/password"
logger_1  |     ],
logger_1  |     "requested_by": "172.18.0.1:54560",
logger_1  |     "timestamp": "2019-08-10T00:46:02.994297Z",
logger_1  |     "metrics": {
logger_1  |       "timer_rego_module_compile_ns": 18800,
logger_1  |       "timer_rego_module_parse_ns": 1723900,
logger_1  |       "timer_rego_query_compile_ns": 248100,
logger_1  |       "timer_rego_query_eval_ns": 206800,
logger_1  |       "timer_rego_query_parse_ns": 2582800,
logger_1  |       "timer_server_handler_ns": 5260600
logger_1  |     }
logger_1  |   }
logger_1  | ]

このように、Decision LogsのMaskingができることも確認できました。MaskのルールはRegoで書かれているので、ルールの中身を書くことでさらにMaskする条件を絞っていくこともできます。

例えば(こんなルールは無いでしょうけど)userjohnのときだけMaskしたい場合は、下のようなMaskルールを書くことができます。

package system.log

mask["/input/password"] {
  input.user == "john"
}

v0.13.0から使えるようになったConsole Decision Logger

この記事を書いている途中でOPAのv0.13.0がリリースされました!そしてその中でタイムリーにDecision LogsにConsole Decision Loggerという機能が追加されました。今までDecision Logsを見るには上のようにLoggerを別サービスで用意しておく必要がありましたが、v0.13.0からは標準出力してくれるようになりました!!これは開発時にはかなりうれしい機能になります。

さっそくこれも試してみます。2種類やり方があります。

config.ymlで指定

config.ymlは以下のようになります。(削除する場所がわかりやすいように元々の設定をコメントアウトしています)

# 必要なのはdecision_logs.console = trueのみ!

# services:
#   logger:
#     url: http://logger:3000/

decision_logs:
  console: true
  # service: logger
  # reporting:
  #   min_delay_seconds: 5
  #   max_delay_seconds: 10
起動引数で指定

OPAの起動引数に含めるやり方でも大丈夫です。

opa run --server --set decision_logs.console=true

今回の例であればdocker-compose.ymlを以下のように変えて、config.ymlを使わないようにすることもできます。

version: '3'
# ...
  opa:
    image: openpolicyagent/opa:0.13.0
 # ...
    command: ["run", "--server", "--set", "decision_logs.console=true", "/workspace/policy.rego", "/workspace/mask.rego"]

Console Decision Loggerを検証

では、再びサービスを再起動(docker-compose down && docker-compose up)してリクエストしてみます!

curl -XPOST -d '{ "input": { "user": "john", "password": "secret" } }' http://localhost:8181/v1/data/foo/allow

今回はLoggerサービスではなく、OPA自身のサービスに注目すると:

opa_1  | {"decision_id":"2bb5bd90-856b-4ba7-87db-d995b96d3cca","erased":["/input/password"],"input":{"user":"john"},"labels":{"id":"d54018d5-6700-437f-829e-6fa24210972c","version":"0.13.0"},"level":"info","metrics":{"timer_rego_module_compile_ns":17900,"timer_rego_module_parse_ns":18300,"timer_rego_query_compile_ns":209900,"timer_rego_query_eval_ns":78600,"timer_rego_query_parse_ns":823000,"timer_server_handler_ns":1331300},"msg":"Decision Log","path":"foo/allow","requested_by":"172.27.0.1:45836","result":true,"time":"2019-08-10T21:44:21Z","timestamp":"2019-08-10T21:44:21.378772Z"}

1行にまとめられてるのでちょっとみづらいですが、OPAのログにDecision Logが標準出力されているのがわかります!これはさり気なく結構うれしい新機能だと思います。開発時に積極的に使っていきたいです!

修正方法については以下PRにもしています。

github.com

まとめ

  • OPAではDecision Logs機能を使うことでどういうインプットでどういう判断をしたかログを残すことができる
  • Decision LogsにはMask機能があるのでログを残しておきたくない情報(パスワードなど)を消すことができる
  • v0.13.0からはConsole Decision Loggerが追加されて、Decision Logsが容易に見れるようになった

おまけ

JSON Pointerと配列に関するドキュメントの記述がちょっとわかりにくかったのでプチContributionしました!

github.com

KubeCon + CloudNativeCon Europe 2019の「Deep Dive: Open Policy Agent」を視聴して

しばらくOPA(Open Policy Agent)の講演を観ることができていなかったので、KubeCon CloudNativeCon Europe 2019の講演をYoutubeで視聴しました。

www.youtube.com

構成

時間 内容
1:27 Example: Application
5:38 Example: Kubernetes Platform
7:14 OPA: Unified Policy Enforcement Across the Stack
8:40 OPA: General-purpose Policy Engine
11:25 Community Growth
12:53 Community Highlights: Configuration Guardrails
15:30 Community Highlights: Chef Automate IAM
16:42 Community Highlights: RPG Engine
17:24 Recent Developments
17:27 play.openpolicyagent.org
20:45 v0.11: Improved Debugging: notes trace filter
22:04 v0.11: Language Improvements: some keyword
23:20 v0.11: Native Integrations: WebAssembly progress
25:25 Looking forward
25:33 Google Summer of Code: IPTables integration
26:43 Use Case: Application & End-user Authorization
30:09 Help Us Improve Discoverability
31:31 Q&A

OPA: Unified Policy Enforcement Across the Stack

OPAは様々な場面においてPolicyを適用できると述べています。以下はそれぞれ紹介されている場面と、関連するサービスや技術スタックです。

  • Admission Control
  • Container Execution, SSH, sudo
  • Microservice APIs
    • e.g. Istio, Linkerd, spring, Kong, envoy
  • Risk Management
    • e.g. Terraform, Forceti Security
  • Data Protection & Data Filtering
    • e.g. ceph, kafka, MinIO, SQLite, elastic

OPA: General-purpose Policy Engine

OPAの概要について述べています。OPAの概要については僕も記事を書いてますのでざっくりどういうものか知りたい方はぜひ!

kenfdev.hateblo.jp

講演の中では以下の話が特に重要だと感じます。

Decouple "Policy Decision Making" from "Policy Enforcement"

APIを実装する場合、何も考えずに実装していくとPolicyに関わる部分も自然とビジネスロジックの中に書いてしまったり、データベースに情報を取得しにいく部分で書いてしまいがちです。Policyによる判断(Decision)をする場所と、判断に基づいた処理(Enforcement)を行う場所を分ける(Decouple)ということです。

Enforcement is the Service's job. Making the decision is OPA's job.

判断(Decision)はOPAがして、判断に基づいた処理(Enforcement)はサービス側で、というマインドが大事になりそうですね!

Community Highlights: Chef Automate IAM

Chef Automate IAMのユースケースを紹介しています。Chef Automateは内部的にOPAをPolicy Engineとして使っていて、OPAをライブラリとして使う例としてもとても勉強になるOSSです。また「Well-documented architecture」と述べられているように、以下のドキュメントがとてもわかりやすくOPAを使った認可の仕組みを説明してくれています。

github.com

中でも、「Introspection (Who Can Do What?)」については、フロントエンドにどのように「誰」「何をできる」かを伝えられるか、というフローについても詳しく説明されていて非常に勉強になります。

play.openpolicyagent.org

f:id:kenev:20190808203446p:plain

Rego Playgroundの紹介がされました。

play.openpolicyagent.org

サクッとPolicyを試すのに便利ですし、書いたPolicyをシェアできるのも便利です!OPAのSlack上でもコミュニケーション手段としてよく使われています。

Use Case: Application & End-user Authorization

認可に対するよくある疑問点とOPA側からの回答が述べられていました。その回答には今OPAが何をできるか、と今後どうしていこうとしているかについて述べられています。

以下については僕も今後かなり期待したいところです。

How do you delegate control to your end-users?

「OPAでIAMのようなことをしたい」という質問はよくSlackでも見かけます。これはエンドユーザーがPolicyの作成や管理ができるユースケースになります。Chef Automate IAMがリファレンスとしてよく紹介されるのですが、今後このようなモデルをOPAから公式に提供できるようにしてくれるそうです。

どのような仕組みで提供されるのか楽しみですね!

How do you leverage context, e.g. HR DB?

大規模なアプリケーションであればOPAに、判断に必要なデータ(context)を全部(インメモリで)あらかじめ読み込ませておくのは現実的じゃないです。今後「Data-fetching」が判断(Decision)のタイミングで行えるような機能が増える予定のようです。(XACMLで言うPIPの機能が強化されるイメージだと僕は捉えています)

現状では僕の知っている限りはOPAをライブラリとして使って、自前のサービスを作るか、OPAのビルトインであるhttp.sendを使って外部にAPIリクエストを行う方法しか無いはずです。この機能が実現するとOPAを使うハードルがぐっと下がりそうな気がします。

How do you render UIs based on policy?

フロントエンドでPolicyに基づいてボタンを表示するかしないかを判断できるようにするためにWASM(WebAssembly)が使えるようになるようです。

これが実現すると、上で述べたChef Automate IAMで提供されているようなIntrospection APIを作る必要が無くなるのではと思います。

まとめ

  • 「Deep Dive: Open Policy Agent」を視聴した
  • OPAをがっつり使っている例としてChef Automate IAMがかなり勉強になる
  • Policyの判断のタイミングで必要なデータをさらに取得する機能は今後に期待
  • WASMが実現すれば、フロントエンドのJSと連携してSPAでもPolicyを適用できそう
Slack

OPAの最新情報や質問は今の所Slackが一番収集しやすいと思います!特に「general」と「openpolicyagent」のチャンネルがおすすめです。

slack.openpolicyagent.org

stackoverflow

これから徐々にQAはstackoverflowに増えていくはずなので、以下のタグをチェックしておくとナレッジがたまりそうです。

stackoverflow.com

おまけ

公式ドキュメントの検索がしやすくなるようにAlgoliaのdocsearchが採用されそうです。ドキュメントの検索が無いというのがOPAのハードルを上げている一つの要因になっている気もするので、これはぜひとも早めにマージにもっていきたいです!

github.com

僕とDDDとClean ArchitectureとやっぱりDDD

DDD(Domain Driven Design)って難しいですよね。難しい難しいとばかり考えていた僕もようやく最近になって少しずつわかってきた気がします。そのきっかけとなった書籍と僕のストーリーを本記事で紹介できたらと思います。

TL;DR

  • Clean Architectureはなんとなくわかる
  • DDDは難しい

と感じている人は「Domain-Driven Design in PHP」を読むと道が拓けるかもしれない。

leanpub.com

僕とDDD

DDDといえばEvansのドメイン駆動設計:

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

そしてVernonの実践ドメイン駆動設計:

実践ドメイン駆動設計 (Object Oriented SELECTION)

実践ドメイン駆動設計 (Object Oriented SELECTION)

が有名です。「ドメイン駆動設計」の内容をより実際の設計に当てはめている「実践ドメイン駆動設計」の方がわかりやすいという意見を聞いたりしますが、僕にとっては両方ともなかなかに難易度の高い書籍です。少しずつ読んでは「なんとなく言ってることはわかる」と思いながらも今ひとつ身についた気がしない類のものでした。このように感じている人はそれなりにいるんじゃないかなと思ってます。

僕とClean Architecture

DDD難しいし、実際のプロジェクトに適用しようと思っても手が止まる。。。

ということでしばらく本棚に戻していたのですが、次に見つけたのがClean Architectureなるものでした。Qiitaの記事で見つけたのがきっかけだったのですが、実際に自分の馴染みのあるコード(Rest APIだったりAndroidのアプリだったり)で書かれていたこともあって「これは実際のプロジェクトでも書けそう」と思えたのが「僕とDDD」の出会いとの大きな違いかなと思います。

実際にはQiitaの記事だけでは心もとないので、以下のような記事や講演で学びながら実案件でも適用していきました。

Uncle BobのClean Architectureの記事

blog.cleancoder.com

Uncle BobによるClean Architectureの講演

www.youtube.com

(いくつかありますが、最初にみて面白かったのがこれです)

Uncle Bobと息子のMicah MartinがペアプロしながらWebシステムを作る講義

learning.oreilly.com

GitHub上のソースコード

github.com

Clean Architecture(書籍)

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Clean Architecture 達人に学ぶソフトウェアの構造と設計

僕がClean Architecture学び始めたころはまだ無かったのですが、正式に書籍も発売されています。

Clean Architectureでモヤッとしたこと

Clean Architectureを採用したプロジェクトでは、オレオレClean Architectureでありながらもそれなりにルールを守った自信はあります(特に依存関係やScreaming Architecture部分)。が、いざ実装するとなったときに「ロジックをどこに置くのか」というところで結構悩むことがありました。

  • ○○ロジックはUseCase(Interactor)に入れる?
  • △△ロジックはEntityに入れる?
  • 同じようなロジック(例えばValidation)が複数箇所にあるけどこれは誰の役割?
  • などなど

Clean Architectureの書籍に「第20章 ビジネスルール」という章があるのですが、

  • エンティティは最重要ビジネスルールを含む
  • ユースケースアプリケーション固有のビジネスルールを含む

と言われてもなかなかエモくて直感的にわかりにくいと感じました。そして結構こういうところがコードレビューでチーム内で意見が分かれたりして変に時間がかかっちゃったりしたのを覚えています。「Clean Architecture良い!」という思いとともに「まだすっきりしない点が残る」という状態が続きました。「やっぱりDDDなマインドが足りていないからなのか?」ということでDDD学習に戻ることにしたのがここ1年くらいの話です。

DDDとClean Architecture

「実践ドメイン駆動設計」を再び読み直してみたのですが、不思議なことにClean Architectureを経てから読み直してみると見えてくるものが変わりました。たぶんですけど、

馴染みのあるコードからClean Architectureを学んだ
↓ 
Clean Architecture(書籍)を読んだら答え合わせのように腑に落ちる点が多かった
↓
この書籍内にドメイン駆動的な話も入っていた(んじゃないかと思う)
↓
無意識のうちにDDDの概念が少し増えた状態で改めて「実践ドメイン駆動設計」を読み始めた
↓
書籍内に出てくる単語や概念に馴染みが増していたので以前より想像しやすくなった

ということだと思います。

ただ、以前より理解がしやすくなったとは言え、モヤッとは残ったままで、以下のトピックを理解したいと思いつつもいまいちピンとこない感じでした。

  • Application Service
  • Domain Service
  • Aggregate
  • Entity
  • Value Object
  • Domain Event

結局繰り返しになるんですけど、「なんとなく言ってることはわかる」で止まってしまって、いざ自分のコードに入れようとすると「どうしたら良いんだろ?」になっちゃうんですね。(つまりわかっていない)

今にして思うと「わかろうとするモチベーションが足りなかった」だけな気もします。

そんな中最近「Domain-Driven-Design in PHP」という本に出会いました。

Domain-Driven-Design in PHP

leanpub.com

O'Reillyのサブスクリプションがある方はこちらのリンクからも読むことができます!

この書籍は結構前から知っていたんですけど、PHPというだけで避けてました。(ごめんなさい。でもそう思った人は僕だけじゃないはず!)

最近は毎日のようにPHP触ってますし、PHPへの抵抗も昔よりだいぶ無くなったので読んで見ることにしました。

久しぶりにこんなにワクワクする技術書を読んだ。

というのが素直な感想です。僕がちょうどモヤッとしているところとジャストミートな内容だった、というのもあるんだと思いますが:

  • ちょうど良いボリューム感
  • ドメイン駆動設計」, 「実践ドメイン駆動設計」, そして「Clean Architecture」にも言及している
  • DDDの概念とリンクする豊富なサンプルコード
  • 書籍用に作ったシステムがGitHub上に公開してある

という4つのポイントが僕の中で高評価です。

ちょうど良いボリューム感

ページ数は394ページとそれなりにあるのですが、コードが占めている比率が高いので思ったよりすぐ読めます(僕は3,4日で読めました)。

目次は以下の通り。

* Getting Started with Domain-Driven Design
* Architectural Styles
* Value Objects
* Entities
* Services
* Domain Events
* Modules
* Aggregates
* Factories
* Repositories
* Application
* Integrating Bounded Contexts
* Appendix: Hexagonal Architecture with PHP

「実践ドメイン駆動設計」と比べてみても、概ねトピックはカバーできていると言えます。

* Getting Started with DDD
* Domains, Subdomains, and Bounded Contexts
* Context Maps
* Architecture
* Entities
* Value Objects
* Services
* Domain Events
* Modules
* Aggregates
* Factories
* Repositories
* Integrating Bounded Contexts
* Application
* Aggregates and Event Sourcing: A+ES

ドメイン駆動設計」, 「実践ドメイン駆動設計」, そして「Clean Architecture」にも言及している

要所要所でEvansの「ドメイン駆動設計」とVernonの「実践ドメイン駆動設計」の引用を使って説明をしてくれます。

例えばDomain Eventについて「実践ドメイン駆動設計」からは:

Vaughn Vernon defines a Domain Event as:

An occurrence of something that happened in the domain.

そして「ドメイン駆動設計」からは:

Eric Evans defines a Domain Event as:

A full-fledged part of the Domain Model, a representation of something that happened in the Domain. Ignore irrelevant Domain activity while making explicit the events that the Domain Experts want to track or be notified of, or which are associated with state change in the other Model objects.

というように両者を引用で出してくれます。書籍内でのわかりやすさも増すのですが、この引用をもとに「ドメイン駆動設計」と「実践ドメイン駆動設計」をリファランスとして要所要所で読み直していける点もすごく良いです。

また、さらにうれしいのはUncle BobのClean Architectureにも言及している点です。(この書籍が発売したときには「Clean Architecture」はまだ出版されていないので、あくまでUncle Bob(Robert C. Martin)について言及しています)

As Robert C. Martin says: The Web is a delivery mechanism [...] Your system architecture should be as ignorant as possible about how it is to be delivered. You should be able to deliver it as a console app, a web app, or even a web service app, without undue complication or any change to the fundamental architecture.

DDDの概念とリンクする豊富なサンプルコード

(ちょっと大げさですけど)文章よりコードが多いんじゃないかってくらいサンプルコードが都度紹介されています。そして、コードの要所要所に関して丁寧にわかりやすく説明がされているので、DDDの概念とコードがどう結びついていくのかが想像しやすいです。

書籍用に作ったシステムがGitHub上に公開してある

書籍用に作られた「Last Wishes」というシステムのコードがGitHub上に公開されています。

f:id:kenev:20190731225924p:plain
Last Wishesトップ画面

コンセプトは「ユーザーが亡くなった場合に、あらかじめ登録されていたお願い(Wish)を、指定したメールアドレス宛に送る」というものです。お願い(Wish)を登録した場合にはゲーム要素もあって、一定ポイントたまるとバッジが付与されるようになっています。

このシステムは大きく2つのアプリケーションに分かれています。

  • ユーザーのWishを管理し、メールを送信するコア部分のアプリ
  • ゲーム要素を提供する、Wishに応じたポイントを換算し、ユーザー毎に管理するアプリ

それが各々以下で公開されています。

github.com

github.com

システム構成は下の図のようになっていて、それなりにまともなメンツが揃ったシステムになっています。

f:id:kenev:20190727222800p:plain
Last Wishesの概要図

サンプルアプリではあまり見られない:

  • CQRSとEvent Sourcing
  • Bounded Context(Last WishesとLast Wishes Gamify)

というのも学ぶことができるのでかなり貴重な素材だと思います。このシステムと書籍を照らし合わせていくことで答え合わせもしていけるので、理解が進みやすいという印象もあります。

まとめ

  • 「Domain-Driven-Design in PHP」を読んだ
  • 馴染みやすいコードでDDDを体系的に学べる良書だった
  • ドメイン駆動設計」「実践ドメイン駆動設計」「Clean Architecture」をリンクさせてくれる1冊だと思った

と、思いをつらつらと書きましたが、同じように迷い、悩んでいる方がもしいるのであれば「Domain-Driven-Design in PHP」はかなりおすすめできる1冊なのでぜひ読んでみてください!ということを伝えたかったです。

この記事が何かしら一歩先に進めるきっかけになれば幸いです。

今後の予定

  • Vernonの「実践ドメイン駆動設計」を読み直す
  • Evansの「ドメイン駆動設計」を読み直す
  • Uncle Bobの「Clean Architecture」を読み直す
  • Last WishesをLumenに移行してみる
  • ↑ができたらgoにも移行してみる
  • 自分でScratchからアプリを作ってみる

という流れで精進していけたらいいなと思います!

Zoomで読書会「アーキ部#2」に参加しました!

会社のSlackで流れていたのを見たのがきっかけで「アーキ部」なるものに初参加しました!

connpassでのイベントはこちら↓

architect-club.connpass.com

概要

connpassにも記載されてますが、Release It!: Design and Deploy Production-Ready Software (Second Edition) のオンライン読書会です!

Release It!: Design and Deploy Production-Ready Software

Release It!: Design and Deploy Production-Ready Software

目次

  • Ch.4 Stability Antipatterns(安定性のアンチパターン)
    • Integration Points(統合点)
    • Chain Reactions(連鎖反応)
    • Cascading Failures(カスケード障害)
    • Users(ユーザ) ←ここの途中まで

参加するにあたって準備するもの

Zoomのクライアント

zoom.us

勉強会はオンラインで開かれていて、Zoomを使います。今回は20時ごろにconnpass経由でZoomのURLが送られてきました。Zoomのクライアントはあらかじめ用意しておくとスムーズに参加できそうです。

参加人数は今回40人を超えていましたが、主催者の共有画面を参照するだけですし、参加者もみなビデオはOFFにしてるので回線速度が気になることは無かったです!

f:id:kenev:20190726231814p:plain
参加中のZoomの画面

Release It!: Design and Deploy Production-Ready Software (Second Edition)

本は無くてもOKですが、あればより理解を深めることができるんじゃないかなと思います。Second Editionは現時点(2019-07-26)では英語版しかありません。(間違えて初版の日本語版を買わないように注意!)

ちなみにSafari Books Onlineを購読している人であれば下記にあります。

learning.oreilly.com

ドリンク、軽食

オンライン勉強会なのでご自由に、というところですね!マイクONにしてボリボリ何か食べちゃわないようには気をつけたほうがいいですね(笑)

参加してみた感想まとめ

  • 司会(@kawasimaさん) が本の内容とともに実体験を話してくれるのですごく良い
  • 本の内容とともにたまに関連記事を紹介してくれるのうれしい
  • ハッシュタグ#アーキ部)で一緒に参加している人のツイートも見れるの良い
  • オンラインなので、勉強会が普段遠隔にしかない人(例えば山に住んでるような僕)にとっては本当にうれしい
  • オンライン勉強会に懇親会が最後に無いのが寂しいですが、この課題は難しい

本の内容に関してピックアップ

  • 統合点はシステムにとって殺し屋No.1
  • (ダイアグラムを見て)新米アーキテクトは箱にフォーカスするが、ベテランアーキテクトは、矢印により着目する
  • ぐっすり寝れるかどうかが安定性のKPI
  • 明らかに遅いレスポンスはレスポンスが無いことよりも悪い
  • ディフェンシブなコードになってないと、道連れに自システムも障害してしまうことになる
  • バグのあるアプリケーションをオートスケールすると、札束がとんでいく

次回!

たぶん日程はまだ決まっていないのですが、connpassのメンバーになっておけば通知がとんできますので、興味がある方はぜひ!

architect-club.connpass.com

このイベントをきっかけに「Release It!」読み始めましたが、かなり面白いです。が、リアルなので胃も痛くなってきます。書評にも挑戦したいですね。

関連記事

t.co

t.co

「CircleCIのconfig.ymlを守ろうとした話」を発表しました

「【大阪】CircleCI ユーザーコミュニティミートアップ #1」に参加し、「CircleCIのconfig.ymlを守ろうとした話」という題名でLTをしてきました。

circleci.connpass.com

発表資料

ストーリー

組織の中でCircleCIを多数のリポジトリで使い始めたとき、config.ymlに一定のルールを課したくなりませんか?セキュリティ的な意味と、Lint的な意味の両方で僕は欲しくなってきます。

ということでconfig.ymlにどのようにガバナンスを効かせることができるのか、という試みについて話しました。主にconftestでCIにルールを課す方法について述べています。

興味がある方はぜひ下の記事も読んでみてください!

kenfdev.hateblo.jp

また、ルールを書くときに用いているRegoについては、Open Policy Agentとともに以下の記事で紹介しています!

kenfdev.hateblo.jp

僕の発表内容には答えがなく、「みなさんはどのようにガバナンス効かせていますか?」と問うて終わりました! いろんな人のconfig.yml話聞きたいですねー!

おまけ

質疑応答コーナーがあったのですが、「config.ymlへのガバナンスの効かせ方で悩んでいる方いますか?」という質問に誰も反応しなかったので、僕が勝手に悩んでいるだけなのかもしれない、ということはそれとなくわかりました(笑)

そして、イベントでは「ワタシハサークルシーアイチョットデキル」Tシャツいただきましたー!感謝感謝です! f:id:kenev:20190717002821j:plain

Netlifyへのデプロイがタイムアウト

最近は仕事から趣味まで作ったもの(主にフロントエンド関連)のデプロイが簡単にNetlifyにできてしまうので多用しています。

www.netlify.com

直近ではVue.jsのコンポーネントをStorybookで公開しようとしたのですが、単純なタスクなはずが思いの外ハマってしまったので対処法をシェアしようと思います。

TL;DR

問題

npm run build のようなビルドタスクが30分以上かかってしまってNetlify側でタイムアウトしてしまう

解決方法

ビルド時のログの出力量に注意!特にwebpackのようにビルド時に大量にログを出力するツールを使う場合は、出力しないオプションを付加しましょう

Netlifyがタイムアウトする

発生していた問題とは、Netlifyがタイムアウトするというものです。

f:id:kenev:20190708214549p:plain

上の画面はVue.jsのStorybookをビルドしている最中のものです。静止画だけで伝えられないのですが、ビルドの進捗がものすごく遅いんです。このときは開始したのが12:45ごろだったので、およそ30分くらいこの調子でちょっとずつ%が上がるだけで、そのあと静かにデプロイが失敗します

サポートに問い合わせ

Netlifyはエラー文言も無しに静かに失敗し、ローカルではなんの問題もなくサクッとStorybookを開始できるのでお手上げでした。ということでサポートに問い合わせしてみることにしました。デプロイに失敗すると簡単にサポートに連絡できるようにリンクが用意されています。

f:id:kenev:20190708215150p:plain
問い合わせ

ここから問い合わせフォームが下のように入力できるようになっていて、Netlify側もデバッグしやすいようにリンクも自動で貼り付けてあるので、送信するだけです。

f:id:kenev:20190708215808p:plain

僕の場合、送信してから半日くらいでサポートから返事がきました。

内容を要約すると

「ビルドのログが多すぎて、Netlifyの Log Serviceが問題を起こしていた。Storybookのビルドするなら —quiet オプション付加してみて」

ってことでした。

なるほど、確かにログの表示が変に空白が多かったりしてましたね。

ログ出力量に注意!

ということでbuild-storybook—quietオプションをつけることにしました。公式サイトでもドキュメントされています。

https://storybook.js.org/docs/configurations/cli-options/

更新して再度デプロイしてみると、サクッと終わってサクッとNetlify上でStorybookが公開されました!

変に長時間悩み続けることなく、サポートに問い合わせてみてよかったです!

まとめ

  • Netlifyのビルドタスクのログ出力量には要注意
  • 可能であればログ出力が抑えられるオプションを付加しておくこと
  • NetlifyのサポートのUX最高でした

調べても同じ問題に遭遇している人を見つけられなかったので、この記事が誰かの助けになればと思います。