トップ 最新 追記

2019-03-02 [長年日記]

_ View FrameworkインスタンスをControllerに預けて管理してもらう現実的で雑なClean Architecture

[2019-04-11 追記]最初 Use Case に預けると書いたけど、Interactor の仕様が constructor のないものだったので Controller に変更しました。

まとめ

Web FrontendでClean Architectureを試す - Qiita を読んで違和感を覚えたので、もうちょっとざっくりでいいし React に詳細突っ込みすぎでは?という気持ちを整理しようと考えてたらたどりついた現時点での考え方。ざっくり言うと

  • MVVM でやってることの範囲をそのまま広げれば Controller まで適用可能なのでは?
  • 枯れたライブラリにもっと任せるとよいのでは?

と思った話。

もっと雑に言うとタイトルの通りで View Framework を Controller に DI しちゃえばだいたいイケんじゃね説。

※ もちろん PubSub や EventBus に置き換えてもよい。少なくとも View Framework を直接 DI してしまうと Framework への変更の通知が直接手続きになってしまいやすい。そうなると密結合となりやすく、View Framework の詳細を知らないといけなくなる。ここは注意が必要。Usecase の実行後の処理も View Framework 側にイベントで通知する形を強制するには PubSub の方が安全ではある。*1

Clean Architectureってなんだっけ

The Clean Architecture - Clean Coder Blog

Clean Architecture はざっくり言うと具体的なテクノロジー、詳細を外側に置き、原則的に外から内へ依存させようという考え方。処理の流れと依存関係が逆転するところがある。テキストで無理やり有名な図を描くとこんな感じ。

+----------------------------+
| Devices                Web |
|  +----------------------+  |
|  |     Controllers      |  |
|  |   +--------------+   |  |
|  |   |  Use Cases   |   |  |
|  |   | +----------+ |   |  |
|  |   | | Entities | |   |  |
|  |   | +----------+ |   |  |
|  |   +--------------+   |  |
|  | Gateways  Presenters |  |
|  +----------------------+  |
| DB                      UI |
+----------------------------+

ではWebフロントエンドってなんだっけ

まず前提として昨今の View Framework は Presenter から Web / UI の層を担っている。というか Web フロントエンドって言ってるんだから当たり前で、そもそも中心ではなく比較的周辺の領域を扱っている。

この View / UI Framework という周辺事項から内側の Controller へは普通に依存を持つことができる。

しかし View / UI 以外の情報も扱う必要がある場合、例えば Storage や Network を扱う場合は、以下の問題にぶつかる。

  • まず Network や Storage などは View / UI コンポーネントの関心外
  • かつ Controller / Use Cases から周辺に依存しない方がよい

じゃあUseCaseを意識しつつViewFrameworkとどう付き合うのがよいのか

上の問題に対する解として例のサークルで言うところの Gateways と Use Cases の境界を曖昧にすることで、以下のように書ける気がする。例は雑に Vue.js + ES2015+ で書いてある。

☆ App.vue
<script>
import Controller from 'controller_case'

export default {
  created() {
    this.controller = new Controller(this)
  },
  methods: {
    handler1() {
      this.$emit('event1', val1)
    },
    handler2() {
      this.$emit('event2', val2)
    }
  }
}
</script>

「Controller に対して ViewModel 自身を DI する」ことで中心を Controller 側にずらす意味合いを表しているつもり。

考え方としては Backbone.js, Angular 1 以降に定着し、ViewModel コンポーネントも実際にやっている、「依存を片方向に限定し、反対方向はすべてイベントで伝える」ということを頼りにしつつ DI で依存関係を逆転させるというもの。

Controller のインスタンスを this の中に保存しているのは単に消えてしまわないようにするためだけで、実際には View / UI Framework から Controller の何かを操作するといったことは行わない。

☆ controller.js
import Repository   from 'repository'
import RemoteFacade from 'remote_facade'
import Interactor   from 'interactor'

class Controller {
  constructor(vm) {
    this.vm = vm
    this.initListeners()
  }

  initListeners() {
    this.vm.$on(event1, usecase1)
    this.vm.$on(event2, usecase2)
    this.vm.$on(event3, usecase3)
  }

  async usecase1(val1) {
    return await Repository.foo(val1)
  }

  async usecase2(val2) {
    return await RemoteFacade.bar(val2)
  }

  async usecase3(val3) {
    return await Interactor.run(val3)
  }
}

Controller 側は ViewModel インスタンスの特定のイベントを処理できるようにしつつ、関連する Repository や RemoteFacade, Interactor などもここで初期化している。

