Elixir から Elm の流れで、いよいよオブジェクト指向に対する懐疑心が無視できないレベルに達した2017年冬。

このエントリは Elm2 Advent Calendar 2017、2日目の記事になります。


Disclaimer: 勢いで書いてしまった後に改めて読み返してみると、Elmの中身には全く触れてないような気もしなくはない感じになってました… その辺を期待している方はブラウザ(のタブ)をそっ閉じして明日の記事にご期待下さい。


東京都港区の会社でインフラの仕事をしているフリをしながら、Elixir や Elm での関数型プログラミングに四苦八苦しつつ、Cotoami というよく分からないアプリケーションを作ったりしています。

今回は、まだ駆け出しの関数型プログラマーである筆者が、関数型プログラミングの洗礼を受けたことによって、長年慣れ親しんできたオブジェクト指向に対する見方が変わってきたという話について書いてみたいと思います。たとえて言えば、外国語を勉強することで、初めて日本語というものを客観的に見る機会を得たという体験に似ているかもしれません。

90年代からゼロ年代の中盤ぐらいまでにオブジェクト指向でプログラミングを始めた人間にとって、その考え方はプログラムデザインの共通言語のようになっていて、それ自体を疑うということには、なかなかなりづらい状況が長く続きました。Paul GrahamJeff AtwoodLinus Torvalds のような著名な人たちがオブジェクト指向に対する批判や懐疑を表明しても、「使い方の問題だよね」という感じで、オブジェクト指向そのものの問題ではないというのが多くの支持者の反応だったように思います。

あの TDD(Test-Driven Development)に対しても「Faith-based TDD」として同じ構造の批判がなされています(参考:「TDD再考 (8) – 凝集性(cohesion)とは何なのか?」)。このような議論の際によく見られる「◯◯が機能しないのは、◯◯のやり方を間違えているからだ」のような論法は、「No True Scotsman fallacy(本当のスコットランド人なら◯◯などしない論法)」だとの指摘もあります。

そもそもオブジェクト指向への批判が、関数型プログラミング界隈から行われることが多かったということもあり、その筋の人たちにとっては自明のことでも、オブジェクト指向しか知らない人たちにとっては、その指摘自体をうまく理解できないという非対称な構造がありました。

Information hiding vs. Explicitness

そのような状況の中で、オブジェクト指向への敬虔な信仰を残したまま、関数型プログラミングの門を叩いたわけですが、そこでいきなりオブジェクト指向の中心的な価値を全否定されるという事件が起こります。

オブジェクト指向では、状態というものがインタフェースの向こう側にあって、どのように実現されているか見えないようになっており、それがカプセル化、あるいは情報隠蔽と呼ばれる、複雑さを扱う技術の核心になっています。

ところが、関数型プログラミングでは状態の遷移を隠さずに、関数の入出力として表現しようとします(状態の遷移が入出力で完結している時、この関数を「純粋な関数」と呼ぶ)。

状態遷移が関数の入出力に限定されている時、プログラムの動作を把握するのは飛躍的に楽になります。一方で、オブジェクト指向ではプログラム上は簡潔に見えても、水面下に沢山の状態が隠されているので、何か問題が起きた時に状況を把握するのは容易ではありません。


The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. — Joe Armstrong(関数型プログラミング言語 Erlang の作者)

「欲しかったのはバナナだけなのに、それを持ってたゴリラどころか、ゴリラがいたジャングルごとついてきた」って、分かりやすくてなかなか面白い表現ですが、それを言ったら、関数型だってバナナだけじゃ済まないだろっていう話もあるような・・・


ところが、なぜオブジェクト指向が状態遷移を隠蔽していたかと言えば、インターフェースに対してプログラミングすることによって、プログラムが実現すべき要求だけを簡潔に表現できるということがあったと思います。そうせずに、単純に状態遷移を全て入出力で表現しようとすると、プログラムはとても読み辛いものになってしまいます。この関数型特有の問題に対応するため、いくつかの関数型言語ではモナドという「本来の計算とおまけを切り離す」ための仕組みが導入されており、これはオブジェクト指向でやっていた情報隠蔽が形を変えて現れたと言えるのかもしれません。

興味深いのは、オブジェクト指向と関数型で、状態の扱いに関して言えば、全く逆の考え方になっているところです。オブジェクト指向では複雑性を扱うための核心となっていた考え方が、関数型プログラミングでは悪として扱われている。

ここでぼんやり、オブジェクト指向と関数型のハイブリッド言語ってどうなのよ? という疑念が立ち上がってくることになります。

A paradigm is a mindset

そんな思いを抱いたまま、8月に開催された Elm Tokyo Meetup #4 に参加して、そこで教えて頂いた、Elmアプリケーションのプログラムデザインについての動画を見ている内に、オブジェクト指向と関数型プログラミングはそもそも両立しないのではないかという印象はさらに強くなっていきました。

この動画を見る限り、Elmのコミュニティには、オブジェクト指向からの編入組が結構いるのではないかという印象を受けます。というのも、そこで語られていたのは、コンポーネントという、オブジェクト指向的なデザインを導入したみたけれど、どうもうまく行かなかったので、一度その辺の考え方をリセットして、もっと原理的なところからプログラムデザインを考え直してみようという話だったからです。

動画の中で、オブジェクト指向と関数型というのは、プログラミング言語の問題というよりもマインドセットの問題なのだという話が紹介されています。つまり、言語のパラダイムとは関係なく、プログラマがオブジェクト指向のマインドセットでコーディングしていれば、たとえElmのような純粋関数型の言語であっても、オブジェクト指向的にデザインされてしまうということです。

そのように考えると、マルチパラダイムの言語では、良く言えば、プログラマのマインドセットによって多彩なプログラムデザインが実現できるということになるけれど、悪く言えば、互いに相容れない複数のマインドセットを想定している場合は、単に混乱の元になるだけではないかという感じがして来ます。

Prefer duplication over the wrong abstraction

そのモヤモヤ感が強まった中で、さらに追い討ちをかけて来たのが、Cindy Sridharan 氏による「小さな関数は有害だと考えられる」というタイトルの記事です。

この記事で彼女は、一般的には名著とされている「Clean Code」に書かれているような、オブジェクト指向時代に生まれた設計指針は、むしろ過剰な構造化を誘発して、可読性や柔軟性を欠いたコードになることが多いのではないかという、いかにもその筋で炎上しそうな指摘をしています。

この指摘の背景には「そもそもオブジェクト指向が想定する抽象化が容易ではない」という問題意識があります。抽象化が容易でないのに、オブジェクト指向の設計指針には、その分割が本当に必要だと確信できるより前に、プログラムの分割を進めさせてしまうような圧力があります。

SRP(Single Responsibility Principle)

SRPでは、「1つのクラスは1つの責務を持つ」を原則とします。複数の責務を 1つのクラスに持たせないこと。1つの分かりやすい役割をクラス分割の境界 とすること。1つのクラス内に入る要素(属性や操作)が、1つの目的に向かっ て凝集していること。これが原則です。

クラスに変更が起こる理由は、一つであるべき。
A class should have only one reason to change.

– ソフトウェア原則[3]

 

ISP(Interface Segregation Principle)

クライアントは自分が使わないメソッドに依存することを強制されない。
Clients should not forced to depend on methods that they don’t use.

クライアントが本当に必要としているインターフェイスのみ が、クライアントから見えるべきで、他のメソッドには依存したくない。依存 を最小にして、変更の伝播を最小限に食い止めたい。Segregationとは分割、分 離、という意味です。つまり、ISPは「インターフェイスをクライアント毎に分離しよう」という原則なのです。

– ソフトウェア原則[4]

この問題について、Elmの作者である Evan Czaplicki 氏も同じような話をしています。上の動画と同じ「Elm Europe 2017」にて行われた発表によれば、

JavaScript での開発では、モジュールを細かく分けて、小さなファイルを沢山作る傾向があるが(”Prefer shorter files.”)、何故そのようなことになるかと言えば、

1) 一つのモジュールが大きくなると、その内部で何か想定外のことが起こる可能性が高くなる(想定外の状態共有や変更)
2) Static type のない JavaScript では、リファクタリングのコストが高くなるので、早い段階で分割を進めてしまう

という理由があるからではないかと指摘しています。Elmでは、1) に対しては、副作用がない純粋関数型であること、2) に対しては、強力な型システムがあることによって、これらの懸念を払拭できるため、先走りのモジュール分割を避けることが出来るというわけです。実際に、Elmでは一つのファイルやモジュールが大きくなることについて、他の言語(特にオブジェクト指向言語)よりも寛容であるということがよく言われています。

そもそも適切な抽象化が難しいのに、その抽象化が有用であるという証拠が揃わない内に分割を進めてしまうと、より不適切な分割をしてしまう可能性が高くなってしまいます。先走って分割したモジュールが、後々の状況変化に対応できなくなって、例外条件に対応するコードが増えて行き、そしてスパゲッティ化していく過程というのはシステム開発の現場で働く多くの人が目撃しているのではないでしょうか。

duplication is far cheaper than the wrong abstraction, and thus to prefer duplication over the wrong abstraction

「間違った抽象化よりも、コードの重複の方を好む」ということで、長らく信奉されてきたDRY原則への挑戦がここでは行われています。

Leaky abstraction

そして、偶然なのか何なのか、これも同じ8月に、オブジェクト指向における抽象化がなぜ難しいかというのを良く表している大変興味深い記事を見つけることになります。

カプセル化することが自己目的化していて、何のためにカプセル化するのかという視点が極めて希薄なことです。その結果、「うまいカプセル化」「まずいカプセル化」の区別がない、という状況に陥っています。

本来は、値引き判定のロジックをどのオブジェクトに配するかを決めるにあたって、どのような知識を隠蔽すべきか、あるいは裏返して言えば、どのような知識は開示して構わないかという点に思いをめぐらすべきでした。

解決策は、「データとロジックを一体に」という、どちらかというとゲームのルールのような具体的で単純なルールから視点を引き上げ、「情報隠蔽(=知識隠蔽)」のような、より本質的な、目的志向的な設計原則に立ち帰って考えることです。

この記事の要旨は、増田亨氏の「現場で役立つシステム設計の原則」という書籍で紹介されているオブジェクト指向のコード例について、「データとロジックを一体に」というオブジェクト指向の表面的なルールに囚われ過ぎて「何を隠蔽して何を表に出すのかという設計判断」を蔑ろにしているという指摘です。

しかし、この記事を読んで個人的に思ったことは、設計判断の根拠となる「スコープが適切でない」ということを、後から文脈をズラすことでいくらでも言えてしまうというのが問題の本質じゃないか、そこに「データ・ロジック一体型設計」の限界があるということなのではないか、ということでした。

うまく抽象化したつもりでも、どこかに必ず漏れが出てきてしまうという話は、「Joel on Software」の「The Law of Leaky Abstractions」という、2002年に書かれた記事に出てきます。

TCPプロトコルが、下位のネットワークをうまく抽象化しているように見えて、実際はいくつかの例外ケースで、その隠蔽しているはずのネットワークの存在が漏れて出してしまう(Abstraction Leak)。そうなった時にかかるコストというのは、抽象化がなかった時よりも高くついてしまう可能性があります。隠蔽された部分の知識も結局のところは必要なのだとなれば、抽象化された部分と隠蔽された部分の両方の知識が必要になるからです。

Objects bind functions and data structures together

オブジェクト指向の問題点を指摘する場合、一番厄介なのは、オブジェクト指向に定まった定義がないという事実です。このブログでは以前、オブジェクト指向の歴史を遡って、あれってそもそも何だったのかということについて検討したことがあります。

歴史的な経緯から言えば、オブジェクト指向を発案したアラン・ケイ氏が言うところの「メッセージング」が、オブジェクト指向の本質だということなりそうですが、一般に普及した「オブジェクト指向言語」と呼ばれるもので、メッセージングをサポートしているものはほとんどありません。メッセージング、あるいはそれが実現する動的結合(late binding)だけを考えると、それは今、オブジェクト指向と呼ばれるものよりも遥かに広い範囲で利用されていますし、実際にはオブジェクト指向言語じゃなくても実現できることを考えると、C++ をきっかけに流行した「抽象データ型」を起源とする流れが、一般的に認識されているオブジェクト指向だと考えて差し支えなさそうです。

ちなみに、オブジェクト指向信者の反論を「No True Scotsman fallacy」だと指摘した Lawrence Krubner 氏によれば、一般的にオブジェクト指向の強みだと思われているほとんどの要素はオブジェクト指向固有ではなく、オブジェクト指向固有の強みなど、実際には一つもないそうです。

オブジェクト指向固有でないものを除外していくと、最後に残るのが「データとロジックを一体に」という先ほどのルールです。そして、どうもこのルールがオワコンになりつつあるのではないかというのが、この約1年間、Elmでプログラミングをしていて実感するようになったことです。

関数型プログラミングをしていると、データとロジックが分かれていることのメリットを実感する機会が度々あります。アプリケーションにはアプリケーション固有の複雑さというものがあって、それらは多くの場合、必要な複雑さである場合が多いような気がしています。オブジェクト指向では、データとロジックを一緒にしなければならないという制約のために、それらの例外的だと思われるケースを捨象して、現実に即さないモデルに(強引にでも)落とし込むことになります。必要な複雑性を無理に捨象しようとするから、Abstraction Leak が起こります。

オブジェクトに関数が結びついているからこそ「このメソッドはこのオブジェクト構造を処理するためのものだ(他の用途には使えない)」という風に専門化できていたんであって、データと関数を個別のものと扱う以上は、「この関数はこのオブジェクトだけを扱う」という前提を置けないのです(当たり前です)。あるオブジェクトと別のオブジェクトが、型であったりクラスであったりが異なったとしても、関数は、そのオブジェクトが、関数の処理できる構造であれば、処理できるべきなんです。関数とオブジェクトが独立しているというのはそういう意味であるべきです。であれば、すべての関数にまたがるような、共通の汎用データ構造があって、すべてのデータはその汎用性を担保してたほうがいい。 Clojureの世界観 – 紙箱

Oscar Nierstrasz 氏が、彼のオブジェクト指向批判の中で、「オブジェクト指向とは、つまりモデリングなのだ」と喝破していますが、複雑な事象を分かりやすい用語(ターム)の集合に落とし込めるという先入観が、アプリケーションレベルの複雑性を扱う時に明らかな障害となって現れるケースが多くなっているような気がします。

メッセージングが今のオブジェクト指向と関係ないのだとすれば、「データとロジックを一体に」がオブジェクト指向の核心になりますが、そうだとすれば、オブジェクト指向自体がオワコンだという結論になってしまいます。そして、それはどうもそうっぽいという感じがしているのです。

Solve problems of its own making

ここからは少し余談になりますが、以上のような気づきを得た上で、過去のオブジェクト指向批判の文章を読むといちいち首肯できることが多くて困ってしまいます。

オブジェクト指向というのは、オブジェクト指向にしかない問題を作り上げて、それを解決するためのツールを作るというマッチポンプ的なことをしてお金を稼いでいるという話があります。

If a language technology is so bad that it creates a new industry to solve problems of its own making then it must be a good idea for the guys who want to make money. Why OO Sucks by Joe Armstrong(さっきのバナナの人)

あるいは、オブジェクト指向ではそもそも過剰な複雑性を作り込んでしまう傾向があるという批判があります。何故かといえば、オブジェクト指向には、インターフェースに対してプログラミングするという考え方があるので、プログラムは自然にレイヤー構造になっていくからです。もう古典と言っても良いかもしれない、Martin Fowler 氏の「リファクタリング」にも、

「コンピュータサイエンスは、間接層(indirection)を設けることであらゆる問題が解決できるという信念に基づいた学問である。― Dennis DeBruler」

とあったりしますが、

しかし、間接層はもろ刃の剣であることに注意しなければなりません。1つのものを2つに分割するということは、それだけ管理しなければならない部分が増えるということなのです。また、オブジェクトが、他のオブジェクトに委譲を行って、その先もさらに委譲を繰り返すような場合、プログラムが読みにくくなるのも事実です。つまり間接層は、最小限に絞り込むべきなのです。

とは言え、実際には過剰なレイヤー構造になっていることが多いような気がします。アジャイルという考え方が出てきて、「Just enough」や「YAGNI」なんてことが言われるようになりましたが、今を思えば、これはオブジェクト指向の側にそもそも過剰な複雑性を生む性質があったために、わざわざ言わなければならなくなったことのようにも思えてきます。

オブジェクト指向特有の問題として指摘されている中で、ああこれはと思ったのは、オブジェクトをどう作るかという問題、すなわち Dependency Injection の問題です。

OOP was once seen as the silver bullet that was going to save the software industry. Nowadays we need a silver bullet to save us from OOP, and so, we are told (in the next paragraph), the Inversion of Control Container was invented. This is the new silver bullet that will save the old silver bullet. Object Oriented Programming is an expensive disaster which must end | Smash Company

これはまさにオブジェクト指向にしか存在し得ない問題を、比較的大掛かりに解決しようとした例の代表だと言えそうです。今改めて考えると、この枠組みには二つの問題があって、一つはインタフェースベースのポリモーフィズムの問題(あらかじめ想定したインタフェースの範囲の柔軟性しか持てない)、そしてもう一つは、本当に複数の実装を必要とするケースがどれだけあるのか? という問題です。後者は仮に統計が取れれば面白い数字が出てきそうですが、少なくともテスト時にモックオブジェクトに置き換えられるという主張は、テスト容易性はすなわち良いデザインではないと、Ruby on Rails の作者である David Heinemeier Hansson 氏に批判されています。

最後に

なんか、「もうやめて!オブジェクト指向のライフはゼロよ!」みたいな感じになってしまいましたが、これは完全に Elm のせいです。

オブジェクト指向と関数型プログラミングは両立するのか?

オブジェクト指向と関数型プログラミングは両立すると言われる。実際に両方のパラダイムを持つプログラミング言語も存在し普及もしている。確かに言語というレベルでは両立しているように見えるが、果たしてプログラムデザインのレベルではどうなのだろうか? 関数型プログラミングに入門してから一年余り、どうもこの二つの考え方は両立しないのではないかという思いが強くなってきた。あるいは、これらが両立するという前提が混乱の元になっているのではないかと思うようになった。

例えば、「関数型つまみ食い: 関数型プログラミングの何が嬉しいのか?」では、オブジェクト指向と関数型での、状態に対するアプローチの違いについて触れた。オブジェクト指向では状態とその遷移を隠蔽することによってシステムを単純化する一方、関数型では(多少冗長になっても)状態遷移を明確にすることによってシステムの動作を予測しやすくするという対比だ。つまり両者は、状態に関しては真逆のアプローチを取っている。

オブジェクト指向でも、不可変なデータを扱うことによって意図しない状態遷移を防ぐことができるし、それによってオブジェクト指向でなくなってしまうわけではないと考えることもできる。あるいは、副作用を避ける(不可変なデータを扱う)場所と、副作用を扱う場所を分けることによって、両者の考え方をうまく共存させることができるのではないかと。

しかし、先月開催された Elm Tokyo Meetup にて教えて頂いた、Elm Europe 2017 のアーカイブ動画を見ていたら、オブジェクト指向と関数型はそもそも根本的に異なる思想なのだという思いを新たにした。ちなみに Elm は Web のフロントエンド開発に特化した、純粋関数型のプログラミング言語である。

この発表では、プログラムデザインの流れをもう一度基礎から再考すべきであるということが語られている。まずは一枚岩で構造化されてない状態から初めて、「Build -> Discover -> Refactor」というサイクルを回しながら、構造化すること自体を目的化せず、今行おうとしているそのリファクタリングによって「そもそも何を保障したいのか?」を問う。

ここでの狙いは、過去の経験によって培われた「構造化の先走り」を防ぐことにある。「構造化の先走り」とは何か? Elm で有名な問題に「コンポーネント・アンチパターン」というものがある。

以前の Elm のチュートリアルでは、UI をコンポーネントの集まりとして表現する、というそれまで当たり前に考えられてきた文脈に沿って、状態とその変更ロジック、そしてその状態を表示するためのビューをひとまとめにしたコンポーネントというものを定義し、それらを組み合わせることによってアプリケーションを構築するという手法を紹介していた。しかし、このデザインでは、アプリケーションが大きくなるにつれてコンポーネント同士を連携させるためのボイラープレートが膨大になったり、柔軟性に欠けるケースや無駄な重複が現れるとして、その後、コンポーネント化はできるだけ避けるべきだと明言されるようになった。

Elm Europe の動画では、コンポーネントとは結局のところ、状態と操作をひとまとめにする、オブジェクト指向の構造化手法であることが指摘されている。長らくオブジェクト指向に親しんできたプログラマーが Elm に同様の手法を適用した結果、思ったような柔軟性を得られなかったという流れだ。

このブログでは過去に「オブジェクト指向とは何だったのか?」という記事で、オブジェクト指向をその起源に遡って解釈しようと試みたことがある。

ケイ氏は、オブジェクトを、ネットワークを形成してメッセージを送り合うコンピュータのメタファーとして捉えており、インターネット上のサーバーのように、リクエストをメッセージとして受け取り、そのメッセージをサーバー側で解釈して何らかの処理を行うというモデルを想定していた。

この全てがオブジェクトであるという原則と、大きなシステム(オブジェクト)は小さなシステム(オブジェクト)の組み合わせで作られるという「再帰的デザイン」によって、どんな複雑なシステムをデザインするときでも、覚えなくてはならない原則は少なくて済むようになる。

オブジェクト指向では上のような再帰的構造を持つものとしてシステムを捉える。これがアプリケーションに応用されると「アプリケーションはより小さなアプリケーションの集合によって表現される」ことになる。凝集性の観点から言えば、これはとても魅力的なアプローチに思える。アプリケーションを構成する、より小さなサブアプリケーションは各々が独立していて、変更の影響も局所化される。

