Kengo's blog

Technical articles about original projects, JVM, Static Analysis and JavaScript.

考察:Reactive Workflowが生まれた背景とその狙い

人に説明するのがスムーズにできなさそうなので、理論武装というか順序立てて話すためにこの記事をまとめる。

対象

TL;DR

  1. 状況やベストプラクティスが目まぐるしく変わる現代において、すぐに変化できるソフトウェアを保つこと・ヒトの手をできるだけ空けることが重要。
  2. かつてIaaSがAPIを提供し環境管理の多くを自動化したように、各種サービスがAPIやWebhookを通じてDevelopment Workflowの多くを自動化してきている。
  3. 多くの視点や知見を活かすcross functionalチームによる共同開発を支えるために、コラボレーションを助ける仕組みも必要。

前置き:課題解決手法としての生産性向上

我々はなぜ生産性向上を目指すのか? それは生産性が十分ではないという課題意識や危機感を持っているからだろう。では、なぜ課題意識を持ったのか? 機能の実装が進まないとか、バグが多いとか、手戻りが多いとか、本質的でない業務が多いとか、現場によって多くの理由があると思われる。そしてそれを深堀りする前に他者とざっくり課題意識を共有するための言葉として「生産性を向上しないとね」といった表現が用いられる。 言い換えれば、生産性向上というのは現場の抱える課題に対する抽象的な解決策である。ので「生産性を向上するには」などと議論しても、地に足をつけた議論にはならない。「なぜ生産性が低いのか」を掘り下げて、その解決策を見出す必要がある。

ここで重要なのは「なぜ低いのか」は比較対象を知らなければ説明できないことだ。それが他のチームが実際に回している開発なのか、スケジュールから逆算した「理想生産性」なのかは課題ではない。そうした「あるべき姿」が共有されてはじめて比較ができ、KPI(モノサシ)が生まれ、議論できるようになる。これは開発手法や組織、プロセスなど、どこに課題がある場合でも同じだ。

これはこのブログで繰り返し出てきているproblem solvingの考え方であり、真新しいものではない。単に課題の設定と共有をきちんとしようという話である。課題が設定されてはじめて、課題解決の進捗や施策の妥当性を評価できる。Development Workflowに手を入れる前に、本当に必要なものがそれで手に入るのか確認する必要がある。

Development Workflow

私はDevelopment Workflowは自分の専門というわけではなく、またウォッチャーを自任できるほど時間をかけてトレンドを追ってもない。いちエンジニアの理解であり何かを代表するものではないことを、はじめに改めて強調しておく。

またここでDevelopment WorkflowというのはCI, CD, DevOpsといった文脈で出てくるパイプライン処理、特に確認や承認や手動テストという非自動化処理をも含めた一連の流れを指している。GitHub ActionsCircle CIでは単にWorkflowと呼んでいるが、ここでは業務向けワークフローシステムと区別する目的でDevelopment Workflowと呼ぶ。Development Workflowの「終点」は成果物のデプロイだけでなく、チームへのフィードバック=学習機会の提供であることも多い。

かつてDevelopment Workflowはどのようなものだったか

自分が10年前にどういうDevelopment Workflowを組んでいたかというと、概ね以下の通りだったように記憶している(便宜上HudsonをJenkins*1と表記):

  • trunk, branches, tagsを備えたSubversionによるコードのバージョン管理(ブランチ切り替えコストが高い)
  • 設定画面を利用したJenkinsジョブの作成・設定
  • ブランチを指定して実行するJenkinsジョブを開発者に対する公開
  • プラグインによるJenkinsの機能拡張
  • Mavenの親pomファイルを利用したプロジェクト管理
  • LANに閉じた開発環境
  • 環境構築や開発作業の手順書をWiki等でメンテ(.jarファイルの手動配置、テスト手順、IDE設定方法など)

自動化されているのはジョブと呼ばれる単発作業のみで、いつどのように実行するかは主にヒトに委ねられていた。出荷作業をするにはこのジョブをこういったパラメータで実行すること、という手順書が必要だったのである。VCS上での変更を知るにはpollingを必要としていたし、他にもマスターサーバの責務は多くある程度の性能を要した。また結果をヒトに通知する手法はメールが主体だったので、何かを強制することは難しかった。

当時はまだビルド職人という言葉があったが、それは成果物の作成を特定の環境や個人でしか行えなかったり、ジョブ実行時間が非常に長かったりしたことを反映している。実際ビルド環境を可搬にすることはまだ難しく、また手順書が必要な現場も多かったのではと推測される。

近年Development Workflow周りで見られる変化