大事なのは Repository や RemoteFacade, Usecase Interactor は外側の技術的な詳細そのものではないということ。あくまで Gateway 的なものですよという言い訳と言ってもよいかもしれない。少なくともここを見ると「何と何を使って何をしようとしているのかは分かる」くらいの粒度に収めたいというのが前提にある。

各 use case はメソッド一つで処理できるかもしれないし、中身が複雑になったら class に分けてもよい。Operator とか Command とか Service とかそんなやつ。Clean Architecture 的には Interactor.

今回改めて考えてみたこと

上で意識したのは

  • View / UI Framework のコンポーネントは*2それだけで複雑であり、その初期化に関して必要以上に情報を持ち込まない方がよい
  • View / UI Framework は「entry point ではあるが全体から見ると周辺事項」であり、「全体を見る人は別にいた方」が扱いやすそう
    • 全体には当然 View / UI 以外も入る
  • Repository や RemoteFacade の詳細は気にしたくないので書かない。その向こうは依存の方向が内→外になる可能性もあるが、気にしない
    • テスタビリティの確保なら stub out とか方法はある
    • 必要ならこれらを Factory にして具象 → 抽象の依存の方向を実現することはできる
  • Repository と Factory の関係は Usecase Interactor にも適用できるかも
    • 直接インスタンスを作るのではなく Factory 経由で全部 constructor DI にするとか

こうしておけば例えば WebRTC などが増えた際には素直に WebRTC を中心に追加できる。*3

いずれにせよ、いかに Clean かということよりも、

  1. View / UI Framework の中だけで頑張りすぎないこと
  2. いかに独立してテストしやすくするかということ
  3. そのうえで関心外の情報が一気に全部見えないようにすること
  4. 中心的に見なければいけないのは Controller / Use Cases であり、そこを見ると関連をたどりやすいこと

を考えている。

本来の Clean Architecture はフレームワーク非依存な考え方だが、現実的にはなんらかのフレームワークが考え方の起点となっていることは多い。

実際に Web フロントエンドはなんらかの View / UI Framework を採用することはほぼ前提と言ってよく、これらのフレームワークでは DOM や Event といった最も外側に位置する最も詳細なテクノロジーから一段階距離を置く手法が取られている。そしてこれと同じことは Storage や Remote Facade 系のライブラリにも言える。

つまりそれぞれの領域において概ね成熟したライブラリに任せていることは同じであり、必要以上に Clean な依存の方向にこだわりすぎてコード量が増えて特定のところで一気に依存解決する複雑さが見えてしまうくらいなら、まずは思い切ってざっくり View(UI) / Storage / Network くらいに分けてそれらをコントロールする人を中心に置く方が全体の見通しは楽になるし、個々のライブラリの機能を生かすことでテスタビリティは十分確保できるので、こんなもんでいいんじゃないの?ということを考えている。

逆に言うとこの考え方では cookie も localStorage も Fetch API もブラウザネイティブのものは極力直接使わないことを前提にしている。

で、「これはフレームワークから距離を置いた方がよい」と考えた場合には、グッと Clean Architecture に寄せてコード量を増やしてゴリゴリ書けばよい。

TDD も Clean Architecture も考え方を参考にしつつ、現実には増えるコード量とコスト感との兼ね合いの部分は必ず出てくる。その際に

  • どこが中心か
  • どこは任せてよいか
    • 外側の詳細に直接タッチしていないか
  • 特定のフレームワークの中だけで頑張ろうとしすぎていないか

くらいだけを気にしておけば、「そこそこClean」な状態は維持できるんじゃないかなーという感じのことを最近は考えている。

参考

Tags: JavaScript

*1 Vueの場合、空っぽのVueインスタンスをEventBus ( EventHub ) として使う方法も公式に紹介されてたりする。https://vuejs.org/v2/guide/migration.html#dispatch-and-broadcast-replaced

*2 以前よりシンプルになっているとは言え

*3 もちろんその役割を表す一段抽象化してものだが


2019-03-05 [長年日記]

_ WebStorageの読み書きにBasil.jsがなかなかよかった

Wisembly/basil.js: The missing Javascript smart persistent layer

要件は以下の通り

以下のプラットフォームの両方で動くこと

  • Node.js
  • Browser

動作するストレージが以下に対応していること

  • WebStorage
  • Cookie
  • Memory

それとここものすごく重要なんだけど namespace に対応していること。Cookie も WebStorage も Same Origin の中で様々なコードが勝手気ままにデータを読み書きするので、ちゃんと自分の namespace を守っていく必要がある。

Memory対応は重要