しかし、関数型の世界(少なくとも Elm の世界)では、このモデルに疑いの目が向けられることになった。

件の動画では、アプリケーションはアプリケーション固有の(そもそも必要とされる)複雑さを持つのだから、その複雑さに対応するために、データから操作を切り離し、両者をより柔軟に組み合わせることによってその複雑性に対応すべきだということが示唆されている。その結果として、Elm ではグローバルな状態やロジックを集めたコードが長くなることについて、他の言語よりも寛容である、ということが説明される。これはあるいは Elm の持つ強力な型システムの支えもあるかもしれないが。

アプリケーションの再帰的構造というオブジェクト指向のモデルは、昨今のフロントエンドのような複雑なシステムを表現するには単純に過ぎるという問題は、オブジェクトとリレーショナルモデルをマッピングするときに問題視されたインピーダンスミスマッチの問題に似ているかもしれない。

最近、この問題に関係していると思われる大変興味深い記事に遭遇した。Cindy Sridharan 氏による「小さな関数は有害だと考えられる」というタイトルの記事だ。

タイトルが若干ミスリーディングであることは本人も認めているが、彼女が言わんとしていることはこういうことである。名著「Clean Code」などに書かれていたり、あるいはソフトウェア開発における著名人が喧伝するような、今では当たり前とされる、オブジェクト指向時代に生まれた設計指針が果たして今も本当に有効なのかどうか今一度確認してみるべきではないのか、と。

彼女の問題意識は、適切な抽象化というものがそもそも難しいのだというところから出発して、DRY原則などによって無条件に構造化を推し進めようとする既存の設計手法は、多くの場合に失敗に終わる抽象化によって、可読性や柔軟性を欠いたコードになることが多いという発見に辿り着く。オブジェクト指向時代の多くのプラクティスは抽象化が適切に行われることを前提にしているため、物事が複雑に揺れ動く現実の世界では、そもそもそのメリットを享受することが難しい。

duplication is far cheaper than the wrong abstraction, and thus to prefer duplication over the wrong abstraction

そもそも完璧な抽象化というものは存在しない。抽象化された概念というのは、ある文脈を前提にした主観的なものに過ぎないからだ。そしてのその文脈はあらゆる要因によって常に揺れ動いている。Sridharan 氏は、既存のコードが持つ影響についても指摘している。すでに書き上がったコードはある種の成果であるため、たとえ文脈が変わってしまっても、そもそもの文脈を廃棄することには心理的な抵抗が働くのだと。その結果、いびつな形で増改築が行われた建築物のように、構造化はされているが、全体像を捉えるのは難しいプログラムが出来上がってしまう。

より速くそして複雑に変化するシステムを捉えるためには、無条件な構造化については慎重にならなければならない。下手に構造化するぐらいであれば、多少関数が長くなっても実直に書かれている方が遥かに可読性が高く修正もしやすい。そして、過剰な抽象化を避けるためにコードのライフサイクルについても思いを馳せるべきだと Sridharan 氏は指摘する。これまでの設計指針では、コードを追加したり、削除したりする際は Open-Closed Principle(開放/閉鎖原則)に則るべきだと言われてきた。しかし、その原則がうまく働くのは抽象化がうまく行われた時だけである。であれば、「修正しやすい」こととは何かということを今一度考え直すべきなのかもしれない。

さて、この議論を呼びそうな記事に対しては、当然のことながら、既存の設計指針を支持する陣営から反論が行われている。以下はその一例である。

反論の要旨は以下のような感じだ。

  • 大きな関数は変更が局所化されないので脆弱である
  • ユニットテストが書きづらくなるため、結果としてテストされないコードが増える
  • 適切に抽象化されれば、やたらと長い名前問題はそもそも起こらない
  • コードは Open-Closed Principle に従って修正されるべきである
  • 大きな関数は副作用を持つ可能性が高くなる

こういう反論はいかにもありそうだという印象を個人的には受ける。「あなたはそもそも適切に抽象化できていないから」小さな関数が有害だと感じるのだという、オブジェクト指向界では比較的馴染みのあるものだ。「そもそも抽象化が難しい」というところには応答しておらず、議論はすれ違っている印象を受ける。

「そもそもやり方が悪いんだ」問題は、以前このブログで紹介した「Is TDD Dead? 会談」にも現れていた。

DHH氏の想定するようなTDDは、Beck氏やFowler氏の想定するTDDとは異なり、単にやり方が悪いからうまく行かないだけで、DHH氏の発言は「わら人形論法」に過ぎないというのが、TDD肯定派の反論である。それに対して「うまく行かないのはやり方が間違えているからであって、正しく実践すればいつかは正解に辿り着ける」というのは「faith-based TDD」という信仰であって、そのようなものは認められないというのが DHH氏の立場であり、ここがすれ違いの中心になっている。

そして、「ユニットテストが書きづらくなる」という主張も、関数型(特に Elm のような静的型付けの)ではユニットテスト の役割が相対的に小さくなること、あるいは「TDD再考」でも取り上げたように、ユニットテストそのものの価値の問題を考えると、それほどクリティカルな反論であるとは言えないように思える。

Kubernetes on AWS: LoadBalancer型 Service との決別

LoadBalancer型 Service (type: LoadBalancer) は、Pod群にアクセスするための ELB を自動的に作ってくれて便利なのだが、ELB に関する全ての設定をサポートしているわけではなく、Service を作り直す度に、k8s の外側でカスタマイズした内容もやり直さなければならないのはつらい。というわけで、type: LoadBalancer を利用するのは止めて、ELB は Terraform で管理し、そこから NodePort型 Service に接続する方法を試してみた。

Kubernetes がサポートする ELB 設定

「ELB に関する全ての設定をサポートしているわけではなく」と書いたが、今現在どれぐらいサポートされているのだろうか? 改めて調べてみた。

Service 定義の metadata.annotations に、以下の値を書くことで ELB の設定を行うことが出来る (v1.5現在)。

  • Backend protocol
    • TCP – default
    • HTTP – service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    • HTTPS – service.beta.kubernetes.io/aws-load-balancer-backend-protocol: https
  • SSL Certificate
    • service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "[cert-arn]"
  • SSL Port
    • service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
    • The SSL certificate will only be used for 443 and not 80.
  • Internal ELB
    • service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
  • Security group
    • service.beta.kubernetes.io/load-balancer-source-ranges: [a comma separated list of CIDRs]
  • Idle timeout
    • service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: [seconds]
  • Access logs
    • Enabled – service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: [true|false]
    • Emit interval – service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: [minutes]
    • s3://bucket/prefix
      • S3 bucket – service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: [bucket-name]
      • S3 prefix – service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: [prefix]
  • Cross-Zone Load Balancing
    • service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: [true|false]
  • Connection draining
    • service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: [true|false]
    • service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: [seconds]
  • Proxy protocal
    • service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: '*'
  • Route53

こうやってまとめてみると、v1.5 においては、ELB の設定項目はほとんど網羅されてるような印象を受ける。Route53 についても Third party の拡張を使えばなんとかなるようなので、ほとんどのケースではわざわざ LoadBalancer を別管理にする必要はないのかもしれない。

それでもあえて、LoadBalancer を k8s の外側で管理する理由があるとすれば、

  • k8s に問題が起きた時に、通常の EC2方式に戻せるようにしておきたい。
  • Application Load Balancer (ALB) を使いたい。
  • ELB に分かりやすい名前を付けたい。
    • k8s側から作ると a5902e609eed711e69a1986001d7b1fb みたいなランダムな名前になる。
    • Tag kubernetes.io/service-name みれば、どの Service のものかは分かるのだけど。
  • Cloudwatch Alarm を Terraform で管理したい。
    • ELB に Cloudwatch Alarm を設定する場合は、ELB も Terraform で管理しておいた方がやりやすい。
  • k8s の LoadBalancer 管理に一抹の不安がある。

ぐらいだろうか。

移行手順

AWS 上の Kubernetes クラスタが kops で構築されていることを前提に、ELB を Terraform で 管理する運用に移行してみる。

  • クラスタの名前を仮に k8s.example.com とする。

1. Terraform ELB から k8sノードへアクセス出来るように追加のセキュリティグループを作る

k8sノード用とELB用の二つのセキュリティグループを作る。

# For nodes
resource "aws_security_group" "nodes" {
  vpc_id = "${module.environment.vpc_id}"
  name = "additional.nodes.k8s.example.com"
  description = "Additional security group for nodes"
  tags {
    Name = "additional.nodes.k8s.example.com"
  }
  # ELB から NodePort経由のアクセスを受け付ける
  ingress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    security_groups = ["${aws_security_group.service_elb.id}"]
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# For ELB
resource "aws_security_group" "service_elb" {
  vpc_id = "${module.environment.vpc_id}"
  name = "k8s-service-elb"
  description = "Security group for k8s service ELBs"
  tags {
    Name = "k8s-service-elb"
  }
  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port = 443
    to_port = 443
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

kops 1.5.x 以降では、マスターあるいはノードに対して、デフォルトのセキュリティグループの他に、独自のセキュリティグループを追加することができる。

上で作ったセキュリティグループ(additional.nodes.k8s.example.com)が、ノード起動時に適用されるようにクラスタの設定を更新する。

$ kops edit ig nodes
...
spec:
  additionalSecurityGroups:
  - sg-xxxxxxxx  # additional.nodes.k8s.example.com
...

$ kops update cluster k8s.example.com --yes

Sticky session を必要としない場合

2. Service を NodePort 型で立ち上げる

通常 NodePort の番号は自動で決定されるが、Terraform ELB から接続できるように固定の番号を設定しておく。NodePort に設定出来る番号の範囲は 30000-32767

apiVersion: v1
kind: Service
metadata:
  name: example-app
spec:
  selector:
    app: example-app
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30000

3. Terraform で ELB を立ち上げる

  • instance_porthealth_checktarget に NodePort を指定する。
  • instances は設定しない(後ほど Auto Scaling group と紐付けるため)。
  • k8sノードへアクセス出来るように、先ほど作ったセキュリティグループ(k8s-service-elb)を設定する。
resource "aws_elb" "example_app" {
  name = "k8s-example-app"
  cross_zone_load_balancing = true
  subnets = ["${split(",", module.environment.subnets)}"]
  security_groups = ["sg-xxxxxxxx"]   # k8s-service-elb
  listener {
    instance_port = 30000
    instance_protocol = "http"
    lb_port = 80
    lb_protocol = "http"
  }
  listener {
    instance_port = 30000
    instance_protocol = "http"
    lb_port = 443
    lb_protocol = "https"
    ssl_certificate_id = "${module.environment.ssl_certificate_id}"
  }
  health_check {
    healthy_threshold = 3
    unhealthy_threshold = 2
    timeout = 5
    target = "HTTP:30000/healthcheck"
    interval = 30
  }
}

4. ELB にドメイン名を設定する

サービスのドメイン名が、たった今作った ELB を参照するように Route53 を設定する。

resource "aws_route53_record" "example_app" {
  zone_id = "${module.environment.zone_id}"
  name = "app.example.com"
  type = "CNAME"
  ttl = "300"
  records = ["<elb-dns-name>"]
}

5. ELB をノードの Auto Scaling Group に紐付ける

3 で立ち上げた ELB を、k8sノードの Auto Scaling Group (nodes.k8s.example.com) の Load Balancers に追加する。これによりk8sノードがELBに追加される。

6. 全ての k8sノードが ELB に追加されて、InService になることを確認する

サービスのURLにアクセスして、サービスが問題なく稼動していることを確認する。

Sticky session を必要とする場合

アプリケーションが sticky session を必要とする場合は、予め ingress controller をクラスタにインストールしておき、そこ経由でサービスにアクセスさせる。なので、このケースではアプリケーション用の ELB は立てない。

参考: Kubernetes on AWS で sticky session を実現する | ゆびてく

今回の移行では、ingress controller の入り口となる Service を NodePort で作っておいて、そこにアクセスする ELB を Terraform で作った。

2. Service を ClusterIP 型で立ち上げる

apiVersion: v1
kind: Service
metadata:
  name: example-app
spec:
  selector:
    app: example-app
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

3. Ingress controller にドメイン名を追加する

サービスのドメイン名が ingress controllerの ELB を参照するように Route53 を設定する。

resource "aws_route53_record" "example_app" {
  zone_id = "${module.environment.zone_id}"
  name = "app.example.com"
  type = "CNAME"
  ttl = "300"
  records = ["<ingress-controller-elb-dns-name>"]
}

4. Ingress を追加する

Ingress controller から対象のサービスにアクセス出来るように ingress rule を追加する。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sticky-session-ingress
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: example-app
          servicePort: 80

5. サービスが問題なく稼動していることを確認する

サービスのURLにアクセスして、サービスが問題なく稼動していることを確認する。

Kubernetes Secrets の紹介 – データベースのパスワードやその他秘密情報をどこに保存するか?

なんてこったい(棒)

GitHub の Public リポジトリには、太っ腹な開発者によって大量の Credentials(外部サービスに接続するための秘密キーなど)が公開されており、賢い人たちが日夜クローラーを走らせてそれらを回収し、5万件もの Uber ドライバーの個人情報を頂戴するために利用したり高価な AWS のインスタンスを沢山立ち上げて、もの凄い勢いでビットコインを発掘したりしているらしい。

ウチのリポジトリはプライベートだから問題ないよねって思われる方もおられるかもしれないが、ほんの5分間違って公開しただけで流出したケースもあるらしいので、そもそもコードリポジトリに秘密情報を入れること自体が太っ腹行為の可能性を高めていることを理解しておきたい。

というわけで、Kubernetes でサービスを運用する場合、そういった秘密情報をどこに保存すれば良いかという要求に応えるのが Secrets という仕組みである。

kube-secrets

秘密情報を Secrets というデータベースで集中管理し、それぞれの情報はそれらを必要とする Pod/Container のみに送られる。Docker Image や Container を作るプロセスから秘密情報を切り離せるので、その過程で情報を漏洩させるリスクは少なくなる。Container に送られた秘密情報は tmpfs 上に置かれるので、ノード上のディスクに書き込まれることもない。

Kubernetes 自体がまだ若いプロジェクトなので、この Secrets にも注意しなければならない点がいくつかある。

  • Secrets のデータは etcd の中に平文で保存されているので、etcd には管理者ユーザーだけがアクセス出来るようにセットアップする必要がある。
    • etcdは kubernetes のあらゆるデータを保管しているデータストア。
  • 現在のところ、Secrets に対するユーザーごとのアクセスコントロールは出来ない(将来的にはサポート予定)。

以下は、社内向けに書いた Secrets の簡単なチュートリアル。


Secret のデータ構造

Secret の中身は単純な Key-Value ペアのリスト:

secret-structure

kubectl コマンドで登録されている Secret のリストを見る:

$ kubectl get secrets
NAME                  TYPE                                  DATA      AGE
default-token-pb7ls   kubernetes.io/service-account-token   3         21m
mysecret              Opaque                                2         38s

その中から一つの Secret を選んで中身を見てみる:

$ kubectl describe secret mysecret
Name:       mysecret
Namespace:  sandbox
Labels:     <none>
Annotations:    <none>

Type:   Opaque

Data
====
password:   12 bytes
username:   5 bytes

mysecret の内容を図に書くと以下のような感じ:

mysecret

Secret を登録する

Secret は、以下の二種類のファイルのいずれかを経由して登録できる。

  1. 中身が Value になっているファイル(便宜的に「Secret Value ファイル」と呼ぶ)
  2. YAML あるいは JSON 形式の Kubernetes Manifest ファイル

以下のような Secret を、

[Secret: test-secret] => [Key: password] => [Value: this-is-a-password]

それぞれのファイル形式で登録してみよう。

1. Secret Value ファイル経由

1) ファイルを作る

$ echo -n "this-is-a-password" > ./password

2) --from-file オプションを使って登録

$ kubectl create secret generic test-secret --from-file=./password
secret "test-secret" created

3) 中身を見てみる

$ kubectl describe secrets/test-secret
Name:       test-secret
Namespace:  sandbox
Labels:     <none>
Annotations:    <none>

Type:   Opaque

Data
====
password:   18 bytes

--from-file に指定したファイルの名前が Key になっていることが分かる。

2. Kubernetes Manifest ファイル経由

1) Value を base64 でエンコードする

$ echo -n "this-is-a-password" | base64
dGhpcy1pcy1hLXBhc3N3b3Jk

2) ファイルを作る

以下の内容を secret.yaml に保存:

apiVersion: v1
kind: Secret
metadata:
  name: test-secret
type: Opaque
data:
  password: dGhpcy1pcy1hLXBhc3N3b3Jk

3) -f オプションを使って登録

$ kubectl create -f ./secret.yaml

4) 中身を見てみる

$ kubectl describe secrets/test-secret
Name:       test-secret
Namespace:  sandbox
Labels:     <none>
Annotations:    <none>

Type:   Opaque

Data
====
password:   18 bytes

Secret Value ファイル経由のときと全く同じ Secret が出来ていることが分かる。

Secret の中身を取得する

$ kubectl get secret test-secret -o yaml
apiVersion: v1
data:
  password: dGhpcy1pcy1hLXBhc3N3b3Jk
kind: Secret
metadata:
  creationTimestamp: 2017-03-01T08:49:49Z
  name: test-secret
  namespace: sandbox
  resourceVersion: "12535581"
  selfLink: /api/v1/namespaces/sandbox/secrets/test-secret
  uid: 0747637f-fe5c-11e6-8f7a-0674330dcd09
type: Opaque

Value をデコードする:

$ echo "dGhpcy1pcy1hLXBhc3N3b3Jk" | base64 --decode
this-is-a-password

Secret を使う

Container から Secret を使うには、

  1. 環境変数
  2. Volume としてマウント

の二種類の経路がある。

1. 環境変数

Container の Manifest から以下のような感じで参照:

containers:
- name: http-debug-server
  image: cotoami/http-debug-server:latest
  ports:
  - containerPort: 3000
  env:
    - name: PASSWORD
      valueFrom:
        secretKeyRef:
          name: test-secret
          key: password

Container にログインして、環境変数を見てみる:

$ kubectl get pods
NAME                                 READY     STATUS    RESTARTS   AGE
http-debug-server-4073343574-ro8s8   1/1       Running   0          2m

$ kubectl exec -it http-debug-server-4073343574-ro8s8 /bin/sh

# echo $PASSWORD
this-is-a-password

2. Volume としてマウント

Container 内のディレクトリに、Key をファイル名、Value をファイルの中身としてマウントできる。

以下のように、volumes を定義しておいて、それを volumeMounts でマウントする:

containers:
- name: http-debug-server
  image: cotoami/http-debug-server:latest
  ports:
  - containerPort: 3000
  volumeMounts:
  - mountPath: /tmp
    name: test-secret
    readOnly: true
volumes:
- name: test-secret
  secret:
    secretName: test-secret

Container にログインして、マウントされたファイルを見てみる:

$ kubectl get pods
NAME                                 READY     STATUS    RESTARTS   AGE
http-debug-server-2197190929-q6lke   1/1       Running   0          1m

$ kubectl exec -it http-debug-server-2197190929-q6lke /bin/sh

# cat /tmp/password
this-is-a-password

マウントされた Secret を後から更新した場合は、Kubernetes が自動的に検出してリフレッシュしてくれる。

参考

Kubernetes の登場でインフラ担当の仕事はますます曖昧になっていくような気がする

アプリケーション開発者は Dockerfile を作るところまで、インフラ担当はサービス構成とデプロイメントパイプラインを整備する、という分担を考えていたけれど…

マニフェストファイルを作ってサービスの構成をデザインしたり、ビルドスクリプトを書いて自動デプロイを実現するのも、もうアプリケーション開発者のレベルで出来る。ここを無理に分業すると、むしろ k8s が提供するサイクルの短さを享受出来なくなる可能性がある。サービスが出来上がってからインフラを移行するというのは k8s の考え方に合わないので、開発の初期から k8s 上で育てるのが標準的なモデルになるが、そのモデルに則って開発して行けば、開発者は無理なくそのプラットフォームを使いこなせるようになる。

つまり、DevOps の問題が、Kubernetes によるマイクロサービス化によって、ますます深刻になる可能性がある。アプリケーション更新のサイクルとインフラ更新のサイクルが限りなく近づいたとき、そこにどこまでのコミュニケーションコストを許容出来るだろうか。

チームとして分かれていることのコミュニケーションコストは決して過小評価することは出来ない。

では、インフラチームは Kubernetes クラスタの管理やモニタリングを担当するようにしたらどうか?

筋的には悪くない気もするが、GKE のような環境が充実してきた時に、アプリケーション開発者が自身で出来る領域はさらに広がって行くのではないかと思う。

さらには、Kubernetes のような環境に適応出来る・出来ない、という形でも大きな分断が起きて行きそうな予感もある。

Kubernetes に限らず、インフラの自動化を進めるためには、アプリケーションパッケージのポータビリティが重要になってくる。しかし、そのようなことを意識して開発しているプロジェクトは案外少ないのではないだろうか。例えば、Twelve-Factor App みたいな指針を全く知らないというのも珍しくないのではないか。そうなると、インフラ担当はアーキテクチャやソフトウェアデザインを指導する立場になるが、これは結構広範な指導を必要とするし、そのような規律を快く思わないエンジニアも多い。

一方で、Twelve-Factor App みたいな指針に慣れているエンジニアは、上に書いたように、自分でアーキテクチャや自動化の基盤を作って行けるので、自律してサービスを開発出来る。

アプリ開発もインフラも、一つのチームに閉じることに越したことはないけれど、多くの組織ではそんな贅沢は許されないだろう。組織全体でインフラを刷新していくためには、どうしても独立したインフラチームが必要になる。でも、マイクロサービス開発のサイクルの短さに合わせて、チーム間のコミュニケーションコストを下げて行くのは至難の業のように思える。

DevOpsの起源とOpsを巡る対立 | ゆびてく

