認証と認可と課金とコアドメインを分離したシステムは勝てるという話

自分が複数のシステムの開発を経験して得た確信として、「認証と認可と課金とコアドメインの分離がめちゃくちゃ重要である」というものがあるので、コレを整理してアウトプットしていく

分離するモチベーションとは

Microservice文脈でいうと、デプロイ独立性だったり、リソースの最適配分だったり、障害の局所化だったり、開発組織とのマッピングだったりがメリットとして語られることが多い。

だが、ここで取り上げたいのは戦術的DDD的観点でのコンテキスト分離の有用性である。

※ちなみにコンテキスト分離のみであればモジュラモノリスだけで実現可能。

戦術的DDD的観点での関心事の分離によるメリットとは

コンテキストが分離されていることによって、境界をまたぐ際に「このI/Fは正しいのか?」を都度考えることを強制することができる。 境界がなければ意図しない密結合を生みやすくなってしまう。

もちろん、境界を超えるためのボイラープレートとトレードオフであるが、ドメインをシンプルに保つためのボイラープレートは、常識的範疇の量であればドメインがシンプルになる効果の方が大きすぎるので、開発速度や保守観点、開発する機能のクォリティに寄与し、十分ペイすると考えている。

処理を書く際に境界を意識し、都度「この境界は正しいのか」「境界またぐ時にここだけ異常にめんどうじゃね?」と考える機会はとても重要で、明確な境界がなければ意図せず不必要な密結合を生み出してしまいがちなところを強制的に考えさせる機会を設けることで、ある種モデルの洗練に強制力をもたせることができる。

(最近自分が自社で実装した)AuthzIOのPermissionReason(権限付与理由)などは「境界またぐ時、ここだけ異常にめんどうじゃね?」から生まれたものだったりする。(例がわかりにくいかもだが、情報の正引き,逆引き両方を効率良く行えるデータ構造を考える機会になった。)

ただ、この部分は定点観測して定量的に差が出せるものでもないので、「メリット」として捉えるのに時間がかかる部分かもしれない。

個人的には1サービス内のHttpAdapter,SecondaryAdapter,UseCase,Domain等の横の境界の有用性と同じくらい有用で、一定の複雑度を持つことがわかっているシステムであれば最初から切るべき境界だと確信を持っているし、モデルの正しさ、汎用性、使い勝手の良さで勝負するシステムではなおさら重要である。(なので縦のドメイン境界を切るモジュラモノリスやマイクロサービスをやっている)

認証と認可と課金とコアドメインの分離は何が良いのか

では、何を分離するのか。という話になるが、何を分離するのかはむずい(おいおい)

ただ、その中でも認証、認可、課金とコアドメインってのはなんの疑いもなく1000%分離すべきものである。

コアドメインの中をどう分離するのかはやりながら考えるしかないが、こいつらはとりあえず分離しておけばサービス成長に伴う成長痛を最低限に抑えることができる。

サービス成長に伴う成長痛とは

急速な機能開発によりシステムに歪な状態が埋め込まれ、それを解消するためにアーキテクチャ変更やリファクタリング、データ移行等が必要になるような状況を指す。

新規機能開発の一時停止や停止メンテを厭わなければ後は馬力でなんとかなるではあるが、相当の馬力を要するのでその馬力はもっと有用な、素敵な機能の開発などに回したいし、ここに馬力を発揮するのはきつい。

当たり前の状態にするための馬力と素敵機能を開発するための馬力は後者のほうが心にも優しい。

成長痛を最低限に抑えられる理由

小規模な時にばーっと書いちゃって密結合になりやすい筆頭の関心事が認証、認可、課金とコアドメインで、分離するのにメチャクチャな労力を要する。(分離なんてきれいな言葉ではなく、もはや引きちぎるに等しい。)

ここをスキップさえしてしまえば後はコアドメインの洗練や分離に注力できるし、ここの分離さえできていれば、かなりの要件に迅速かつ柔軟に対応ができるので無駄な痛みを抑えられると考えている。

開発を止めるレベルでのリファクタリングや停止メンテを必要とする変更は、自分の経験上はすべて認証、認可、課金周りだった。

これらはコード上だけでは済まない外部要因が絡むため、整合性を保つためにかなりの労力を要する。

  • 課金ならPaymentGateway側のデータと自DBに持つデータの整合性
  • 認証ならフロントとJWTのやり取りだったり(全員強制ログアウトさせてOKなら何も考えなくてOKだが・・・)、FirebaseのようなIDaaS
  • 認可なら認証,課金,コアドメインに食い込みまくってる

