エンジニア的なネタを毎週書くブログ

東京でWebサービスの開発をしています 【英語版やってみました】http://taichiw-e.hatenablog.com/

更新系のAPIって難しい1 部分更新のはなし

勉強不足なのは承知のうえで、実務上困ってることとか考えていることを今日は書いてみたいと思います。

 

例えば以下の様なデータを取り扱うREST APIをつくろうとしたとします。

  • あるECサイトにおいて、商品の料金を取り扱うAPI
  • 商品は既にDBに登録されていて、料金のみこのAPIで扱う
  • 1商品あたり、料金は2種類ある。通常料金と、お得意様用料金。
  • 2種類の料金は、以下の組み合わせが許容される

    f:id:taichiw:20140705125422j:plain

初期投入の時は、こういうデータをPOSTすると思います。(Item IDはURIに入れるほうが正しいかも)

{ "itemID": 123,
  "rate":[
      {"rateType":"通常料金", "value":"1000"},
      {"rateType":"お得意様用料金", "value":"800"} 
  ]
}

通常料金のみも可能なので、こういうリクエストもありです。

{ "itemID": 123,
  "rate":[
      {"rateType":"通常料金", "value":"1000"}
  ]
}

お得意様料金のみはNGというルールがあるので、このようなリクエストは許容されず、エラーになります。

{ "itemID": 123,
  "rate":[
      {"rateType":"お得意様用料金", "value":"800"} 
  ]
}

新規登録はとてもシンプルですね。
一方、困るのが、更新の時のRequest設計です。

全部送ってこいやモデル

APIの実装者として一番簡単なのは、「部分更新は認めない」「全部送ってこい」です。
元のデータがこういう状態で

{ "itemID": 123,
  "rate":[
      {"rateType":"通常料金", "value":"1000"},
      {"rateType":"お得意様用料金", "value":"800"} 
  ]
}

こういうリクエストが来たら…

{ "itemID": 123,
  "rate":[
      {"rateType":"お得意様用料金", "value":"800"}
  ]
}

通常料金は、「設定なし」に変更される という考え方です。

ですので、既存のデータがどうであれ、このリクエストはNGです。
なぜならこのリクエストを実行すると、お得意様用料金のみ存在し、通常料金が存在しない という、ルール上許容されていない状態になるからです

このような、全更新モデルですと、APIの実装はシンプルになります。
予想される更新結果が全てリクエストに含まれているので、DB上の既存の値がどうあれ、このリクエストに対してルールのチェックをすればいいからです。

一方で、APIの使い手であるクライアントのアプリケーションは不便を強いられます。
くアライアントの実装者は、
「通常料金だけ更新する画面」のを作りたいかもしれません。
そのような場合、クライアントは一旦Get系のAPIで通常料金/お得意様料金双方を取得した上で、通常料金だけ書き換えて、通常料金とお得意様料金双方をAPI送る必要があります。

部分更新モデル

だったら、rateオブジェクトは送られてきたものだけ更新する
というルールにしたらうまく行きそうな気がします。

こういうリクエストが来たら、

{ "itemID": 123,
  "rate":[
      {"rateType":"通常料金", "value":"1000"}
  ]
}

 お得意様用料金は何もせずに、通常料金だけとにかく1000に更新する。
これなら、クライアントは使い勝手がいいです。

ではこのようなリクエストが来た場合どうするでしょう?

{ "itemID": 123,
  "rate":[
      {"rateType":"お得意様用料金", "value":"800"}
  ]
}

 この場合少し複雑で、まず、DB上の現在のデータを調べ、通常料金が存在するかしないかを確認します。
通常料金が存在していれば、問題ありません。
一方、通常料金が存在していない場合(つまり、もともとどっちの料金も入っていなかったケース)、このままではお得意様用料金のみ登録されてしまうので、エラーにしないといけません。

同じような話ですがもう一例。

