余白

https://blog.lacolaco.net/ に移転しました

なぜ落合陽一が僕の心を震わせるのか

最近落合陽一氏の本を数冊読み、あっさりとファンになってしまった。 おそらく彼の思想や野望に触発されたんだろうと思うが、その理由を自己分析してみるメモ書き。

落合氏は一貫して「日本」という国をベースにしている。 彼のスキルがあれば日本にこだわらなくてもアメリカやヨーロッパで十分活躍できるし、個人として大きく成功できるだろうと思う。 なのになぜ日本に貢献しようという軸がぶれないんだろうと疑問に思っている。

もし万が一、落合氏の目にこのブログが止まったら、そのモチベーションの源泉が知りたい。

わからないなりに想像してみたのが、彼のモチベーションは貢献そのものなんじゃないだろうか。 つまり、日本というコミュニティが好きで、そのコミュニティへの貢献そのものが目的なんじゃないか。 見返りや報酬のための手段ではなく、目的化した貢献がそこにあるんじゃないかと思った。

そうしてみると、規模は小さいながらオープンソースコミュニティに貢献している自分と重なる部分があることに気づいた。 イベントのオーガナイザやドキュメントの翻訳、ソースコードへの修正パッチなどコントリビューターとしての活動を毎日している。 それをなぜ?と言われると、貢献したいから貢献している、という状態であるのは間違いない。 自分が好きなコミュニティがあり、その中で自身の役割を自分は貢献者だと決めているんだろうと思う。

つまり、日本というコミュニティに対する落合氏と、オープンソースコミュニティに対する僕の相対的なポジションは、それほど違いがないのではないかと思った。

落合氏に勇気づけられるのは、彼もまた僕と同じ20代、同世代であるということだ。 同じ時代で同じ社会現象、出来事を見てきた仲間が世界に影響を与える人物になっていて、その上でまだ日本を見捨てていない。 そこにすごく希望を感じるのだ。 巷では「日本は終わり、プログラマは価値があるうちに海外に行くべき」のような煽りをよく見る。 でも20代なら、まだ再興した先の日本を自分の目で見られるギリギリの世代かもしれない。 日本が好きなら、再興に賭けてみてもいいんじゃないかと、そういう勇気をもらえる。

オープンソースの世界でも、コントリビューターによるプロポーザルやパッチによってとても便利になったり、いままで見えなかった可能性が見えたりする。 僕にとって落合氏は日本というコミュニティに対して再興戦略というプロポーザルを送っているスターエンジニアのようなものだ。 そして同じコミュニティに属している仲間として、コントリビューションを生きがいとする者の端くれとして、彼の野望に巻き込まれたいと思ってしまうのだろう。 そうして僕も「これからの世界をつくる仲間たち」の一員になりたいと思っている。

完全にただのファンレターになってしまったが、一言でいうなら「貢献者」としての親近感が、ファンになってしまった原因じゃないかと思う。

もし少しでも興味を持った同世代の仲間がいれば、ぜひ日本再興戦略を読んでほしいと思う。アフィリエイトはやってないので自分で勝手に買ってほしい。

日本再興戦略 (NewsPicks Book)

日本再興戦略 (NewsPicks Book)

FlutterのBLoCパターンをAngularで理解する

この記事ではAngularDart/Flutterの文脈で新しいコンポーネント設計パターンとして広まりつつあるBLoCパターンを、Angularの語彙で理解し、実装する方法を紹介する。

BLoCパターンとは

BLoCとは、Business Logic Componentの略である。 BLoCを使ったアプリケーションの実装パターンをBLoCパターンと呼ぶ。

まず誤解を招きそうなポイントとして、この"Component"はReactやAngularなどでいうところのビューを構築する"コンポーネント"ではない。 一般的な単語としての、アプリケーションを構成するひとかたまりの要素という意味の"Component"なので誤解しないこと。 対比するレベルとしては、"UI Component" vs "Business Logic Component"のようになる。