等など。

引きちぎると形容したように、整合性のために完全には分離できないことが多い。 引きちぎり元に残った患部とはシステムの寿命が続く限りメンテし続けるか、またまた停止メンテを行い連携先と自分らのデータに破壊的な変更を加えて整合性を保つかだが、後者の判断を下すにはかなりの合理性がないと労力に見合わないので大抵はメンテし続けることになる。

コアドメインであれば、データは自分たちのDBに入っているし、入るまでの経路も全て自分たちでハンドリングしている。 変更を加えるのは比較的現実的である。

また、サービス成長に伴ってお金に余裕がでてきたときにマーケティング系の施策をうつことが多くなる。 その時点で分離してないと「そんな要件みたせねーよ」といった機能を求められる。(キャンペーンうつぞ!最初から課金してくれてる人に感謝のほげほげ!等等)

Dropboxの「最初期課金ユーザー永遠無料」みたいな施策は、認可、課金が分離してない状態で実現するのは地獄。

※課金のデータを日付で漁って最初期課金ユーザーだと判定できれば●●を許すみたいなコードが各所に散るか、最初期ユーザーリストみたいなのをもって毎回参照する等。キャンペーンの種類が増えるたびにユーザーリストを作るのは現実的ではない。

分離されていれば「最初期課金マン」みたいな権限用意して権限チェックのときに考慮するだけで済む。

施策を最適なタイミングで最速でうてるのはかなり強いと思う。

まとめ

システムにおける表現力は、あっちを立たせればこっちが立たずみたいなバランスが存在していて、ある要件Aに沿って実装しすぎると後から出てきた要件Bへの対応が難しくなる。みたいな力学がある。コンテキスト分離はこのバランスを保つためにとても重要で、バランスを保つことでシステムの表現力を失わずに済む。

早い段階で取り組むことにより成長痛を抑えつつ、分離による表現力を高めることで今後起こり得る要件に柔軟に対応しつつ、痛みなく爆速で成熟していけるので結果勝てると思う。という話でした。

2020年抱負

7億年ぶりに書く

2019年は所属している会社で主に以下の仕事をしていた。

  • 一部機能へCQRS+ESの導入
  • データ分析基盤導入(Spark on EMR)
  • Effの促進
  • 権限,課金周りの整理
  • Twilio通話機能の整備
  • 新規アプリリリースのサポート等

2017年にマイクロサービス化し、2018年に個人的に現時点でなし得る最強の構造化設計としていたEffの導入ときて、2019年は2020年につなぐ足場作りをしてきた印象。
出島戦略的に置かれていたSREチームがSREグループとして独立もできた。良い年になった。

2020年はエンジニアがHowを完全に主導するだけでなく、What,Whyも考えていけたら強い組織として成熟していきそう。
ミニCEOと呼べる強いPMが数人いるので負荷なくやっていけるはず。
自分は権限,課金周りを進めつつ、音声認識ゲーミフィケーション系の取り組みに首突っ込んで行きたい。


創業時からお手伝いさせていただいている副業先では以下の部分をやっていた。

  • 設計
  • Effの促進
  • データ分析基盤導入(Spark on EMR)
  • 細かい機能開発

2020年は決済周り実装、今はどこも解決できていない価値が提供できる機能開発あたりをやっていきたい。


めずらしく2件も登壇したが、2020年はどうするか未定。

speakerdeck.com

speakerdeck.com

2019年は会社バスケ活動がかなり活発でたくさん参加できた。ユニフォームも揃ったし来年は更に楽しめそう。

一方あまり読書はできなかった。増やしていきたい

  • 1兆ドルコーチ
  • ソフトウェアファースト
  • サイロエフェクト
  • メルカリ ~稀代のスタートアップ、野心と焦りと挑戦の5年間~
  • ソードアートオンライン ~ムーンクレイドル~
  • 2030 世界地図帳
  • NEW POWER
  • エッセンシャル
  • Eirbnb Story

SAOはWEB小説でアリシゼーションまで読んで完結したと思っていたので、続きが出てるのを知ってテンション上がった

漫画はめっちゃ読んでる。へうげものを1巻から読んでドハマりしたので2019のベストヒットはへうげもの

2020年もやっていき

vagrant sahara先輩

簡単にスナップショットをとってロールバックしたりできる
sahara pluginのインストールがクソ楽になっていた。

$ vagrant -v
Vagrant version 1.2.7

$ vagrant plugin install sahara