{ "itemID": 123,
  "rate":[
      {"rateType":"通常料金", "value":null}
  ]
}

 あまり正しくないかもしれませんが、valueにnullがあった場合、その料金を空にするというルールにしたとします。
このようなリクエストが来た場合、やはり既存のデータによって、このリクエストが成功するか失敗するか決まります。

  • 既存のデータが通常料金のみの場合 → 成功。料金が何も登録されていない状態になる
  • 既存のデータが通常料金・お得意様用料金双方ある場合 → エラー。通常料金のみ消すのはルール違反なので、更新されない。

つまりいいたいのは

このように、APIで取り扱う対象の中にルールが存在した場合に、

  • 全部送ってこいやモデル
    →リクエストに対してチェックを行えばいいのでシンプルな実装になる。
     反面、クライアントは使いづらい。
    (更にクライアントとサーバ間で無駄な通信が発生することになる)
  • 部分更新モデル
    →クライアントの実装はシンプル。
     反面、APIの実装は複雑になり、バグの温床にもつながる
    (今まで触れていませんでしたが、実際に、この手の複雑さが引き起こしているバグを見つけたことがあります)

という問題がありまして、
私が、更新系のAPIって難しいなぁと思っている理由の一つになっています。

…といいつつ、実は自分の中では、これがベターかな?という
解決方法が見えているので、次回はそれについて触れたいと思います。

 

※古く、かつ実装面に関しては言及されていませんが、IFがどのようにあるべきかについて議論された記事を見つけました。
http://www.infoq.com/jp/news/2010/11/rest-partial-update

私がかつてTDDに対して誤解していた点

半年前のTDD Boot Campでの気付きだけど、改めて。
※これを読んで、「いや、相変わらず誤解してるんですけどww」 と思われた方は是非ご指摘いただけると幸いです…。


1. 先にテストを書けばTDDなんでしょ?
⇒リファクタこそがTDDの命!
 Red -> Green はまだ入り口。そのあといかにRefactorしていくかが大事。

f:id:taichiw:20130727105010j:plain



2. 最初に全ケース網羅するテストを書かなきゃなんでしょ?
⇒TDDで書くテストコードは設計のための作業なので、必ずしもそうじゃないと思う。
 ただし、それとは別に、「変更に対してコードを守る」ためのテストは整備したいので、
 後でもいいから最終的にはケースは網羅してほしい。

Gitでローカルブランチを一斉に削除する with xargs

知らないうちに溜まってる、ローカルリポジトリ上のブランチのお掃除方法。こちらを参照。xargsというものを知らなかった私…。

 

マージ済みのブランチを削除

git branch --merged | grep -v '*' | xargs git branch -d

マージ関係なく、問答無用で削除

git branch | grep -v '*' | xargs git branch -D

 

Regional Scrum Gathering® Tokyo 2014 2日目基調講演 by Jutta Eckstein氏 #sgt2014

二日目の基調講演は、Jutta Eckstein(ユッタ・エクスタイン)さんによる、「組織にアジリティを取り入れる – どうすればアジャイルになれる?」でした。

「アジリティを取り入れる」というタイトルではありましたが、アジャイルはもちろん、組織内に何か新しい事を取り入れる際に、どのようにしたらいいか、というお話で、非常に参考になりました。

私は現在、チーム内でのTDDの普及率を高めたいと考えており、そのためのアプローチとして、非常に参考になるお話が聞けました。

「変化」があるときの人の心の動き

何かが変化する際、どのような心理的変化が人にあるか、ということについて、著名な2つのモデルが有るそうです。
一つが、Elisabeth Kübler-Rossによるもので、もうひとつが、Virginia Satirによるものとのことです。

どちらも、パワーポイント2010(多分、「リボン」のことだと思う)を例に説明されていました。
※この辺までは英語で聞いてたんだけど、「難しくなってきた」&「これは聞き逃しちゃいけない話だ」と判断したので、このへんで諦めて、翻訳機装着。