Docker(コンテナ型仮想化)と Kubernetes についての簡単な紹介

社内向けに書いた文書です。

コンテナ型仮想化とは何か?

OS上に、コンテナと呼ばれる、隔離されたアプリケーションの実行環境を作り、一台のホスト上であたかも複数のホストが動いているかのような環境を実現するのが、コンテナ型仮想化技術です。

コンテナ型仮想化と従来の VM 型仮想化を比較したものが以下の図です(Xen は AWS EC2 などの仮想化を実現する実装で、Docker はコンテナ型仮想化の代表的な実装)。

container

これらの仮想化技術に共通するのは、

  1. アプリケーションの実行環境(ホスト)を仮想化すること
  2. それらのスナップショットをイメージとして保存することで、アプリケーションを環境ごとパッケージング出来るようにすること

という二つの目的です。

VM 型仮想化ではハードウェアのレベルで仮想化が実現されているので、ホスト上で動く一つ一つの VM の中でそれぞれ別々の OS を動作させることができます。しかしその一方で、実行環境(VM)のサイズが大きくなり、アプリケーションの起動にかかる時間も長くなります。

コンテナ型仮想化では OS 上の実行環境を仮想化するので、OS 自体は限定されますが、その分実行環境(コンテナ)のサイズはコンパクトになり、起動にかかる時間も短くなります。

基本的に VM 型仮想化からコンテナ型仮想化への移行は、一つ一つのアプリケーションの粒度が小さくなる、いわゆるマイクロサービス化を促進します。インフラ上の更新の単位はより小さくなり、変更のサイクルは短くなります。

パッケージング技術の変遷

packaging

Kubernetes とは何か?

1. Docker 実行環境をクラスタ化する

通常、Docker の実行環境は一台のホストに閉じています。

docker-network

同一ホスト内で動くコンテナ同士は、プライベートネットワーク経由でやり取りができますが、ホストの外側とやり取りする場合は NAT (IP Masquerade) を経由する必要があります。

このように、標準の Docker 実行環境では、ホスト間の連携が煩雑になるため、コンテナの数が増えて要求されるリソースが大きくなった時に、容易にスケールアウトすることが出来ません。

この問題を解決するのが Kubernetes です。

Kubernetes によって、複数台のホストから構成される実行環境を あたかも一台の実行環境のように 扱うことができるようになります。

kube1

コンテナを起動する際は、イメージと台数を指定するだけでよく、クラスタのどこにどのように配置するかは Kubernetes 側で面倒を見てくれます(スケジューリング)。

そして、クラスタのリソース(CPU、メモリ、記憶領域など)が足りなくなった場合は、単純にノードを増やすだけで、既存のサービスに影響を与えることなく、いくらでも拡張することができます。

2. Self-healing – 耐障害性

kube2

上図はデプロイプロセスの詳細です。

  1. オペレーターが、どのようなコンテナを何台起動するかといった情報を Spec として Kubernetes 側に渡すと、
  2. Scheduler が、空きリソースを見ながらそれらをどのように配置するかを決定し、
  3. 各ノードに常駐している Kubelet というプログラムがその決定に従ってコンテナを起動します

オペレーターが直接コンテナを起動するのではなく、必要とする状態を Spec として渡すと、Kubernetes 側がクラスタの状態を Spec に合わせようする、というこの挙動が重要です。

仮に、運用中のコンテナに不具合があってサービスがダウンしたとします。Kubernetes はこの状態変化を察知し、Spec の状態に合わせようとして、そのコンテナを自動的に再起動します(Self-healing)。

多少の不具合であれば問題なく運用出来てしまう反面、問題の発覚するタイミングが遅れてしまう可能性もあるのでモニタリングが重要になります。

コンテナではなく、ノードとなるホストマシンに障害があった場合はどうなるか? Kubernetes 環境のセットアップによりますが、AWS の場合は Auto Scaling グループでクラスタが組まれているので、自動的にインスタンスが作り直され、その中に元の状態が復元されます。

3. Pod – 管理上の基本単位

Kubernetes 上で動作するプログラムの最小単位はコンテナですが、管理上の基本単位は Pod というものになります。

pod

Pod は、Volume という記憶領域を共有するコンテナの集まりで、Volume の他には一つのIPアドレスを共有しています。つまり、Pod は Kubernetes 上でホストに相当する単位です。

VM イメージによるパッケージングでは、一つのホストがデプロイの単位になっていました。あるいは、Java EE の WAR パッケージだと、必要最低限のプログラムだけをデプロイの単位に出来て軽量ですが、上図で言うと、デプロイの対象は webapp の部分だけになります。Docker/Kubernetes 環境でマイクロサービス化を推進すると、ホストを構成する様々な部品(webapp, nginx, log collectorなど)全てを、独立してデプロイ出来るようになります。

kubectl というコマンドがセットアップされていれば、以下のようにして Pod の一覧を見ることができます。

$ kubectl get pods
NAME                                 READY     STATUS    RESTARTS   AGE
cotoami-2660026290-81y2g             1/1       Running   0          9d
cotoami-2660026290-xzua4             1/1       Running   0          9d
default-http-backend-t4jid           1/1       Running   0          6d
grafana-807516790-8pszk              1/1       Running   0          63d
http-debug-server-2355350955-38jht   1/1       Running   0          6d
http-debug-server-2355350955-c64y1   1/1       Running   0          6d
http-debug-server-2355350955-g3rjq   1/1       Running   0          6d
nginx-ingress-controller-ka0od       1/1       Running   0          6d
node-exporter-556yr                  1/1       Running   0          63d
node-exporter-euprj                  1/1       Running   0          63d
node-exporter-hzdqk                  1/1       Running   0          63d
prometheus-1314804115-9v0yk          1/1       Running   0          54d
redis-master-517881005-ceams         1/1       Running   0          70d

コンテナのデバッグ

Pod 内のコンテナにログインしたい場合は、リストにある Pod の名前をパラメータにして以下のコマンドを実行します。

$ kubectl exec -it http-debug-server-2355350955-38jht /bin/sh
/app # ls
Dockerfile    README.md     circle.yml    index.js      node_modules  package.json

Pod に複数のコンテナが含まれる場合は、-c オプションでコンテナの名前を指定します。

$ kubectl exec -it POD -c CONTAINER /bin/sh

あるいは、以下のコマンドでコンテナが出力するログを見ることができます。

$ kubectl logs redis-master-517881005-ceams
1:M 16 Feb 03:27:35.080 * 1 changes in 3600 seconds. Saving...
1:M 16 Feb 03:27:35.081 * Background saving started by pid 226
226:C 16 Feb 03:27:35.084 * DB saved on disk
226:C 16 Feb 03:27:35.084 * RDB: 0 MB of memory used by copy-on-write
1:M 16 Feb 03:27:35.181 * Background saving terminated with success

# 複数のコンテナがある場合
$ kubectl logs POD CONTAINER 

4. Deployment – Pod の配備と冗長化

Pod の配備と冗長化を担当するのが Deployment という仕組みです。

deployment

ある Pod について、Spec で定義されたレプリカの数を維持する責任を負うのが Replica Set、Replica Set の配備・更新ポリシーを定義するのが Deployment です。

以下は、Deployment 定義の例です。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: http-debug-server
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: http-debug-server
    spec:
      containers:
      - name: http-debug-server
        image: cotoami/http-debug-server:latest
        ports:
        - containerPort: 3000

この内容を、例えば http-debug-server.yaml というファイルに保存し、以下のコマンドを実行すると Deployment として定義された Replica Set がクラスタ内に出来上がります。

$ kubectl create -f http-debug-server.yaml

$ kubectl get deployments
NAME                DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
http-debug-server   3         3         3            3           6s

Rolling Update

Deployment には標準で Rolling Update(無停止更新)の機能が備わっています。

例えば、以下のようなコマンドで Docker イメージのバージョンを更新すると、Replica Set の中のコンテナ全てを一斉に更新するのではなく、稼働中の Pod を何台か維持したまま、一つずつ順番に更新を行います。

$ kubectl set image deployment/DEPLOYMENT CONTAINER=IMAGE_NAME:TAG

5. Service – Pod へのアクセス

Pod への安定的なアクセス手段を提供するのが Service です。

Deployment で配備した Pod にアクセスしようと思っても、実際に Pod がどのノードに配備されているかは分かりません。仮に分かったとしても、Pod は頻繁に作り直されるので、いつまでも同じ Pod にアクセスできる保証はありません。Replica Set に対するロードバランシング機能も必要です。

ClusterIP

そこで Service は、Pod の集合(一般的には Replica Set)に対して安定的にアクセスできる仮想の IP アドレスを割り当てます。これを cluster IP と呼び、Kubernetes クラスタ内だけで通用するアドレスです。cluster IP にアクセスすると、Service の対象となる Pod 群の中のいずれかの Pod にアクセスできます。

NodePort

さらに Service は、クラスタの外部から Pod にアクセスするための経路も開いてくれます。具体的には、各ノードの特定のポートを経由して Pod にアクセス出来るようになります。

service

上図のように、Service の実体は、cluster IP のルーティングとロードバランシング機能を実現する kube-proxy というプログラムです。

クラスタの外部から Service が用意してくれたポートを経由してノードにアクセスすると、kube-proxy 経由で適切な Pod に接続することができます。

Kubernetes on AWS で sticky session を実現する

  • 対象の環境
    • Kubernetes 1.4.x(筆者の環境は 1.4.6)
    • AWS

AWS 上で稼動する Kubernetes (k8s) で LoadBalancer 型の Service を作ると、自動的に入り口となる ELB (Elastic Load Balancer) を立ち上げてくれてとても便利なのだが、AWS では当たり前に出来ていた sticky session が、この構成だとどうも実現できないらしい。

k8s で新規に開発しようという場合なら、sticky session が必要な実装はなるべく避けるのかもしれないが、稼働中の Web アプリを k8s に移行したいとなった場合に sticky session が必要になるケースもあるのではないだろうか。

というわけで、k8s on AWS における sticky session の実現方法について調べてみた。

諸悪の根源 kube-proxy

まず、単に ELB の sticky session を有効にしたらどうなるだろうか?

[ELB] = sticky => [Node] => [kube-proxy] => [Pod]

ELB から kube-proxy までが、Service の内部構造である。まず ELB が選択するのは k8s クラスタの各ノードである。ELB が sticky に設定されている場合、ノードの選択が sticky になるだけで、その先の Pod の選択は kube-proxy に委ねられている。

もう少し詳しく見てみると、

k8s-sticky-session

こんな感じで、ELB が sticky になってても意味はなく、Pod の選択を行う kube-proxy が sticky にならないといけない。

Service の sessionAffinity 機能

実は k8s の Service (kube-proxy) には sessionAffinity という機能がある。これを有効にすれば IP アドレスベースの stickiness を実現できるらしいのだが、なんと type が LoadBalancer の場合はこの機能を利用出来ないという。

ClientIP value for sessionAffinity is not supported when service type is LoadBalancer.

IPアドレスが隠れちゃう問題

では、LoadBalancer 型を利用するのはやめて、NodePort 型の Service にして、sessionAffinity を有効にし、自前で ELB を立てたらどうなるか?

その場合も、ELB がクライアントの IP アドレスを隠してしまうので sessionAffinity は正しく動作しない。現状の kube-proxy は、ELB がサポートしている X-Forwarded-For ヘッダや、proxy protocol を解釈しないので、[ELB] → [kube-proxy] の構成だとどう頑張っても sticky session を実現できないことになる。LoadBalancer 型の Service が sessionAffinity をサポートしない所以だ。

Proxy protocol のサポートについては多くのリクエストがあるようなので、将来的にはサポートされる可能性が高い。もしサポートされれば IP アドレスベースの stickiness については標準構成で利用出来るようになる。

解決策 1) ELB と kube-proxy の間に nginx を立てる

kube-proxy は X-Real-IP ヘッダを解釈するらしい。これを利用して、ELB と kube-proxy の間に nginx を立てて、そこで X-Real-IP ヘッダを追加するという手段が紹介されていた(ちなみに、ELB は X-Real-IP を追加してくれない)。

以下のような構成になる。

[ELB] = proxy protocol => [nginx] = X-Real-IP => [kube-proxy]

解決策 2) Nginx Ingress Controller

解決策 1) も悪くないように思えるが、IP アドレスベースの stickiness だと Web アプリとしてはあんまり嬉しくないので、なんとか諸悪の根源である kube-proxy をバイパスする方法がないだろうかということで見つけたのが、最近サポートされ始めたばかりの Ingress を使う方法だ。

Ingress とは、Service に対する柔軟なルーティングやロードバランシング機能を提供する k8s では比較的新しいリソースで、機能的には LoadBalancer 型の Service を切り出して、独立したリソースにしたような感じである。

Ingress を利用するためには、まず Ingress Controller というものを k8s 内に立ち上げておく必要がある。Ingress Controller の実体は proxy server であり、その proxy server に適用するルールのセットを Ingress リソースと呼ぶ。

ingress-controller

Ingress Controller には色々な実装が提供されていて、その中でも k8s の公式実装と思われる Nginx Ingress Controller が sticky session をサポートするために kube-proxy をバイパスするらしい。まさに今回のニーズにぴったしな実装だと言える。

The NGINX ingress controller does not uses Services to route traffic to the pods. Instead it uses the Endpoints API in order to bypass kube-proxy to allow NGINX features like session affinity and custom load balancing algorithms. It also removes some overhead, such as conntrack entries for iptables DNAT.

Nginx Ingress Controller のセットアップ

実際に Nginx Ingress Controller をセットアップする手順は以下の通り。必要なマニフェストファイル一式は、cotoami-infraのリポジトリ に置いてある。

1) まず、ルーティング先が見つからなかった場合にリクエストを送るための、デフォルトのバックエンドサービスを立てる。このサービスは単に 404 を返すだけである。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/default-backend.yaml

2) Ingress Controller を作る前に、ConfigMap による設定を先に作っておく。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/nginx-load-balancer-conf.yaml

この ConfigMap の中に、sticky session を有効にする設定 enable-sticky-sessions: "true" が含まれている。

3) 設定が登録出来たら、主役の Ingress Controller を作る。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/ingress-controller.yaml

上のマニフェストファイル ingress-controller.yaml では、Ingress Controller に外部からアクセス出来るように、LoadBalancer 型の Service をくっつけてある。これで、ELB 経由で Ingress Controller にアクセス出来るようになる。

(※) Controller本体は、公式の例に倣って ReplicationController で作成しているが、Daemonset で作ることも出来る。

以下のように Service の一覧を見ると、nginx-ingress-controller へアクセスするための Service が登録されているのが分かる。

$ kubectl get svc
...
nginx-ingress-controller   100.66.116.210   a5902e609eed6...   80/TCP     20s
...

a5902e609eed6... と省略表示されているのが ELB の DNS name である。試しにこのアドレスにアクセスしてみると、

default backend - 404

という感じで、先ほど立ち上げておいたデフォルトサービスの応答が得られるはずだ。

4) Ingress をテストするための、テスト用 Web アプリを立ち上げる。

Ingress 経由でアクセスする Web アプリを立ち上げる。ここでは、HTTPやサーバーに関する情報を表示するだけの http-debug-server を立ち上げてみる。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/http-debug-server.yaml

マニフェストファイルにあるように、Nginx Ingress Controller 経由でアクセスする場合、Service の type は ClusterIP にしておく。

5) Ingress リソースを登録する

最後にルーティングルールを登録する。

$ kubectl create -f https://raw.githubusercontent.com/cotoami/cotoami-infra/master/kubernetes/ingress/ingress.yaml

このルールでは、ホスト debug.example.com に対するアクセスを、http-debug-server Service にルーティングするように設定している。以下のように、curl コマンドでアクセスすると、デバッグ用の情報がレスポンスとして返されるはずだ。

$ curl <ELB-DNS-name> -i -H 'Host: debug.example.com'

sticky session をテストするためには cookie が必要になるので、以下のようにしてレスポンスヘッダーを表示させる。

$ curl <ELB-DNS-name> -I -H 'Host: debug.example.com'

以下のような感じで、ヘッダーに cookie の情報が含まれているので、

...
Set-Cookie: route=acfc2f2f4d692d7a90b9797428615e29d3936e95; Path=/; HttpOnly
...

この cookie をリクエストに含めて送信する。

$ curl -b route=acfc2f2f4d692d7a90b9797428615e29d3936e95 <ELB-DNS-name> -H 'Host: debug.example.com' -s | python -mjson.tool

レスポンスの JSON データが出力されると思うが、hostname のところに注目して欲しい。何度リクエストを送っても、同じホスト名が返ってくるはずだ。試しに-b オプションを外して送信すると、送信するたびにホスト名が変わる。

大分ややこしい感じになったが、以上が Ingress で sticky session を実現するための手順である。

解決策 3) service-loadbalancer

その他、k8s が提供している service-loadbalancer を使うことで sticky session を実現する事例が紹介されていた。

組織やチームの話を追いかけると、プログラミングは基礎教養なんだって話に辿り着いてしまう件

まさにそんな感じの話がInfoQで紹介されていた。

自律的なチームを「管理」するという矛盾にどう立ち向かうか

シュレーディンガーの猫
シュレーディンガーの猫

組織とはそもそもピラミッド(階層構造)なのだということを、筆者の中で今流行りのハーバート・サイモン先生は主張して目出度くノーベル賞を獲得したのだが、時代は変わってチーミングの本では、そのピラミッド型組織が親の仇のように扱われていた。

21世紀になって、それまではどちらかと言えば個人の才能に頼っていたイノベーションという難題に、組織として取り組む企業が出現し始め、それに成功した企業が高い競争力を持つようになったため、チーミングやらボトムアップ組織のようなものが脚光を浴びるようになったというのがその背景だ。

しかし、組織とはそもそもピラミッドなのである。その中では、上位の人間の役目は下位の人間を「コントロール」して成果を出す事だと信じる人間が圧倒的なマジョリティを占めるだろう。

このような環境の中で、自律的に考えて動くチームを組織の中心に据えるというのは、相当な難題である事は想像に難くない。

この「自律的なチームを組織の中でどう育てるか?」という、一見矛盾するかのように思える問題を、量子力学の「シュレーディンガーの猫」に喩えて解説したのが、Instagram で技術責任者を務める James Everingham 氏だ。

観測という行為自体が観測対象に影響を与えてしまうという「観察者効果」のメタファーは、自律的なチームを管理するという矛盾(難題)をうまく表現していると思う。管理者は常に、自分が介入する事でチームの自律性(シュレーディンガーの猫)を殺してしまう可能性があることに注意を払う必要がある。

Everingham氏が提案する、量子力学的チームマネジメントの5つの法則はこちら。

  1. 成功のパターンを出来るだけ沢山想定すること。
  2. 観察者効果を常に意識すること。
  3. 箱を開ける(観測する)タイミングを心得ること。
  4. 自律性を壊さずにチームに良い影響を与える方法を心得ること。
    • 背中を見せる
    • 共感による動機付け
    • インセンティブ
    • 連帯感
  5. チームが正しい方向に向かっているか、外部からのフィードバックに常に気を配ること。

この新しいマネジメント手法のエッセンスは、当該記事の最後の言葉に集約出来るかもしれない。

By not suggesting a destination, we all ended up somewhere extraordinary — a place I didn’t even know we were going.

「あえて行き先を押し付けない事で、想像もしなかったような、すごいところに到達できる。」

受託開発でアジャイルというのはほとんど語義矛盾ではないだろうか

ここ最近、立て続けにアジャイル絡みの事を書きながらこぼれ落ちた考えをここに書き留めておく。

筆者にとってアジャイルというのは、かなりの程度、個人的な問題だということはまず認めておかなければならない。

個人的だと思う背景には、(自分にとっての)アジャイルというものを、それを知らない人に伝達する事の難しさをつくづく痛感しているからである。

アジャイルはそもそも道具ではない。すでに書いたように、アジャイルというのは価値観であるし、もっと言えば文化だと言っても差し支えないだろう。

人間の集団の中で理屈抜きで共有出来る何らかの価値観があれば、それを文化と呼ぶ。

例えば、一つの組織の中で、ある事が組織の利益になるから同意するというのは、基本的に契約の問題であって文化ではない。つまり、組織にとって利益になるからアジャイルを導入しようという(ベタな)言明はそもそも成り立たない。

アジャイルが結果的に組織にとって利益になるという事は当然あり得るが、利益になるからやるという順番だとそれはもはやアジャイルではない(それが「道具ではない」ということの意味だ)。このようなジレンマが文化というものには必ず付いて回る。

文化を築くのには気が遠くなるほどの時間と幸運が必要になるが壊れるのは一瞬である。特に営利組織の中で働いていて、その利益以外の事で価値観を共有するというのは基本的に至難の業である。

アジャイルはもう終わったという話が度々出てくるが、そのほとんどはハイプ・サイクル絡みの話だった。そもそも無理筋なことをやろうとしていた、というだけのことではないだろうか。

アジャイルはそもそも「無理」から出発した方が良いのだろう。

アジャイルはソフトウェア開発にクリエイションを持ち込もうとしたが、ほとんどの現場ではそんなものを求めていなかった。

日本ではソフトウェア開発の多くが受託開発だと言われている。

受託開発というものがそもそも「アイデアを他者によって実現してもらう」という考えで成り立っている以上、開発側にクリエイション(アイデアをいかに発見するか)が入り込む余地がない。

文化という側面で言えば、人に頼まれて何かをやるという仕事の方式そのものが減点ベースの評価にならざるを得ず、アジャイルとそもそも相容れないと言う事も出来る。

これは受託開発だったらウォーターフォールという選択の問題ではない。人に頼まれて何かをやるときは「交渉」が最重要事案になるというだけだ。