逆に、当時に無くここ10年で開発され普及したものを思いつく限り列記する:

  • ブランチを利用しやすいVCS(Git)
  • 画面による設定ではなく、VCSによって管理されたファイルよる設定
  • CIパイプライン
  • インクリメンタルビルド
  • 複数ノードにおけるテストのparallel実行
  • プロジェクトごとに利用するJDKを切り替える仕組み
    • rvm はあったようなので、他言語には実行環境をプロジェクトローカルのファイルで指定する仕組みはあった?
    • 開発環境にはあらかじめ複数のJDKを入れておくことで、Toolchainを使って使うものを指定する仕組みはあった(どの程度普及していたのかは不明)
  • DisposableでReproducibleな、頻繁に壊して作りなおせる環境
    • Vagrant Sahara pluginが出たのが2011年
    • Boxenが出たのが2012年
    • Dockerが出たのが2013年
    • ChefとPuppet, EC2にAMIは10年前既にあったので、自前でコード書いて実現する手はあったと思う(自分はやった記憶がない)
  • CLIから実行できる、設定がVCSで共有可能で環境非依存なコードフォーマッター
    • gofmt, rustfmt, Prettier, Spotlessなど
    • コードフォーマットが個人の環境・設定に依存する必要性をなくし、差分レビューを容易にした
  • チケット中心主義の緩和
    • PRで変更を提案し、その中で議論・修正できるようになった
    • 不具合も再現するテストをPRで送り、同一PR内で修正しマージできる(チケットによる報告も残ってはいる)
    • GitHub Releasesなどでタグに対して情報を残せるのでリリース作業もチケット不要
    • チケットを作って議論を尽くしてからコードを書くケースはもちろん残っているが、関連作業の記録・集約のためにまずチケットを作るというフローはほぼ不要になった
    • チケット管理システムの責務から変更の記録が外れ、要件の記録に注力できるようになった
  • 他システムとの連携を前提としたAPIやWebHook、アクセストーク
  • 運用を肩代わりし本質に注力させてくれるSaaS
  • ベンダ間で統一されたインタフェース(WebDriver, JS, CSSなど)

多様にわたるが、その多くがビルド時間の短縮や環境管理を楽にすること、システムを”繋ぐ”ことに貢献するものである。

10年前と比較して、どのようなニーズの変化があったのか

技術の進展というのはあとから振り返ると「なんで今までなかったんだっけ?」と感じるものが多いが、Development Workflowを構成するサービスの成長にも同じことを感じる。 例えば10年前はヒトに使われることを前提にGUIを通じて提供していた機能の多くが、サービスから使われることを前提にWebAPIやVCSで管理されたファイルによって使えるようになった。しかしGUICUIどちらが技術的難易度が高いかと言うと、一般的にGUIだろう。GUIを提供できるがCUIはできない、というケースはあまりないはずだ。

ではなぜ10年前はGUIを中心としていたのか?それは(特にJenkinsについては)利用者に対するハードルを下げた面もあると思うが、手実行することがあまり問題にならなかった、設定や運用をオープンにし誰でも触れるようにするモチベーションが高くなかったことが要因として挙げられる。もっと言えば、当時は1日に数回のビルドで十分にビジネスを回せたのだ。DevOpsの草分けとなったFlickrの発表がちょうど10年ちょっと前*2だし、おそらく当時はまだ多くの組織で「開発と運用の対立」「出荷前検証の重視」が残っていたはずだ。Flickrを始めとした多くの組織で、インフラのコード化や共有されたVCSリポジトリがいかに生産性へ貢献するか、注目された頃とも考えられる。

つまり10年前と今の大きな違いは、開発における試行錯誤の速度だ。自動テスト、継続的デプロイ、カオスエンジニアリング、コンテナなどの様々な技術が生まれたことで、ソフトウェアがより素早く変化するものになっている。またソフトウェア開発の現場に経験主義の考えが強く根付いていることや、スタートアップとソフトウェアの相互の結びつきから、ソフトウェアが素早く変化できることがビジネスで有利に働くと強く信じられるようにもなっている。DevOpsやSREが生まれた背景もそれを裏付けているように思う。

速度が違うとは、どういうことか。例えば10年前の私に「Gradle Pluginの開発でテストに使うGradleのバージョンを増やしたい、なぜこんなことをJenkins管理者にお願いしなければならないのか」と言ったところで、「え、メール一本だし別にいいじゃん、何気にしてるの?」と不思議がられるに違いない。今はこれもPRひとつで実現可能だが、当時はそうではなかった。
他の例では、Mavenのバグを回避するためにバージョンを上げるとして、Jenkins管理者にお願いしてCI環境のバージョンを上げるだけでなく、各開発者の使っているMavenのバージョンまで上げるにはメールでバージョンを上げるよう依頼しつつmaven-enforcer-pluginで意図しないバージョンでビルドした場合に落ちるようにする必要があるだろう。さらに複数プロジェクトで実施するにはすべてのプロジェクトのpom.xmlを書き換えるか、あらかじめ親pomを作成して各プロジェクトに依存させ、maven-enforcer-pluginの設定を親pomに加えてprivate maven repositoryにdeployしてから各プロジェクトのpom.xmlを書き換える必要がある。文が長くてわかりにくいが、とりあえず面倒なプロセスが必要ということが伝われば良い。これもMaven Wrapperを使うようにすればPRひとつで済む。ミドルウェアについても同様で、Dockerfileなりdocker-compose.ymlなりAnsibleなり、何らかのファイルを少し変えるだけで意図したバージョンが確実に使われるようにできる。