Elisabeth Kübler-Rossのモデル

  1. 新しいパワポが発売される。
    「何このUI、使いたくねー。なるべく古いバージョンで粘ってやる」
  2. 「仕方ない、そろそろ最新の使うか…」
  3.  慣れたのでなんとも思わなくなる。

Virginia Satirのモデル

  1. 快適な状態。「パワポ2003使いやすくて好き!」
  2. カオス。2010を使ってみる。「新しいUI、使いやすくていい!」という時と、「なんでこんな使いにくいUIにしたんや! 元の出せ!」という時がある。
  3. 「あー ここを変えたのはこういう意図が… 確かに使いやすい。」と腑に落ちる
  4. Integration & Practice. 新しいバージョンを練習して修得する。

大事なことは、誰かが組織に新しい手法(アジャイルとかね!)を持ち込もうとした場合、他の人にはこうした変化が起こっていることを理解することだそうです。
カオス期にいるような人も中にはいるので、きちんとそれをわかりましょうと。

さらに、こうした変化は一度に一つだけ起こるわけではなく、社内に同時に何本も走っているもので、周囲の人達はそういった変化を、同時にいくつも受け入れているのだということでした。

変化を導入する方法

そういうわけで、「変化をもたらす」ことはとても大変なのですが、
効果的なアプローチ手順が紹介されていました。

  1. Preparation (今から導入したいことを説明する)
  2. Retrospective (これまでの自分たちを振り返る)
  3. Readiness/Enabling Workshop
  4. (Customized) Training
  5. Monitoring / Coaching
  6. Sustaining Change
  7. Leassons Learned

2. と 3. は、チームレベルならこの順、部門レベルくらいだと逆のほうがいいかも…?とのことでした。

この中でも特に、おお!と思ったものをご紹介させていただきます。

Readiness/Enabling Workshop

「今から導入しようとしているもの」を以下の4つに分類するそうです。

  1. 既に行われていること
    名前が違うだけで導入するまでもなくやっていることがあるかも。
    例えばScrumを始めようとするときに「デイリースクラム」って名前ではやってないけど、オレ達もともと朝礼はやってるね。という感じ。
  2. 導入が簡単そうなこと
  3. 導入が難しそうなこと
  4. 会社の文化的に、どうしても導入できないこと

また、3, 4については、どうして導入できないのか、何が障害なのかということを洗い出すことも大事だそうです。

私達のプロセスにTDDを導入する場合であれば、「既に行われていること」は、「テストコードを書くこと」になりそうです。
(順番はおいておいて、テストコードを書くこと自体はされているので)

(Customized) Training

実際に新しいことを導入することが決まったあとは、練習することになります。
ただしここで陥りやすい点として、練習用の課題を設定して、それで練習すること。

それでは実際に仕事で導入しようとした時に、「あれ、練習と違う」「この場合はどうやるんだろう?」となってしまうとのこと。
そうではなく、実際の課題に合わせた、「Customized」されたTrainingを実施する必要があるとのことでした。

3つの役割

順番が前後しますが、新しいことを導入するにあたって、3人の協力が不可欠、という話がありました。

  • Passinate change agent
    → 変えようというパッションを持っている人。この人がいなくては始まらない。
  • Project Leader
    → プロジェクトのリーダー。かつ、管理職とも関係が築ける人。
  • Architect / Technical leader
    → チームのプロダクト、技術に最も詳しい人。アジャイル(導入しようとしているもの)に詳しい必要は必ずしもない。

なるほど、と思う反面、この3つ全部、ないしは2つを誰か一人が兼任してしまうことが、実際の現場ではあるのではないかと感じました。
テクニカルリードが新しい手法を持ってきたりとか。
そこで、そういった場合、一人に負担がかかるけどどうすればいい? という質問をさせてもらいました。

すると、
「上記の意図は、普通はこういったロールの人(プロジェクトリーダやアーキテクト)が組織内にいるものなので、その人達をどう巻き込んでいくかが重要である、ということ。」

