システムの利用者に安全に独自のビジネスロジックを定義させる

SaaS の活用が日々盛んになってきている昨今。皆様精力的に SaaS システムを開発されていますでしょうか。

私は最近、子供とScratch3.0でゲームを作ったりしています。

マルチテナントシステムが利用者の個別要件を受け入れるためには

さて、マルチテナントシステムを提供する際の困難な仕事の1つは

  • 「うちがこの製品を利用するにはこの機能が必要だ」
  • 「ここにこのようなビジネスロジックを入れることができなければ使い物にならないね」

といった個別の要件を「この要件は全顧客に提供すべきだね」と取り入れながら「これを必要とするのは御社だけですね…」と払い除けることです。

しかしながら、そのような要求を声に出すお客様というのはやはりとても重要なお客様であることが多いので無碍にすることはできません。

またそのような要求を持ちつつ、追加開発を要求する予算や権限が無いというお客様も当然多いです。サービスベンダとしては提供するシステムを末永く活用していただくためにはお客様の個別の要求にも応えられる仕組みを備える必要があります。

実現方式

さて、ではそのような顧客の個別要件を受け容れるためにはどのようなやり方があるでしょうか。

  • サービスベンダで開発
  • APISDKの提供
  • 独自DSLやその実行環境の提供

ぱっと思いつくのは上記のようなものでしょうか。ざっくり検討していきましょう。

サービスベンダで開発

一番古典的です。サービスベンダが顧客の要求からカスタマイズを開発し、提供するシステムそのものに組み込みます。該当御客のときのみ機能フラグでその機能が動作するようにします。ソースコード中に「// ○○社様向けフラグ」みたいなのが入るわけですね。

これは(正しくハンドリングすれば)お金は大きくいただけますが、システムが抱える依存関係・負債も大きくなります。開発者が辛いだけでなく、システムの健全な成長を妨げる事が多いです。「○○社様向けカスタマイズの機能があるので、これを考慮すると新機能Xが入れられない」というわけです。また、システム本体に手を加えるため多くの会社のカスタマイズ要望に応えきれません。

APISDKの提供

現代的です。システムの拡張ポイントとしてREST APIやそれを呼び出す言語用のSDKなどを提供することでシステムをカスタマイズします。これにより、開発がサービスベンダのシステムの主担当チームだけでなく、他部署や他社のSIer、開発力のあるユーザー自身が行うことができるようになります。

これにより、開発者は公開したAPISDKをメンテナンスするだけでよくなります。また、個別の開発をスケールさせることができるので多くの顧客の要望に答えることができます。ただし、開発がスケールする代償として個々のカスタマイズの品質のコントロールが困難になり想定を超えた(異常な使い方をする)ユーザーが登場します。適切なリミッターやスロットリングをするだけでなく、カスタマイズ開発者の教育や開発体験の向上などに継続的に取り組んでいく必要があります。

独自DSLやその実行環境の提供

こちらも現代的です。システムそのものに独自のDSLを組み込みユーザー自身がカスタマイズを作成します。「カスタマイズできる」というよりも「もともと自由度の高いメタなシステム」という見え方になるかもしれません。 Salesforce では Apex などが提供されていますし、最近では low-code/no-code(LCNC) development platform と呼ばれるものもこれに当たると考えています。

システム利用者の裾野の広がりに伴って、システム利用者のうち開発力のあるユーザーの割合が無視できない規模になっているという市場の変化によって、ユーザー自身が手軽かつ安全に開発を行えるようにすることでカスタマイズの開発を更にスケールさせるという戦略が取れるようになってきたと言えそうです。

ただし、カスタマイズの自由度は独自DSLやその実行環境が提供する語彙・機能に限定されますし。これは、利用者側に謎な行いをさせないという点ではメリットになりますが、システム開発者は一段メタな機能の開発に携わることになるため機能要件・非機能要件の策定に苦しむことになるでしょう。

どうでしょう

私個人としては以下のような評価をしています。

  • 二度とやりたくない
    • サービスベンダで開発
  • 今やってる
  • なんとなく夢がありカッコいいので憧れがある
    • 独自DSLやその実行環境の提供

やってみましょう

サイボウズ株式会社の kintone というサービスをベースに「独自DSLやその実行環境の提供」のプロトタイプを作ってみましょう。