BLoCは複数の環境向けにアプリケーションを開発するときのコードシェアカバレッジを高めるための、リファクタリング指針のようなものだ。 具体的には、以下の指針を与える。

  1. BLoCの入力・出力インターフェースはすべてStream/Sinkである
  2. BLoCの依存は必ず注入可能で、環境に依存しない
  3. BLoC内に環境ごとの条件分岐は持たない
  4. 以上のルールに従う限り実装は自由である

詳しくはBLoCパターンの初出であるこのセッションを見るとよい。

Flutter / AngularDart – Code sharing, better together (DartConf 2018) - YouTube

AngularにおけるBLoCパターン

AngularにおいてBLoCパターンの恩恵がどれほどあるのかは議論の余地があるが、 持続可能なAngularアプリケーション開発のために大事なこと - lacolaco でも述べたようにフレームワークに依存しない部分を明確に分ける、というのは設計指針として重要である。 サーバーサイドでの実行やNativeScript、Ionic、あるいはReact/Vueなどへの換装など考えても、BLoCパターンはアプリケーションのAngular依存度を適切に保つために良いルールに思える。

さて、さっそくAngularでBLoCを実装してみよう。 Dartには言語標準のStreamとSinkがあるが、JavaScriptにはまだ存在しないため、非標準の実装が必要である。 幸運にもAngularはRxJSと相互運用可能なので、RxJSのObservableをStreamに見立ててBLoCを実装することができる。

まずはUIコンポーネントビジネスロジックを持ってしまった状態の例を以下に挙げる。

https://stackblitz.com/edit/angular-bloc-example-1?file=src%2Fapp%2Fapp.component.ts

@Component({
  selector: 'my-app',
  template: `
  <div cdkTrapFocus [cdkTrapFocusAutoCapture]="false">
    <mat-form-field appearance="outline" style="width: 80%;">
      <input 
        matInput
        placeholder="Search for..." 
        ngModel (ngModelChange)="onInputChange($event)">
    </mat-form-field>
  </div>

  <span> {{preamble}} </span>

  <ul>
    <li *ngFor="let result of results">
      {{ result }}
    </li>
  </ul>
  `,
})
export class AppComponent {
  private query = '';
  results: string[] = [];

  get preamble() {
    return this.query == null || this.query.length == 0 ? '' : `Results for ${this.query}`;
  }

  constructor(private repository: SearchRepository) {}

  onInputChange(query: string) {
    this.query = query;
    this.executeSearch(query);
  }

  private async executeSearch(query: string) {
    const results = await this.repository.search(query);
    this.results = results;
  }
}

UIコンポーネントAPIの呼び出しや状態の保持などさまざまなビジネスロジックを持っているので、もしこのアプリケーションを別プラットフォームにも展開したくなってもコードが共有できない。

BLoCの作成

BLoCはポータビリティを考えると、ほとんどの場合は単なるクラスとして宣言される。 ここではSearchBlocクラスを作成する。 もともとAppComponentが持っていたビジネスロジックをすべてSearchBlocに移動すると次のようになる。

class SearchBloc {
  private query = '';
  results: string[] = [];


  get preamble() {
    return this.query == null || this.query.length == 0 ? '' : `Results for ${this.query}`;
  }

  constructor(private repository: SearchRepository) {
  }

  async executeSearch(query: string) {
    this.query = query;
    const results = await this.repository.search(query);
    this.results = results;
  }
}

そしてAppComponentSearchBlocに依存して次のようになる。

@Component({
  selector: 'my-app',
  template: `
  <div cdkTrapFocus [cdkTrapFocusAutoCapture]="false">
    <mat-form-field appearance="outline" style="width: 80%;">
      <input 
        matInput
        placeholder="Search for..." 
        ngModel (ngModelChange)="bloc.executeSearch($event)">
    </mat-form-field>
  </div>

  <span> {{ bloc.preamble }} </span>

  <ul>
    <li *ngFor="let result of bloc.results">
      {{ result }}
    </li>
  </ul>
  `,
})
export class AppComponent {
  bloc: SearchBloc;