なぜかというと Node.js には WebStorage も Cookie もないので、Node.js 上でテストを実行した際に死んでしまう。Memory で動けば少なくともロジックのテストは行える。もちろんより厳密には localStorgae や Cookie をエミュレートするライブラリを追加したり実ブラウザでのテストを行なってもよいが、最初からそこを目指すのはちょっとコストが大きすぎる感じがする。まず API を固めて中の処理を書くことをさっさと進めるために Memory 対応があるとすごく助かる。

意外とすべて満たすものがない

日本語でも情報の多い Store.js を使ってみたのだけど、これは v 2.12.0 の時点で namespace 対応が壊滅的にダメだった。set, get はできるけど、each で回すと namespace から外れたデータまで取得できるうえにご丁寧に namespace ( 実際には prefix ) を取り除いた key が返ってくるので、どれが自分の管轄のデータかさっぱり分からない。

もちろん contribute するのも手なんだけど、さすがに storage 系のライブラリを今から育てていくぞ、ってのはモチベーション的にはなかなか厳しいっすよねー。

まとめ

こんな感じでブラウザ独自 API はできるだけ直接叩かないのが大事だなーと思ってる昨今。

Tags: JavaScript

2019-03-07 [長年日記]

_ AnyproxyがNode.js製のLocal Proxyとしてなかなかよい

Introduction &#183; AnyProxy

Node.js で動く proxy で

  • https を扱えて(証明書生成もできる)
  • Web UI があって
  • header も body も全部 Node.js で書き換え可能

なやつ。Mitmptoxy の Node.js 版と言えば分かる人は分かると思う。

でまぁ、そんな目新しい話はないんだけど、昔話と今回の狙いを残しておこうと思う。

かつてCocproxyがあった

azu/node-cocproxy: Convention over Configuration Proxy written in Node.js.

いろんなバージョンがある*1が、基本的には

  • ホスト名と同じ名前のディレクトリ、パスを掘ってあげて
  • そこにコンテンツを置いておき
  • proxy を起動し
  • ブラウザの通信にその proxy を利用するように設定

以上を満たすと local に該当リソースがあったら利用し、なければ remote のリソースを取得して返すという動作をしてくれる。これを利用することで例えば開発環境を手元で全部再現しなくても Web サイトのフロントエンドなどだけを準備、改善していくことが可能だった。

もちろん今でも HTTPS になっていないサイトに対しては普通に利用できる。手元でだけ別な CSS を当てるといったことが簡単に行えるので、もし知らなかったという人は試してみると面白いと思う。

いわゆるLocal Proxyのユーザー層は複数ある

Local Proxy という言葉はそこまで定着してるわけでもないんだけど、初出はこの辺かな?

ここまで出来る!LocalProxyベースの開発手法紹介

まぁ本当に便利。

ただこの手の Proxy はフロントエンド開発な人たちとは別にセキュリティ業界の人も重宝していて、どっち方面のツール、情報なのかを意識できるようになっておくと、見つけた記事が何を狙っているものなのかを判断しやすくなってよいと思う。Web のフロントエンド開発を目指して OWASP ZAP とか見つけても欲しい関連情報はなかなか辿れないはず。

HTTPS時代、Local Proxyは不便になった

CocProxy は目的に向かって挙動が整っていたのですぐにローカルでコンテンツを書き換え始めることができてとても便利だったが、最大の泣き所は HTTPS に対応できないことである。

そもそも Secure 通信は Hijack できないのがポイントなわけで、Proxy を挟んで中身を書き換えられると困るわけだけど、開発時にはまさにそれをやりたいという矛盾。

ま、解決策はあって、サーバが自分で証明書を持っておいて、そいつをクライアントに信用させればよい。いわゆるオレオレ証明書である。

これをやるのに Mitmproxy を使っていたこともあるんだけど、Cocproxy のような動作をさせるには自分でスクリプトを追加する必要がある。そのうえ一時期ゆっくりだった開発が active になり、独自に追加するスクリプトで利用する API がゴリゴリ変わってこの手のツールが次々死んでいくという事態になっている。

強くて気の利いたツールにこだわる

自分だけの話ならわざと Mitmproxy の古いバージョンを使うとかいろいろ手はあるんだけど、フロントエンド向けに定番ぽいものがあった方がよいかと思って Node.js 製のものを探していたところ Anyproxy を見つけた。まさに最近の Mitmproxy が実現していることがほぼ再現されていて、かつ独自のスクリプトは JavaScript で書ける。

本当はまさに Cocproxy みたいな気の利いた設計の専用 Proxy で HTTPS 時代に対応できているものがあればそれがいちばんよかったんだけど、結構頑張ってみたけどそういうものを見つけることはできなかった。