自分達で発見したものを世に問うという仕事に就きたいというのは完全に価値観の問題であって、多くの人にとっては「人に頼まれて何かをやる」のがそもそも仕事の定義であり、その上で生活が成り立っている。社会全体としては価値を生み出していなくても「交渉」によってお金を回す事は出来る(それが持続可能なのかは別にして)。

アジャイル最大の難点は、当たり前だが、そこで価値の発見が保証されているわけではないというところだろうか。以前、スティーヴン・キング氏の自伝「On Writing: A Memoir of the Craft」を紹介したが、それを読めば誰もが小説が書けるわけではないのと同じように。

何が見つかるか分からない状況で旅に出ることをこの上ない喜びと感じるか、そんな博打を打っていたら生活が成り立たないと嘆くか、そこが文化の分かれ道かもしれない。

アジャイルはプログラマーに万能を求める。

アジャイルの問題意識がそもそも役割の分断にあるので、必然的に分断された役割を小さな範囲に集約していくことになる。そこを突き詰めると、それは単にフルスタックエンジニアになるというレベルを超えて、各人が自律したマーケターになることも要求されるようになる。

興味深い事に、これはマーケティング分野の側でも同じような事が言われているようだ。

アジャイルにおいて創造のエンジンを担っているのはプログラマーの側である。その外側にいてアイデアや舵取りの責任を持つ人間の存在はアジャイルにとっては単なる非効率に過ぎない。技術がイノベーションの源泉になっている、あるいはそのような組織を目指しているのなら尚更、外側の人間がアジャイルのエンジンに噛み合って仕事ができるという幻想を捨てなければならない。

しかし、冷静に考えてみよう。こんなことが可能な環境や組織が果たしてどのぐらい存在するだろうか? あるいはそれこそがクリエーションの希少性と呼べるものなのかもしれないが。

#noprojects: もう「プロジェクト」というアプローチでは価値を生み出せない

ちょっと前に InfoQ から「#noprojects」という電子書籍が出ていた。

cover-noprojects-emag

この書籍には、それぞれ別の論者によって書かれた6つの記事が掲載されている。「No projects」というそのタイトルの通り、これまで当たり前のように採用されて来た「プロジェクト」というアプローチは既に有効ではない、というのがこれらの記事に共通する主張である。

一見、過激で論争を呼びそうな主張であるが、内容を仔細に検討してみると、アジャイルというものを追求して行けば自然にこの形に到達するだろうなと腑に落ちる内容だった。アイデアとしては、アジャイルがより多くの領域に援用されるきっかけとなった、リーン、あるいはリーン・スタートアップや、このゆびてくでも紹介したインパクトマッピングなどのアイデアをミックスしてより一般的な形で展開させたものである。

そもそも「プロジェクト」とは何だろうか?

プロジェクトには始まりと終わりがある。プロジェクトはあるタイミングで開始されて、そして終了するタイミングは予め決定されている(実際にその通り終了するかどうかは別にして)。プロジェクトの最大の目的は「プロジェクトを終了させる」ことである。プロジェクトには固定の期間と人員、そしてそれに合わせた固定の予算、そして固定のアウトプットが想定されている。つまり、プロジェクトとは、予算を持ってそれを計画する人の都合に合わせる事を目的にしたアプローチなのである。言って見れば、予算と青写真を入れてボタンを押せば、決まった期日にアウトプットが出てくる魔法の箱である。

そんな魔法の箱は存在しない、そして、たとえ青写真通りのアウトプットが出てきたとしても、その時にはすっかり価値がなくなっている、というのが #noprojects の主張である。

プロジェクトというのは、ウォータフォール型開発の最後の遺産だと言えるかもしれない。これまでその存在があまりにも自明過ぎたためにアジャイルの時代になっても生き延びてきた。しかし、アジャイルでは既に、どの段階でもプロダクトをリリース出来るという継続的デリバリー(Continuous Delivery)が普及しつつあり、そのような現場においてプロジェクトというアプローチは形骸化しつつある。

最初に計画を行い、そしてその通りに物事が進んで、予定の期日にものが出来上がってくるという想定は間違っている、必ず想定外の事象が発生して変更を迫られることになるのだから、予め変更に強い体制を整えておかなければならない、というのがアジャイルの考え方であり、自然に #noprojects に繋がるものであるが、実際には多くの管理者がガントチャートを作りたがるし、物事が想定通り進まないのはどこかに原因があって、それを個別に対処して行けばプロジェクトを元のレールに乗せる事が出来ると信じている。頭の中では変化を想定しなければと思う反面、行動としてはどうしてもウォータフォールの影を引きずってしまう。プロジェクトという形態を取る限り、失敗の原因を計画に求める他なく、故により多くの事を予め想定しておこうという考えから脱する事ができない。

あるいは「変化に対応しよう」というアジャイルのスローガンに問題があるのかもしれない。変化があるかないかで言えば、変化がなく想定通りに進むプロジェクトだってあるだろうと主張する人も出てくる。よって、アジャイルかウォータフォールなのかは適材適所なのだという主張も成り立つ。しかし、本当の問題はそこにはない。ソフトウェア開発が開発の中途で想定外の変化を遂げるのは、その中でより価値のあるものを「発見」しようとするからである。ほら、想定通り物事が進んだじゃないか、そういうことだってあるんだ、と言っても、そのようにして出てきたものには既に価値はない、というのがソフトウェアの世界である。よって、スローガンとしては「発見を主眼におかないソフトウェア開発にはもはや価値を産み出せない」と言った方が良いのかもしれない。

継続性

プロジェクトに代わって、新たなモデルとして提案されているのが「継続的な変更の流れ(Continuous stream of change)」あるいは「流れ作業生産(Flow production)」と呼ばれるものだ。「流れ作業」というと、アジャイルが目の敵にしていたテイラリズムを想像してしまうが、ここでのフォーカスは顧客価値に基づく小さな変更を継続的に行うことにある。リーンの「かんばん」などはこのモデルに近い。

そして、ここでのキーワードは「継続性」である。プロジェクトは基本的に large-batch なアプローチであり、リスクも高いしオーバーヘッドコストも馬鹿にならない。これまではプロダクト(アウトプット)ごとにプロジェクトを組むのが一般的であったが、この方法だと新しいプロダクトを計画する度にチームを編成してプロジェクトを立ち上げなければならない。同じプロダクトでもバージョンアップや改修があるときに別のプロジェクトを立ち上げるのはよく見かける光景だ。#noprojects が提案するのは、アウトプットではなく「成果(Outcome)」にコミットするチームを編成し、その成果を実現するために継続的な変更をリリースしていくモデルである。成果とは、組織にとってどのぐらいの「価値(Value)」があるかという尺度で評価される何らかの「変化(Change)」のことだ。

このようなモデルにおいて、プロジェクトに代わって重要な存在になるのは「チーム」である。チームは結果的な価値にコミットするため、同じチームが複数のプロダクトに関わることは自然なことであり、そこに継続性が生まれる。このようなチームを「価値提供チーム(Value-delivery team)」と呼ぶ。これまでのプロジェクトモデルの問題は、プロジェクトごとにチームが編成・解散されることが多く、せっかく蓄積した知識やノウハウの連続性がそこで失われる事であった。

これまでのプロダクトにフォーカスするプロジェクトベースの開発では、開発者、営業、マーケティング、サポートといった、組織内の機能ごとにチームを分けるのが一般的だった。そして、このように役割で組織が分断されるという問題が、このゆびてくでも繰り返し取り上げてきた、アジャイルやDevOps、インパクトマッピングといった考え方が繰り返し指摘する、現代のソフトウェア開発における最大の障害なのである。チームが価値にコミットするためには、そのために必要な全ての機能をチームの中に備えておかなければならない。これを「機能横断型チーム(Cross-functional team)」と呼び、たとえれば、チームがミニチュアなベンチャー企業となるようなイメージである。実際に、#noprojects の記事の中では、ベンチャーキャピタルの組織内バージョンだと指摘されている。

このような機能横断型チームが、小さなサイクルで実験と学習を繰り返しながら価値を発見しようと継続的な努力を続ける、これが #noprojects が提案するソフトウェア開発のあり方である。

#noprojectsの重要なコンセプト

#noprojects では、その手法を構成する重要な要素として、以下の5つのコンセプトを挙げている:

  • 価値(Value)
  • 成果(Outcome)
  • アクティビティ(Activity)
  • ルール(Principle)
  • 継続的デリバリー(Continuous delivery)

チームは「成果」を生み出すために「ルール」に基づいて「アクティビティ」に従事し、その「成果」は「価値」によって測定される。そして、その継続性とサイクルの短さは「継続的デリバリー」によって担保される。

価値(Value)

価値というのはなかなかに厄介な概念である。ほとんど「主観」と同義だと言っていいぐらいに不確かなものであるし、多くの賢人が指摘しているように、数字に置き換えて理解しようとすると途端に落とし穴にハマる危険性が高くなる。#noprojects では、その不確かさはそもそもビジネスをする際には付き物であり、そこが正確に予測出来るのであればマーケットも簡単に予測可能になるはずだが実際にはそうなっていないと、価値問題に深入りする事は避けている。しかしながら、組織が成功するかどうかのファクターのほとんどはこの価値に関わっていて、仮に #noprojects を実現したとしても、それは単にスタート地点に立っただけに過ぎない。

この価値の問題については、以前「我々は何のためにソフトウェアを開発するのか?」という記事の中でも取り上げた。その中で、Ron Jeffries氏による価値の定義「Value is what you want(価値とはあなたが欲しいもの)」を紹介した。価値を突き詰めれば、経済の問題だと考える人も多いだろうし、あるいは文学の問題だと思う人もいるかもしれない。

#noprojects によれば、一度成果として実現された価値というものは、変更を継続しなければ、時間が経つにつれて減衰していくと言う。これが、プロジェクトモデルに代えて継続的手法を取るべきだという主張の直接的な根拠になっている。

成果(Outcome)

成果とは、具体的なプロダクトやアウトプットではなく、価値によって計測可能な変化の集積である。このように表現するとなかなかイメージを掴みづらいが、記事の中では「アクティブユーザーの獲得」や「スタッフ満足度の向上」などが例として挙げられている。

Agile India 2016 - Leadership Day Summary より
Agile India 2016 – Leadership Day Summary より

この「成果」は、以前ゆびてくで紹介したインパクトマッピングの「ゴール(Why?)」に相当する。つまり、インパクトマッピングを利用すれば、成果を実現するためにはどのようなアクティビティを行えば良いかを考える際の助けになるし、かつチーム内での情報共有においても効果的である。

map

im_example

アクティビティ(Activity)

アクティビティとは、成果を生むための必要な個別のタスクのことである。#noprojects では、個々のアクティビティを事前に評価するための手法として「アクティビティ・キャンバス(Activity canvas)」というものを提案している。以下のように、アクティビティにかかる手間をX軸、実現できる価値をY軸とするキャンバスを用意し、そこに個々のアクティビティをマッピングして行く。

Agile India 2016 - Leadership Day Summary より
Agile India 2016 – Leadership Day Summary より

上のようにアクティビティをマッピングして行くと、どのような順番でアクティビティを実施したら良いかという優先順位が自然に現れてくる。#noprojects に限らず、アジャイルで重要なのは、タスクを「高」「中」「低」のような役に立たない優先順位に分ける事ではなく、実施の順序を決めることである(次に何をやればよいか?)。タスクを順序付けることによって同時並行で処理するタスクの数を減らせば、チーム全体のスループットを向上させることができる。

ルール(Principle)

アクティビティの実施方法は完全に自由というわけではなく、多少をコストをかけてでも若干のルールを作っておくことによって、アクティビティとプロセス全体の親和性、あるいはアウトプットの品質を保つ。アジャイルで言えばプラクティスに相当するが、プログラミング、コミュニケーション、セキュリティ、ブランディングなど、共有するルールはプラクティスよりも多岐に渡る。ルールでチームを縛るというよりも、最低限守らなければならない作法を明確にしておくことによって、その他はメンバーの裁量に任せるという意味合いが強い。

ルールの導入にはコストがかかるため、「MoSCoW」という優先度割当の方法が紹介されている。MoSCoWではルールごとに以下の4つのカテゴリーから選んで優先順位を割り当てる。

  • M (Must have) – 必ず従わなければならない
  • S (Should have) – 正当な理由がない限りは従う
  • C (Could have) – 個人の裁量に任せる
  • W (Won’t have) – 出来るだけ避けるべき

継続的デリバリー(Continuous delivery)

#noprojects のような考え方が提唱されたり、あるいは現実的になってきたのは、インフラ技術の進歩によって継続的なデリバリーが実現出来るようになってきたからだと言っても過言ではないだろう。継続的デリバリーを、アジャイル以後のDevOps時代に生み出された最も重要な技術的達成だと位置付ける人も多く、より具体的で分かりやすい継続的デリバリーという考え方をアジャイルの後継とすべきだという主張も見かけた。

まとめ

以上、#noprojects の考え方を概観してみた。

プロジェクトは計画の別名だと言ってもよく、計画を軸にビジネスを進めると本当に重要な価値の追求よりも計画の履行を優先してしまうことになる。#noprojects の問題意識をひと言で言えば、そんな感じになるだろうか。計画・施行のモデルは、同じようなプロダクトを作り続ける事に意味があった20世紀型のビジネスモデルであり、21世紀の今では発見的な手法に移行しなければ新たな価値を生み出す事は出来ない。その意味で、本来は技術者よりも、経営者やマネジメントがアジャイルや #noprojects のような手法を理解しなければならないはずである。しかしながら、マネジメントの利害を考えると #noprojects が世の中の大勢を占めるトップダウン型の企業に浸透するとは到底思えない。これまでプロジェクトマネジャーが担当していた責任をチームに委任することになり、マネジメントの役割は大きく変わる事になるからである。

プロダクト開発とアイデア信仰

先週の記事で紹介したスティーブ・ジョブズ氏のインタビュー。その中で彼は、パーソナルコンピューティングやオブジェクト指向開発だけでなく、ジョブズ哲学を総括するかのように実に様々な話題について自説を展開している。その中でも特に興味深いのが、プロダクト開発における「アイデア」の位置付けについての話だ。

この中で、インタビュアーはジョブズ氏に対して「プロダクト開発において重要なことは何か?」と聞く。ジョブズ氏はしばらく考えた後、そのインタビューの10年前、1985年に彼をアップルから追い出したジョン・スカリー(John Sculley)氏の話をする。

ジョブズ氏によれば、彼がアップルを去った後、スカリー氏は深刻な病に冒されていた。その病とは「素晴らしいアイデアを手に入れたら、それで90%の仕事が完了したと思い込む」ことだと言う。

しかし実際には、素晴らしいアイデアと素晴らしいプロダクトの間には気が遠くなるような craftmanship(職人芸)の集積がある。そのような集積を経て実現したプロダクトは、スタート地点のアイデアとはかなり異なるものになっている。その変化(発見)を可能とする数限りない試行錯誤、トレードオフにまつわる決断、このようなプロセスこそがプロダクト開発の魔法なのだとジョブズ氏は言う。

この気づきの有無は、この話の前に出てくる、marketing (or sales) people と product people の話題にも関係している。ジョン・スカリー氏はペプシコーラのプロモーションで大成功を納めた人物だ。この分野の人物が craftmanship やプロダクト開発の魔法を理解するのはなかなか難しいのではないかと想像できる。これら職能間の断絶は深く、2016年の今でも、プロダクト開発におけるアイデアの重要性について疑う人はそれほど多くないように見える。

ジョブズ氏のインタビューから15年以上経った2012年、似たようなことを主張している記事を見かけた。アメリカを拠点とするコンピュータ科学分野の国際学会 ACM(Association for Computing Machinery)の機関誌「Communications of the ACM」に掲載された「The idea idea」という記事である。

著者は、高名なコンピュータ科学者であるピーター・J・デニング(Peter J. Denning)氏。

デニング氏の問題意識は、現代の我々はアイデアの重要性を信じて努力を継続しているため、アイデアの獲得には苦労していないが、それを実際のイノベーションに繋げる段階になると極端に成功率が下がるのは何故なのか? ということであった。実際の成功率は4%ぐらいに過ぎないのだと言う。

彼は、イーサネットを発明したロバート・メトカーフ(Bob Metcalfe)氏が、コンセプトの発明にかけた労力よりも、それを普及させるのに費やした労力の方がはるかに大きかった経験を「花と雑草」にたとえた話を紹介し、重要なのはアイデアよりも実践の方なのではないかという仮説を展開している。

アイデアよりも前に、まず誰かによる実践があり、その実践に効果があると見た他の人たちがそれを真似する。しばらくするとその実践をより容易にするようなツールが開発され、その実践は更なる広がりを見せる。このようなプロセスを前提に考えると、「アイデアとは既に起こったイノベーションを説明するための後付けの理由」に過ぎなくなる。

デニング氏は記事の中で「氷山の一角」というアナロジーを紹介している。プロジェクト全体の中でアイデアというのは氷山の一角に過ぎず、海中に沈んでいる多くの部分がイノベーションにとって重要な実践の部分に当たる。そして、実践を継続することでアイデアの部分が氷山として浮き続けることが出来る。一方、盲目的にアイデアの重要性を信じてアイデアの獲得にコストを費やすと、このバランスが崩れてイノベーションの実現は難しくなる。

この記事が出てきた文脈には、ソフトウェア開発の世界で第一段階目の成熟を迎えつつあったアジャイルの影響があったと思われる。スティーブ・ジョブズ氏は、1995年の段階でその核心に到達していた数少ないビジネス界の人物だったと言えるのかもしれない。

このような考え方は、さらにクリエイティブな領域に行くとそれほど珍しい話ではなくなる。世界的な小説家であるスティーヴン・キング(Stephen King)氏は、彼の作家人生を記した自伝的著書「On Writing: A Memoir of the Craft」の中で、自身の興味深い小説技法について説明している。

on-writing

彼は「プロット」を信じていないという。プロットは小説における設計書のようなものだ。

I distrust plot for two reasons: first, because our lives are largely plotless, even when you add in all our reasonable precautions and careful planning; and second, because I believe plotting and the spontaneity of real creation aren’t compatible … I want you to understand that my basic belief about the making of stories is that they pretty much make themselves. The job of the writer is to give them a place to grow (and to transcribe them, of course). (p.159)

我々の人生にはそもそもプロットがないこと、そして真のクリエイションに備わっている spontaneity(自発性)とプロット(計画)はそもそも相性が悪いこと。そして、キング氏によれば、ストーリーは、適切な場を与えさえすれば自然発生的に育つものだと言う。

I told the interviewer (Mark Singer) that I believed stories are found things, like fossils in the ground, he said that he didn’t believe me. I replied that that was fine, as long as he believed that I believe it. And I do. … Stories are relics, part of an undiscovered preexisting world. The writer’s job is to use the tools in his or her toolbox to get as much of each one out of the ground intact as possible. (p.160)

キング氏は、ストーリーは発見するものだと考えている。地中に埋まっている化石のように。それは遠い昔に存在した、未だ発見されてない世界の遺品のようなものである。そして作家の仕事は、自分が持てる道具を駆使してそれらの遺品を出来るだけ無傷で掘り起こすことだと言う。

オブジェクト指向とは何だったのか?

このブログでも何度か取り上げているように、プログラミングにおけるここ数年間のトレンドで最も大きなものは、関数型プログラミングの隆盛だと言って良いだろう。そのトレンドと共に相対的に存在感を失いつつあるのが、それまで長らく主流を占めていた「オブジェクト指向」という考え方である。関数型プログラミングの観点から眺めると、オブジェクト指向プログラミングではシステムの状態を暗黙に扱う(情報隠蔽)ために実行時の挙動が予測しづらくなり、高度な並行性が求められる現在の環境では、信頼性を確保する際の大きな障害となるように見える。

しかし、筆者も含めて、オブジェクト指向という考え方にあれだけ熱狂した立場から考えると、昨今のオブジェクト指向に対する評価に対して若干の違和感を感じざるを得ないのも事実である。というわけで今回は、オブジェクト指向の発案者による発言を参照しながら、改めて「そもそもオブジェクト指向とは何だったのか?」ということについて考えてみたい。

まず始めに「オブジェクト指向」という言葉の意味については、どこかに正式な定義が存在する、というわけではないようだ。これは「関数型」についても同様のようである。なので、このブログで書かれていることはある種の解釈や立場表明をしているに過ぎないということに注意して頂きたい。

メッセージング

「オブジェクト指向(object-oriented)」という言葉を最初に提唱したのは、Smalltalkというオブジェクト指向環境を発明したアラン・ケイ氏だと言われている(これは本人も認めている)。

When and where was the term “object-oriented” used first?

At Utah sometime after Nov 66 when, influenced by Sketchpad, Simula, the design for the ARPAnet, the Burroughs B5000, and my background in Biology and Mathematics, I thought of an architecture for programming. It was probably in 1967 when someone asked me what I was doing, and I said: “It’s object-oriented programming“. – Dr. Alan Kay on the Meaning of “Object-Oriented Programming” (強調は筆者)

そのアラン・ケイ氏によれば、オブジェクト指向で最も重要なのは「メッセージング」なのだと言う。

Smalltalk is not only NOT its syntax or the class library, it is not even about classes. I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea.

The big idea is “messaging” – that is what the kernal of Smalltalk/Squeak is all about – Alan Kay On Messaging (強調は筆者)

上記引用にもあるように、ケイ氏は「オブジェクト(指向)」という言葉を持ち出したことに後悔さえしていると言う。メッセージングの観点から言えば、クラスやオブジェクトさえも重要ではないということから、本来は「メッセージ指向」と呼ぶべきだったのかもしれない。

さて、このメッセージングであるが、オブジェクト指向の発案者によってその重要性が強調されているのにも関わらず、職業プログラミングの主流を占めていた C++ や Java でオブジェクト指向を実践してきた多くのプログラマにとっては、むしろ馴染みの薄いものではないだろうか。