できたプロトタイプが以下です。Chrome 拡張として実装していますので kintone ユーザーの方はお試し可能です。

KanariIi - Chrome ウェブストア

f:id:mitoma_ryo:20191218004904p:plain
KanariIi

説明

kintone ではアプリと呼ばれる RDBMS におけるテーブルのようなものを画面から定義することができ、それを用いて日常業務をなんだかいい感じにすすめることができます。詳しいことは こちら(kintoneの公式サイト)

kintone では「APISDKの提供」によってシステムの機能を超えるカスタマイズの要求に応えています。

今回紹介したプロトタイプではBlocklyでラップする形で独自DSLやその実行環境および開発環境を作ってみました。

Blockly

Blockly とは Google が開発したビジュアルプログラミング環境を作成するためのライブラリで、その名の通りブロックを組み立てるようにプログラミングをすることができます。冒頭で出てきたScratch3.0でもそのコアとして採用されていますし、Microsoft Makecodeプログルを始めとした多くの教育系プログラミングのサービスで利用されています。

Blockly は根本的にはプログラマから見ると AST エディタでしかなく、そこで構築した AST から任意の言語のコードや実行環境の呼び出しをすることでプログラムを実行します。ブロックを積むように AST を構築しているだけと考えると、多くの人が「括弧を並べて AST を構築してるだけの、自由奔放な言語があったような…」と思いを馳せる事があるかもしれませんがそれは別の話。

しかしながら、これから数年後の世代の人間はブロックプログラミングに触れた経験がある状態で社会人になる方が多数になる可能性があるので、システムのビジネスロジック定義にブロックプログラミングを導入するのは悪くない選択かもしれません。

この記事の目的

途中から話がおかしくなってきたことにお気づきかもしれませんが、この記事の目的はせっかく作った Chrome 拡張のユーザーが居ないので宣伝がしたかったということに尽きます。

kintone ユーザーの皆様や、興味を持った開発者の皆様はためしに使ってみてください。kintone アカウントがなくても開発用途であれば開発者ライセンスというものがあります。

KanariIi - Chrome ウェブストア

GitHubリポジトリ

簡単なドキュメント

それはさておき

マルチテナントシステムの設計やブロックプログラミングの活用などなど、夢があり楽しいことを日々やっていきましょう!

プログラマとミールキット

男子厨房に入らず。そんな時代はとうに過ぎた現代。しかし我が家のキッチンは依然妻の独壇場となっていた。

手伝いをしようにも男子大学生自炊飯(必修)しか履修していない我が身では土曜に餃子を包んだり、焼きそば、たこ焼き程度が貢献できる限界であった。

特段育児が得意でない夫婦なので子供二人を相手するだけでも日々の疲労は常にマックス状態。この状態で毎日の献立を考え調理するのは高コストであり、担当者一人でこの作業を続けるのはとうてい持続可能な生活と呼べるものではなかった。

というわけで一つの決断をした。「我々は献立を考えたりそれに合わせて食材を適量調達する事を放棄する。マニュアル通りに食事を作る作業者で構わない」

多くの職場では「あなたはただの作業者で居てはいけない。常に新しいアイデアで現場を楽しみ、より良くしていこう」というような事を言われるだろう。だけど我々はより自由であるはずの家庭で、創造性を発揮する事をやめて作業者になることに救いを求めた。

仰々しく書いたがなんのことはない、ミールキットと呼ばれる献立の手順書と人数分の食材を定期注文して夕食作りで楽しようとした話である。

「毎日食事作るの大変そうだしミールキットで手順通り作る食事でよければ自分も手伝えるかも」みたいな雑な提案だったのだけど、意外とすんなりOKが出た。

なので、我々は特に考えもなしにヨシケイオイシックスの二つをお試しで注文した。いまでだいたい一か月ぐらい続いている。どちらをメインにするかはまだ決めかねている。

↑と、ここまで下書きで書いた後にさらに二か月ほどたった。個人的にはヨシケイがよかったが妻的にはオイシックス希望だったのでそっちになった。