ということで次善の手として上に挙げた

  • https を扱えて(証明書生成もできる)
  • Web UI があって
  • header も body も全部 Node.js で書き換え可能

を基準に Anyproxy にした。ケータイに証明書をインストールするには QR コードをパシャっとやれば済む親切設計。これなら openssl 叩けとか言わなくて済む。

また 2019-03 時点で GitHub Star 5000 超えだし、repository の owner は alibaba なので、吹いて飛ぶようなこともないだろう。

OSごとの証明書を信用させる方法の違いに注意

具体的に自分がまさにやらかしたことを書くと、かつてやったことのあった iOS での方法が OS のバージョンアップで変わっていたことに気づかずにだいぶ時間をロスしてしまった。

*1 初出はRubyでその後gem化され、Node.jsもPlackもNginxもある


2019-03-30 [長年日記]

_ local開発環境でhttpsの動作検証時にlocal-web-serverとmkcertが便利

何を解決するのか

mkcert

FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you'd like.

  • CAと証明書を作成できる
  • CAを開発マシン上にインストールできるので、開発マシン上の https サーバを正しくユーザーに検証してもらえる

開発環境を https にしておく必要がある場合に CA と証明書を管理するために openssl コマンドを覚えてくださいと言わなくて済む。

local-web-server

lwsjs/local-web-server: The modular HTTP2 web server for productive full-stack development

開発環境で便利に使えるサーバで

  • https / http2 対応
  • reverse proxy になるのでどんな開発環境とも組み合わせることができる
  • 単体でも static assets の serve はできる

ということでいくつかの環境*1で開発を行う機会がある場合はそれらの https 接続を全部まかなうことができて便利。

実際に https reverse proxy として動かす際には上の mkcert で作成した cert と key をコマンドラインオプションや設定ファイルなどで与えてあげるとよい。

どのようなシチュエーションで必要になるか

HTML5 の JavaScript API は、以前はいつでも利用できたが、最近ではプライバシーの観点から https 接続を必須にする方向に変わってきている。ただし、localhost は特別扱いしてくれるため http でも利用できる。したがってサーバを動かしている PC 上ではいつでも API を利用することができるので、PC 上では動作、デバッグが可能である。

しかしモバイルデバイスでこれらの API を利用する場合、モバイルデバイス上では開発サーバが動いていないので、モバイルデバイスからは localhost でアクセスすることができない。つまり、モバイルデバイス実機での API の利用のためには開発サーバが https を喋っている必要がある。

使い方

  1. まず mkcert -install して rootCA を開発サーバ上のシステム全体(あるいは Firefox)が認識できるようにする
  2. mkcert <hostname and/or IP> で接続するアドレスに対応する証明書を作成する

2 の際はサーバへ接続する際に利用する名前、IPアドレスを列挙する必要があることに注意。例えば

  • localhost
  • 192.168.0.1

で接続するのであれば

mkcert localhost 192.168.0.1

で生成する。すると localhost+1-key.pem と localhost+1.pem ができる。

勘のいい方はお気づきだと思うが、つまり、 IP アドレスが変われば証明書も作成し直しである*2

で、証明書ができたら以下のように local-web-server を起動する。

ws --cert <cert file> --key <key file> --rewrite '/* -> http://localhost:3000/$1'

これで例えば rails server への接続を https にすることができる。ws は local-web-server のコマンドの名前。デフォルトでは 8000 で立ち上がるので

https://localhost:8000

に接続すると https で rails server に繋がるということになる。

※ すべて reverse proxy 経由でのアクセスになるので、開発サーバが Host ヘッダを見ていない場合はリンクを辿った際に思ったように動作しなくなってしまう。まぁそんな環境をそのままイマドキの https 環境に持っていこうというのはそもそも無理がある。

モバイルデバイスへのCAの転送

実機から開発サーバへ https 接続するには、どうにかして rootCA.pem をモバイルデバイスへ転送してインストールする必要がある。手段は

  • mail
  • Dropbox
  • BitTorrent Sync
  • AirDrop

なんかもあるけど、

claudiodangelis/qr-filetransfer: Transfer files over wifi from your computer to your mobile device by scanning a QR code without leaving the terminal.

こういうの一つ用意しておくといいかも。

terminal 上で QRCode を表示する系のやつは設定によってはちゃんと読み取れるコードを描画できないので、デフォルトのターミナルとかじゃないとダメです。

自分で試した限りでは

svenkatreddy/qr-filetransfer: Transfer files over wifi (or) internet from your computer to your mobile device by scanning a QR code without leaving the terminal. とデフォルトの Terminal.app で転送できました。他のツールは mime/type が設定できずにテキストファイルが表示されるだけになる可能性があります。