ケイ氏は、オブジェクトを、ネットワークを形成してメッセージを送り合うコンピュータのメタファーとして捉えており、インターネット上のサーバーのように、リクエストをメッセージとして受け取り、そのメッセージをサーバー側で解釈して何らかの処理を行うというモデルを想定していた。オブジェクトへのリクエストという意味では、C++ や Java のメソッド呼び出しがメッセージングに相当するように思われるかもしれないが厳密には異なる。ケイ氏の開発した Smalltalk では、このメッセージを非同期にやり取りするという、今まさに広がりを見せつつある並行処理のモデルを70年代の段階で実現していた(後にアクターモデルと呼ばれる)。しかし、その Smalltalk においても、ある段階から現在主流になっている(メソッド呼び出しと同等な)同期的な呼び出しに移行し、その後も「メッセージ」のような用語が維持されたためにメッセージングにまつわる誤解と混乱が生じた、という経緯があったようだ(参考: Alan Kays Definition Of Object Oriented)。


[2016/05/09追記]
この部分に事実誤認があるとの指摘をコメント欄にて頂いた

「ケイ氏の開発した Smalltalk では、このメッセージを非同期にやり取り」については、

Versions of Smalltalk before Smalltalk-80 were still largely based on the (asynchronous, unidirectional) ActorsModel of computation, but with Smalltalk-80, the developers of SmalltalkLanguage switched entirely to the (synchronous, bidirectional) procedural model, while misleadingly retaining the ActorsModel terminology (such as “messages” for what essentially are procedure calls rather than one-way notifications). – Alan Kays Definition Of Object Oriented

この言及だけを参考にして書いてしまっていたのだが、