  constructor(private repository: SearchRepository) {
    this.bloc = new SearchBloc(this.repository);
  }
}

https://stackblitz.com/edit/angular-bloc-example-2?file=src/app/app.component.ts

Observableへのリファクタリング

先述のとおり、BLoCパターンではBLoCのすべてのインターフェースはStreamでなければならない。 これはFlutterのStatefulWidgetやAngularDartのChange Detectionの間で、データの変更に対するUIのリアクションのアプローチが違うからだ。 同期的な状態の管理ではプラットフォームごとに特別な処理が必要になる。

一方StreamであればFlutterはStreamBuilderでStreamからデータが流れてくるたびに再描画する仕組みをもっており、AngularDartもasyncパイプにより同様の反応機構をもっている。 プラットフォームに依存せず非同期的な値を描画するために、DartBLoCパターンではStreamを活用する。

Angularの場合はRxJSがBLoCの実装を助けてくれる。

DartのStreamをObservable、SinkをObserverに置き換えると、SearchBlocは次のようになる。

class SearchBloc {
  private _results$: Observable<string[]>
  get results$(): Observable<string[]> {
    return this._results$;
  }

  private _preamble$: Observable<string>
  get preamble$(): Observable<string> {
    return this._preamble$;
  }

  private _query$ = new BehaviorSubject<string>('');
  get query(): Observer<string> {
    return this._query$;
  }

  constructor(private repository: SearchRepository) {
    this._results$ = this._query$
      .pipe(
      switchMap(query => observableFrom(this.repository.search(query)))
      );
    this._preamble$ = this.results$.pipe(
      withLatestFrom(this._query$, (_, q) => q ? `Results for ${q}` : '')
    );
  }

  dispose() {
    this._query$.complete();
  }
}

results: string[]results$: Observable<string[]>になり、preamble: stringpreamble$: Observable<string>となった。 これらはqueryの変更に反応して変化する非同期的な値として表現される。

queryObserver<string>インターフェースを外部に公開し、新しい値の追加をUIに許可する。 SearchBlocの内部では_query$: BehaviorSubject<string>を実体として持ち、コンストラクタでは_query$に反応する_results$_preamble$が宣言されている。

これをAppComponentから使うと次のようになる。テンプレート中でasyncパイプを使い、Observableの変更に反応してビューの再描画が実行されるようになる。

@Component({
  selector: 'my-app',
  template: `
  <div cdkTrapFocus [cdkTrapFocusAutoCapture]="false">
    <mat-form-field appearance="outline" style="width: 80%;">
      <input 
        matInput
        placeholder="Search for..." 
        ngModel (ngModelChange)="bloc.query.next($event)">
    </mat-form-field>
  </div>

  <span> {{ bloc.preamble$ | async }} </span>

  <ul>
    <li *ngFor="let result of bloc.results$ | async">
      {{ result }}
    </li>
  </ul>
  `,
})
export class AppComponent {
  bloc: SearchBloc;

  constructor(private repository: SearchRepository) {
    this.bloc = new SearchBloc(this.repository);
  }

  ngOnDestroy() {
    this.bloc.dispose();
  }
}

https://stackblitz.com/edit/angular-bloc-example-3?file=src/app/app.component.ts

これでBLoCの実装が完了した。

考察

まとめ

皮肉屋とシステム

ここ数年で一番好きな映画は「マネーショート」(原題 "The Big Short") だ。

映画館でも2回観たし、Netflixにあったから頻繁に見てたんだけどなくなってしまった。今はGoogle Playで買って見ている。

役者もストーリーも演出も全部好きなんだけど、中でもひとつ心に残っていつも頭の片隅から離れないセリフがある。