考えなきゃいけないこと

手元の環境用の証明書の管理およびそれを .gitignore すること

mkcertは共有には使えない

Installation Warning: the rootCA-key.pem file that mkcert automatically generates gives complete power to intercept secure requests from your machine. Do not share it.

と書かれている。

共有の際は ngrok などを利用するのがよいのかもしれない。

逆に、頑張って我慢すれば ngrok だけで開発できなくはないけど、当たり前だけどかなり遅いし制限もあるので、素直に手元で https サーバ、証明書の管理ができるようになりましょう。

参考

*1 例えば Rack / PHP / Node.js

*2 もともとSSLが分かってる人には当たり前ですが


2019-03-31 [長年日記]

_ JavaScriptでQR Codeのencode / decodeと、これを試してJavaScriptの画像周りで気づいたこと

Ruby で QR Code を作るのは以前からやってるんだけど、

これを JavaScript でやってみたらどうなるか実験。

特に scanner 部分を独自アプリで作り込むと独自ルールを仕込むことができて便利*1なので、生成された QR コードのチェックを目視に頼り切る必要がなくなるし、scanner を UI と融合させやすいのが JavaScript の強み。

気づいたこと

encodeは「見える」が最優先で画像化は後回し

Ruby の場合は

  1. QR Code のパターンを生成
  2. これを内部で保持しつつどのフォーマットで出力するか

を考えるだけでよかった。上の rqrcode_png_bin で言うと、もともとは rqrcode がパターンを保持し、rqrcode_png が rqrcode と chunky_png をミックスして PNG 出力できるもので、rqrcode_png_bin はそれに CLI のガワを被せただけのものだ。

ところが JavaScript の場合はそもそもブラウザ上でならそのまま見える状態にすぐ持っていけるので、それを目指す場合が多い。具体的には HTML タグだったり Canvas 要素だったり。

ところが HTML タグも Canvas 要素も Node.js 環境ではそのままで使えない。少なくとも「画像ファイル」としてそのまま取り出すことはできない。UI と融合させてブラウザで一つ一つ手作りする分にはこれでよいが、画像として取り出してデータとして活用したい場合にはもう一工夫必要になるし、同じ生成処理を使って一括生成したい場合にもこのままでは使えない。

具体的には流れとしては以下のようになるようだ。

  1. dataURL にする
  2. dataURL を Buffer に変換する
  3. Buffer をファイルに出力する

ブラウザであれば 1 の dataURL を img 要素に与えればおしまい。画像ファイル化も、右クリックして保存すればおしまいである。Node.js であれば 2 以降を実装することで自動的に画像ファイルを生成することができる。

※ ただしこの際、画像の縦横のサイズが指定通りにぴったり同じになるかどうかは実装による。そもそも QR コードはピクセルでなく「モジュール」が基本単位であり、ピクセルは基本単位ではない。QR コードのパターンをそのまま dataURL に変換した img を生成した場合、基本的には指定サイズより小さいものができあがる。Canvas 上に描く場合は Canvas 側で先に指定サイズにしたがって要素ができあがり、その上に描画するので大きさが足りない場合は単に余白が大きくなる。

画像の取り込みはArrayBufferを作るところから
  • 画像フォーマットに左右されずにピクセル情報を取り扱うには Canvas の ImageData を利用する
  • ImageData.data は ArrayBuffer なので
  • Scanner は ArrayBuffer を解釈すれば ok

実際、以下のライブラリは第1引数に imageData と書かれているが、これは ImagaData() そのものではなく ArrayBuffer を受け取る。

cozmo/jsQR: A pure javascript QR code reading library. This library takes in raw images and will locate, extract and parse any QR code found within.

これを前提にすると、ついでに FileReader API も .readAsArrayBuffer() を使えば ArrayBuffer を返すので、

Camera -> getUserMedia() -> Canvas Context -> ImageData  -> ArrayBuffer
       -> input type="file"                -> FileReader -> ArrayBuffer

の両方のルートでカメラから ArrayBuffer を経由して QR コードリーダーを実装できることが分かった。

カメラ画像の取り込みはデバイスとブラウザの特性によって選び分けた方がよさそうという話もあるけど、そこはまた今度。

※ ちなみに昨日の https サーバの話はこの video 要素の取り回しがきっかけ。

cf.

使うとよさげなライブラリ

Accelerated Shape Detection in Images によると WICG で扱ってて Barcode Detection も standard な機能になりそうなんすね…。

Tags: JavaScript

*1 例えばNGワードとか