アラン・ケイらの Smalltalk-72 が大きな影響を与えてはいますが(https://www.cypherpunks.to/erights/history/actors/actor-induction.pdf の Acknowledgements 等)、アクターモデル自体はカール・ヒューイットの考案であり、Smalltalk や ケイのメッセージングとはまた別物です。念のため、ケイの関わった Smalltalk(-72、-74、-76、-78)は非同期のメッセージングベースで実装されたことはありません。 – コメント欄(sumimさん)より

とのこと。ご指摘感謝!

[2016/05/10追記]

さらに別の方からコメントを頂く。

Actor Model自体がCarl Hewittの考案であり、Kayのメッセージングとは「別物」ということですが、歴史はしばしばもうちょっとややこしいものです。私が見聞きしたか限りにおいては、KayがMITに行ってSmalltalk-72に関する講演を行った時に、実際の実装とは別に元々のアイディアとして「ネットワークで接続されたオブジェクト群がメッセージを交換しながら計算を行う」という話をし、その時に聴衆にいたHewittがActor Modelの着想を得た、という経緯もあります。Actorの当初の論文には謝辞としてAlan Kayの名前があるもの、それ以上のクレジットがなかったために、Carlが「コミュニティー」からもうちょっとアイディアの出典を正直に述べなくてはならないという批判を受けることになったという話もあります。 – コメント欄(よしきさん)より

Smalltalk に興味がわいた方は、sumim氏による Smalltalk-72 入門も是非ご参考あれ。


messaging

このメッセージングに関係して、最近タイムリーなブログ記事を見かけた。メッセージングを実現している環境として、実は Erlang/Elixir が最もアラン・ケイ氏のオブジェクト指向を体現している言語環境なのではないかという考察である。ちなみに、この二つの言語は一般的には関数型プログラミング言語として認識されているはずである。

この記事の中で紹介されているように、Erlang の開発者である Joe Armstrong 氏が「Erlang が最もオブジェクト指向に近い言語である」という趣旨の発言をしているようだ。

しかし、何故メッセージングが最も重要なのだろうか? それは、メッセージングという仕組みの内に、ケイ氏が考えるオブジェクト指向の要点が全て含まれているからである。ケイ氏は、メッセージングの位置づけを日本語の「間」という言葉で説明しているが、氏によれば、システムの「間」つまりコミュニケーションに着目することによって、システムにとって重大な目的(ゴール)だけを表現し、その他の詳細については二次的に決定すれば良い、そしてそのような決定の先送りがシステムの寿命を延ばす鍵になるのだと言う。メッセージがどのように解釈されるのか、クラスから作られたオブジェクトによって処理されるのか、そのクラスは継承によって構造を共有しているのか、あるいはプロトタイプベースなのかという問題は全て二次的な問題である。

出来るだけ多くのことを「work in progress」として扱うことによって進化してきた Smalltalk は、ある段階からあらゆることを決定し固定化したがる人たちによって不毛な議論が繰り返され、その結果、進歩が停滞しているとケイ氏は苛立ちを見せていた。この「work in progress」の考え方は、21世紀になってから普及したアジャイルの考え方そのものである。アジャイルはオブジェクト指向のコミュニティが生み出したものであるが、その考え方の源流はオブジェクト指向の始まりから既に埋め込まれていた。

抽象データ型

ここまでの話で、オブジェクト指向の発案者であるアラン・ケイ氏の、メッセージングという真意が明らかになった。しかし、これだけでは職業プログラマの多くが実践してきたオブジェクト指向らしきものは一体何だったのかという疑問が残る。

One of the things I should have mentioned is that there were two main paths that were catalysed by Simula. The early one (just by accident) was the bio/net non-data-procedure route that I took. The other one, which came a little later as an object of study was abstract data types, and this got much more play. – Dr. Alan Kay on the Meaning of “Object-Oriented Programming”

上記引用のように、ケイ氏は、オブジェクト指向の起源となった Simula という言語からは、ケイ氏の主張する「the bio/net non-data-procedure route」の他に、「抽象データ型」を中心に据える流れも起こり、こちらの方がむしろその後主流になったと説明している。抽象データ型の流れは、C++ や Java に引き継がれ、最近では Scala など、いわゆる静的型付け(static typing)として職業プログラマの間では主流になった。

ケイ氏の考えとしては、抽象データ型系列の C++ はオブジェクト指向としては認識していないようであるが(「”I invented the term object-oriented, and I can tell you that C++ wasn’t what I had in mind”」)、型システムそのものについて否定的に見ているわけではないようである。

I’m not against types, but I don’t know of any type systems that aren’t a complete pain, so I still like dynamic typing. – Dr. Alan Kay on the Meaning of “Object-Oriented Programming”

クリエイティビティ

さて、結果的に抽象データ型が主流になったのあれば、それがいわゆる多くの人が認識するところのオブジェクト指向なのだから、今更「アラン・ケイ氏のオブジェクト指向」についてわざわざ考慮する必要があるのかと考える人もいるかもしれない。しかし、筆者にとってこの二つの立場の違いは、単にプログラミング言語の差異に留まらず、コンピューティングというものに対する立場を二分する大きな断絶を象徴する分岐であるように思えるのである。

抽象データ型というのは、単にプログラミング言語の問題である。あるいは、システム開発者側の問題だと言っても良いかもしれない。例えば、開発側の観点から仕事上のトラブルを少なくするといった、よりプラグマティックな理由から型システムという仕組みを評価できるだろう。職業プログラマの間で型システムが普及した理由にはこのような側面もあったのではないだろうか。

しかし、アラン・ケイ氏による1993年の論文、オブジェクト指向が生まれる過程が仔細に書かれた「The Early History Of Smalltalk」を読むと、オブジェクト指向は単にプログラミングだけの問題ではなく、もっと壮大な理想を追求するために考案されたのだということが分かる。

オブジェクト指向が生まれる直前の60年代という時代は、コンピュータが、予め計画された計算を行うだけというイメージの、一つの部屋を専有する巨大な機械から、もっと人間の力を高める形でインタラクションを提供する新しいメディアへと生まれ変わろうとする、いわゆるパーソナルコンピューティングの黎明期に当たる。そして、そのような運動の中心にあったのが、ARPA(Advanced Research Projects Agency)というアメリカ国防総省管轄の研究機関である。ARPA はインターネットの原型となった ARPANET を開発したことでも知られる。

このパーソナルコンピューティングを模索する流れの中で、アラン・ケイ氏が立ち上げたのが Dynabook 構想であり、この構想には今日では当たり前になっているパーソナルコンピュータやネットワーク、GUI(ウィンドウシステム)など、パーソナルコンピューティングにとって重要なあらゆる要素が詰め込まれていた。

パーソナルコンピューティングの主題は、コンピュータを使っていかに人間の能力を高めるか(「Amplify human reach」)ということであったが、これは言い換えれば「クリエイティビティ(創造性)」の追求である。ケイ氏が最も熱心に取り組んでいたのはコンピュータを使った子供の教育であった。オブジェクト指向プログラミングを子供に教えることによって、ソフトウェアを自身の問題に合わせて自在に変更出来るようにし、より高い問題解決能力を獲得させることを目指した。

この教育プロジェクトの試行錯誤によって得られた知見には大変興味深いものが多い。例えば、Smalltalk がクリエイティビティに寄与する一つの根拠として、より少ない原則で多くを表現できるというものがある。有名な「Everything is an object」というやつである。後のケイ氏であれば「Everything is a message」と言ったかもしれない(そのようなプログラミング言語が実際に存在する)。この全てがオブジェクトであるという原則と、大きなシステム(オブジェクト)は小さなシステム(オブジェクト)の組み合わせで作られるという「再帰的デザイン」によって、どんな複雑なシステムをデザインするときでも、覚えなくてはならない原則は少なくて済むようになる。さらに Smalltalk において重要なのは、システムを使うという行為と作るという行為が全く同じになるということである。システムを使うときと全く同じ操作で、その延長線上でシステム自体を変更することができる。このように、導入において覚えることが少なくて済む、そしてその少ない道具立ててであらゆることが表現できるという枠組みが、子供の教育にとって重要であることは想像に難くない。しかし、実際に試してみて分かったことは、あるオブジェクトにメッセージを送るという1ステップの変更については小さな子供でも理解することが出来るが、複数ステップの変更を組み合わせないと解決できないような問題になると、それがほんの2、3ステップだったとしても極めて難しくなってしまうということだった。これは子供だけでなく、プログラミング経験のない大人でも似たような現象が見られたようである。初歩の問題は容易にクリアできるが、問題が複雑化すると、それがプログラマから見たら些細だと思われる問題でも全く歯が立たなくなる。これは今で言うプログラムデザインの問題である。このデザインの教育に対応するためにケイ氏の同僚である Adele Goldberg 氏が「design templates」という仕組みを考案した。これはぼんやりとしたデザインのアイデアとプログラムによる実例の中間に位置する道具立ててで、今で言うところのデザインパターンやフレームワークに相当するものである。

ケイ氏は、このような大きなビジョンを掲げることの重要性について、そして出来るだけ多くを「work in progress」にすることの重要性について繰り返し語っているが、それはいかに多くの技術者や専門家が手段に埋没し、そして宗教論争に明け暮れているかということに対しての警鐘にもなっている。

スティーブ・ジョブズ

「The Early History Of Smalltalk」の一つのハイライトは、1979年、ケイ氏の勤めるパロアルト研究所(PARC)に、あのスティーブ・ジョブズ氏が訪れる場面である。当時のジョブズ氏は77年にアップルコンピュータを設立したばかりで、次世代のパーソナルコンピュータを生み出そうと Lisa プロジェクトを立ち上げていたが、決定的なアイデアがなく模索中の段階であった。ケイ氏は、PARCに訪れたアップルの面々に対して、ALTOというパーソナルコンピュータ試作機のデモを行う。ALTO には Smalltalk による OS が搭載されており、その上ではウィンドウベースのGUIが動いていた。

Xerox Alto Computer
Xerox Alto Computer

そのデモの最中、ジョブズ氏は、試作機で動いていたウィンドウシステムのスクロールをもっと滑らかな方式に変更出来ないかと指摘し、開発者の一人である Dan Ingalls 氏がその場で修正して訪問者を驚かせたという場面が紹介されている。Smalltalk 環境の強力さが垣間見えるエピソードである。

この邂逅の後の歴史は多くの人の知るところなのでここでは深追いしないが、1995年に行われたインタビューにおいて、ジョブズ氏がパロアルト研究所で見たデモについて、そしてその当時は理解出来なかったというオブジェクト指向の重要性について語っているので、その動画を紹介しておきたい。

この映像は放送後に一度紛失したと思われたマスターテープがジョブズ氏の死後に偶然見つかったということで、95年の放送時はカットされた部分も含めて全編放送されたものであるが、その内容にはただひたすら圧倒されるのみである。

このインタビューの中で、1995年当時、今後10年間で最も重要だと思われるテクノロジーとして、ジョブズ氏はオブジェクト(指向)と Web の二つを挙げている。

そして現在

オブジェクト指向についてその起源から検討してみたが、この歴史を前提に改めて考えてみると、昨今言われる「オブジェクト指向から関数型へ」という話が大分狭い領域の話であることに気づかされる。モジュールシステムの進化という観点から言えば、関数型プログラミングが有効である場面が増えているのは疑いようがない。しかし、より大きなスコープで考えるとオブジェクト指向の考え方は依然として重要であり、将来的には、以前紹介した「Functional in the small, OO in the large」という形で適材適所に住み分けることになるのではないだろうか。

そして、アラン・ケイ氏のオブジェクト指向という観点からより視野を広げて考えると、その枠組みにおいてオブジェクトはプログラミング言語とは切り離されたメタファーあるいはコンセプトに過ぎず、メッセージや遅延束縛(late binding)と言ったコンセプトは形を変えてあらゆる場面で見かけるようになった。その代表的なものの一つが、アジャイルという考え方である。オブジェクト指向はその出自から考えても分かるように、より上流の考えを重視する目的指向のパラダイムである。アジャイルはその後、経営やデザインなど、ソフトウェア開発の枠組みを超えて、クリエイション一般に適用出来るような普遍的な考え方として普及しつつある。

プロダクト開発とアイデア信仰 | ゆびてく

ビジョンを曇らせる誘惑: 技術偏重、一貫性

James Hague (@dadgumjames) 氏は、少年時代に一世を風靡した『パックマン』などのゲームに影響を受けてプログラミングを始めた。高校時代にはコンピュータ雑誌の常連投稿者として活躍、大学卒業後もゲームプログラマーとしてSNES(海外版のスーパーファミコン)向けのゲームを開発した。当時はアセンブリ言語で開発していた彼も、次第にマンネリを感じて高級言語を物色し始め、丁度その頃オープンソース化された Erlang で関数型プログラミングの門戸を叩いて今に至る。

パックマン from Wikipedia

そのようなキャリアを持つ彼が、長らくプログラマとして活動する内に気がついた事があると言う。それは、いつの頃からか新しいアプリケーションをデザインする時には、技術的・実装的な見地から逆算的に物事を考えるようになり、ユーザーやプレイヤーの観点から完成品を想定することが少なくなっていたということだった。

技術者は長くその専門領域に棲息するうちに、その領域の尺度だけで物事の良し悪しを判断するようになる。技術者のコミュニティでは、そのような尺度の元に序列付けが行われて、個々の技術者にプレッシャーを与え続ける。その結果として、何のためにそのアプリケーションを開発するのかということは脇に置かれ、パフォーマンスやプログラムデザインのような技術的達成が優先的に考慮されるようになる。

Hague 氏がプログラミングを始めた頃、プログラミングは彼のビジョンを実現するための道具に過ぎなかった。しかしそれが、プログラミング自体にのめり込むにつれて次第に自己目的化してしまった。そのねじれを解消するために、もう一度原点に戻ろうという思いがこの The Recovering Programmer というタイトルに込められている。

前回の記事「TDD再考 (8) – 凝集性(cohesion)とは何なのか?」で紹介した論理的凝集と機能的凝集の対立もこの問題と密接に関係している。アプリケーションの目的から考えれば機能的凝集を目指すべきところでも、油断すれば技術偏重に陥り、論理的凝集を優先してしまうことになる。そしてこの問題は Hague 氏だけでなく、このブログでも紹介してきたように、ビジネスと技術の対立を解消すべく現れたアジャイルムーブメントによってもフォーカスされてきた。特にその流れの中で提案されたドメイン駆動設計(Domain-driven design, DDD)は、機能的凝集に集中するための方法論だと考えると分かりやすいかもしれない。

アジャイルが指摘したのは、機能的凝集は動くターゲットであるということであった。それまでのソフトウェア開発で念頭に置かれていたような静止したゴールは存在しない。継続的に効果を得るためには、ユーザーとのインタラクションを重ねて、ユーザーにとって意味のある「文脈」を構築していかなければならない。

デジタルマーケティングの世界で長年活躍してきた Joanna Lord (@JoannaLord) 氏は、ブランドマーケティングの観点から、一貫性への過剰な執着がブランドの機能的凝集(coherence)を損なう要因になり得ると指摘する。

彼女によれば、ブランドの一貫性とは、裏を返せばそのブランドが変化せず、(環境が変わっても)常に同じ体験を提供し続けるときに実現されるものだと言う。コメント欄で指摘されているように、コカコーラのような長い歴史の中でブランドが確立しているようなケースではその一貫性を維持する理由があるが、現代の状況の中で新しいブランドを構築していかなければならない場合、一貫性より機能的凝集を目指さなければ生き残りは難しい。

一貫性というのは分かりやすい価値であるが故に過剰に追求されやすい。そして、一貫性を保とうとすれば、新しいアイデアを試したり、大胆な変更を行う事に対して臆病になってしまう。その結果としてユーザーにとっての価値、つまり機能的凝集が時間とともに失われてしまうことになる。

規模の経済と理想主義

クラウドファンディングで有名な Kickstarter のCEO、Yancey Strickler氏の提言。

利潤のみを追求する大資本が地域社会や多様性を壊して行く。数字至上主義に対して我々は何ができるか?

先月、UberやAirbnbに代表されるシェアリング・エコノミーについてのTim O’Reilly氏の論考を紹介したが、そこで経済的効率性からフランチャイズチェーンが生まれる過程について触れた。そういったチェーン、つまり大型のスーパーやファーストフード、コンビニと言ったものが、地域社会に根付いていた商売 ― 商店街などが象徴的であるが ― そういったものを根こそぎにしてしまうというのは高度経済成長期以降の日本でもよく見られた光景である。筆者の地元(千葉の田舎)にあって、かつては賑わいを見せていたアーケード街も、数年前に帰省した際に訪れてみたら、アーケード自体が撤去されて、営業している店もほとんどなく「兵どもが夢の跡」状態になっていた。

ニューヨークでは、新しく入ってくるビジネスの多くが銀行であるという。現在では1800以上の銀行支店がニューヨークに存在し、10年前より60%以上増加している。ニューヨークタイムズの調査では、ほんの158の家庭がアメリカ大統領選に投入される資金のほぼ半分を提供していることが判明している。音楽業界では、全米で行われるコンサートの8割がTicketmasterというチケット販売会社に独占され、レコードレーベルの多様性も失われつつあり、ヒットチャートTOP40の内、驚くべき割合の曲がたった4人の北欧出身の人間によって書かれているという。そして、ハリウッドでは、リスクを回避するための続編や前日譚といったシリーズ物が溢れている。

「お金」という単一文化(モノカルチャー)に我々はどのように対抗したら良いのか? 資本主義社会の中で生活する以上、その社会からドロップアウトして隠遁生活でも始めない限り、そこから逃げるのは至難の業のように思える。

Strickler氏は、そういった社会の中で生活しながらも、現実主義に陥らずに理想主義を貫くことが大事だと説く。自分が働く業界の標準に迎合しないこと。自分が正しいと信じる道を貫くこと。現状に満足せず、常に新しいアイデアを探し求めること。そして、短期の利益を追い求めるばかりに、長期の努力を軽視しないこと、持続可能性を考えること、など。

世の中が「規模の経済(economies of scale)」に向かって邁進して行く中、Strickler氏以外にも、その価値観に疑義を呈する人たちがいる。その中でも有名なのが、このブログの連載「TDD再考」に度々登場する Ruby on Railsの作者、David Heinemeier Hansson (DHH) 氏である。

ビジネスの世界ではより大きな数字を追い求める数字至上主義が蔓延しているが、例えば、ハーバードやオックスフォードといった有名大学の価値はそのような「数字」では計れない。であるならば、ビジネスの世界でも、組織ごとに適切なサイズというものがあって良いのではないか? 「小さい」ことを大きくなるための通過点として捉えるのではなく、「小さい」こと自体をゴールにしても良いのではないか?

DHH氏は2008年にもYcombinatorが主催するスタートアップスクールで、「ベンチャー・キャピタルからお金をもらって次のFacebookを狙うのをやめよう!」というスピーチをしており、投資を受けてイグジットを目指すシリコンバレー的なスタートアップ文化を批判していた。

  • 次のfacebookを目指してなんの意味がある? Ruby on Rails 作者が語る「お金を生み出して幸せになるためのたった1つの方法」 – ログミー

Facebookのような巨大企業を目指すためにあらゆることを犠牲にするよりも、自分達が良いと信じられるものを、自分達のペースで継続するのに必要最低限の規模を目指すこと。

ほかにもイタリアンレストランがたくさんあるなかで小さなイタリアンレストランでうまくいっているところがたくさんあるようにたくさんの勝者がいていいし、小さな問題を解決している会社が世の中には山のように存在する。そういった会社は2000とか10000の顧客を持っていて、何度も生まれて来た素晴らしい企業の多くは200の顧客からスタートしている。例外はFacebookのような会社で紙の上では2年で1.5兆円の価値になっている。あんな会社をロールモデルにしてはいけない。

そして、そのようなイタリアンレストランを目指すことは第二のFacebookを目指すよりも遥かに容易であるし、実現できれば、たとえ規模はささやかであっても素晴らしい達成なのだとDHH氏は言う(「人生の楽しみ方にはいろんな部屋がある」)。

フィーチャーブランチを利用した開発はチームを継続的インテグレーションから遠ざける

つい最近、たまチームでは Git Flow を利用したブランチ管理に移行したばかりだったので、この記事を見かけた時は一瞬ムムッとなったが、実際に読んでみて、なるほどと思える反面、どのようなブランチ管理を導入するかはそのプロジェクトの性格に依る部分も大きいのではないかと感じた。というわけで、今回はブランチ管理とアジャイルの関係、そしてその理想からはやや距離を取ることになったたまチームの現状についてまとめてみたい。

Git Flow による Integration Feature Branching

以下は、Steve Smith氏によるブランチ管理方法の4分類:

これまでたまチームは、Gitのブランチ管理に関しては何も方針を決めておらず(単純に master に push されたものがステージング・本番、双方へのデプロイ可能なバージョンとしてビルドされる)、結果的に Trunk Based Development という、今回の記事で推薦されている、ブランチを極力作らないシンプルな開発モデルになっていたのだが、後述する事情で以下のような Vincent Driessen氏提案のブランチ管理に移行した。

このブランチモデルについては、Driessen氏による図を見てもらうのが一番分かりやすい。

git-model@2x

基本のブランチとして master と develop の二つを用意し、developが開発用の基本ブランチ、masterが本番用のブランチとなる。開発の基本的なフローは、「フィーチャーの開発」と「リリース」の二つが軸となり、それぞれ、

  • フィーチャー
    • フィーチャーブランチを作って、終わったら develop にマージ
  • リリース
    • リリースブランチを作って、終わったら master と develop の双方にマージ
    • masterにマージしたところで、リリースバージョンのタグを付けておく

これらのフローにまつわる git の操作を簡単にしてくれるのが、git-flow というgitコマンドの拡張である。以下のようなコマンドを打つだけで、ブランチの作成から移動・マージまで全部自動でやってくれる。

$ git flow feature start feature_name
... (code and commit)
$ git flow feature finish feature_name

Smith氏は、この Git Flow によるブランチ管理を Integration Feature Branching と呼び、継続的インテグレーションを実現するためには全くお勧め出来ないとしている。

フィーチャーブランチと継続的インテグレーション

フィーチャーブランチと継続的インテグレーションの関係については、2009年に書かれた Martin Fowler氏の記事でより詳細に説明されている。

今回の InfoQ や Fowler氏の記事で説明されているフィーチャーブランチの問題点を一言で言えば、共有ブランチに対する変更の粒度が大きくなるため、マージの際のトラブルが起こりやすくなる、ということになるだろうか。

フィーチャーブランチのメリットは、共有のブランチに影響を与えることなく個々の機能を独立して開発出来ることである。一つ一つの機能がブランチとして分かれることによって、リリース(マージ)する機能をその場で選択する cherry-picking のような運用が可能になる。しかし、独立しているが故に、個々の機能の間のコミュニケーションが不十分になり、いざ共有ブランチでマージしようとした時に、解決の難しい衝突を引き起こす可能性があるとFowler氏は指摘する。

衝突のリスクは開発者に心理的な影響を及ぼす。例えば、自分のフィーチャーブランチで行った変更が、他人のフィーチャーブランチに対して意図せぬ影響を及ぼすのが怖くなり、大胆なリファクタリングがやりづらくなる。これは一つ一つのフィーチャーブランチが大きくなればなるほど顕著になるだろう。

継続的インテグレーションのそもそもの目的は、プログラム全体を頻繁に結合することによって、結合に関する問題の粒度を小さくしようということであった。つまり、フィーチャーブランチの考え方は、そもそも継続的インテグレーションと相容れないということになる。

インクリメンタルな開発を実現するためのコスト

継続的インテグレーションの理想は、全てのコミットがリリース可能な単位になることである。その理想状態の元では、当然のことながらブランチを分けて開発する必要は無くなる。しかし、そのようなインクリメンタル開発を実現するコストは決して低くない。例えば、一つの機能の開発が一つのコミットで完了することはむしろ稀だろう。一つのコミットで終わらない場合、その中途半端に実装された機能を抱えたシステムをどのようにリリースすれば良いのか? その問題に対処するために提案されているのが FeatureToggleBranchByAbstraction と言ったテクニックである。

上のようなテクニックがあったとしても、全てのコミットをリリース可能にするためには、Steve Smith氏も認めているように、かなりの訓練と経験を必要とする。これは以前の記事でも書いたように、システムをそれぞれ完結した vertical slice 単位で開発しなければならないアジャイルの本質的な難しさである。

たまチームの場合

理想的な継続的インテグレーション、さらにはその到達点である継続的デリバリーを実現するためには、いつでもカジュアルに本番を更新できるような体制と環境を整えておかなければならない。それは技術的な面だけではなく、文化的な面でも高いハードルをクリアしておく必要があり、残念ながらたまチームの場合は、まだそこまでのレベルに達しているとは言い難い。カジュアルに本番を更新出来ない場合、つまり本番更新の頻度が比較的少ない場合、ステージングと本番でビルド(ブランチ)を共有しているといろいろと不便なところが出てくる。例えば、どのビルドが本番に上がっているかが分かりづらくなったり、本番に対する Hotfix がやりづらくなったりする。そういった問題に対処するためにステージングと本番でブランチを分けておいた方が良いのではないかという話になり、Git Flow によるブランチ管理を採用するに至った。

フィーチャーブランチについては、現状だと一つのリポジトリは基本的に一人の開発者が担当していることが多いので、それほどこだわらずに開発者個人の裁量に任せる感じで問題ないように思える。しかし、これが複数人で共同開発するようになった場合は、フィーチャーブランチの扱いには十分気をつけなければならないだろう。

UberやAirbnbが経済にもたらす革命的なインパクト

オライリーメディアの創始者であるTim O’Reilly氏の論考。「シェアリング・エコノミー」と呼ばれる、UberやAirbnbといった新時代のサービス。このようなサービスの出現は、単なる雇用形態の変化などではなく、これまで我々が当たり前だと思っていた経済の構造を、根本から変えてしまうようなインパクトを持つ歴史的な出来事なのだと O’Reilly氏は主張する。

「会社」というものが存在する理由

現代社会で働いて生活する我々は、ほとんど例外なく何らかの形で「会社組織」と関係している。多くの人は会社に所属して働いているだろうし、そうでない人もどのような形であれ会社というものに関わりを持っているはずである。今となっては我々の経済活動から会社というものを切り離すのは不可能であるように思える。では、この会社というものが存在する理由は一体何なのだろうか?

アダム・スミスが『国富論』を発表した18世紀。多くの経済学者は、経済システムがうまく機能するために「中央管理」は必要ない、むしろ有害でさえあると考えていた。市場の自律的な仕組みに任せておけば、いわゆる「(神の)見えざる手」によって自然にバランスが保たれ、経済はうまく回るはずであると。

この分散型自律経済の考え方はその後もしばらく経済学の主流であったが、20世紀に入って初めてその考え方に疑問を呈したのがアメリカの経済学者ロナルド・コース(Ronald Coase)である。コースは、市場の中で取引が成立するためには、自律型経済の考え方では想定されていないコストが必要になる事を発見した。例えば、需要と供給はどうやってお互いを発見するのか、価格を決定するための交渉はどうするのか、契約も締結しなければならないし、立場の違いから揉め事も起こるだろう。このような問題を解決するためのコストを「取引コスト(transaction costs)」と呼び、このコストこそがマネジメントや会社組織の存在が要請される根本要因だとした。

21世紀型フランチャイズの出現

コースの理論によれば、会社によって提供されるマネジメントのコストが市場で本来かかる取引コストを下回る限り、会社は存在し続ける事ができる。しかし、会社が大きくなって、その管理コストが市場の取引コストを上回るようになると、会社の存在意義は怪しくなる。この規模拡大によるコストを抑えようとして生まれたのがフランチャイズの仕組みである。マネジメントを行う組織を出来るだけ小さくし、各地に分散した事業者はネットワークを形成してそのマネジメントのサービスを受ける。

しかし、もし市場でかかる取引コストが劇的に下がって、マネジメントにかかるコストを下回った場合、どのようなことが起こるだろうか。そのようなことが実際に起きているのが、今我々が現在進行で経験している「インターネットの時代」である。インターネットが取引コストを劇的に削減する、というのは、GoogleやAmazonがそれまでの市場をどう変えたかを考えれば、そのインパクトを容易に想像出来る。それまでのフランチャイズの仕組みでは避けようがなかった店舗や販売にかかるコストがほぼゼロになり、さらには経験を積んだ管理職が担当していた、商品と顧客のマッチングもソフトウェアによって置き換えられた。つまり、それまでに存在していた会社組織は、その多くが(店舗や一部のマネジメントに至るまで)不要となり、その存在意義や構造を大きく見直さなければならなくなった。

このインターネットによる革命の新たな波がUberやAirbnbといったシェアリング・エコノミーである。シェアリング・エコノミーによって生まれた新しい経済構造を、O’Reilly氏は「個人のフランチャイズ化(The franchise of one)」と呼ぶ。テクノロジーの進歩によって、フランチャイズの末端を構成していた小さな事業者のネットワークは、自身のリソース(Uberでは車とその運転、Airbnbでは家)をパートタイムで提供する「個人」のネットワークに置き換わった。

個人のフランチャイズ化によって、会社のフランチャイズでは提供出来なかったような広大な選択肢(「excess capacity」と呼ばれる)を顧客に提供出来るようになる。例えばUberの場合で言えば、広大な選択肢とは、これまで想定しなかったような場所やタイミングでタクシーを利用出来るようになるということであり、この選択肢の増大が結果としてUberの利用者を増やし、それが価格を下げてさらに需要を拡大するという好循環を生むことになる。

集中型から分散型、そしてまた集中型へ

ロナルド・コースによって理論的な根拠が与えられた集中型の経済構造は、インターネットが出現して18世紀にアダム・スミスが信奉した分散型に形を変えた。個人のフランチャイズ化によって、その傾向はますます強くなっているように見えるが今後はどのように変化していくのだろうか。O’Reilly氏は、いずれまた集中型へ向かうだろうと予測する。インターネットが出現したとき、それは個人と個人を結びつけるネットワークだった。それが時を経て、今では情報の流通を仲介するYahooやGoogleような数多の企業が生まれている。シェアリング・エコノミーの世界でも同様の事が起こるだろうというのは確かに想像に難くない。

アジャイルがうまくいかないのは何故か?(2015年版)

The GROWS Method™

「アジャイルの失敗」という話は、今となっては特に目新しいものでもない。筆者が記憶している限りで古いものだと、2008年に書かれた James Shore氏の「The Decline and Fall of Agile」という記事がある。

「ソフトウェア開発に銀の弾などない」と、あらゆるIT専門書のイントロダクションに断わりが入れてあるのに、未だに「○○を試したけど効果がない」という苦情が後を絶たないのは何故なのだろうか。

今回のブログ記事「The Failure of Agile」を書いた Andrew Hunt氏は、「達人プログラマー」の共著者であり、「アジャイルソフトウェア開発宣言」の発起人17名の中の一人でもある。彼は、7年前の James Shore氏と同じく、アジャイルがうまく行かないのはアジャイルを正しく実践していないからだと指摘する。

具体的にはこういうことである。初心者がアジャイル開発を始めるに当たっては、何かリファレンスになるような書籍やルールを参照するのが普通である。しかし、ほとんどの初心者はルールに従うという最初の段階から先に進む事ができない。アジャイルは参加者に「変化」を求める。既存のルールに問題があれば、そのルールを変えて、自分たちの状況にあったルールを新たに生み出し、そしてそれを継続的に更新していかなければならない。アジャイルを継続的に行うためには、アジャイルの書籍に書かれている事にこだわっていてはいけない、というねじれを乗り越えて行かなければならない。その上、コミュニティの中には正規のプラクティスを実践しているかどうかに執拗にこだわるアジャイルポリスみたいな人たちがいる(cf. TDDポリス)。ますますアジャイル本来の意図からは遠ざかるわけである。

この状況を打開するため、Hunt氏は「GROWS™」という手法を提案している。GROWSは「GRowing Real-World Oriented Working Systems」の略語にもなっており、これは商標として扱われている。誰でも自由に使えるようにしてその本来の意味を歪められてしまったアジャイルの轍を踏まないように、というのが商標にした理由らしい。

GROWS™の詳細は現段階でまだ明らかにされていないが、基本のステップを「実験」と捉えることや、フィードバックに対するエビデンス(証拠)ベースの判断等、かなりリーン・スタートアップに近い手法なのではないかという印象を受けた。

さて、言葉を占有して解釈を限定させようという方法は、本当に良い方法だろうか? 個人的にはそう思えない。オブジェクト指向やアジャイルという考え方は、本質的には、物事のInsight(見識)を提供してくれるものであって、何らかの手順を踏めばプロジェクトがうまく行くといったような処方箋を与えるものではないと思う。もしそれらの考え方が役に立たなかったのであれば、それは単に受け手側の問題である。自分(の状況)には合わなかったと思って次をあたるべきだ。しかしながら現実には、以前の記事で書いたように、ある考え方が流行るとその周辺をコンサルタントやコンサルティング会社が商機を求めて集まってくる。そこで「○○を使ったらお宅のプロジェクトもうまく行きますよ」という具合に喧伝する。その中でことごとく裏切られた期待が炎上を引き起こし、Andy Hunt氏や James Shore氏のようなコミュニティを代表する人たちが火消しに走らなければならなくなる。この風景はソフトウェア開発産業に限らず、セールスマンが存在する限り、あらゆるところで繰り返されているのだろう。

さらに思うのは、アジャイルのような手法が難しい原因は、ほとんど政治的な問題に収斂するのではないかということである。その内容が誰かに歪められたから正しいアジャイルが実践されなくなったというよりも、従来的な組織の中でアジャイルになるというその事自体が根本的な矛盾を孕んでいるのではないか。アジャイル以後にリーン・スタートアップが出てきたのも、このことに関係しているのかもしれない。そもそもスタートアップのようなラディカルな環境でないとアジャイルというのは成立しないのではないか。

アジャイルを完全体にするインパクトマッピング

ソフトウェア開発は、開発の根拠となる「必要」をどう捉えるか、その考え方の変遷とともに進化してきたと言えるかもしれない。

例えばウォーターフォール開発では、「必要」は疑いの無いもの、予め決定可能なもの、固定のものとして存在していた。製造業において当たり前のように繰り返されて来た、青写真(設計図)があって、それを実現(施行)するという手法である。その後、ソフトウェア開発において「必要」は予め決定出来ないという問題意識の元、計画・実施を細かく繰り返してフィードバックを得られるようにし、ビジネスと開発で密なコミュニケーションを取りながら本当の「必要」を探る、アジャイルという手法が生み出された。そして更には、計画段階で提案された仮説を、計測に基づく客観的な検証にかけて「必要」の精度を上げようというリーンスタートアップに辿り着いた。

インパクトマッピングは、「必要」の背後にある根拠をマインドマップで図示化することによって、アジャイルやリーンスタートアップが見過ごしていた部分を補完し、ソフトウェア開発を更に一歩先に進めるための技法である。

インパクトマッピングの問題意識は、アジャイルと同じところから出発する。我々が見当違いなものを作ってしまう、あるいは必要とされるスピード感がなかなか得られないのは、開発プロジェクトが役割で分断されているからである。ビジネスはビジネスの言葉を語り、エンジニアはエンジニアリングの言葉を語る。利害も一致しない。このようなコミュニケーションの問題をまず解決すべきであるというのがアジャイルの問題意識であった。そこでアジャイルはフィードバックにフォーカスを当てる。イテレーションによりフィードバックの機会を増やし、決して読まれない重厚なドキュメントを廃して、ユーザーストーリーと動くソフトウェアによるコミュニケーションの効率化を計った。

アジャイルによって色んな役割の人間が同じ船に乗り込み、効率の良いコミュニケーションが可能になったが、それでも最後に大きな問題が残されていた。それは、その船がどこに向かっていて、そして何故そこに向かっているかという航海のビジョンをどのように共有するかという問題である。いくらフィードバックを頻繁に密に実施していても、何のためにその作業をするのかという根拠が、単にステークホルダーやマネジャーがそのように言っているから、というだけでは、エンジニアは依然として下請け作業者のままである。チーム全体で「何故」を共有しなければ、マネジャーはエンジニアから知恵を引き出す事は出来ず、ゴールに最短で辿り着くのは難しくなってしまう。

そこでインパクトマッピングは、「我々はどこに行くのか?」というゴールをマインドマップの中心に配置し、そこから具体的なプロダクトを導出する事によって、作業に根拠を与えて、開発チームが自律的に動けるようにする。

map

上図のように、インパクトマッピングは極めてシンプルである。「ゴール(Why?)」に辿り着くためには、「誰に(Who?)」に働きかけて、「どのような変化(How?)」を生じさせるか? そしてその変化(これを「インパクト」と呼んでいる)を実現させるためには「何(What?)」をすればよいのか(作れば良いのか)? おそらく、ゴールを合理的に説明する手法として、これよりシンプルな方法はないのではないだろうか?

何を作るかの根拠は、いつでも中心のゴールを念頭に考える事が出来るので、作業の優先順位や重要度は、このインパクトマッピングを共有する人間全てがクリアに理解出来るようになる。今まではステークホルダーの頭の中だけにあったものが、マインドマップとして視覚化されるわけである。さらにこのマップは、情報の共有だけでなく、新たな「必要」を発見するための強力なツールとしても機能する。誰に働きかけるべきなのか、何を作るべきなのかは、もうマネジャーだけが考えるべき問題ではない。インパクトマッピングによって、参加者誰もがゴールを念頭にアイデアを出す事が出来るようになった。

以下は、インパクトマッピングの公式サイトで紹介されているオンラインゲーム開発におけるマッピングの例である。中心には「100万プレイヤー」というゴールが掲げられている。

im_example

創業者CEOがうまく行かないのは何故か? – プロダクト指向CEOのパラドックス

シリコンバレー最強のベンチャーキャピタルリストの一人」と呼ばれるベン・ホロウィッツ氏の記事。プロダクトの発案者がそのまま起業し、創業者CEOとなった場合に起こる問題について。

組織の規模が500人になるぐらいまでは、経営をしつつ、プロダクトの細かいところまで管理・コントロールすることが可能で、プロダクトの競争力も維持出来る。ところが、それ以上の規模になってくると、プロダクトに関する全ての意思決定に関わることが難しくなり、中途半端な意思決定をしてしまったり、現場への過剰な介入として従業員から反発を受けるようになる。かと言って、そこで現場に権限を委譲してしまうと、プロダクトは徐々にそのフォーカスを失い、日和見的な凡庸でつまらないものへと変貌を遂げてしまう。

ホロウィッツ氏によれば、マイクロソフトのビル・ゲイツ、オラクルのラリー・エリソン、スティーブ・ジョブズ、マーク・ザッカーバーグのような著名な経営者たちは、大規模な会社を経営しつつも、プロダクトの意思決定から完全に手を引く事なく、以下のような、プロダクトに関する本質的な意思決定にのみに関わる事によって、このパラドックスを回避しているらしい。

  • ビジョンの方向性を維持する
    • ビジョンの全体を作る事はせず、その方向性をコントロールする。
  • 品質基準を維持する
  • プロダクトの統合を担当する
    • プロダクトラインに共通する部分についての意思決定。
  • メトリクスに現れない情報について考えさせる

現実にこのようなシフトを行うのは容易ではなく、そのために必要なプロセスは人それぞれであるが、一般的に役に立つであろうプラクティスとして以下のものが紹介されている。

  • 口頭やメールで伝えるのではなく、正式なドキュメントを書く
    • ビジョンを明確化しつつ、過剰な参画を避ける。
  • 正式な定例レビューを行う
    • レビューではビジョンとの整合性、デザイン品質、進捗等をチェックする。中途での方向性転換は避けること。
  • 正式な経路以外で現場のやり方に口出ししないこと
    • 状況を把握するために現場の人間とコミュニケーションを取るのは問題ないが、恣意的なディレクションのような介入は避ける事。

アジャイルにとって不変なのは価値であって、方法論は状況に応じて変化していく

以前の記事で少し触れた「Agile Is Dead (Long Live Agility)」について、InfoQ が Dave Thomas氏本人に行ったインタビュー。

2001年、ユタ州のスノーバードに集まった、Kent Beck氏を始めとするソフトウェア開発の思想的リーダー17名。そこで自分たちの思想の共通点は何なのかという事を議論し、その成果を「アジャイルソフトウェア開発宣言」として世に公表したとき、その宣言がここまで大きく世の中を変える事になるとは全く想像していなかったと、Dave Thomas氏は語る。

agile

この宣言が発表される以前から、Kent Beck氏らによるエクストリーム・プログラミング(XP)や、Martin Fowler氏の記事「The New Methodology」など、開発者コミュニティの中では、アジャイルのような思想を受け入れる素地が出来上がっていた。

このように最初は開発者による日々の実践だったものが、成功したスタートアップの秘訣として紹介されるなどして、徐々に開発者以外の注目を集めて行く。

この過程を、Thomas氏は面白い表現で説明している。組織の管理者がアジャイルの評判を聞いて、それを自分の組織にも導入しようと号令をかける。すると、血に引き寄せられるサメのごとく、その周りをコンサルタントやコンサルティング会社が旋回し始める…

当然の事ながら、コンサルタントのアドバイスは管理者受けが良いように最適化されるため、その段階で個人の主体性を大事にするアジャイルの価値は失われてしまった。

Thomas氏曰く、「我々が提案する〜をやればアジャイル開発を実現できます」というコンサルタントは間違っている。理想的なコンサルタントはこのように言う、

「あなた達にとって何が最適か、私には分かりませんし、あなた達自身にも分からないでしょう。でも、とりあえずどこからかスタートすることは出来ます。まずは適当なプラクティスを選んで一ヶ月間実践してみるところから始めてみましょう。その後、何がうまく行って、何がうまく行かなかったか評価をします。そして、その評価に基づいてやり方を変えて行きます。おそらく最初に採用するプラクティスは、XPやスクラムの本を参考にするでしょう。でも、今から半年後、あなた達のやっていることは、どの有名な手法にも当てはまらなくなっています。何故なら、自分たちに最適な方法を自分たちで編み出す事になるからです。やり方はずっと変わって行きます。変化を楽しみましょう。」

アジャイルの導入として、これ以上分かりやすい説明はないのではなかろうかと、個人的には思う。

アジャイルで不変なのは「アジャイルソフトウェア開発宣言」にも謳われているその「価値」であって、方法論はチームによって、あるいはチームが置かれている状況によって変化する。

ここが、アジャイル以前に存在したウォーターフォール手法などとの決定的な違いである。そもそも同列に比較されるものではない。価値原則に照らし合わせれば、アジャイルを方法論として売り込む言説に対しては常に疑ってかからなければならない。逆に言えば「〜を実践してないなら、お前らはアジャイルじゃない」ということも言えないはずだ。

Thomas氏は、アジャイル実践手法の基礎となる、メタプラクティスなるものを提案している。それは、

  • 自分たちの現在地を把握する
  • ゴールに向けて小さなステップを踏み出す
  • そのステップによって起こった事を評価し学習する
  • 繰り返す

によって構成されるプロセスだ。ここに具体的なプラクティスは何も含まれていない。自分たちのアジリティーを向上させたいのなら、このプロセスをあらゆるレベル(関数の命名レベルの細かさから、新しいビジネスの計画に至るまで)に適用出来るように、自分たちでプラクティスを編み出して行かなければならない。

改めて考えてみると、管理者というのは常に「状況をコントロールしなければならない」というプレッシャーを受け続けている。計画出来ずに「今はまだ何も分かりません」では、ステークホルダーを説得出来ないからだ。そのような管理者によって、アジャイルは流行のバズワードとしてステークホルダー向けのプレゼンテーションに利用されてきたのかもしれない。しかし、アジャイルの考え方というのは、そのような(事前に青写真を書かないといけないという)ビジネスのあり方自体に疑問を投げかけるものだ。

レガシーなWebアプリにサービス間連携のためのAPIを追加する

運用するWebアプリの数が増えてくると、それらの間で何らかの連携を取らなければならないケースが増えてくる。

例えば、二つのWebアプリ間での連携を考えてみる。最も典型的なのは、下図のように同じデータベースを共有するやり方だろう。

web-api1

異なる立場の人が同じ情報にアクセスする必要がある場合に、このような形でアプリケーションを分ける事はよくある。例えば、エンドユーザーとシステム管理者が使うアプリケーションを分けておきたいケースは多いと思う。

二つのアプリケーションが、同じデータベースに対して等しく関心を持つのならこれで何の問題も無いように思えるが、同じデータベースを扱うので、少なからずデータアクセスの処理やビジネスロジックが重複する可能性が高い。ただ、この段階では、コミュニケーションコストを考えて若干の重複には目をつぶるか、共有ライブラリを作るぐらいの解決策が適当で、Web APIを作って共有するのは大げさに感じられるかもしれない。

Webアプリの横恋慕的依存関係

では、以下のケースだとどうだろうか?

web-api2

二つのWebアプリは、それぞれに関心を持つ別々のデータベースにアクセスしており、一見それぞれ独立しているように見えるが、例外的に「Web app 1」の方は「Web app 2」で管理している情報の一部にアクセスする必要が出てきた。果たして、「Web app 1」は直接「Database 2」にアクセスすべきだろうか?

というのが、たまチームが最近直面した問題であった。

サービス全体が小規模、あるいは二つのWebアプリが両方とも同じ人間によって開発されているのなら、手っ取り早く「Web app 1」から直接「Database 2」にアクセスしてしまうのもアリかもしれない。しかし、Webアプリごとに別々の担当者がいるようなケースでは、自分の担当するアプリケーションのデータベーススキーマを変更する必要が出てきた時に、そこに依存している他のアプリケーションの動作に影響を与えないよう、細心の注意を払わなければならなくなる。

あるいは、このような例外を際限なく認めると、どのWebアプリがどのデータベースにアクセスしているかを把握するのが難しくなってしまうという問題もある。

web-api-dep-hell

共有ライブラリ

例えば、最初のケースで挙げたように「Database 2」についての共有ライブラリを作って、それを「Web app 1」から利用するという手がある。これによってデータベースの変更をライブラリの背後に隠蔽出来るようになり、より安全に変更を行えるようになる。

web-api3

しかし、ライブラリ方式にも問題はある。まず一つは配布の問題である。一度作って配布したら終わりというわけにはいかないので、変更がある度に利用者に知らせて、利用者はライブラリを更新し、アプリケーションをビルドし直す必要がある。あるいは、ライブラリ方式は言語やフレームワークに依存するというのが一番大きな問題かもしれない。全てのWebアプリが同じ言語・フレームワークで開発されていれば問題ないが、このMicroservicesの時代ではそうでないことも多々あると思われる。実際に、たまチームでは「Web app 1」と「Web app 2」で異なるフレームワークを利用していたために、ライブラリ方式を選択肢に入れることは出来なかった。

Web API

そこで選択肢として残ったのが、「Database 2」を利用するための Web API を立ち上げて、「Web app 1」からはそのAPIにアクセスするという方法である。

もし仮に Web APIサーバーを立ち上げる場合、データアクセスやビジネスロジック部分は共有したいので、「Web app 2」からその部分を切り離して単独のAPIサーバーとし、残った「Web app 2」のフロントエンド部分と「Web app 1」でそのAPIを共有するというのが、一番美しい、いわゆるマイクロサービス的な解法のように思える。

web-api4

もし、今から「Web app 2」を開発するなら、今風に始めからAPIサーバーとフロントエンドを分けた形で実装するだろうと思う。しかし、「Web app 2」は数年前にフルスタックなフレームワークの上に構築された、ガッチガチな一枚岩のWebアプリであり、ロジック部分だけを切り出してAPI化するというのは結構な大手術になってしまう。「Web app 1」側から利用する機能もそれほど多くなく、独立したAPIサーバーを置くほどのメリットも感じられなかった。

Webアプリに追加したAPIへのアクセス制御

そこで、「Web app 2」に Web API を追加することにしたが、この場合に問題になるのは API へのアクセスコントロールである。「Web app 2」は、エンドユーザーが利用するWebアプリなので、そこにそのまま API を追加すると、エンドユーザーもそのAPIにアクセス出来てしまう。

web-api5

APIに認証を追加するという方法も考えられるが、セキュアな認証の仕組みを導入すること自体が大仕事である上に、本来の目的がアプリ間連携であることを考えると、もっとより簡単で安全な方法があるような気もする。

そこで思い付いたのが、元から設定されているエンドユーザー向けのTCPポート(80番)の他に、新たにAPI専用のポート(8091番)でもリクエストを受け付けるようにして、そのポートに対してファイヤーウォール(たまチームではAWSのセキュリティグループ)でアクセス制御するという方法である。

web-api6

たまチームの「Web app 2」はTomcat上で動くWebアプリだったので、API用のHTTPポートを追加するためには、設定ファイル conf/server.xml に以下の一行を追加するだけでよかった。

<Connector port="8091" protocol="HTTP/1.1" connectionTimeout="20000" />

ポートを追加するだけだと、依然として既存のポートでもAPIにアクセス出来てしまうので、API内部ではアクセス元のポートを調べて、専用ポート(8091)経由の場合にのみアクセスを許可するようにする。

たまチームのケースでは、Webアプリはクラスタリング(冗長化)構成になっていてロードバランサーが間に入るため、実際の構成は以下のようになった。

web-api-in-aws

開発者を叱責の無間地獄から解放するためのメトリクス

DevOps色の強いインフラチーム(infrastructure team)に所属していた著者が、DevOpsの原則が通用しないプロダクトチーム(product team)に移って受けたカルチャーショックと、そこから、従来の「運用チーム(operations team)」という考え方は止めて、プロダクト開発者のためのサービスを開発・運用する「プラットフォームチーム(platform team)」へ移行するべきだという発想に至るまでの話。

この記事では、運用というものが、障害を起こさないという方向に強く動機付けられていて、それがプロダクトを常に更新していかなければならない開発者の利害と衝突してしまうという問題を指摘している。障害が起きたら、たとえ深夜であろうが、まず対応しなければならないのは運用チームであるため、運用としては出来るだけ障害が起こらないよう、システムの変更に対しては保守的にならざるを得ない。

つまり、問題の本質は「維持」と「変更」の対立であって、これは運用だけに限ったことではなく、障害が起きた時に顧客対応しなければならない営業やカスタマーサポートでも同様の問題が起こる可能性がある。

DevOps界隈でよく話題になる、開発者から嫌われる運用チームの人物像というものがある。何故こんな人物像が生まれるかと言えば、「維持」に動機づけられた側の人間が、システムを変更し障害をもたらした人物に対して責任の追及を行う(finger-pointing)ということが頻繁に発生するからである。これは上に書いたように運用に限らず、「維持」と「変更」のトレードオフを理解しない人間によっても度々行われるため、組織で働く開発者というのは常に、完璧に防ぐことが原理的に不可能な問題で、叱責され続ける運命にある。

この問題を解決するためのヒントとなりそうなのが、この記事で紹介されている MTTRMTBF という二つのメトリクスである。

MTTRというのは Mean Time to Recovery の略で、障害が起きてから回復するまでにかかった時間の平均である。一方、MTBFは Mean Time between Failures の略で、障害から回復後、次に障害が起こるまでの時間の平均、つまり平穏無事にシステムが動き続ける時間の平均である。

「維持」に動機づけられた人間は、これら二つの指標の内、MTBF、つまりシステムが平穏無事に動きづける時間の最大化を目指す。これを MTTR の方に重きを置くように考え方をシフトさせる事が出来れば、開発者の精神的な負担は大分軽くなる。

「維持」と「変更」がトレードオフの関係であること、システムを変更し続けなければ組織のミッションは果たせないというところから、「変更」に対する理解を得ることはそれほど難しくない。しかし、どこまで障害に対して許容するかとなれば、利害が異なる二つのグループの間でそう簡単に折り合いがつく訳でもない。そのような時に「障害をいかに起こさないか」という考え方から「障害が起こる事自体はそれほど大きな問題ではない、問題はいかに早く障害を解決するか」という方向に考え方を変える事が出来れば、変更に対する抵抗も低減するし、変更は MTTR を最小化するための学びの場となる。

Nagiosから早朝のアラート「CHECK_NRPE: Error – Could not complete SSL handshake」

早朝4時にNagiosから何通かのCRITICALアラートが届いた。その後、10分程度でRECOVERYしたとの通知。

アラートの Additional Info の欄には以下のメッセージ:

CHECK_NRPE: Error - Could not complete SSL handshake.

ネット上で調べると、/etc/nagios/nrpe.cfg ファイルの allowed_hosts= を設定せよという情報がすぐに出てくるが、

今まで接続出来ていたものが突然出来なくなったという現象なのでこれには当たらず。

「繋がる時もあるし繋がらない時もある」という現象では以下のサイトと同様なのだが、

xinetdは使ってないので、これでもない。

Nagiosサーバー側には以下のようなログが出ている。

NRPE: Call to fork() failed
CHECK_NRPE: Error - Could not complete SSL handshake.
...

そこで辿り着いたのが、NagiosのTrackerにあった以下のIssue:

NRPE will fail to fork or run the check command(s) on a host with multiple simultaneous tests when the soft limit on open files is reached. (Even if the hard limit is higher.) I suggest, upon a failed fork, you use setrlimit to increase the soft limit as needed until the hard limit is reached.

何故この時だけファイルディスクリプタ数の上限に達したのかについては、毎朝4時に行われるウィルススキャンと関係しているんじゃないかと推測することも出来るが、この一度だけのアラートだけでは何とも言えない。今回はとりあえず様子を見て、再度発生したら具体的な対策を考えよう。

我々は何のためにソフトウェアを開発するのか?

達人プログラマー』の著者であり、アジャイルソフトウェア開発宣言の発起人の一人でもある Dave Thomas氏によって書かれたブログ記事「Agile Is Dead (Long Live Agility)」が、アジャイル界隈に少なからぬ波紋を投げかけたのは記憶に新しい。

その記事で彼は、アジャイルという言葉が、その人気ゆえに、マーケティングの道具にされて有名無実なものになっていることを嘆いていた。

同じくアジャイルソフトウェア開発宣言の発起人である Ron Jeffries氏が年初に出した「The Nature of Software Development」という本は、もしかしたらこの流れに何らかの影響を受けているのかもしれない。

この本でJeffries氏は、いわゆるアジャイルと呼ばれるものの本質について、アジャイルという語やその周辺の専門用語を極力使わずに、おそらく技術者でなくても理解出来るような平易な言葉で解説をしている。150ページほどのコンパクトな内容にたくさんの手書きイラストと専門用語を排した説明。2015年の今、アジャイルの(再)入門として読むには最適な書籍だ。

Dave Thomas氏の記事もそうだが、アジャイルが生まれてから十年以上が経過した今、アジャイルってそもそも何だ? というところに一度立ち返ろうという流れが出てきているように見える。

本来、アジャイルというものは、自分が置かれた状況に合わせてどのように行動したら良いか、ということを考える上での原則を与えるものであって、複雑な手順やツールを利用したり、専門用語に精通したり、ましてやスクラムの資格などとは本質的に関係のないもののはずである。

Jeffries氏は、我々は何のためにソフトウェアを開発するのか? それは「価値」を提供するためである、という当たり前(だけど忘れがちな)事実からスタートして、その効率を最大化するための基礎となる要素「Guiding」「Organizing」「Building」「Slicing」「Quarity」について説明する。

この本の中で個人的に面白いと思ったのは、「価値とは何か」という問いに踏み込んでいるところである。

これまでのアジャイルでは、価値の決定は顧客、あるいはそれに準ずる利害関係者に託されていた。開発の透明性を高めて判断材料を増やし、イテレーション開発で判断機会を頻繁に設けることで、より高い価値を目指す。

アジャイルの考え方を起業に応用したリーンスタートアップでは、さらに踏み込んで、計測可能な価値というものを考える。価値仮説を立てて、実験と計測を繰り返す事で価値の最大化を目指す。

そして、「The Nature of Software Development」では、基本的に価値は計測可能なものではなく「Value is what you want(価値とはあなたが欲しいもの)」だと定義する。一見、何も説明していないようにも読めるが、Jeffries氏は価値というものの多様性を説く。我々は色んなものに価値を認める。例えば「情報」だったり「人命」「資本」「スピード」「幸福」「創造性」などなど、あらゆるものに。

つまり、「価値とはあなたが欲しいもの」の意味は、価値、あるいは何が役に立つかという事は、その価値を受ける人とその人が置かれた状況・文脈に依存するのであって、その状況ごとに自分の頭で考える他はない、ということになる。

DevOpsの起源とOpsを巡る対立

たまチームとして技術ブログを書くようになって、ある日ふと気付いたのだが、そのメインのトピックの一つであるDevOpsという言葉の起源については深く追求しないまま今日に至ってしまっていた。開発と運用が一緒になるとか、あるいはインフラの自動化ぐらいのぼんやりとしたイメージはあったのだが、その運動を起こした人たちとその意図、歴史などについては無頓着なままであった。そこで今回はこの辺の事情について調査を行い、今の知識と照らし合わせながら現状の考えをまとめておきたい。

DevOpsの起源

まず、DevOpsの起源と歴史については以下のサイトに簡単にまとまっている。

Agile 2008 conference

これらの記事によれば、DevOpsという言葉が生まれる一年ほど前、2008年にカナダのトロントで開催された Agile 2008 conference にて、ベルギーのITコンサルタントであるPatrick Debois氏が「Agile Infrastructure & OperationsPDFのプレゼン資料)」というプレゼンテーションを行い、これが後のDevOpsムーブメントの萌芽となったらしい。