わかったこと

  • 素朴な献立でいくならヨシケイ。現代的な味を求めるならオイシックス
  • 宅急便のオイシックスよりも玄関前においてくれるヨシケイのほうが受け取りは楽
  • とにかく何も考えなくてもいいのはストレスフリー
    • おそらく栄養士がこれなら標準的と考える一人前の料理の分量がわかる
    • 栄養にバランスも自分で作るよりも良いような気がするので偏りを何も気にしなくてよい
  • 意外と料理に使われる野菜の分量はちょっとだという知見(玉ねぎ1/4とか人参1/3とか)
  • 誰でも同じメニューが作れるので手が空いている方が作れば済む
    • 更に手順書があるので作業途中の引き継ぎが容易
  • 時短レシピなのもあるだろうけど「家庭料理なんてこの程度雑でも成立する」という事が実感できる
  • 何を作ったって子供は期待通りには食べてくれない

意外だったこと

  • いちょう切りとか小口切りとか謎の専門用語が出てくる。いや、小学校の時の家庭科で習った気はするんだけども覚えてないよ…。
  • 家に当然あるべき調味料、みたいなもののレベルが意外と高い(特にヨシケイ
    • 出汁とかコンソメとかゴマ油とか「料理しないって言っても、これぐらいどのご家庭でもあるでしょ」のレベルが高い
    • なのに「こういうのは常備しといてね」みたいなのがあまり文書化されていない印象

結論的なこと

便利。こういうミールキットに以前は割高で非効率ではないかというよくわからない抵抗感があったのだけど、「金の弾丸で家事を効率化する」と表現されるもの、いわゆるルンバ的なものと同じカテゴリで考えるとその価値がよくわかる。特に子供がいるなど安定的な生活を回すことが重要な人たちにとって助かるものだと思う。

この記事は結局ミールキットを試してみた所感であって別にタイトルで書いたプログラマであることは特に重要ではない。

以上。

チームのやっていきを言葉にする

先日 ovento というイベントで対外発表をしてきた。

Osaka Venture Today Meetup という freee さんの主催するイベントで、技術的に良い話をしながら参加企業が良いエンジニアと出会って良いことが起きればいいなという感じの会。

kansai-venture.connpass.com

自分は今やっているプロジェクトで「コンシューマ駆動契約」というテスト手法のひとつを紹介した。マイクロサービス時代に、どうやってサービス間で動作を保証しながらも独立した素早いリリースサイクルを実現するかという、わりかし新し目の要求に対する一つの解となるものだ。

www.slideshare.net

詳細は資料を読んでもらえばと思うので、特に説明はしません。

他人のふんどし

自分はわりとスライドやブログでプロジェクトのアーキテクチャや技術的なポイントを説明する上記のような資料を書くのが好きなので「我々がやっているのはこういう目的意識で〜こういう構造を目指して〜ここが面白くて〜」みたいなことを自分の理解の整理も兼ねて書いている。

しかし、プログラマ人生も長くなってきたせいか自分は会社においてはすでに「やっていきプログラマ」としては引退気味である。

「チームのやっていき >>> 自分のやっていき」みたいな状況で資料をまとめたりしていると「こういう行いは他人のふんどしで自己のビジビリティを高めるズルい行いなのではないか〜」とか「自分自身のコンテンツやプロダクトを生み出して世に問いかけるべく精進すべきでは〜」みたいな謎の葛藤がしばしば発生する。

「チームのやっていきを言葉にする」というのはそういう葛藤の中でふと思いついたフレーズだけど、こう表現すると他人のふんどしよりは自分のやっていることをもう少し前向きに捉えられる。

やっていく人間の中にはすでにチームで十分共有された事を改めてまとめるよりも、よりやっていくことを大事にする人が多く、しばしば外部からは何をやっているかが全く見えないという状況が発生する。

だから自分はチームのやっていきを言葉にして、チームの姿が周囲からよくわかる助けになれば、それは自分が中心となって進めたことでなかったとしてもそれで良いことなんだろう。

いい話につなげようとしたけどなかなか難しいので、ただの日記でフィニッシュです。

「コンピュータシステムの理論と実装」をRustで。

良い本がある

コンピュータシステムの理論と実装という良い本がある。

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

nand と flip-flap だけが最初に与えられた素子として、それをもとに加算器やメモリを組み立ててコンピューターを組み立てる。 そして、そのコンピューター上で動くテトリスというコンピュータゲームを開発するというものだ。(実は最後はテトリスではないが)

本書の前書き曰く、ハードウェア、アーキテクチャオペレーティングシステムプログラミング言語コンパイラ、データ構造とアルゴリズム、ソフトウェアエンジニアリングという、コンピュータサイエンスの基礎をザックリ学べてしまうようなものだ。

プログラマ人生が終わる前に

プログラマという職で日々過ごしていると、同じ職種の人と学生時代にどのようなカリキュラムでコンピュータサイエンスを学んできたかという話になることがある。

こういう時、良い学校を出ている人などは「CPU作りました」とか「コンパイラ書きましたね」というような話がサラッと出てきてさすがに良い学校出ていると深いところまでやるのだなと感心する。少し前には、東大のCPU実験はすごいぞという話もありましたね。

そして自分はこのあたりの事を真面目に修めてないなという劣等感のようなものが頭をもたげてくる。

低レイヤの基礎というのは、実際のところ実務で使うシーンがある人はごく一部の人たちだけだ。我々は巨人の方の上に乗って生きている。自分は幸いにも(不幸にも)このレイヤに携わる必要はないのだけど、そういうのを全く知らないままプログラマとしての人生をいつか終えてしまうのだろうかと考えると、それはそれで損をしている気がしてくる。

プログラマ人生が終わる前にやっておきたい。

というわけでやっている

この本自体は通読するだけなら簡単。なるほど、こういう風にできているんだねと理解したふりが簡単にできてしまう。

けど実習をやらないとさっぱり身につかないので実習をやらないと意味がない。

しかし、そのまんま実装したって与えられた環境の上で実装することになってそんなに楽しくないので新しい言語の勉強がてら Rust で実装を始めることにした。

具体的には、以下のルールで始めた。

  • CPUを作るにあたって Rust の機能で使っていいのはlet、自作関数、struct、traitだけ。
    • if とか for とか配列操作に便利な関数とか使わない。
  • CPUは nand と flip-flap のみを積み上げて実装する。
    • メモリだけはさすがに速度的に厳しいのでネイティブ実装を使ってもいい。
  • テストやログ出力するときは Rust の機能を使ってもいい。
  • Screen と Keyboard といった入出力の実装は Rust のライブラリを使ってもいい。

実際のプリミティブは以下の2つで始めた。

// nand 関数
pub fn nand(a: bool, b: bool) -> bool {
    !(a && b)
}
// data flip-flop
pub struct Dff {
    pub pre_value: bool,
}

impl Dff {
    pub fn new(initial_state: bool) -> Dff {
        Dff {
            pre_value: initial_state,
        }
    }

    pub fn dff(&mut self, a: bool) -> bool {
        let result = self.pre_value;
        self.pre_value = a;
        result
    }
}

途中何度か中断が入ったが兎にも角にもnandとflip-flapだけでコンピュータの実装が終わった。

書籍でいうと5章までが終わったというところだ。

リポジトリはこんな感じ。 github.com

感想

実装してみてわかったのは「何もわからん」という事だ。

もちろん「なるほど、こういう風に動くのだな」とか「これを組み合わせてこういう動きを作っているのだな」という細部の動きは実装時にはわかる。

だけど「なぜこのCPUの仕様を考えつくことができたのか」みたいな大きなデザインについては自分が一から組み上げる想像は全くできなかった。

感じたのは「ノイマンという人はまさに天才なのだな」だったり「CPU設計者のジム・ケラーみたいな人が同時代に何人も出てくるはずがない訳だ」という事ばかりだった。

つまり「自分にはたどりつけない巨人の影が以前よりはっきり見えた」という事だ。

あと nand と flip-flap のみで実装する縛りだと全然 rust の勉強にならないという事に途中で気づいたのも辛かった。

先は長い

ともあれ、5章まででいわゆるハードウェアの世界は終わり。6章からはソフトウェアの世界になるので今までよりは想像できる領域を勉強できそうなので、プログラマ人生が終わる前に最後までやろうと思っている。

この本は積読や通読はしたけど実装はせずに終わってしまったりする人が多いタイプの本だと思うので、今後梅田方面でモクモク会などができればいいなと思っております。

その節はどうぞよろしくお願いいたします。

リモートワーク、ひとりでやるか、みんなでやるか

前置き

自分はいま週に3日の在宅勤務、2日はオフィスに出社という働き方を3年程度続けている。

このペース自体は3年間ほとんど変わっていないのだが、2018年1月からチームとしての仕事のやり方が大きく変わった。 具体的にはチームでモブプログラミングと1週間スプリントを始めた。

10年以上プログラマーとしてやってきているが、プログラマーとはこういう働き方をするものだっただろうかという新鮮な気持ちがある。

つまり、ブログタイトルのひとりでやるか、みんなでやるかとは、リモートワークをしながらその業務は個人で非同期的にやるのか、複数人で同期的にやるのかという意味である。

今までのやり方と今のやり方、どちらが良い悪いというものではないが、とにかく今自分はどう感じているかというメモを残しておくことにする。

所感

  • 3人よれば文殊の知恵という言葉は正しい。2人(ペア)と3人(モブ)ではかなり違う。
  • リモートメンバーを含めたモブプログラミングも難しくない。いまや普通にできる。
  • リモートメンバーと一緒にモブプログラミングをしようとすると全員がヘッドセットつけたリモートスタイルでやるのが一番いい。
  • リモートモブプログラミングは物理オフィスと在宅勤務の間での情報格差をかなり小さくできる。
  • とはいえ一つのオフィスに集ってやるのが最高効率という論はくつがえせない。ホワイトボードを用いた議論がネック。
  • 静かに一人で検討しながらコードを書くのが何よりも好き…という人にはそもそもモブプログラミング向かない。
  • リモートメンバーが自分一人ではこれ以上リモートで仕事の進め方の新しい知見は出なさそう。
    • 自分はリモートモブプログラミングで普通に仕事できてるが、ほかの人も同じようにできることなのかよくわからない。
    • だれかリモート枠でもう一人チームに入ってみてほしい。応募はコチラ

2017年の働き方

  • 2人チーム
    • 自分は大阪オフィス勤務で普段は在宅勤務。もう1人は東京。
  • 要件や設計は事前にほぼ決定している
  • ドメインは非常に狭い(単一システムの単機能の開発)
  • タスクは分担してそれぞれが分担して計画立てて実施
  • レビューは github のやり取りがメイン
  • 共有は毎日5分程度の昼会と週に一度マネージャーを交えて進捗共有ミーティング

良かったこと

時間の制約がほとんどなくて自由だったことが大きい。
共有したスケジュールに対して、自分のペースでもくもくと実装するだけなので気が乗る日はガンガン実装するし、気が乗らない日は勉強したり設計について気になっていることを洗い出して仕事した気になったりできる。

ひとりで静かに考えることができるので、精密に設計を検討できる。これはもしかしたら錯覚なのかもしれないが満足感がある。

子供がまだ小さいこともあり、突発的に仕事部屋からリビングに行ってあやすというのも気軽にできた。

悪かったこと

設計の共有やコードレビュー、知識共有が文字ベースになることが多かったため密なコミュニケーション取れずに間延びしたコミュニケーションがしばしば発生してしまっていた。

謎のハマりや見当違いの方針で1日無駄にしたりという事が時々発生した。

2018年の働き方

  • 10人程度のチーム
    • 自分は大阪オフィス勤務で普段は在宅勤務。あとはみんな東京。
  • 要件はほぼ決定しているがディティールは開発メンバーに委ねられている
  • ドメインの範囲は広く、知らない部分が多い(複数システム&アプリ~基盤レイヤ&AWS
  • タスクは一週間以下で終わる単位まで分解し、3~4人のスプリントチームを組んでやる
  • チームは設計・実装・テストといったすべての作業をモブプログラミング
    • 毎日、休憩挟んで5~6 時間程度
    • Slack でIDEの画面共有しながらヘッドセットで会話
  • 余った時間は小さなリファクタリングとか改善作業(余暇)

良かったこと

メンバーがさまざまなチームから引っ張ってきた製品横断の混成チームのため、全員ドメイン知識が異なっていたためモブプログラミングをすることで素早く知識共有ができる。

自分が乗ってる日でなくても、他メンバーがノリノリであればそれにあてられてやる気がでたりした。

モブプログラミングでやってるので誰かが休んでも、作業が止まらない。自分だけが抱えるタスクで心が重くなるという事がない。(個人的にはこれが一番大きい)

個人でプログラミングしてる時のようなつまらないハマりどころみたいなのは滅多に起きない。

レビュー時の待ち時間みたいなのがほとんど不要になる。

悪かったこと

時間を決めてタイムボックス内は集中してモブプログラミングをするため、その間は在宅勤務といえどもスッと席を外しづらい。

静かな環境で複雑な設計をするという事ができない(気がする)

大阪オフィスに出社する意義がなくなる(出社してもヘッドセットで東京オフィスの人と話し続けるので)

パターンマッチングマン

この正月休みにルービックキューブを久しぶりにやったら、終盤の手順を忘れていて完成目前で失敗してしまった。 何度かやって、急がずにやればふたたび3分程度で解けるレベルに戻ったようだ。

昔、友人に「ルービックキューブの解放なんてパターンを覚えるだけだよ?」と言われ「やり方を覚えても自分でその解法に辿りつけないなら、そのパターンを覚えてもその人の知力の証明にならない」と意地を張っていたことを思い出した。 結局、自分の力では全面揃えることなんてできなかったのにもかかわらず。

しかし去年何人かの優秀だと感じる知り合いがルービックキューブをスラスラと解くのを見た時に考えを変えることにした。

自分で考えている、考えていない、その解法の意味を理解している、理解していないは別にしてまずはできるようにならないと勝負にならない。 勝負できるような段階になってから、きちんと理解していけばいいのだろう。

序盤の手順については、理屈もわかって自分でアレンジしながら説くことができるようになってきたが、最終盤の手順はまだ「局面Aのときに手順Xを行うと解ける(ブロックがどう動いているかはよくわからない)」という状態のままだ。 それでも、「ルービックキューブぐらい3分もあればどこからでも全面揃えられるよ」とかいうと、その分野について何も知らない人は「へぇー」と反応してくれるので多少自尊心がくすぐられる。

勉強でもプログラミングでも何でも、ある一定のレベルまではパターンマッチングで到達できるという話があった気がするし、達人はそもそも右脳によるパターンマッチングで状況判断をしているという話もある。

実は最後まで自分で考えることって大事だと思っていたけど、そうではないのかもなという(自分にとって)斬新な価値観が頭をもたげる。

「この局面、実際にはどのような構造になっていてベスト解は何か…?」みたいなのは会議でもプログラミングでもルービックキューブでもよくある戸惑い。 自分はそこでいつも「詳細まで理解してないのに、自分自身の頭で考え抜いていないのに結論を出して動いてしまうのは不誠実ではないかな」と立ち止まってしまう。

けれど、自分が立ち止まっている間にも優秀な人は(きっといくつかの失敗もしながらも)パターンマッチを駆使しながら前進していっている。

今年は自分もパターンマッチングマンとして、深く考えずに、世間の定石に乗りながら、生きてもいいのかも。

と、特に理由もなく感じている。

マルチテナントアーキテクチャについて

SaaSシステムを開発しているみなさま、お元気でしょうか。

SaaSシステムというといわゆるWebサービスよりももう少しBtoBの雰囲気が漂ってまいります。

SaaSシステムでは契約者(ここではテナントと呼ぶ)が複数いて、テナント毎に複数のログインユーザーやロールが存在するのが一般的です。そして当然ながらテナント毎のデータは漏洩・混濁が許されない高いセキュリティが求められます。

SaaSシステムの構築はスケーラビリティにおいても100テナント程度から始まりゆくゆくは数千、数万テナントまで少なくとも線形にスケールするアーキテクチャを開発当初から求められ、さらに突発的な大規模テナントも問題なく吸収したいという要求があります。

その要求を満たす設計・開発・保守・運用をやっていくのは当然ながら簡単ではありません。

というわけで今日はマルチテナントアーキテクチャのお話です。

世に出る情報がとっても少ない

マルチテナントアーキテクチャはBtoB領域で必要とされることが多い構成のため、設計に関する概要からその詳細に至るまで各社あまり表に出したりしません。 情報を積極的に出している企業は海外ではSalesForce、国内ではサイボウズといったところでしょうか。

さくっと検索すると以下のような情報がてにはいります。

SalesForceのマルチテナントアーキテクチャ。老舗の実績あるアーキテクチャ

www.publickey1.jp

サイボウズクラウド基盤の解説。サービスセットと呼ばれる概念を導入している。

ascii.jp

手前味噌ですが私の過去記事でも、とあるマルチテナントサービスにおけるアーキテクチャの一例をうかがい知ることができるでしょう。

www.techscore.com

日本語で読めるマルチテナントアーキテクチャの分類やその中で考慮すべき項目を整理した記事として、IBM developerworks のこの記事は外せないでしょう。

Web アプリケーションをマルチテナント型 SaaS ソリューションに変換する

特にマルチテナントアーキテクチャの分離レベルとして以下の3つに分類できるというのは覚えておくとよいです。(以下引用)

  1. クラウド内での単純な仮想化により、ハードウェアのみを共有
  2. 1つのアプリケーションで、テナントごとに異なるデータベースを使用
  3. 1つのアプリケーションでデータベースを共有 (最も効率的な真のマルチテナンシー)

https://www.ibm.com/developerworks/jp/cloud/library/cl-multitenantsaas/figure0.gif

Salesforceは3のタイプを採用しています。

私の経験したマルチテナントアーキテクチャ

私自身も今まで数度のマルチテナントアーキテクチャによるシステムの構築や他企業のアーキテクチャの実装の調査をしてきましたが、追求していくとなかなかおもしろいものです。

私が経験したマルチテナントアーキテクチャはオーソドックスな2です。

顧客データを全て一つのDBに混ぜてしまうのはデータ混濁のリスクが怖いのでDBの機能・ユーザーでデータを分離してしまうパターンです。

また、世のOSSのWebフレームワーク、DBフレームワーク(含むORマッパ)はたいていシングルテナント前提に作られていますから、OSSの恩恵を受けつつマルチテナントを実現しようとするとだいたいこの選択になるかと思います。

Salesforceのマルチテナントアーキテクチャ

Salesforce複数テナントを1DBにストアするアーキテクチャを選択したというのは、アプリケーションレイヤでのテナントのデータ分離について非常に強力なフレームワークを自社で構築しているからこそ選択できたのではないかなぁと思います。 また、開発時期的にOracleで大量のスキーマを作るのは現実的ではなかったという時代背景があるのかも知れません。

アプリケーションレイヤでテナントのデータを分離すると、多数のテナントに少ないリソースでサービス提供できるとよく言われますね。

IBMの記事でも「最も効率的な真のマルチテナンシー」などと言われちゃってます。

開発時の考慮すべき点

さて、私がマルチテナントシステムの構築にあたって、こうすべきだなと思っていることについてつらつらと書いていきます。 ベースにしているマルチテナントアーキテクチャの実現手法は2(テナント毎にデータベースを分離)です。

テナント毎にドメインを分け、ログイン画面も分けよう

URLのドメインはテナント毎にできる限り分けるようにしましょう。

同一ドメインだとCookieに保存するセッションIDのキー名が同じだと他のテナントと混ざってはよくありません。 お客様は複数契約してくださって、テナントAとテナントBに同時にログインしたいと思うかもしれません。

ワイルドカード証明書で *.example.com を取っておいて tennant1.example.com, tennant2.example.com などとするのがよいでしょう。

特定テナントだけ全く別のドメインにしやすいとか、JavaScriptのセキュリティだとか、特定クライアントだけ別基盤に振るとか、IPアドレス制限とかそういうのもやりやすくなります。 一番メリットとして大きいのはDBを引かずともどのテナントからのアクセスか区別できるので障害調査やアプリレイヤのFWが作りやすくなる事でしょう。

歴史のあるSaaSシステムだと時期的にそういうことを考慮して構築できなかった場合もありますが、今から構築するなら分けたほうが苦労が少ないでしょう。

DBの接続先の切り替えはユーザーのリクエストを処理する最初の段階でフレームワークレベルで行う

タイトルが全てですね。

アプリケーションレイヤで開発するときに、自分がどのテナントのデータを処理しているか、どのDBに接続しているかを意識しないといけないとなると開発者のうっかりミスでデータの混濁が発生する可能性がとっても高くなります。

マルチテナントアーキテクチャの方式設計をする際は、慎重なアーキテクトで居るよう心がけましょう。開発者が意識しなくてもデータが混濁しないフレームワークを作りあげることがとても大切です。

また、フレームワーク設計時にはリクエスト完了時にはそのコネクションを忘れず close しておくのが安心です。 なぜかコネクション切り替えがうまく行かず、前回のリクエスト時に利用したコネクションを使いまわしたりすると困ったことになります。

コネクション確立コストの高いRDBMSではコネクションプールを用意しよう

PostgreSQLのことです。MySQLではそういうことを考えずとも十分コネクション確立コストが低いですが、PostgreSQLはコネクション確立コストが高いためコネクションプールを導入しないとアクセス数の多いサービスでは耐えられません。

しかしマルチテナント対応したコネクションプールというのはさほど多くありません。マルチテナント対応したコネクションプールとは、1テナント(スキーマ)あたり最大30コネクション、コネクションプール全体で500コネクションといった制限のかけられるコネクションプールの事です。

Javaではこのような要件に対してマッチするコネクションプール製品は残念ながらありません。 Javaのコネクションプーリング製品とは CommonsDBCP, HikariCP, TomcatCP, C3CP 等の事です。 私の調査では外部コネクションプール製品の pgPool2 などはこの要件を満たせず、最終的に pgbouncer がいいだろうという結論になりました。

複雑なクエリを発行する BtoB システムを作るなら PostgreSQL を使いたいところですが、コネクションプール周りは悩ましくなる事が増えるのでよく考えましょう。

テナントのデータを処理しているログにはテナントを特定できるキーが出るようにしよう

これは、まともに運用しようという意志があれば当然思いつくことですね。

テナント毎のコネクションを確立するのと同じタイミングでリクエストスコープの変数に入れてログ出力時に必ず出力されるようにしましょう。 JavaのLoggerであればMDCなどを用いて実現するのがよいでしょう。

ログインについてはSSOの仕組みをなるべく備えておこう

自社サービスとしてマルチテナントサービスを開発する際、余力があるのであればSSOの仕組みは最初から考慮して構築することにしましょう。

特にあなたの会社がマルチテナントサービスを複数提供する予定があるのであればこれはやっておいたほうが良いことです。

ある会社があなたのサービスAとサービスBを契約したいと考えた時、それぞれのログインID、パスワードを発行し、利用ユーザー情報を登録する手間を考えてみましょう。ゲンナリですね。

世にある様々な ActiveDirectory, LDAP, OpenID Connect, SAML などなどへの対応はサービスが成功してからでも構わないと思いますが、とにかくアカウント情報は一箇所にまとめるのがおすすめです。

このあたり、ヌーラボさんがサービスが複数の製品群を普及後にSSOのローンチに成功させましたが、良き知見・見解などがありそうで、一度そのあたりのお話を詳しくお聞きしてみたいものです。

クォータ・スロットリング

さて、マルチテナントの運用で起きる辛いことは「ある特定のテナントが多くのリソースを利用するために、他のテナントが割を食う」ということです。

で、公平性をなるべく高めようとするとクォータ・スロットリングなどの仕組みをマルチテナントシステムに組み込みたいという解決策に惹かれることになります。

しかし、実際のところ IaaS などのような計算資源を直接提供するならいざ知らず、SaaSでそのような制限を加えるのは実際ツラミが多い&知見少ないので、テナント毎のリソース使用を計測だけできるようにして最初は運用サポートでもいいんじゃないかと思っています。

SalesForce はそこのところはちゃんとガバナ制限という形で用意されており、さすがは老舗だなぁという感はありますね。

疲れてきたので今日はここまで

この記事は本来GWに描き上げる予定だったのですが、書き終わらなかったので放置されていたものです。

先日の勉強会で「マルチテナントアーキテクチャ、最高に面白いのでみんなと話したい」と言ったのものの、面白さが伝わってないとつまらないので取り急ぎでも公開してしまおうということで公開することにしました。

他にも以下のような事を紹介したいと思っていましたが、これは今後「マルチテナントアーキテクチャ大好き」な仲間ができてからということにしようと思います。

  • 過負荷時の時に特定ドメインのリクエストだけ別サーバーにふれるようにしておこう
  • DBのマイグレーションはテナント数だけかかるので作業時間の見積もり困難。無停止でリリースするならなるべく今あるテーブルに影響を及ぼさないよう別テーブルにしよう。
  • テナントのデータはDBインスタンスへの依存を減らし、特定テナントだけ別DBインスタンスに移動することも可能にしておこう(大規模顧客対応)
  • 課金対象項目の計測の仕組みを考えておく
  • 休止、解約、メンテナンス、縮退など正常稼働時以外の状態を設計する

みんなで最高のマルチテナントアーキテクチャの知見を共有し、障害のないSaaS型業務システムが世を席巻する世界を目指そう!