done. successful……

まじかよ!
1.2.2の時、会社の隣の席でインストールしていた人が死ぬほど苦労していたのでテンション上がった

インストール後はこんな感じ↓↓で使用

Sandboxモード有効(スナップショット作成)
$ vagrant sandbox on
Sandboxモードを有効にした状態まで戻す
$ vagrant sandbox rollback
状態変更を確定(スナップショットの更新)
$ vagrant sandbox commit
Sandboxモード解除(スナップショット削除
$ vagrant sandobox off

Chefのレシピテストで非常に有用。

ハマったメモ

chef-soloのノリでattributeを最上位に書いてたら全く認識されなくてハマった・・
chef-serverでnodeにattributeを指定するときは'nomal'以下に記述する必要があった

"normal": {
"tags": [

],
"network_eth0": {
"HWADDR": "00:0c:29:9f:ea:47",
"ONBOOT": "yes",
"IPADDR": "192.168.53.103",
"PREFIX": "24",
"GATEWAY": "192.168.53.1"
}
}

Chef-solo導入〜簡単な設定まで

mac再インストールのついでに最近勉強中のchef-soloで状態を管理することにした。

※Webで資料を探してみましたが、Macの状態管理に触れている記事が少ないので参考になれば。**結局マニュアル最強ですね^^**

用語

chefは独自の用語が多い。

  • Knife:リポジトリ操作用のコマンドセット。(cookbookの生成等
  • Recipe:コード化された手順書、サーバーの状態
  • Cookbook:特定のレシピに必要なデータやファイルを纏めたもの
  • kitchen:Cookbookを含むChefの実行に必要な一連のファイルを纏めたもの
    • Kitchen > Cookbook > Recipeの階層で管理される。
  • Attribute:Recipeで使用する変数をまとめたもの
  • File:Recipeで作成するファイル。設定ファイルなんかを作成できる。
  • Library:Recipe上から実行するスクリプト。
  • Template:Attributeを埋め込んで内容を編集する前提の設定ファイルのテンプレート。
  • Fileとは内容を動的に変更する点が違うっぽい
  • Package:パッケージの状態を記述する。
  • Provider:パッケージの扱い方などOS依存な部分の抽象化を行う。
  • Metadata:Cookbookの名称、含まれるRecipeの情報、Cookbook同士の依存関係などを記述したRubyまたはJSONファイル

Resource

Chefのレシピ記述に使用するDSL。
構文とかは基本的にはRubyだけど、
Chef的な処理(インストールやら、設定ファイルのテンプレ指定やら)を定義する
Resourceと呼ばれる関数。

結構あるけどココにまとめられている。
http://docs.opscode.com/resource.html

よく使うのはこのへん
log:Chefのログを操作する
package:パッケージの状態を記述する
service サービスを操作する(start,restart,reload,stop等
template:設定ファイルをChefでいじる


chef&knifeインストール

$ curl -L http://www.opscode.com/chef/install.sh | sudo bash

knifeの設定

全部デフォでエンター連打

$ knife configure
※knife.rbの場所は覚えておく

knife-soloインストール

$ gem install knife-solo

Kitchen(リポジトリ)生成

Cookbookの入れ物であるKitchenを生成します。

$ cd ~/Dev/Chef
$ knife solo init mac
$ cd ./mac

※knife soloはリモートにレシピを反映させるための拡張なのですが、
 kitchenの生成もできます。なんだかややこしい。knifeデフォルトできたほうがスマートなんじゃ・・・

mac用のcookbookをopscodeからダウンロード

対象がLinuxだとここまででさくっと動くのですが
macはデフォルトではmacportsを使ってインストールしようとしますし、dmgのインストールにも一手間必要。

その辺をサクッとやってくれるcookbookがopscodeに上がっているのであやかります。

$ vim Gemfile
+ source 'https://rubygems.org'
+ gem 'librarian-chef'

$ bundle --path cookbooks

※ちなみにcookbooksには流用するcookbookを置いてます。自作はsite-cookbooksへ。
なお、cookbookはknife.rb(リモート用)とかsolo.rb(localhost用)に以下のようにカンマ区切りで書くと書いたパスを読み込んでくれます。
今回はkitchen配下にsolo.rbを作ります。(リモート反映時も使うのでknife.rbもついでにやっててもいい。

$ vim solo.rb
cookbook_path ["cookbooks", "site-cookbooks","vendor/site-cookbooks"]
※knife.rbの配置場所はknife configureで設定した箇所。
$ vim Cheffile
+ site 'http://community.opscode.com/api/v1'
+ cookbook 'mac_os_x'
+ cookbook 'dmg'
+ cookbook 'homebrew'
+ cookbook 'zip_app'
$ librarian-chef install 

これでmac用のcookbook4つが入る

Cookbook作成

dmg

$ knife cookbook create vagrant -o site-cookbooks
$ vim site-cookbooks/vagrant/recipes/default.rb
+ dmg_package "vagrant" do
+    volumes_dir "Vagrant"
+    source http://files.vagrantup.com/packages/7ec0ee1d00a916f80b109a298bab08e391945243/Vagrant-1.2.7.dmg"
+   type "pkg"
+   package_id "com.vagrant.vagrant"
+   action :install
+ end
zsh(brewインストール=>gitで設定をclone=>linkファイル生成)
$ vim site-cookbooks/zsh/recipe/default.rb
+ package "zsh" do
+   action :install
+ end
# gitでoh-my-zshも落としてくる
+ git "#{ENV['HOME']}/.oh-my-zsh" do
+  repository "git://github.com/robbyrussell/oh-my-zsh.git"
+  reference "master"
+  action :sync
+  user "wing"
+  group "staff"
+end

#  gitにあげているzshrcを落としてくる
+git "#{ENV['HOME']}/personal" do
+ repository "https://github.com/ma2k8/personal"
+  reference "master"
+  action :sync
+  user "wing"
+  group "staff"
+end

# リンクはりはり
+ link "#{ENV['HOME']}/personal/mac/zsh/.zshenv" do
+   to "#{ENV['HOME']}/"
+ end
ミドル系の起動までやるなら

brewインストールのレシピ書いて
(start|stop|restart)_command でサービス制御のコマンドをデフォルトから上書きしちゃえばいけた。
(Chef側ではmacのサービス起動コマンドを見つけられないみたい

※vagrant使うまでは僕もmacのローカルに開発環境立ててvhostをgusmaskで切り替えて頑張ってたんだけど
 vagrantでやったほうが間違いなく捗るからmac上にミドル立てるのはおすすめしない。

run_list作成
$ vim nodes/localhost.json
{
  "user": {
    "name": "wing",
    "group": "staff",
    "home": "/Users/wing",
    "dotfiles_repo": "https://github.com/ma2k8/dotfiles.git"
  },
  "homebrew": {
    "run_as": "wing"
  },
  "run_list": [
    "recipe[mac_os_x]",
    "recipe[dmg]",
    "recipe[homebrew]",
    "recipe[zsh]",
    "recipe[vagrant]"
  ]
}
設定反映
$ sudo chef-solo -c solo.rb -j nodes/localhost.json

次はvagrant
その次はchef-serverまで。。。ブログ書き慣れてないからとまとめるの大変(ってかまとまってない

ruby2.0.0インストール

$ brew install readline openssl rbenv ruby-build
$ brew link readline openssl --force
 
$ echo 'eval "$(rbenv init - zsh)"' >> ~/.zsh_profile

$ source ~/.zsh_profile

homebrewから証明書を取得
※ruby2.0をhomebrewから入れたopensslを使ってコンパイルするとssl証明書エラーになるので

$ brew install curl-ca-bundle
$ brew list curl-ca-bundle
/usr/local/Cellar/curl-ca-bundle/1.87/share/ca-bundle.crt
$ cp /usr/local/Cellar/curl-ca-bundle/1.87/share/ca-bundle.crt /usr/local/etc/openssl/cert.pem

ruby インストール

# 最新版を確認
$ rbenv install -l

# インストール
CONFIGURE_OPTS="--with-openssl-dir=`brew --prefix openssl` --with-readline-dir=`brew --prefix readline`" rbenv install 2.0.0-p247

# デフォルトのrubyではなく、インストールしたrubyを使用するよう設定
$ rbenv rehash
$ rbenv global 2.0.0-p247

# バージョン確認
$ rbenv version
2.0.0-p247 (set by /Users/wing/.rbenv/version)
$ ruby -v
ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.4.0]

# 最後にrehashの自動化
$ gem i rbenv-rehash

homebrewインストール

MacOSを久しぶりにクリーンインストールしたので、ついでに諸々の手順を残しとく

前提:XCodeでCommand Line Toolをインストール

$ ruby -e "$(curl -fsSLk https://gist.github.com/raw/323731/install_homebrew.rb)"

$ brew doctor
Your system is raring to brew.
 
$ brew -v
Homebrew 0.9.4