このプレゼンテーションで提示されている文脈としては、まず「開発と運用の分離」がある。「What is DevOps?」という資料にも歴史的経緯が解説されているが、元々は分かれていなかった開発(Development)と運用(Operations)という役割が、コンピュータの進歩と共に、次第にそれらを担う別々の専門家が生まれて、最終的には職能として、組織の中では別々の部門に配属される事が多くなったという流れである。

そして、発表が行われたカンファレンスのテーマである「アジャイル開発」も重要な文脈である。このプレゼンテーションのために実施されたアンケートでは、(科学的なサーベイではないと断り書きがあるものの)開発者はアジャイルという方法論に慣れ親しんでいるが、それ以外の部署、運用やインフラ、営業などになると、いわゆるアジリティーはかなり低くなるという結果が出ている。

そして、発表者のPatrick Debois氏は、開発と運用の両方で豊富な経験を持ち、両者の問題をよく理解している、いわば開発と運用のブリッジとなるような人である。その彼がプレゼンテーションで紹介しているのが、データセンターの移行というインフラの仕事をどうやってアジャイル(スクラム)でやるかという事例で、アプリケーションや運用チームがProduct Ownerになるという大変に興味深いものだ。

Velocity 2009 – O’Reilly Conferences

Patrick Debois氏の発表から約一年後、カリフォルニア州サンノゼで開催されたVelocity 2009というイベントで、Flickrのエンジニア(John Allspaw, Paul Hammond)によって発表された「10+ Deploys per Day: Dev and Ops Cooperation at Flickr」というプレゼンテーションがDevOpsという言葉を生み出すきっかけとなった。

このO’Reilly主催のVelocityというカンファレンスのことは知らなかったのだが、インフラ・運用(Web Performance & Operations)がテーマになっていて、参加者もどちらかと言えば開発者より運用系の人が多いようである。このカンファレンスの存在自体が「開発と運用の分離」という事情を象徴するようであるが、そこからDevOpsという言葉が生まれたというのはなかなか興味深い事実である。

さて、件のプレゼンテーションの内容はと言えば、開発者と運用担当者が共通の目的に向かって手を携える事で、一日に10回以上デプロイするような高速な開発を実現できるというものである。まさに Wikipedia などに書かれているようなDevOpsの定義につながる。そして、そこで指摘されていたのは、職能によって分けられてしまった開発者と運用担当者の利害が衝突するようになり、いわゆるセクショナリズムが生じて、いつまでも対立を続けるようになった結果、組織全体が目指すべき本来の目的を見失っているということであった。

この問題は、開発・運用だけにかぎらず、例えば、営業・開発の関係でも、職能で部署が分けられている場合は不可避的に生じる問題である。何か問題が発生した場合に、セクショナリズムが存在すると、その問題を「我々の問題」として認識出来なくなる。そして責任転嫁(finger-pointing)や責任を回避するような保守的な言動が横行するようになる。これは普通の会社の中で働いているたまチーム自身もまさに経験していることでもある。

そして、このプレゼンテーションをベルギーからストリーミングで視聴していたPatrick Debois氏が、「Devopsdays」というイベントを開く事を思い立ち、それが本格的なDevOpsムーブメントの始まりとなった。

devops

NoOps炎上事件

さて、以上の流れでDevOpsというムーブメントが生まれるまでの経緯は大体把握する事が出来たが、その後の2011年から2012年にかけて「NoOps炎上事件」とでも呼ぶべき興味深い事件が起こっているのでここで紹介しておきたい。

事の発端となったのは、2011年にマサチューセッツ州ケンブリッジに本拠地を置くフォレスター・リサーチという調査会社の出した「Augment DevOps With NoOps」というレポートである。

このレポートの主張は、クラウド環境の進歩により自動化が促進されて、将来的に DevOps は NoOps となる、つまり運用というものは必要なくなるというものである。開発者の立場で考えると、昨今のPaaSの隆盛などを見るに、普通に納得出来そうな将来予測ではある。しかしながら、この NoOps という言葉が、運用だけでなく、運用を担ってきた人間さえも否定するように響き、DevOpsという言葉を生み出すきっかけを作ったコミュニティに少なからぬ波紋を引き起こす事となった。

象徴的なのが、DevOpsでは頻繁にその名前が言及される Netflix という会社に当時勤めていたAdrian Cockcroft氏の NoOps に関する記事「Ops, DevOps and PaaS (NoOps) at Netflix」と、それに対するJohn Allspaw氏の反論である。

John Allspaw氏の反論を全部読み通すのはなかなかに骨が折れるが、その長さからして運用のコミュニティを代表する彼の怒りが伝わってくるようだった。しかし、そもそもここで問題にされている事は何なのか?

それは「運用」とは一体何なのか? ということである。Allspaw氏によれば、Cockcroft氏が言うところの「運用」とは、90年代に存在した古い存在であり、相互不理解が原因で開発者にとっての障害かつフラストレーションの原因となっていたが、その反省によってまさに DevOps が生み出されたのだと、そして現代の我々運用コミュニティの人間はとっくにそちらにシフトしているのだと、今更お前は何を言っているんだと、Opsを無くして悦に浸っているようだけど、「運用」という専門領域あるいは責任は変わらずそこにあるし、全然 NoOps になってないじゃないかと、そのような言葉の誤解を指摘している訳である。

この議論は果たして有益だろうか? Allspaw氏の反論は、Opsの誤用を諭しているようで、その実は、彼の所属するコミュニティが信奉しているOpsの定義を主張しているだけのようにも見える。つまり、彼はOpsコミュニティの利害を代表して抗議する事によって、DevOpsが避けようとしていた落とし穴に再び陥っているかもしれないのである。

専門用語の定義問題というのは、その専門性によって縄張り意識を生み出し、セクショナリズムにつながる危険性を孕んでいる。

物理から仮想へ

この言葉を巡る炎上問題から学べる事は何だろうか? それはおそらくDevとOpsという区別自体がナンセンスな時代になったということではないだろうか。その区別が引き起こしていた障害を解消するために DevOps が発明された訳だが、依然として言葉上では区別していたために「NoOps炎上事件」のような対立が起きた。