元々バカなシステムなんだ (中略) 自分を見ろ 皮肉屋を気取ってもどこかでシステムを信じてる

ローンのデフォルトが増えているのに債権の価格が高騰するという、合理的に考えればありえないことが起きていた。 格付け機関も債権のランクを下げることなく、詐欺的なシステムが稼働していた。

作中のマーク・バウムはなぜ合理的に考えればありえない詐欺がまかり通っているのかと、ドイツ銀行のジャレド・ベネットを問い詰めた。その時にジャレドが返したセリフ。

ウォール街のシステムの崩壊を予期し、破綻に賭けて空売りを仕掛けた悲観主義の皮肉屋の彼らは、それでも格付け機関や銀行が正しく機能して正しく債権が暴落し、空売りが成功すると信じていたわけだ。 システムを疑いながらもどこかでシステムを信じている、この二律背反は気づかないうちに自分にも当てはまっていることが多い。


www3.nhk.or.jp

2週間、宿泊費自己負担で8万人のボランティアを集めるためにやりがいをPR、どうかしている。 システムが狂っている。まともに考えたらこんなのでうまくいくわけがない。

最初はこう思うわけだが、この まともに考えたら ってのは、社会の合理性をどこかで信じていることの現れじゃないかと考えてる。 社会は まともに 運営されて稼働している、という観念が根強く響いてきている。 マイナンバー、新元号、まともじゃないものはいままで何度も見てきているのに、やはりどこかで信じてしまっている。

非合理的な社会はそこに確かに存在していて、だからこういった非合理的なシステムが生まれようとしていて、元になった社会自体が非合理なのだから、多分これは"まかり通る"んだろう。 なんだかんだ、詐欺的なシステムの上で、東京オリンピックは終了し、さまざまな形で負債を残していくんだと思う。祭りが終わるまでは誰も現実を見ないんだろう。

特に何か結論があるわけでもない話。おしまい。

危機感の話

常に危機感がある。これは自分が博士号も修士号も持っておらず、第三者から観測可能な価値を持っていないどころか、コンピューターサイエンスの教育を受けたことがないくせにソフトウェアエンジニアとして専門職に就いて生計を立てているのが根底にあるかもしれない。 プログラマーとしてインターネット上で活動しはじめたころからずっとアカデミーに対して劣等感がある。 自分がやっていることなんて高度な教育を受けた人間がちょっと参入してくればあっという間に淘汰されるだろうし、常に風前の灯火っていう感じがしてる。

正直なところ1年後に自分に仕事があるかどうかまったく自信がない。半年後すら曖昧だ。3年後なんてまったく想像もできない。 何をしても足りてない気がするから、毎日生き急いでいるような気がする。

本当は価値がないかもしれないけども、少なくとも価値があると錯覚してもらうために、常に可能な限り優れた成果を出してそれをアピールして生きてる。 そういうセルフブランディングだけで生きているので、それをやめた瞬間すべてが止まってしまうという危惧がある。

これは克服できるのか、すべきなのか、なんもわからん。

持続可能なAngularアプリケーション開発のために大事なこと

Webにかぎらず、アプリケーションというのは作って終わりではなく、その後も継続して改修・改善されていくケースが多い。受託で開発して納品して終わりというケースでも、納品した先にメンテナンスする人がいる。

この記事では、Angularアプリケーションの開発において、いかにメンテナンス性を維持して、持続可能なプロジェクトを構成するかについての個人的な見解をまとめる。

フレームワークを邪魔しない

Angularアプリケーションのメンテナンスにおいて、いちばん重要なことはいかにAngularのアップデートを阻害しないかという点に尽きる。 これはAngularに限った話ではなくフレームワークと呼ばれるものを使うなら常に必要なことであるし、 アップデートが定期的に降ってくることが決まっているAngularであればなおさらである。 アプリケーションの一番根幹となる部分の鮮度が落ちれば、その他の部分はそれに引きづられて腐ってしまう。