こうして製品ないし開発環境を変えることがPRひとつでできるようになっただけでなく、マージ前のプロセスの簡素化・短縮化も図られるようになった。Tracでチケットを作って手元で対応するパッチを作ってReview Boardでレビューして問題なければパッチを適用するという複数のシステムを行き来する方法から、検証済みマージやPull Requestといったひとつのシステムで完結する方法へと変化した。またPre-merge build自体の実行時間も各種技術によって短縮されたし、ミドルウェアを使った統合テストも使い捨て環境を作りやすくなったので実行したいときに他のテストとの衝突を気にせず実行できるようになった。気にすることが減るというのは、Pre-merge buildでいろんなことを自動的に確認・検証したいというニーズを満たす上で必要だ。統合テストを回して互換性が壊れていないことを確認したいのに、DBが他のテストで使われているので待つ必要がある、なんて事態は避けたいのだ。

10年前と今の「生産性」を比べたときに、この開発プロセスの違いは大きな差を生むに違いない。

自動化、有機的連携、そしてリアクティブワークフロー

別の表現をすると、10年前は省力化のために作業の自動化が推奨され、それで充分に競争力を生んでいた。その後、自動化された作業を有機的に繋ぎ合わせるようになり、CIパイプラインが登場した*3。その後でWebAPIやWebHook等の整備により、PRやcron以外でもパイプラインを自動発火することが増え、リリース作業やデプロイ、ドキュメント整備など多様な使い方をされるようになったと言えるだろう。 近年は脆弱性対応の観点から、ライブラリのリリースや脆弱性の公開をイベントとして使えるようになってきている*4が、これも従来なら開発者が各ベンダからの情報をRSSSNS等を使ってウォッチする必要があった。

さてジョブの並列実行・並行実行ならびに有機的連帯についてはPipelineやWorkflowという用語が存在するが、このヒトの指示による実行ではなくイベントに応じた実行を意識した組み方については特に固有名詞がないように思う。イベントを受け取りステートレスな要素をつなぎ合わせて実行する様がリアクティブシステムの考え方に近いことから、ここではリアクティブワークフロー(Reactive Workflow)と呼ぶ。

なぜリアクティブワークフローが好ましいのか、その説明はReactive Manifestoが流用可能だ。使いたいときにすぐ使え(Responsible)、ジョブがステートレスなため壊れても再実行でほぼ対処でき(Resilient)、ビジネス上の要請あるいは技術的な必要性から生まれる突発的な負荷に対応する(Elastic)。また各ジョブの入出力がコミットハッシュやパラメータなど不変なものがほとんどであることと、ワークフロー基盤がサーバーレスアーキテクチャで構成されることが多いことから、メッセージ駆動のメリットも享受できると思われる。

おそらくユーザ(開発者)としてはResponsibleであることが最も重要だ。JenkinsのマスターやエージェントをLAN内に構築していた(サーバーレスでなかった)ころは、資源をうまく使うように夜間に定例処理を回したり週末にテストを回したりといった工夫が必要だった。これはリアクティブワークフローを組み、資源の管理をSaaSあるいはIaaSに押し付けることで、必要なときに必要なだけ実行し即結果を受け取れるようになった。Elasticであることも合わさって、開発者が本質的でない気遣いを排し(ビルド並列化などによって)開発速度を上げることに貢献できるわけだ。

こうしてみると、コンテナ(ジョブはコンテナ内で走ることがある)やサーバーレスといった新技術によって最近のDevlopment Workflowも支えられているのだなと感じる。

コラボレーションの重要性

近年の開発手法、例えばSREやDevOps, Lean, cross-functional teamについて紐解くと、個人の役割を明確化・細分化すると同時に組織のサイロ化を防ぐことに多大な関心を持っていることが伺える。著名なのがError Budgetで、Reliabilityの実現を目的としたSREと多様なデプロイを多数行う(ことによるプロダクトの改善)を目的とした開発という一見相反する行動原理を持つ役割をうまく「同じ課題を解決するチーム」へと仕立て上げ、サイロ化を防いでいる。詳しい話は書籍に譲り割愛する:

他にもプロダクトマネジメントトライアングルでも、立場が異なるヒトをいかに連帯させ機能させるかという関心ごとについて説いているが、その議論の中心はProductという誰もが共有しているモノにある(と私は解釈した)。デザイナーは開発者の生み出す価値をユーザーに届けるためにどうデザインすべきか、経営は開発資源を効率的に使うためにどうマネジメントすべきか、開発者はユーザに製品の魅力を伝えるためにどうコミュニティを築くべきなのか。そのすべてのコラボレーションが製品というOutputを通じてOutcomeを生み出す。開発、ビジネス、ユーザのどれを取っても独立しておらず、すべてがProductや他のロールと繋がっている。プロダクトマネジメントはヒトのコラボレーションを認めてはじめてできるものだ。

もうひとつ、面白い観点にロックスターエンジニアというやつがある。有能なエンジニアはそうでないエンジニアの10倍、あるいは27倍の生産性を持っているというold termだ。これもたぶん昔は説得力のある概念だったのだろうが、近年では全く使われなくなっている。代わりにtechnology raderでは10x teamという表現を紹介していて、素晴らしいoutputが素晴らしいチームによって生まれるとしている。これはGoogleが研究で明らかにしたチームやマネジメントの重要性によっても肯定されていると言えよう。

つまり近年のソフトウェア開発の現場では、個人を育てること以上にチームを育てることに関心を持っている。個人を育て組織を開発することで、ビジネス上の競争力を効率的に高めていけると信じているわけだ。 チームを育てるには様々な手法があるだろうが、成長が内省によって生まれることを考えればBlameless Postmortemのような事実と向き合い理性的な議論を通じてより良い姿を模索する活動には一定の価値があると思われる。失敗時に限らず日頃から反省の機会を持つこと、Scrumで言うSprint Retrospectiveのような定例イベントも助けになるだろう。

さてこの手のイベントをやってみるとわかるのが、準備のコストが高いことだ。Postmortemのためには事実をリアルタイムに記録しなければならないし、ベロシティを測るには日々チケット消化の状況を記録する必要がある。自分とは違う専門性を持つチームメイトに向けて資料を整理することもあるし、わかりやすくするためにグラフを整理することもあるだろう。定期的なイベントの準備をすべて人力でやっていては、継続的にコストを払うことになる。

Development Workflowはこのコストを下げることができる。人手を使って証跡を残していた運用を、自動的にログを残す運用に変えられる。ChatOpsやGitOpsが最もイメージしやすいだろう。 また自動化やSaaSなどによって、相手の専門性に合わせたビューやファイル形式で情報を提供するコストも下げられる。例えばデザイナの要請を受けてCSSを変更したときに、それが既存ページにどういった影響を及ぼすか、percyのようなGUI regression testingが整っていればスクリーンショットを取ること無くPR一本で説明できる。

まとめ

とりとめのないままに書き下した結果、ひどいことになってしまったが。

結局は「cross-functional teamとして高速度で成果を出す」という時代の要請に自動化やコンテナ、サーバーレスといった技術が応えた形がいまのリアクティブワークフローなのだと思う。機械学習や業務改革の文脈で「機械ができることは機械にやらせて、ヒトはヒトにしかできないことをやろう」と言われるが、開発プロセスにそれを適用したものがこれだ。 事実と向き合い考えることこそが、ヒトにしかできないことだ。事実(ログ)の集約や解析、テストやビルドといった開発作業、ライブラリの更新や脆弱性報告の精査といった定例作業はすべてリアクティブワークフローにやらせる。私達は上がってきたデータをもとに考え、議論し、内省し、立てた仮説をもとに次のPRを作る。Gitにpushすれば機会が変更の妥当性をまず検証してくれるので、ヒトは既知の問題が無いことを前提に議論を尽くすことができる。タブ文字かスペースか、括弧の位置はどこにするかなんて議論をPRでする時代ではもう無いのだ。

もちろんここで議論したのはある種の理想形で、すべてのプロジェクトがこれを満たせるとは限らない。私が見ているFOSSプロジェクトにも、まだ手動で色々とやらなければリリースすらできないものもある。また10年前でも既にリアクティブワークフローを回していたところもあるかもしれない。

*1:このエントリではJenkinsが「古いWorkflowシステム」の代名詞として使われている節があるが、それは単に筆者個人の経験がそうだっただけで、Jenkinsというプロダクトがレガシーというわけではない。Cloudbees Flowなどを参照

*2:この点で、本考察はここ10年ではなく13〜15年とかで切ったほうが多くを学べるのかもしれないが、その頃は私が技術的情報収集をしていなかったのでよくわからない

*3:時期はよくわからないが後発のJenkins 2.0が2016年4月なので2014〜2015くらいか?

*4:GitHubこれとか、試してないけどSonatypeのこれとかjFrogのこれとか