「一方、一人が兼任しなければならないケースの場合、はじめはそれでもいいと思う。まずはやってみることが大切。
 ただし、仲間はいた方がいい。」

「ちなみに、時間がないからできない!と言ってる人は、何かに余計な時間を使ってるはず。
 例えばテストコードを書かずにコーディングしてるからデバッグに時間がかかってません?
そういうのを見つけてあげましょう」

という回答を頂きました。

セッションが終わってからもう一度思い返してみると、自分のチームでは、上司も、技術力がある人も、非常に協力的で(そして自分じゃなくて)、だからこそうまくいってるんだなぁ、ありがたいことだなぁ と思いました。

 

ということで、なにか新しいことを導入するときのアプローチの、具体的なヒントを貰うことができました。
是非トライしてみたいと思います。

 

English ver.

Regional Scrum Gathering® Tokyo 2014 「プログラマのためのScrum」 #sgt2014

本セッションでは、日本で唯一(!)の認定スクラムデベロッパー(Certification Scrum Developer)の資格をもつ、土肥さんから、認定スクラムデベロッパーの研修の紹介や、資格を取った後、CSDとしての活動について紹介がありました。
(なお、土肥さんは認定スクラムマスターもお持ちとのことです)

認定スクラムデベロッパ研修

認定スクラムマスタや認定プロダクトオーナーと違い、CSDの研修は実施が少ないそうで、アジアではまだシンガポールでしか行われていないそうです。そこで土肥さんは、シンガポールで一週間の研修を受け、CSDを取得したそうです。

研修では実際に仮想のプロジェクトに入っての開発を行う傍ら、講義の時間もあったそうです。講義では特に、ATDD、TDD、モックの使い方、リファクタリング、設計などに時間が割かれたそうです。
ここから、スクラムガイドで示されている方法で開発を進めるためには、テストファースト、自動テストが重要であることが伺えます。

また、研修会場の天井には巨大なモニターがあり、これが常にCIの状態が表示されていたそうです。

具体的には、最後にコミットされてからの経過時間が表示されていて、コミット間隔があくと、講師から、「何時間もコミットしてないの?」と突込みがきたそうです。さらにこのモニター、テストが通らないコードをコミットしてしまうと、真っ赤な画面になり、Warning!の表示とともに音が延々となり続けたそうです。
それだけ、テストが通らない、ということは非常事態なんですね。

プログラマにとってのScrum

もともとこのセッションに参加した私のモチベーションが、
「俺はScrumがいいと思ってるので自分のチームでもみんなを巻き込んでるんだけど、実際どう思われてるんだろ?」
ということに対するひとつの回答が見つかりそうだからでした。
(もちろんメンバと対話するのが一番なんですが、ほかの意見も聞いてみたかったので)

土肥さんの経験では、ScrumによってプログラマがHappyになれる点として

  • 裁量が増える
    (設計などの提案をする機会が増える、とのこと)
  • 成長を促す
    (必要なスキルが多くある)
  • チーム開発
    (「みんなで」同じ目標に向かってがんばる)
  • TDDのサイクルに乗れる
    (TDDって書いたコードがすぐ動かせるし、プログラマは好きなはず!)

といった点があるとのことでした。
一見、Scrumでなくてもできそうな物たちですが、スクラムの肝である「群がる」とか「透明性(タスクボードによる可視化とか)」から生まれる、チーム開発の楽しさは、私もScrumならではだとおもいます。

また、CSDの研修でも重視されていたように、テストの自動化、テストファーストの開発は、できるとScrum開発が非常にやりやすくなりますので、こういったスキルの必然性が高まるのもなるほどと思いました。

 

TDDやATDDなどのプラクティスは簡単に習得できるものではないですし(私も人に教えられるほどできていないのが現状です)、できなくても「プログラミング」はできるので、「何でこんなことをしなければならないんだ?」と、開発チームのメンバからは思われてしまうのかもしれません。