なので、Angularの新しいバージョンがリリースされたときにアップデートのブロッカーとなるものはなるべく作らないのが第一のポイント。 具体的には、ngx-**系のサードパーティ製のAngular用ライブラリは、極力減らす。 とはいえすべて使わない、というのはある程度の規模になると現実的ではないので、 利用するときは、そのライブラリのメンテナンスが頻繁に行われているかどうかをしっかりチェックするべき。

何か機能を実装するためにライブラリが必要だと思ったときには、まずフレームワークに依存しない実装のnpmモジュールを探す。 そのnpmモジュールとAngularとのインテグレーション部分は、自分でアダプターやブリッジを実装するのがベスト。

例: Google Analytics

例として、AngularアプリケーションでGoogle Analyticsと連携したいと思った時、angular google analyticsのように検索すると当然Angularのエコシステム上には専用のライブラリが出てくる。 例えば有名所で言えば https://github.com/angulartics/angulartics2 のような。

しかしこれはAngularのバージョンアップデートを阻害するリスクを冒してまで導入するべきものかどうか、個人的にはNoである。

Angularの基本的な機能を理解さえしていれば、ルーティングでURLパスが変わるたびにpageviewイベントをGAに送信するなど造作もない。たった5行のコードで書ける。

@Component({...})
export AppComponent {
  constructor(private router: Router) {}

  ngOnInit() {
    this.router.events.subscribe(e => {
      if (e instanceof NavigationEnd) {
        window.gtag('config', 'UA-*******-**', { page_path: e.urlAfterRedirects })
      }
    });
  }
}

クリックイベントの送信だって、(click) でイベントを受け取ったあとコンポーネント側で処理できる。 テンプレート上だけで簡単に済ませようと思うのは間違っていないが、Angularのバージョンアップデートを阻害するリスクと天秤にかけて考えるべき。 ディレクティブにしたって自分で書けばいい話だ。

Angularとサードパーティライブラリ

Angular用のサードパーティライブラリは、基本的には次の3種類になる。

  1. AngularのAPIブラックボックス化したユーティリティライブラリ
  2. 便利なディレクティブを提供するUIライブラリ
  3. 外部ライブラリをラップしたアダプタライブラリ

AngularのAPIブラックボックス化したユーティリティライブラリは極力避けよう。これは数行の手間を惜しむことで数ヶ月後の自分の首を締めることになる。特にこういう小さなユーティリティ系は作者が作って公開しておしまいになるケースが多く、そうして結局あとで自分で書くことになる。

UIライブラリも要注意。たくさんのコンポーネントのテンプレート内に散らばり、ライブラリを差し替えようとなったときにも一苦労になる。Angular MaterialやClarity、Ignite-UIなど、企業レベルでメンテナンスされていると安心して使える。

アダプタライブラリは、細かく分けると2種類ある。その違いはアダプタされた先のコア部分がAngularに依存するかどうか。 例えば、先ほどのGoogle AnalyticsをラップしたAngularticsやAngularFireなどは、コア部分は普通のJavaScriptなのでラップするのは自分でも簡単にできる。 よくあるのはFirebaseに新機能が入ったがAngularFireに反映されてなくて使えない、みたいなケースで、そういうリスクもあるので注意が必要である。 Firebaseのラッパーくらいは自分で書ける範囲。

一方でApollo-Angularは少し毛色が違い、Apollo-AngularはApollo Clientのラッパーというよりは、AngularのHttpClientをベースにApollo Clientを初期化するためのブリッジのようなもの。 この場合はAngular HttpClientとのブリッジを自分で書くのはApollo Clientの内部ドメインに精通している必要があるので少し難しい。 とはいえ Angularとaxiosを使ったHTTP通信 - lacolaco でも述べたように、Angular HttpClientを使わなければいけない理由などどこにもないので、標準のApolloClientを使ってしまうのもよいだろう。このあたりはそのライブラリのメンテナンス状況と信頼度に応じて考えるべきところ。