DevOpsの背景にあった重大な変化は、あらゆるものがソフトウェア化しているということである。分かりやすい例で言えば、仮想化技術によって実現されたクラウドやVPSのような、物理リソースから論理リソースへの移行である。その他、元々はOpsの領域だった、OSのインストールやコンフィグレーション、アプリケーションのデプロイなどの操作もコード化されるようになった。それぞれ、Infrastructure as Code, Operations as Code と呼ばれるものである。

そのようなソフトウェア化した世界にとって、Infrastructure や Operations の区別というのはそれほど重要ではなくなる。あるいは別のレイヤーの話になる。例えば、Microservices概念の出現によって、一つのサーバー(Service)と、プログラム上の一つのモジュールとの概念的な違いが無くなっていくように。

物理から仮想へと言っても、物理的なハードウェアが無くなる事はあり得ない。もしOpsという言葉が有効に機能する領域があるとすれば、それは物理的装置に関連する領域ではないだろうか。言葉というのは視点の問題である。Infrastructure も Operations も、コード化してソフトウェアとして扱えるようなれば、ソフトウェア開発の技術がそこに応用出来る。そこで重要なのはDevとOpsの区別ではなくて、物理なのか仮想なのかという問題である。

Microservices時代のプロジェクト管理を考える (2) – サービス開発者の責任 / 優先順位

水平型から垂直型への移行」によって、今までアプリケーションのビルドまで面倒を見ればよかったアプリケーション開発者は、(マイクロ)サービス開発者となって、サーバーの構築 (Provisioning) から運用の一部まで面倒を見ることになった。一つ一つのサービスの独立性が高くなり、開発者あるいは開発チームの自律性は高まるが、やることが増えた分、外部に対する責任をより明確にしておかないと運用やメンテナンスの局面で様々な問題に直面することが予想される。

前回も少し触れたが、開発者やチームの責任を考えるときは、ソフトウェアのモジュール、あるいはオブジェクト指向でいうところの「責務 (responsibility)」の考え方が参考になる。まずシステムが果たすべき役割を検討して、そこから個々のサービスの責務を割り出し、その責務を実現することを第一優先とする。責任を明確にする理由の第一は、優先度に沿ってリソースを効率よく分配することである。「そもそも我々は何のためにこのシステムを作っているのか」という組織やチームのゴールが共有されてないとバランスの悪いリソース配分をしてしまう危険性が高くなる。

よくある問題の一例として、技術者が技術者の責任を考えると、この優先度の考え方が欠落して必要以上に内部の問題に耽溺してしまうということがある。例えば、技術者の間でよく行われる成果物のチェック方法としてコードレビューがある。スケーラビリティやセキュリティといった比較的目的が明確で判定しやすい指標はよいのだが、可読性や保守性といった、人によって判断が分かれることが多い指標は、無駄に議論が長引いたり、最悪なケースだと技術者同士の衝突に発展してしまうケースもある。

お前さえいなければあああああ!!!
お前さえいなければあああああ!!!

プログラムデザインの良し悪しを判断する時に厄介なのは、重要性が高いものほど多くの人間の同意を取るのが難しくなることである。関数やメソッドレベルの設計はまだ良くても、クラス・パッケージレベルやフレームワーク、アーキテクチャレベルになると、必要になる前提知識も増え、考え方のスペクトラムも大きくなるため、宗教論争的になって同意に至るのは容易ではない。結果的に、声が大きい人の意見が採用されるか、衝突を避けるために同意しづらい事項についてはスルーして、反論されづらい些末な指摘ばかりを繰り返すことになる。

このようなことを避けるため、レビューをするとしても出来るだけ価値の高い外部視点のもの、例えばテストコードをレビューすることを過去の現場では奨めてきたのだが、これも現実にはなかなか難しかった。技術者同士でレビューするとすれば、単体から結合テストレベルのコードになるが、テストの価値が相対的に低いので些末な感じは否めない。受け入れテストなど、もっと上位のテストになれば、ユーザー視点からのレビューも可能になるというのが、Acceptance Test Driven Development (ATDD) などの考え方であるが、以前の記事でFitの失敗を紹介したように、この分野もなかなか前途多難である。

さて話を戻すと、まずサービス開発者の責任を明確にして、責任を果たす限りは自由な裁量に任せるという形にした方が、プロジェクトの参加者全員にゴールへの意識を持たせつつも、無駄なコミュニケーションコストを省くことが出来るし、ゴールと関係ない内部の詳細に耽溺することも無くなる。というわけで、次回以降では、まずサービスの受益者は誰かということを考えて、そこからサービス開発者の果たすべき責任を一つ一つ定義してみたいと思う。

運用中のサービスに与える影響を最小限に抑えつつ、EC2-Classic上のRDSをVPC上に移行する

たまチームで開発しているWebサービスの大半は AWS EC2-Classic 上で動いているのだが、去年から標準でサポートされるようになった VPC (Virtual Private Cloud) に移行しないといろいろと不便なことが多くなってきた。というわけで、移行にあたって一番厄介だと思われる RDS の移行手順を以下にまとめた。

ちなみにこの手順は、EC2-Classic から VPC への移行だけでなく、VPC から VPC の移行にも適用出来るはず。

rds-migration1

大まかな流れとしては、

  1. リードレプリカ経由でClassic側のdumpを取り
  2. それをVPC側で復元
  3. 復元したデータベースをClassic側のslaveとして動かしてデータを同期
  4. 読み込み専用のアプリはここでVPC側に移行して動作確認
  5. VPC側をmasterに昇格した後(ここで一瞬だけ Read Only になる)
  6. 書き込みのあるアプリケーションもVPC側に切り替えて
  7. Classic側を潰せば

めでたく移行は完了する。

1. リードレプリカ経由でClassic側のdumpを取る

rds-migration2

まずはClassic側で移行用のdumpを取得する。リードレプリカを作ってすぐにリプリケーションを停止し、そのときのdumpとバイナリログの位置を取得しておく。リードレプリカ経由なので、この間、サービスの運用には一切影響を与えない。

バイナリログの保持期間を変更する

デフォルトのバイナリログの保持期間が短いので、VPC-slaveを作る間にログが消失してしまわないよう、Classic-masterで以下のように変更しておく。

CALL mysql.rds_set_configuration('binlog retention hours', 24);

現在の設定を確認する:

CALL mysql.rds_show_configuration;

レプリケーション用のユーザーを作る

Classic-masterでレプリケーション用のユーザーを作っておく。

GRANT REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'repl_user' IDENTIFIED BY 'some-password';

Classic-masterのリードレプリカを作る

AWS管理コンソールで Classic-master となるRDSインスタンスを選び、”Instance Actions” から “Create Read Replica” を選択する。

レプリケーションをストップする

Classic-slave(今作ったレプリカ)のレプリケーションをストップする。

CALL mysql.rds_stop_replication;

バイナリログの位置を記録する

Classic-slaveで、レプリケーションの状態をメモる(どの位置からレプリケーションを再開すれば良いか)。

show slave statusG
...
    Master_Log_File: mysql-bin-changelog.050354
Read_Master_Log_Pos: 422
...
   Slave_IO_Running: No
  Slave_SQL_Running: No
...

Slave_IO_RunningSlave_SQL_RunningNoになっているので、ちゃんとレプリケーションが止まっていることが分かる。

Classic-slaveのdumpを取る

  • Classic-slaveのエンドポイントを classic-slave.rds.amazonaws.com、管理ユーザーを dbuser とする。
mysqldump -u dbuser -h classic-slave.rds.amazonaws.com --single-transaction --compress --order-by-primary --max_allowed_packet=1G -p --databases database1 databases2  > classic-slave.dump

2. VPCにClassic-masterのレプリカを作る

rds-migration3

VPC内に立てたRDSインスタンスに、Classic-slaveのdumpをインポートしてClassic-masterへのレプリケーションを再開させる(VPC-slave)。

VPC-slaveとなるRDSインスタンスを立ち上げる

AWS管理コンソールでサクッと立ち上げる。

VPC-slaveにClassic-slaveのdumpをインポート

  • VPC-slaveのエンドポイントを vpc.rds.amazonaws.com、管理ユーザーを dbuser とする。
mysql -u dbuser -h vpc.rds.amazonaws.com -p --default-character-set=utf8 < classic-slave.dump

Classic-masterのセキュリティグループ設定を変更する

レプリケーションを可能にするために、VPC-slave から Classic-master にアクセス出来るようにセキュリティグループ(DB Security Groups)を設定する。

以下のような感じで VPC-slave のIPを調べて、そのIPからのアクセスを許可する:

$ ping vpc.rds.amazonaws.com

以下を Classic-master のセキュリティグループに追加。

CIDR/IP to Authorize:  xxx.xxx.xxx.xxx/32

レプリケーションをスタートさせる

VPC-slave にログインして、レプリケーションをスタートさせる(さっき作った専用ユーザーがここで活躍)。
パラメータは先ほどメモしたレプリケーションの状態を参考に。

CALL mysql.rds_set_external_master('classic-master.rds.amazonaws.com',3306,'repl_user','some-password','mysql-bin-changelog.050354',422,0);
CALL mysql.rds_start_replication;

レプリケーションの状態を確認:

show slave statusG

以下のような項目があれば、正しくレプリケーションが動作している。

...
 Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...

レプリケーションがうまく行ったら、Classic-slaveは用無しなので削除する。
この段階で読み込み専用のアプリはVPC内に移行出来る。

3. VPC-slaveをmasterに昇格する

rds-migration4

Classic-masterをReadOnlyにする

FLUSH TABLES WITH READ LOCK;

Closes all open tables and locks all tables for all databases with a global read lock. This is a very convenient way to get backups if you have a file system such as Veritas or ZFS that can take snapshots in time. Use UNLOCK TABLES to release the lock.

として、全てのテーブルをロックしてから切り替えたいところなのだが、RDSではこの命令を実行するための権限がないことが判明。

簡単な代替策もないようなので、とりあえず編集を行う可能性があるサービスを全て「メンテナンス中」として操作出来ないようにした。

VPC-slaveのレプリケーションを停止する

CALL mysql.rds_stop_replication;
CALL mysql.rds_reset_external_master;

以後、こちらを master として扱う。

アプリケーションをVPC内に移動する

VPC内にアプリケーションサーバーを立てて、そちらを指すようにDNSの設定を書き換える。

後は古いClassic環境を潰して移行は完了。

ドキュメンテーションを成功させるには

ドキュメンテーションをうまく機能させるのは本当に難しい。

たまチームの開発は、同じフロアにいるマーケティングチームからの要求に従って行われている。ミーティングなどで要件のヒアリングを行い、それを簡単なタスクの箇条書きに落とし込む。PDCAサイクルを一週間(場合によっては二週間)ごとに行うイテレーション開発を採用しているので、全ての要件を一度に明らかにせず、次のイテレーションのタスクが埋まる程度にヒアリングを行う。ミーティングではイテレーションの成果を踏まえて次の要件やタスクを考えるので、想定外の事象が発生しても早い段階で軌道修正を行える(ここに落とし穴があったのだが、これはまた後日)。

いわゆるアジャイル的な開発である。進捗は可視化されていて、軌道修正もしやすく、見当違いなものを作ってしまうリスクも抑えられるので、なかなか効果的な開発方法に思えるが一つ大きな問題があった。それは、きちんとしたドキュメントを残すタイミングがなく、ほとんど全てが現物主義になってしまうことだ。

そもそもアジャイルという手法に現物主義の傾向があるとも言える。動くソフトウェアとクリーンなコードが何よりも現状を雄弁に語るという考え方だ。

しかし、それはある程度小規模な開発であれば当てはまるかもしれないが、一年以上に渡って開発し続ける複雑なシステムではなかなか難しい状況になる。たとえ動くソフトウェアがあったとしても全貌を把握することは難しくなるし、全てのコードをクリーンに保つことも現実的には難しい。

ある日、こういうことがあった。開発がスタートしてから一年半が経過し、システムの機能も大分増えてきた頃だ。

マーケティング担当者「そう言えば、この機能って XXX のような仕様だったと思うんですけど。」

開発者「え、そうでしたっけ。私は YYY と聞いたように記憶してるんですが。」

マーケティング担当者「うーん、それは困るな。お客さんになんと説明すればよいのか。。。」

もの凄くありがちな光景である。イテレーションごとに現物を確認していても、同じ機能を何度も修正している間に、マーケティング担当者の想定と現物がズレてきてしまったのだ。

その後もシステムはどんどん複雑化することが予想されたので、たまチームとしても何か対策を打たざるを得なくなった。仕様書を書こうということになったのだが、常に変化し続けるシステムの仕様書を維持管理することの難しさは重々承知している。そこで、どのようにドキュメントを管理したよいかを入念に検討して以下の指針を作った。

  • 分かりやすさ: XXXを知らない人でもシステムの目的・機能要件が理解できること
  • 網羅性: 要件が一通りカバーできていること
  • 探しやすさ: 必要な情報をすぐに見つけられること
  • ステータス管理: 要件ごとに「実装待ち」「実装済み」「検証済み」のようなステータスを管理すること
  • 変更容易性: 文書の内容が常にup-to-dateになるように、変更のコストを最小化すること
  • テスト仕様書: この文書を見ながらシステムを操作して要件をテストできること

今回のお題である「Lean Documentation」に書かれている指針と大体同じである。Lean Documentationで紹介されている「良いドキュメンテーションのためのルール」は以下の通り:

  • 素早く書けて更新出来るようにする。現状を反映してない情報は、まったく情報が存在しないことより有害である。
  • 簡単に正しい回答を得られること。必要な情報が見つけづらければ誰もそれを利用しなくなる。
  • ドキュメンテーションが対話を置き換えるわけではない。「プロセスやツールよりも、個人や対話に重きを置くべきである(Manifesto for Agile Software Development より)」。

変更容易性はかなり重要な要件で、これを考えるとWordやExcelなどで仕様書を管理することの問題がよく分かると思う。プロジェクトメンバー全員で情報を共有しつつ、かつ変更も容易に行えるとなると、データベース的な仕組みがどうしても必要になる。

昨今の開発プロジェクトだとWikiで仕様を管理することも多いのではないかと思う。しかしWikiでも、まだ変更容易性に若干の不満があり、ステータス管理やフィードバックなどの扱いに難がある。そこで、たまチームではチケットシステム(具体的には GitHub の Issues)を試してみることにした。

仕様を独立した小さな単位に分割してチケットとして管理すれば、その単位でステータスを管理したり、フィードバックをチケットのコメントとして行えるようになる。変更についても、そのチケット自体を修正したり、破棄したり、また新しく作ったりと、より柔軟に行える。どのような機能が修正されたかも把握しやすい。

全体の構成について、Lean Documentation では「Structure it like Google Earth(グーグルアースのように構造化せよ)」というプラクティスが紹介されている。全体像から詳細に向かって徐々にズームインしていく仕組みがあれば、情報が格段に見つけやすくなるというわけだ。たまチームでは、細かい単位であるチケットだけだと全体像が見えないので、Wikiページに概要とチケットへのインデックスを作った。

「さて、これで完璧なドキュメントが出来上がったぞ(ドヤァ)」

数ヶ月後、苦労して作ったドキュメントは誰にも読まれなくなっていた。読まれないので更新もされない。ドキュメントは徐々に現実からズレていく。

つまり、大失敗である。

原因は一体何だったのだろうか? Lean Documentation にあるように、ドキュメントがマーケティング担当者のニーズに沿った構成や内容になっていなかったのだろうか(「Practice 1 – ドキュメントの利用者とその目的を特定せよ」)?

マーケティング担当者曰く「GitHubを見ることが習慣化していないというのと、同じフロアにいるので、つい直接尋ねてしまう。」

ドキュメンテーションを成功させるために、最も重要でかつ最低限必要なことは「(継続的に)読まれること」である。

このことは文書そのものをどう書くかということよりも遥かに重要である。文書が適切に書かれるから読まれるのではなくて、読まれるから適切な形に変わっていく。読まれなければその文書は死んだも同然である。

ドキュメンテーションというのは固定化されたものではない。コミュニケーションの記録である。口頭の対話は記録されないが、ドキュメンテーションという対話は記録され、かつ構造化される。かなり効率は落ちるが、その記録が残ることの価値はかなり大きい。

ここで重要なのは、「読まれること」は「読ませること」ではないということだ。「GitHubを見ることが習慣化していない」という人に対して「じゃあ、習慣化して下さい」と言うのは間違っている。ドキュメンテーションを行うものは、その文書を読んでもらうためのあらゆる努力をしなければならない。それには、Web上にブログを開設して読者を集めることと同様の難しさがある。

仕様を表現するための一手法である「Specification by Example」も、手法としては素晴らしいと思う。Living document のコンセプトも良いし、Given-When-Then 形式の Acceptance Test Scenario(あるいは、Acceptance Criteria)も分かりやすく書きやすい。実際に、たまチームの一つのプロジェクトでは英語話者と日本語話者が効率的にコミュニケーションするためにこのフォーマットを採用している。しかしこれでも「読まれること」とは、全く別次元の話なのだ。

ビジネスパーソンとエンジニアは、そもそも異なる言語で会話をしている。アジャイルコミュニティではドキュメントの限界を理解しつつも、テストプログラムに通じる形式的な文書を、いかにビジネスパーソンに読ませる(書かせる)かということに苦心してきた。しかしこの試みも Fit の失敗 に代表されるように、うまく行っているとは言えないように見える。ビジネスパーソンとエンジニア、両者の想定があまりに違うのである。

書いたものを読んでもらうというのは、人を動かすということである。Stackoverflow の共同創業者で、Joel on Software で有名な Joel Spolsky 氏は、編著「The Best Software Writing I」の中で「ストーリー」の重要性を説いている(「”Show, don’t tell” rule」)。いくら内容が正しくとも、ストーリーなき文章を長々と読めるわけがないというわけだ。

ストーリーなんて仕様書のような形式的な文書とは関係がないと思うだろうか? アジャイル開発で要件の単位となる「ユーザーストーリー」は、形式的表現に慣れているエンジニアからビジネスパーソンに歩み寄った結果として生み出されたものだ。ドキュメンテーションとはコミュニケーションであり、その質にはコミュニケーション能力がそのまま反映される。だからこそプログラミングそのものよりも難しいと言っても過言ではないように思える。

Microservicesアーキテクチャの思想性

Microservices and the Goal of Software Development:
http://www.infoq.com/news/2015/03/microservices-software

  • ソフトウェア開発の目的は business impact を生み出すこと
    • Business impactとは、組織内で観測可能な効果のこと(新規顧客やオペレーションコストの削減など)
    • Lead time(機会の発見からその解答を生み出すまでの時間)を最小限にすること
    • これを持続的に行うのが難しい
  • 三種類のコード
    1. 最近書いて理解しているコード
    2. よくテストされ、文書化されているコード
    3. 誰も知らないコード(不明瞭な依存関係、変更の影響範囲が局所化されない)
  • ソフトウェア開発最大の問題は、大部分のコードが 3 に分類されるということ
  • コードはきちんと運用出来るものだけを維持するようにするべき(3のコードを排除すべき)
  • North氏の提案する二つのパターン
    1. コードの目的(意図)を明確にすること
      • ソフトウェアの半減期は短い(「半減期」とは放射性崩壊の速さを示す物理学用語)
      • なので、それぞれのコードの目的を明確にして安定化に努めるべし
    2. 理解しやすいようにコードを分割統治すること
  • 上の二つのパターンを実現するために有効なのが「置換可能なコンポーネントによるアーキテクチャ (replaceable component architecture) 」
    • APIによって意図が明確にされたコンポーネント
    • コンポーネントは Alan Kay の発明したオブジェクト指向のオブジェクトのごとく、メッセージのやり取りをする小さなコンピュータのようなもの
  • Microservicesは、置換可能なコンポーネントによるアーキテクチャになり得る
    • 置換可能性と一貫性を追求すること
    • コンポーネントの「小ささ」よりも「置換可能性」の方が重要

現場のエンジニアがMicroservicesアーキテクチャを評価するときは、どうしてもその実用性や現実性、例えばプロセス間通信の多用によるオーバーヘッドなどが問題にされがちである。しかし、そのコンセプトをつぶさに見ていくと、これは単にアーキテクチャだけの問題ではなく、ソフトウェア開発はどうあるべきかという、ある種の思想を提示していることに気がつく。

開発の現場には「エンジニアとは外から持ち込まれた問題に解を与える者だ」という強い思い込みがあることが多い。しかし、これはプログラミングに対する誤解を含んでいるだけでなく、一つのシステムを長く育てるというケースにおいては、持続可能なモデルではない。

Martin Fowler氏が「The New Methodology」の中で「ソフトウエア開発は、全てが「デザイン」である」と指摘したのはもう大分昔のことだが、この考え方が開発の現場においてどれだけ浸透しているだろうか。

ソフトウエア開発は、本質的に、問題に対する解法の実装だけでなく、問題自体の検証と再設定を含むサイクルを繰り返す作業で、プログラミングというのはそのための強力な武器である。既に設定された問題を解決するためだけにプログラミングが存在し、そしてその問題設定を開発から切り離せると考えるのは、ソフトウエア開発に対する大きな誤解である。

Microservicesで提案されているのは、一つのサービスだけでなく、それを開発する開発者(あるいはチーム)も、できるだけ完結した存在になることが望ましいということである。中央集権的な開発体制ではなく、いくつものサービスとその開発を担当する cross-functional なチームが自律的に連携して、全体として大きなシステムを実現するという、オブジェクト指向が理想としていたようなモデルである。

あまり理想主義的になり過ぎるのも良くないが、このような考え方のルーツの一つにオブジェクト指向があり、それが考え方としてはデザインパターンやアジャイル開発などにつながり、プロダクトとしてはインターネットやiPhoneなどにつながっていることを考えると、実用性や現実性はともかく、その思想の重要性に注目しておく必要はあるだろう。