しかし、こういったスキルを全員が身につけたうえで、チームで「群がる」Scrum開発ができるようになれば、開発チームとして一段上の開発ができそうだと、私も一エンジニアとして改めて感じました。

Regional Scrum Gathering® Tokyo 2014 : 1A-3 「Bing開発グループはどのようにして毎日リリースをしているのか」 #sgt2014

今年もRegional Scrum Gatheringに参加させて頂いています。

参加したセッションについてのレポートを書いていきたいと思います。
まずは、マイクロソフトのヤマモトジン氏による、「Bing開発グループはどのようにして毎日リリースをしているのか」です。

2ヶ月×4の、「6ヶ月」周期

Bingでは、以下の様な開発サイクルで開発を行っているそうです。

最初のステップのデータ解析・ゴール設定フェーズでは、Bingが持つ、膨大なユーザの行動データを解析し、どのような改善を行ったら良いか、そして何を目標とするか(基本的には数値で表せる、「検索精度」を目標にするそう)を2ヶ月かけて練るそうです。

その後、4ヶ月の開発期間があり、開発締め・効率改善フェーズがあります。
ここではリファクタリングなど、開発効率向上につながる取り組みを、デベロッパが行うそうです。
また、この4番目のフェーズと、次のサイクルの1番目のフェーズは期間が重なっており、デベロッパが改善を行っている間に、製品に責任を持つプログラムマネージャは次のゴール設定を行うそうです。
既に現在のサイクルのゴールは明確になっている状態なので、プログラムマネージャは開発チームと別れて、次の案件に集中することができるそうです。

Daily Shipping

上記のようなサイクルで開発は行われていますが、企画開始から6ヶ月、ないし8ヶ月後にようやく機能がリリースされるわけではありません。

Bingでは常時新しい機能をリリースし(100%のリリースではなく、基本的にABテスト)、ユーザの反応を見て、さらなる改善につなげる、ということをしているそうです。

Dailyとは言っても、次の日に引っ込めるようなことをするわけではなく(そりゃそうだ)、大体2週間ほどデータを集めた後、専門のビッグデータ解析部隊によってデータ解析に回されるとのことでした。

Daily Shipping を支えるインフラ

セッション後に、リリース周りの仕組みや準備/オペレーションのコストについて質問してみました。

曰く、デプロイ作業自体はコンフィグファイルにABとかプロダクションとか的なことを書くだけで切り替え可能とのこと。
専門のオペレータもいるそうです。

また、検索ということで多くのサーバを使っており、さらにDCも分散されているため、全てのDC、全てのサーバにデプロイされたことを確認するのが大変で、様々なノウハウが有るとのことでした。

現在はこのノウハウは、Azureの運用部隊に引き継がれているそうです。
(この辺、AmazonからAWSが生まれてきたのと似ていると感じました)

ただし、こういったシステムは元からあったわけではなく、Bing開始当時は少ないサーバで自動デプロイしていたこと、その後時間をかけて現在の仕組みが構築されてきたことを聞くことが出来ました。

さて、私・我々は何をするのか

このセッションで聞いて、ぜひ取り入れたい、自分たちのプロセスを改善したい、と思ったのは、

  • 膨大なサーバ群に対して簡単にA/Bテストや本リリースが行える、リリースの仕組み化
  • 明示的なリファクタリング期間の設置と、効率改善が評価されている

の2点です。
どちらも昨年から取り組んでいるもので、本年も計画的に行っているものではありますが、引き続きトライしていきたいと思います。

前者に関しては、今まで野良的に行われてきたものが、今年はいよいよ組織化されそうなので、うまく協力して、いい方向に持っていけるようにしたいと思います。

後者は、昨年末試しに一週間、集中して改善だけ行う期間をとってみましたが、細切れにやるよりこちらのほうがいいと感じましたので、またどこかで同じようなことをしてみたいと思っています。