Angular用に作られたサードパーティライブラリを採用できる条件は次のようになる。

  1. その機能はAngularに依存するか => しないなら非依存のnpmパッケージを探す
  2. その機能はユーティリティ(いくつかのAngular機能のショートハンド)に過ぎないか => そうなら使わない
  3. そのライブラリはこまめにメンテナンスされているか => そうでないなら使わない

Angularに依存する部分と、そうでない部分を明確にする

アプリケーションの設計のなかで、Angularに依存すべき部分とそうでない部分を明確に分けておくことが大事。 ビュー層はどうしようもなくAngularの領域なので、コンポーネント、ディレクティブ、パイプの中にドメイン固有のロジックを持たない、ビューとしての仕事だけに専念させる。

ドメインロジックをまとめたサービスクラスへの@Injectable() は特に内部にAngularが侵入するわけではないので、大きな問題にはならない。 ただし、ドメインロジックの中でもAngular APIが必要になることがある。たとえばタイトルを変えるためのTitleサービスや、現在のURLを扱うためのLocationサービス、あるいはHttpClientやRouterなど。 そういった部分は、アプリケーションのインフラ層に逃し、やはりドメインロジックからは排除したい。下の画像の真ん中のロジック部分からはAngularを排除する。 ドメインロジックの変更をAngularに邪魔されないために、また、Angularのアップデートをドメインロジックで邪魔しないために。

f:id:lacolaco:20180515113943p:plain

ロジック内部の設計は自由だが、Angularに依存する部分とそうでない部分を明確にすると、自然と最低でもこの3層はできあがるはず。

プリミティブに書く

AngularもRxJSも、凝ろうと思えばいくらでもテクニック重視の書き方ができる。たとえばRxJSのpipeだけでほとんどの処理を済ませてしまうとか、たくさんのパイプを繋げてテンプレート内で処理を済ませてしまうとか。

書いてる間は気持ちがいいが、同僚や数ヶ月後の自分を困らせることになるのは誰の目にも明らか。なるべく素のTypeScriptでプリミティブに書けないかどうかを考えたい。 ライブラリのコードの最適化はライブラリが頑張るしかないが、TypeScriptで素直に書いておけばコンパイル時の最適化や実行時のエンジンでの最適化も受けやすい。async/awaitがそのいい例。 AngularやRxJSなどのアップデートでよく提供されるマイグレーションCLIも、使われている箇所が複雑になると適用漏れが生まれやすい。

テンプレート自体にも要注意で、*ngIf*ngForngSwitch などでビューの構造を操作しまくるテンプレートが大きくなると読みづらい。 こういった構造ディレクティブの内側は、ある程度スコープが切られた小さなテンプレートになるはずなので、その単位でコンポーネント分割するとけっこう見通しが良くなる。

こういったテンプレートから

<ng-container *ngFor="let user of users">
  <div class="user">
    <div>{{user.name}}</div>
  </div>
</ng-container>

このように切り出すのがよい。

<ng-container *ngFor="let user of users">
  <user-list-item [user]="user"></user-list-item>
</ng-container>

結果的に、構造を担うコンポーネントと、表示を担うコンポーネントが分かれていくので、コンポーネント設計としてもオススメの考え方である。

まとめ

持続可能なメンテナンス性の高いAngularアプリケーションを開発するために重要なことは以下の点。

  • Angularのバージョンアップデートを邪魔しない
    • Angularの特定バージョンに依存するサードパーティライブラリは極力減らす
    • Angularにない機能にはまずプレーンなnpmライブラリを探し、アダプターが必要なら自分で書くようにする
  • コードベース中で、Angularに依存する領域とそうでない領域を明確にする
  • 複雑なAPIを使いこなすよりも、プリミティブに書く