もともと、ビールのマスターデータ自体は Spreadsheet で管理してたんだけど、最初は、メニューは店長(デザイナー)がイラレで作ってたが手間がかかる→JSON化してNext.jsで生成して印刷するように→JSONの更新面倒だからSpreadsheetのAPIから直接static generateされるようになりました。 https://t.co/ZDKC7koqCs sotarok さんのツイートから

seen from Malta
seen from United States
seen from Malta

seen from Malaysia
seen from United States
seen from China
seen from France
seen from United States
seen from United States

seen from United States
seen from United Kingdom
seen from United States
seen from United States

seen from Malta

seen from United States
seen from United States

seen from United States
seen from United Kingdom
seen from United States
seen from China
もともと、ビールのマスターデータ自体は Spreadsheet で管理してたんだけど、最初は、メニューは店長(デザイナー)がイラレで作ってたが手間がかかる→JSON化してNext.jsで生成して印刷するように→JSONの更新面倒だからSpreadsheetのAPIから直接static generateされるようになりました。 https://t.co/ZDKC7koqCs sotarok さんのツイートから
期限付酒販免許、通りました。4/16 から販売できるぞ。 (公式では4/16につぶやこうかな)
— Sotaro Karasawa🍺 (@sotarok) April 14, 2020
横浜ベイブルーイングから24本届いた🍻 もう野菜室入りきらないから上の冷蔵庫も1段ビール専用にした。 うむ。 pic.twitter.com/NQGGpdAKN2
— Sotaro Karasawa🍺 (@sotarok) April 13, 2020
Crocos Wiki を Crowi として公開しました
こんにちは @sotarok です。
はじめにお知らせ
既にお知らせしてあったとおり、株式会社クロコスは今月いっぱいでヤフー株式会社に吸収合併されます。
てことで、今日が最後のブログになります。
ま、出せるもんは出しておけ、ということで準備を進めてきたのですが、Crocos社内で使っていた Wiki ツールを公開します。 実は前からしてたんですがw、もうちょっとアレコレこうしてからバーンと公開しよー!と思っていたらなんかバタバタしてるうちに開発あまり進まなかったので(ありがち)、結局いろいろ道半ば状態ですが、一旦バーンとしちゃいますw
この先はきっと趣味で色々開発をすすめるかと。
Crowi - The Simple & Powerful Communication Tool Based on Wiki
crowi/crowi - GitHub
Crowi Demo and Docs
Crowi
設計思想は「コミュニケーションツールとしてのWiki」です。
大事にしていることは シンプルであること。
読み方は「クロウウィ」です。由来はもちろん、Crocos Wiki ですw 開発を始めたきっかけですが、創業した年の年末年始休みで
ドキュメントを共有したいがまともなWikiツールがない (当時はとりあえずRedmineのWikiをつかってました)
Node.js で遊んでみたい
くらいの感じで、最初3日くらいで形をつくりました。 最初のバージョンは、非常に中途半端でアレだったんですが、わりと使えちゃうから1年半位放置したまま運用してて、とはいえもっと色んな機能つけたい、便利にしたいと本腰いれてやりはじめ、ボチボチとデザインを変えたり機能を追加してきて ... 今に至る、というかんじです。
コミュニケーションツールとしての Wiki
現在のクロコスでは、すべてのコミュニケーションのベースに、Wiki を据えたいと思い、こうしたツールに明るいエンジニア職の人以外にも積極的に使ってもらえるように推進してきました。
ミーティングのアジェンダと議事録の共有
日々のメモ書き
技術仕様のドキュメント
ちょっとしたプレゼンテーション
などなど...
特に、Wikiに情報を載せていくこと徹底する前は、「以前話したことがあると思うんだけど、いつだっけ」といった、意思決定の根拠が残らず、確認をしたり、同じことをまた話したりする無駄がありましたが
口頭で話したことも必ずWikiに書く
書いた後「こんな話でしたよね」とチャットで共有
というフローが身についてからは、認識のズレ (話した時は「あーそうだよね、うんうん」とお互いなったのに、書き起こしてみたら認識違っていたみたいなこと) がなくなったり、後から見返せるようになったりなど、情報がよく回るようになりました。
主な機能
せっかくなので紹介しておきます。
Markdown で書く&リアルタイムプレビュー
まず、記法ですが、昔であればはてな記法とかPukiWiki記法とかいろいろありましたが、方言含めやっぱりグローバルに Markdown で間違いないでしょ、という感じなので、当然 Markdown で書けるようになっています。 (そういや Wiki の当初のバージョンでは、Markdownにくわえ、はてな記法とRawテキスト の3種類をサポートしてましたが、やめちゃいました)
リアルタイムプレビューというほどでもないですが、横に一応プレビューも出ます。
リスト表示とタイムライン表示
一番こだわったのはここで、 / で終わるページが必ずリストビューになっています。 /user/ にアクセスすれば /user/sotarok や /user/yudoufu などの下層ページが自動的にリストされます。 リストとページを行ったり来たりして、遡れたり掘れたり、というのが気持ちよく出来るように考えました。
/どこそこ/2014/10/31/ほげほげ といった形でページを作成しておけば、
/どこそこ/ にアクセスすればどこそこの記事一覧が、
/どこそこ/2014/ にアクセスすれば2014年に書いたどこそこの記事一覧が、
/どこそこ/2014/10/ にアクセスすれば2014年10月に書いたどこそこの記事一覧が、
表示される、みたいなイメージです。これが一番好きなところです。
また、リストページでタイムライン表示では、それを展開できるようにしました。ページを廻らなくても見られるようにですね。
また、これは副産物的に出来たのですが、 /user/.+/日報/ といった正規表現を含むURLでアクセスすると、例えばこの例であれば全員の日報一覧を出す、などの仕掛けもあります。 はたして便利なのかわかりませんがw、とりあえず日報一覧、くらいの活用はしています。
まだダサイのでデザインとかはまだ色々考えています。^^;
「今日のメモ」
右上から「今日のメモ」をクリックすると、 /user/sotarok/2014/10/31/xxxxx のようなページにすぐジャンプできるようになります。 今議事録を書きたい、今メモをかきたい、といったときのクイックな行動を促せるように設計しました。
見た人とかお気に入りとかいいねとか
意外と「見た人」機能が便利です。 見た人がアイコンとして並ぶことで、「共有したあの議事録見てくれた?」のようなことはすぐにわかるようになりましたし、他の人が書いてくれたメモやアイデアに、何も言わなくても「見たよ」といえるようになります。
あといいね!も実装してあります。 日報にいいね!、議事録にいいね!、アイデアにいいね!、どんどんつければ良いと思います。
お気に入り機能は、お気に入りにいれたやつの変更を追えたり、ブックマーク的にあとで探せたりとかしたい!と思ってつけました。 ちなみに お気に入りはまだ「みる画面」がありません (大笑 えー、がんばりますw
FacebookログインとかGoogle Appsログインとか
一応、設定すればできるようになっております。 最初は Facebook ログインでつかえる Wiki!みたいに始めたんですが、結局Facebookの仕様変更に追従するの面倒になって、ログインくらいはできるけどね、くらいの実装になっています。
Google Apps ログインは、Google Apps 使ってる企業とかだと使いやすいかもねえ!?
競合検知とか、プレゼンモードとか、なんか諸々
WebSocket でリアルタイムで他の人が変更を加えたことがわかったり ... Markdown で書けば勝手にプレゼンになったりとか ... (まぁ reveal.js つかってるだけだけど)
いろいろ中途半端ですが、まあw
裏側とか
いちおう Heroku で動くよねってところを意識して作ってあります。
Node.js
MongoDB
Express 3
S3とか ... AWS 系の諸々とか ...
Socket.IO とか?全部書くの?
パーサーは marked です
(crowi-search は groonga。そのうちこの2プロダクトは統合しよう .......)
どういうノリで書けば良いかわかりませんがまあ、こういう感じのツールになっています。
やりたいことはたくさん
ページのコメント
階層構造の表現をもっと自動で色々やりたい
リストの見た目もなー
画像とかはそもそもアップロードしたい
通知機能欲しいよねとか、
Diff やっぱほしいな
ページステータスとか!、
GitHubの [ ] のチェック記法とか
emoji とか
そういや Angular 化しようとおもってた
コミュニケーションツールとしてもうちょっとちゃんとしたものにするためにはまだまだやりたいことたくさんあります。 アイデアをまとめるのはこっちのデモ代わりに建てた Wiki でやっていこうと思います。
Crowi Demo and Docs
/Crowi
まあ、まだ正直バグもあるしw、手放しにオススメできるかというと、PukiWikiとかを使うくらいならこっちのほうがいいよ、程度ではありますが、今のところ人柱と協力者は募集しておりますw
最後に
カッチョイイロゴを考えてくれた @misuyumi、クロコス退職後某社CTOとなって Pull Request を送ってくれる @riaf、皆さんに感謝! あとかっちょいいサイトが簡単につくれる Strikingly にも感謝。
それでは、次回どこかでお会いしましょう。
最後の最後に
Good bye Crocos!
Pull Request のフォーマットを決めるとレビューの効率が3倍よくなる
あけましておめでとうございます ....
と、この下書きの先頭に書いてありましたので、この記事を書き始めてから、こちらもすでに9ヶ月以上が経過しております。今年ももう後半戦入っておりますが、ベリースローペースでがんばらせていただきます。
そうこうしているうちに、株式会社クロコスの、ヤフーへの吸収合併が発表されました。 この媒体で記事を出すことは、もうあと何回もないかとは思いますが、皆様これからも変わらず、よろしくお願いします。
さて、そういうわけで。
長くなるので先に結論
Pull Request でレビューするとき、レビューを提出する文書フォーマットを決めておくと非常に捗る
レビューが早くでき、
レビューの質があがり、
ハッピー
Pull Request のレビューについて
本題です。
前提条件:
基本的には、OSS 等ではなく、業務で利用する GitHub を想定しています。
(もちろん OSS に当てはめても良いとは思いますが、OSS の場合は、より Pull Request を受け取るハードルを下げる方向のほうが、全体的には活発になって良いのかな、と思います。もちろんレビュワーは大変ですが。そこはそのソフトウェアの開発ポリシーによりますかね)
開発には常に Pull Request を利用していて、メインブランチへの merge 前に必ず Pull Request によるレビューを行うものとします
その上で、どのようにレビューをしたら効率が良いか (レビュワーにとって負荷が少なく、漏れが少なく、効果的で、効率的) を考えます
実際に運用しているうちの環境としては、
開発チームを、リーダー1人 + メンバー2-4人程度の小規模なチームを保持
Pull Request のレビューは以下の体制で行っています
チームリーダーが行う
リーダーのコードは、リーダー同士でチェックする (都度「XXさんこれ見てください」のように依頼してる)
最低1人以上から「:+1:」コメントがつくと merge 可能
大きなブランチはなるべく作らない。大きくなりそうなときはリリースを分割したり、サブブランチに分割したりする。
また、本稿の背景にある考え方として、以下の記事が非常に参考になりましたので、是非一度読んでみてください。
コードレビューを円滑に行いたい #cross2014 のお話
Pull Request のフォーマットって?
フォーマットとは、Pull Request を作る際の説明のテンプレート/ルール です。
具体的に見ないと、イメージが湧かないと思うので、うちで使っているテンプレートの1つの例をみてみます。
## 関連URL * WikiのURL(Markdown形式)や ... * 画像URL や ... * 操作可能な画面のURLや ... * (あれば...) ... ## 概要 * なぜこの変更をするのか、 * 課題は何か、 * これによってどう解決されるのか、 * など、この変更に対する概要を記載 ## 影響範囲・ターゲットユーザ * 主催者へのメリット・デメリット * 応募者へのメリット・デメリット * その他のユーザへのメリット・デメリット ## 技術的変更点概要 * なにをどう変更したか * ロジックがどういう手順で動くのか、 * DBからどういうクエリで何をとってそれに何を処理するのか、 * などなど、 * レビュワーにわかるように、技術的視線での変更概要説明 ## 使い方 * 使い方の説明 * 再現条件 (例えば、いいねの状態、デバイスの状態、プランの状態や、設定によってこういう風に使える、とかこういう画面が出てくる、とか条件があれば) ## UIに対する変更 * 変更前のスクリーンショット * 変更後のスクリーンショット ## マイグレーション * マイグレーションが必要な場合、`ALTER TABLE` 等を記載する 記入例: ```sql ALTER TABLE message_send ADD body_replacements ... ``` ## クエリに対する変更 * 変更されるクエリ (変更前、変更後) * 新規に追加されるクエリ * 観点: * WHERE句に指定した条件の index 設計は適切か * 想定される対象のデータ量 * 想定されるデータ量が大量のとき大丈夫か (ダメな場合、いつまでにどうにかする予定があるのか、忘れないために別チケットを用意したか) ## 個人情報の取り扱いに変更のあるリリースか * メールアドレスや住所情報等、個人情報にあたるものを新規に保存する場合、ドキュメントを書き、そこへのリンクを貼る ## TDログ 重要な機能を追加・変更した場合、ログを仕込む必要があるか、ログが入っているか、入れているログが変更によって漏れていないかなどを確認する必要があります。 * [ ] 重要な機能であり、ログを仕込んだ (説明用Wikiへのリンク) * [ ] ログを入れる必要ナシ ## テスト結果とテスト項目 * [ ] テストする際の項目を、このように、チェック可能な形式で記載する。 * [ ] テストしたらチェックを入れていく。(staging でテスト、など出ない場合、基本すべてのチェックが終わった時点でレビューに入る) * [ ] 特に、PC、モバイルの違い、応募フローの再現手順など、できるだけ広い視野で。 * [ ] 詳しくは、テスト観点リストも参照のこと。 * [ ] できれば、**開発した人以外が** 項目を洗い出してください。(開発者本人は、盲目的になっている可能性があります) * [ ] 機能のON/OFF によって画面の出しわけ、機能が有効になったり無効になったりする場合、場合分けしてテスト項目を書く * [ ] 他チームへテストを依頼した場合はチーム名書いておこう * [ ] それをテストしたチームの人が、チェックを入れよう ## 今回保留した項目とTODOリスト 箇条書きで書く。できれば次のチケットを作る。 * あれこれを治す #xxxxxx * あれそれを実装する #xxxxxx ## 共有先 ↓ 共有したい先にチェックをいれてください。 merge 時 `HipChat: 部屋 @sotarok` などを記載すると、共有時に自動的に mention もつけてくれます。 記法: `HipChat: 部屋 @sotarok` 社内共有: * [ ] HipChat: Crocos ALL * [ ] HipChat: リリース情報 * [ ] HipChat: 開発 * [ ] HipChat: グロースx開発 * [ ] HipChat: 営業x開発グロース * [ ] HipChat: サポート * [ ] メールで共有: (ここにメールアドレスを記載) 社外アナウンス (上から大きい順): * [ ] プレスリリース * [ ] 告知用ページ作成 (w/ グロース) * [ ] 事前メールアナウンス (internal のアナウンス機能で) * [ ] Facebookページでアナウンス * [ ] アナウンス無し ## その他 * レビュワーに対する注意点 (ここはこういう風に思ったけどこういう風に実装した、ここはこうしようと思ったんだけどこういう悩みがありやめた、など注意点があれば) * リリースに対する注意点 (その他)
上記フォーマットは、「機能追加・機能変更系」のための Pull Request のフォーマットです。 これは社内の Wiki に置いてあって、Pull Request を送るときに↑からフォーマットをコピーして中身を埋めて(変更したり削ったりして)、Pull Request を送信する、という流れになっています。
フォーマットの内容にどういうことを書くのか、というのは、フォーマットの定義自体に解説が書いてありますので大丈夫ですかね。
ちなみに、このフォーマット中にある テスト とは、ユニットテストではなく、ファンクショナルな、人力テストということです。 コードレベルでの保証は自動テストで行ったほうが良いでしょう。クロコスの場合は、Pull Request ごとに Jenkins のテスト結果が Pull Request 上に表示されているため、まあこちらは問題がありません。
Pull Request のタイトル
タイトルはフォーマットに記載していないのですが、以下のようにルールづけています。
変更の内容が他の人にも伝わるように1行でまとめる。エンジニア以外が見ても分かる内容にする。
さて、これにそって記入後、ペロっと GitHub に貼れば、もちろん Markdown 形式なのでフォーマットされて表示されます。素敵で便利ですね。
フォーマットの種類
以下の6通りのフォーマットを用意していて、それぞれ多少項目が違ったりします。(例えば、フロントエンドのみの変更では、技術的解説欄はなかったり、不具合修正系には障害報へのリンクや影響範囲の詳細調査結果などの内容があったり、など)
機能追加・機能変更系
リファクタ・構造の変更・基盤開発系
不具合修正系
あるPRの修正PR
小さな変更
フロントエンドのみの変更
フォーマットを定義するメリット
上記フォーマットをみてもらうと分かる通り、結構盛りだくさんですが、このようにフォーマットを決めておくと、
レビュイー (Pull Request を送る方) にとっては:
レビュワーに 何を見てほしいか が明確になる
その機能が利用者に何をもたらすかというプロダクト視点を育てる
Pull Request を堤出する人による観点のズレが減る
この変更について、事前に把握しているか/いないか、あるいはコード対象コードを見たことがあるか/ないか、などレビュワーにもそのコードへの精通度合いが違います
その違いを吸収し、ある程度、だれでもレビューできるようになります (もちろんコーディングや設計のレベルとしてエンジニアとしての一定のレベルは必要。)
リリース前後の手順の確認
何をテストしたのを明確にする、テストをさぼらない
レビュワーにとっては:
何をレビューすれば良いか が明確になる
この変更によって何が変わるのか、何が行われるのか、その理由もinputできる
変更前後の画像が添付されるためひと目でわかる
UIのどこが変わったのかが明確になり、そこから影響範囲の想像力を働かせるうえでも役に立つ
関連文書 (Wiki などに書かれたドキュメントや、申請系) にすぐアクセスできる
何をテストしてくれたのかがわかる
例えば、「ここを変更すると、ここにも影響範囲がありそうだな、ここってテストしたのかな」等、フォーマットが導入される前は、聞いてみて「あ、それもテストしました」というやりとりもありましたが、今では、何をテストしていて、何をテストしていないのかがすぐわかるため、コミュニケーションコストの軽減にもなります
その他のメリット
フォーマットにそって記載されていると、Parsability が上がるということもメリットの一つとしてあげあられます。 クロコスでは、Pull Request のフォーマットにそってパースされ適宜様々なところへの情報共有に利用されます。
Pull Request マージ時に hook して...
HipChat への通知
どの部屋に通知するのかを指定しておくと、merge hook 時に自動的にフォーマットの中から重要なものだけを抜き出してbot が発言してくれます。 例えばこんなかんじ:
これにより、例えば、営業部から依頼を受けて作業していたものが、「あれの作業って終わりました?」「あ、実は一昨日リリース済みです」のような、共有漏れによるロスがなくなりました。
@sotarok などと記載しておけば、mention も飛ばせます
リリーストピック収集ツール
週1回の全体定例MTGで、今週なにがリリースされたのかを発表する場があるのですが、その際に、注目すべきリリースが何か、後からリリースを振り返らずとも、自動収集でレポートを作ることができます
また、ここで発表される、ということを前提におくと、「Pull Request にかかれている内容が当人にしかわからない」という意味不明なことが少なくなります。(ただし、エンジニア以外にも見られる情報だからね、というのを浸透させるのは少し時間が必要でした ^^; 繰り返し言うこと、が重要です。)
レビュイーが、色々書かなければいけないという、時間的/労力的に大変というデメリットもあります。しかし、そもそもフォーマット自体、「いくら急いでいても、多少大変でも、このレベルは保ちましょう」という一定のガイドラインを設けることでもあり、これを満たせないならば (その会社での/そのチームでの) その人の活躍の場は無いよ、というだけの話であり、まあそれはそれで。
したがって、フォーマットのレベル感といいますか、どのレベルまで書かせるか、というのは、当然
アプリの特性
サービスのステージ (立ち上げ期?成長期?運用期?)
チームの特性
会社のステージ (立ち上げ期?成長期?運用期?)
などによって異なります。
会社やサービスが成長していくにしたがってフォーマット自体の見直しも適宜行っていかなければいけませんね。
例えば、会社の立ち上げ期、エンジニアが3人くらいだとすると、みんなが同じものを作っています。そうすると、お互いの責任範囲や、書いたコードへの理解、作っているものをみんなが共有し、知っている状況ですよね。そういう時は、前提条件となるものの説明などはある程度省けるかと思います。((それでも変更の概要はもちろんあるほうが効率は良いと思いますけどね))
このフォーマット導入後は、3ヶ月に1度くらい、「いまのフォーマットでの不満、過剰な点、内容としてこれも欲しいよ、という点」などをメンバーにヒアリングしていて、それによって既に3-4回の改定が行われています。ルール自体も、陳腐化しないように育てていく必要があります。
フォーマットをうまく運用するコツ
定義しても誰も使ってくれないのでは意味がありません。 まずは、必ずこのフォーマットにそっていなければレビューされない、というのは大前提としてあります。レビュワーにとって非常にメリットがあるため、Pull Request が堤出された段階でこのフォーマットにそっていなかったら突き返す、というのは自然と発生しますw
ただし、色々書くのが面倒、というのを超えるメリットが無ければ、なかなか大変です。 そこで、上の方に書いた、フォーマットにそっていれば、自動でチャットや定例MTGで共有されるといった負荷軽減ができると、理解してもらいやすくなります。
まとめ
Pull Request を送るときの文書フォーマットを決めて運用することで効率が良くなる話をしました!みなさん、お気づきかもしれませんが、タイトルでは「レビューの効率が...」と書きましたが、実は、レビューだけでなく全体の業務や情報共有の効率がめちゃくちゃ良くなるのですよ。
次回は、Pull Request を用いた、半自動リリースのフローについてお話できると良いなーと思います。
社内チャットの Skype + IRC という環境を撤廃して HipChat 一本にした話
こんにちは。久しぶりです。@sotarok です。 久しぶりにもほどがありますが、そしてエンジニアブログでこのネタというかんじですが、まあ良いとして、今日はそんな話をします。
弊社では、これまで
Skype (非エンジニア・全社的やりとり)
IRC (エンジニア)
でやっていたのですが、ココ最近これを HipChat に移行し、一本化しました。 HipChat は Confluence などで知られる Atlassian 社の運営するチャットツールです (2012年に買収したらしい)。以前は月額 $2/user から始められる小規模な人数の会社等ではコスト的にもバランス感の調度良いツールでしたが、最近無料になりましたので、ますます薦めやすくなりました。(ちなみに弊社では検索ログ保持のため $2 で使っております。
昨年末くらいからテスト導入を始め、1月末に本格移行、それから半年ほどが経過し、運用もこなれてきました。 実は2月くらいに「それから1ヶ月ほど経過し」と書き、4月頃に「それから3ヶ月ほど経過し」と書き、ずっと下書き状態だったブログですが、ようやく公開しますw
ということで、今日は、移行の際に検討した内容などを元にチャットツールとは本質的になにが満たされるべきかを考えたいと思います。
Skype と IRC、そしてそれらを併用することの問題点
そもそもなぜ Skype と IRC を使っていたのか、統合・移行しようと思ったかというと、エンジニア・非エンジニアどちらもそれなりに人数が増えてきて (それぞれ15人くらい...半々程度)、ツールの分断があることが、結構面倒になってきたところが主な理由です。 Skype、IRC にそれぞれ部屋が増えてきて、どこのツールのどこの部屋でしていた会話だかわからなくなることが多々ありました。
そもそも Skype と IRC を使っていたワケ
Skype には、
1on1 チャットがある
グループ会話ができる
だいたいみんなアカウントを持っていて導入が簡単だった
また、過去の経緯で、クロコスは設立当初リモートで Skype 通話で会議したりしていた
IRC は開発者全員もともと使っており、
色んな物を通知してくれて、
bot も簡単に導入できて、
使い慣れていた
そんなこんなで創業当初から、開発IRC、それ以外Skype という構成でやっていました。
IRC、Skype それぞれの、問題
…という、導入しやすさがそもそもあったのは事実ですが、皆さんご存知の通り、この2ツールにはそれぞれ色々面倒な問題がありますよね?ありますよね?
Skype
P2P には限界がある
メッセージ送ったのに届かないことがある
複数マシンでログインしっぱなしだと時々メッセージが消失する
モバイルアプリがダメすぎる
個人アカウントの問題
なんかイケてない (えっ
IRC
正直開発者以外には微妙
プロクシ立てたり
アカウント管理が曖昧 (社内アカウント管理とプロクシのために znc 使っていました)
複数行とか ... まあ細かいことはいいや
ま、それぞれ、挙げたらキリがないんですが。
チャットツールとしての本質的要件
そんなわけで、乗り換えを検討していたのですが、僕がどんな視点でツールを選んでいたかちょっと書き出してみます。 まず、チャットツールとしての本質的な要件は何か、を考えます。
本質的には、
普段は非同期コミュニケーションとして流れる
見たいときに見る
返信できるときに返信する
見て欲しいものは流しておく
同期的コミュニケーションをとりたいときには同期的なコミュニケーションが取れる
呼び出したい人を簡単なUIで呼び出すことができる
呼び出された人はオンライン・オフラインにかかわらず気づくことができる
すごく抽象的になりますが、必達要件はこの2点で、これを実現するためにいかにバランスのよいUIや通知系統を兼ね揃えているか、が重要なポイントになります。
それをブレイクダウンした必須要件
以上の問題点、要件から出された必須要件としては、
専用クライアントがあること (ブラウザベースではない)
ブラウザは他の仕事でも利用しタブも埋もれる、通知系統が未成熟、ブラウザ内におけるキーバインドの限界などからNG
通知系統のOSとの統合が必須
OSの通知機能などと統合された通知ができ、人または任意のキーワードで通知ができること
Mac であれば Notification Center
Windows ではOS標準の機能ではないものの、たいてい右下からポコンと出てくるタイプのやつ
非同期かつ、必要に応じて同期的コミュニケーションができることとして、呼び出しができることは必須
不在時でもメッセージが到達し、通知ができ、ログがさかのぼれること
到達されないのは論外
botによる通知ができること、各種ツール (社内ツール、GitHub、Jenkinsなど) との連携が可能なこと
開発者が使うには絶対に必要
各種ツールと統合できるには必須
の4点です。 これ以外の要件は "必須ではない" ものとし、しかしできればの要件としては以下のようなものをあげておきました。
過去ログの検索
モバイルアプリの有無
使い慣れたデータモデル (部屋を作成 → その中でその話題をする)
HipChat
上記要件を満たせるものとして検討したものを挙げます。評価軸は上記必達要件がどの程度満たせるか、です。(昨年末時点での検討なので、サービスの状況はこの半年で変わっているので今評価すると色々違うかもしれません。)
ChatWork
HipChat
Slack
ま、その他いくつかも検討しましたが、要件を満たすものとして HipChat を試してみることに決めました。
HipChat の良いところ
要件を満たしている、ということは最低条件として、特筆すべきところは、こんなかんじです。
通知系統が優秀 (これが決め手)
名前のコールでポップアップ通知 (Mac なら Dock ではねてくれる)
オフラインのときに名前通知が来るとメールでも通知くれる (さらに、前後の会話がメールに含まれるので文脈もわかりやすい)
iPhone アプリも使いやすかった (まあ、というか普通?)
海外ですでにある程度名前が売れていたおかげで、各種ツールからの連携がしやすかった (GitHub の Hook 設定でもデフォルト準備されている、など)
bot も作りやすかった!
ま、ざっくり言うとこんなもんでしょうか。
導入試験〜導入
まず、実際の使い勝手を知らないと全社に展開しづらいので、主にリーダー陣の使うチャットで、ひと通りの機能や使い勝手を試す期間を2週間ほど置きました。 (ちなみに導入試験中は、リーダー陣は、Skype IRC HipChat の3つを使う必要があり、完全にカオスでした ...)
リーダー陣を先にこういう試みに巻き込むことは、その後のメンバーへの展開で各リーダーが自分の言葉で語れるようになるので良い方法かと思います。
実際導入にあたっては、ドキュメントの整備と、説明会を開催しました。
全社でコミュニケーションツールが一本化された
ツールが分断していることは、組織に想像以上に大きな悪影響を与えます。
それは「コミュニケーションが分断される」ということです。
コミュニケーションが分断されて良いことは何も起こりません。ツールは所詮ツールなので、円滑なコミュニケーション促進には、ツールの統一だけで「十分」にはなりませんが「必要」である、とは思います。 「誰々に連絡をとりたいけどあの人XX使ってないから連絡とれないんですよね」によって損なわれるコミュニケーション機会は非常にもったいないんですよね。
導入前後では、エンジニア <-> 非エンジニア のコミュニケーションが以前に比べて自然とできるようになったと感じます。
通知系統が一本化された
これは特に開発者向けのメリットですが、IRC で bot からの通知を受けたり、Skype で人から呼ばれたり、というわけのわからなさw がなくなり、常に HipChat からの通知が来るようになりました。
今は、GitHub のレビュー状態や、デプロイの情報を bot に通知させているので、基本的に HipChat の通知を気にすれば重要な情報は通知される、という状況を作っています。
たとえば、うちだと PR に +1 で出るとマージして良いのですが、こんなかんじで通知してくれます。
あとは、デプロイ時に、関係ある人 (そのデプロイに含まれる人) にも通知してくれたり
逆に HipChat のイケてないところ
ま、それもそれなりにあるわけで、一応書いておきます。
任意の通知キーワードを設定できない (名前 here all のみ)
アバター表示がない、Nickname 表示がない (本名表示のみ)
組織によっては、英語ツール、というのが気になることはあるかも
Windows Client がだいぶイケてない ...。
リンクがクリッカブルじゃない (これが致命的すぎて一部の人は Chrome の切り出す機能を使ってアプリっぽく使ったりしている)
Mac 版ではあるのに (そして便利なのに) ...
名前クリックで mention とか できない
Show divider for unread message の設定がない
そういうわけで
ツールとして何を採用するかは、その組織やチームの状況、ステージによって違うと思うのですが、なんでもいいけど大部分の要望を満たせるツールの導入のために本質的要件を考えるのは非常に重要だよ、というお話でした。
今や当たり前のようにオンライン上でコミュニケーションを取りながら仕事をしていきますが、日々環境や組織の状況は移り変わるので、不便が積み重なる前にツールの整理をしておくクセはつけたいな、と思います。
これをお読みの皆様も何かのヒントにしてみてくださいね!
Let's Happy Chatting!
Facebook PHP SDK 解体新書 第5回「JavaScript SDK と連携してる際に適切に Access Token が取得できない問題について」
こんにちは。 @sotarok です。 Facebook PHP SDK について深〜い解説をする「解体新書」シリーズ、今日はじめて書くんですがいきなり第5回から行ってみようと思います。
というわけで、表題の通り、「JavaScript SDK と連携してる際に適切に Access Token が取得できない問題について」のお話をします。
※ ちなみに執筆次点でリリースされている Facebook PHP SDK v.3.1.1 をベースとしています。
補足の前提知識、JavaScript SDK と PHP SDK の連携
Facebook PHP SDK は、JavaScript SDK と、特に何も意識せずに COOKIE を介して連携しています。具体的には、JS で FB.init() された際に生成される fbsr_xxxxx という COOKIE を PHP SDK が読み取り、その情報 (Signed Request 相当のデータが格納されています) を元に PHP SDK 上でもログインすることができるようになっています。
突然発生しはじめたエラー
Facebook ではよくあることですが、今まで動いていたコードが、APIサーバー側に施された何かによって動かなくなると言うことがあります。 で、今日出会ったのは、
$facebook->getAccessToken();
が動かない!という問題です。特に認証周り何もいじっていないのに、次のような OAuthException が発生するようになりました。
This authorization code has expired.
しかも、
人によって発生したりしなかったり
アプリによって発生したりしなかったり
という状況で、やれやれな雰囲気が出ています。わかったからこそこういうタイトルで記事を書いていますが、JavaScript SDK と連携していたせいで発生していたエラーだということに気づいたのはほとんど解決する直前でした。
このエラーは、 getAccessToken() を呼んだ際に発生していたのですが、それを発見するに至った経緯も含め、を詳しく見ていくことにします。
コールされたAPIとその結果
開発環境で再現条件を探るなかで、手順を最小限にするため、次のような画面遷移のみに絞って検証をしました。
PHP SDK の getLoginUrl() メソッドで取得できる URL にジャンプ (ご存じの通り、飛び先は Facebook の認証画面)
戻ってきたページで getAccessToken() メソッドで Access Token を取得
つまり、HTTPリクエスト的には、
うちのアプリでログイン用URLの生成
Facebookの認証画面
うちのアプリに code パラメータ付きで戻ってくる。それを元に Access Token を取得
という遷移をします。一応弊社のライブラリでは、Facebook PHP SDK がリクエスト飛ばす部分に手をいれて、
どんなリクエストが飛んで、
どんな結果が返ってきているのか、
がすべてログに出せるようになっています。このとき発生したログは、「2.」のタイミングで、
[2012-07-10 17:26:42] app.DEBUG: Facebook API[1]: https://graph.facebook.com/oauth/access_token - {"client_id":"10472078961xxxx","client_secret":"daa8a082d4db023bf1316f2e49dfxxxx","redirect_uri":"","code":"2.AQCOriuA5Anxxxxx.3600.1341910800.1-58888xxxx|1341905535|iOoQHNn-ZPesX6RsXBwUCibxxxx"} (10472078961xxxx|daa8a082d4db023bf1316f2e49dxxxxx) [] [] [2012-07-10 17:26:43] app.DEBUG: Facebook API[1] result: {"error":{"message":"This authorization code has expired.","type":"OAuthException","code":100}} [] []
というログが出ていました。このとき、Facebookからどいういうパラメータ付きで戻ってきたかというと、正常に認証が完了し、 /login/check?state=xxxxxxxxx&code=XXXXXXXXXXXXX のように、CSRFチェック用tokenであるstate (今回は詳しくは言及しません) と Access Token 取得のための code を query string に付与して戻ってきていました。 で、その画面で、/oauth/access_token に code をつけてリクエストを送ったタイミングで、OAuthException の "This authorization code has expired." が発生した、ということです。
最小の手順でこれが発生するということは、もう getAccessToken() が怪しくてしかたありません。ということで、このメソッドの中身を見ていきます。
getAccessToken() がやること
このメソッドは、
public function getAccessToken() { if ($this->accessToken !== null) { return $this->accessToken; } $this->setAccessToken($this->getApplicationAccessToken()); $user_access_token = $this->getUserAccessToken(); if ($user_access_token) { $this->setAccessToken($user_access_token); } return $this->accessToken; }
こういうメソッドで、つまり、
$this->access_token がセットされていたらそいつを返す
デフォルト値としてとりあえず Application Access Token をセットする
これは、 APP_ID と APP_SECRET をパイプ | でつないだものです
User Access Token を取得できたら、そいつをセットする
で、この中で怪しいのは、というか、/oauth/access_token にリクエストを飛ばすのは、明らかに getUserAccessToken() です。
getUserAccessToken() がやること
メソッドは、次のようになっています。
protected function getUserAccessToken() { // first, consider a signed request if it's supplied. // if there is a signed request, then it alone determines // the access token. $signed_request = $this->getSignedRequest(); if ($signed_request) { // apps.facebook.com hands the access_token in the signed_request if (array_key_exists('oauth_token', $signed_request)) { $access_token = $signed_request['oauth_token']; $this->setPersistentData('access_token', $access_token); return $access_token; } // the JS SDK puts a code in with the redirect_uri of '' if (array_key_exists('code', $signed_request)) { $code = $signed_request['code']; $access_token = $this->getAccessTokenFromCode($code, ''); if ($access_token) { $this->setPersistentData('code', $code); $this->setPersistentData('access_token', $access_token); return $access_token; } } // signed request states there's no access token, so anything // stored should be cleared. $this->clearAllPersistentData(); return false; // respect the signed request's data, even // if there's an authorization code or something else } $code = $this->getCode(); if ($code && $code != $this->getPersistentData('code')) { $access_token = $this->getAccessTokenFromCode($code); if ($access_token) { $this->setPersistentData('code', $code); $this->setPersistentData('access_token', $access_token); return $access_token; } // code was bogus, so everything based on it should be invalidated. $this->clearAllPersistentData(); return false; } // as a fallback, just return whatever is in the persistent // store, knowing nothing explicit (signed request, authorization // code, etc.) was present to shadow it (or we saw a code in $_REQUEST, // but it's the same as what's in the persistent store) return $this->getPersistentData('access_token'); }
コード中のコメントが解説してくれていますが、今回画面遷移の場合とあわせて解説すると、
Signed Request があったらそれを使って access token をとりにいくよ
Signed Request は、Facebook App Canvas や Page App の Canvas で開いたとき、iframe ごしにくるリクエストなので、今回のような、App Canvas 内ではない場所で利用しているアプリでは基本的に関係ない(はずである)
そうじゃなかったら code を使って、Access Token を取得しにいくよ
今回の場合、Facebookの認証画面から code 付きで戻ってくるので、こっちでリクエストが飛ぶはずである
Signed Request もなくて、code もなかったら、一応最後に、Persistent Data (セッションです) に入ってる access token を返すよ
今回はここにはこないはず
という処理になっています。
エラーメッセージが「This authorization code has expired.」なので、/oauth/access_token に対して Access Token の発行のリクエストを飛ばすタイミングでつけている code が expire してるぜってことっぽいので、まず、どんな code を使ってるのかを確認します。 こういう場合は当然必殺の「var_dump()とexit」なので、次のように、code を出力してみます。
$code = $this->getCode(); var_dump($code);exit;
この状態で、同じ遷移をテストすると ...
お ... code が出力されない!
あれ、code のところに来てない?ってことは signed_request に何か入ってるんだっけ?という疑いのもと、以下のようにデバッグコードを仕込むと ...
$signed_request = $this->getSignedRequest(); error_log(var_export($signed_request, true));
ログには ...
[10-Jul-2012 08:31:36 UTC] array ( 'algorithm' => 'HMAC-SHA256', 'code' => '2.AQCOriuA5Anxxxxx.3600.1341910800.1-58888xxxx|1341905535|iOoQHNn-ZPesX6RsXBwUCibxxxx', 'issued_at' => '1341909094', 'user_id' => '58888xxxx', )
見事に、入っていました。このときハッとしたのですが、これは、JavaScript SDK によって入れられたものです。 getSignedRequest() の中身は今回は紹介しませんが、大まかにいうと、POST リクエストで Signed Request のデータが渡されていればそれを、それがなければ COOKIE から復元という手順で Signed Request を返してきているからです。
つまり、認証手順で Facebook から code が返されているにもかかわらず、それを使わず、それより古くにJSで設定された Signed Request 内の code を使って access token をとりにいくから expire してました 、という話でした! これを無視して、クエリで渡された code を使って認証すれば、正常に Access Token を取得することができました。
人によって・アプリによって発生したりしなかったりしたのは、その人の COOKIE にそのアプリのデータが残っていたかどうかによるからですね。
問題の概要
今日突然起こるようになったことに対する想像
今までは、code に対する expire のチェックをしていなかったのではないか
つまり、JavaScript SDK によって COOKIE につっこまれた古い code を元に Access Token の発行ができた
これじゃまずいよね、とおもった中の人がついに expire のチェックをするようになった
しかし、このように、
query string などに code を受け取っていても、Signed Request があったらそれを最優先で認証に使う
Signed Request は JavaScript SDK によってセットされる可能性がある
code は expire する
という仕様になっているのであれば、「Signed Request を元に Access Token を取りに行く」というのは、必要だとして、正規の認証経路で戻ってきて code が query string に渡されていて、かつ、JavaScript SDK によって COOKIE に Signed Request がセットされている、という場合が存在するので、 Signed Request を元に access token を取得しにいって、だめなら query string に渡された code を元に Access Token をとりにいくという fallback 処理があるべきなのではないか、という感じです。 Signed Request が Signed Request によるものなのか、JavaScript SDKによるものなのか区別されていないので。
ところで、この問題は、以下の条件下で発生します。
どこかでそのアプリが FB.init されたことがあり、その時点から一定時間が経過している (expire する程度には)
getLoginUrl() がコールされて Facebook にジャンプする前に FB.init() される場所が無く、COOKIE も生きている
この条件を満たさない場合は回避できるでしょう。(たとえば、getLoginUrl() のURLにジャンプする直前の画面で JS で FB.init() されているとか)
ちなみに、Authorization Request の code に関しては、OAuth 2.0 の最新の proposal には、以下のような記述があります。
4.1.2. Authorization Response
code REQUIRED. The authorization code generated by the authorization server. The authorization code MUST expire shortly after it is issued to mitigate the risk of leaks. A maximum authorization code lifetime of 10 minutes is RECOMMENDED. The client MUST NOT use the authorization code more than once.
code の lifetime は最大でも 10 分が望ましい
client は、authorization code は1回しかつかっちゃだめ
このあたりを、ちゃんとチェックするようになったんですかね(いままでやってなかった...?)。
対策
対策としては、
FB.init() する画面を挟む
認証のリクエスト前に COOKIE 消す
getUserAccessToken() に Signed Request → code の fallback 処理を入れる
といったものが考えられますが、今回はアプリの画面遷移の特性上、COOKIE 消すことにしました。というか、destroySession() メソッドを呼ぶことにします。
本来、COOKIE が PHP にとって Persistent Data に近い存在として扱われるので、clearAllPersistentData() のタイミングで COOKIE を消すべきなんじゃないかなーと思っていますが、後日このへんのアイデアをまとめて SDK の修正案を Pull Request でもしてみようと思います。 ブログ書いてたら疲れたのでまた後日 ...
まとめ
JS SDK と連携してると、COOKIE で意外とハマる
突然エラーになったりする
まぁだいたいコードに書いてあるので1つずつ追えば原因はわかる
では、第1〜4回と6回以降もご期待ください。 次回は「JS SDK と連携している際にユーザの切り替わりを検知できない問題について」をお届けする予定です。
Facebook Night Vol.5 で Facebook アプリ開発についての発表をしました&告知
Facebook Night Vol.5 で Facebook アプリ開発についての発表をしました&告知
こんにちは。@sotarok です。ちょっと日がたってしまったのですが、先月4/24に行われた Facebook Night Vol.5 で発表した「CrocosにおけるFacebookアプリの開発と運用」というの資料をアップしました。
お話の中では、色々苦労しているよ、という点と、色々工夫しているよ、という点をお話させていただいたのですが、フレームワーク側の仕組みや、コツの情報は共有していけるものが多いので、今後積極的に発信していきます。 サイバード白尾さんのご発表の中であったように、「PMD認定企業だからすごいとかそういうことではなく、日本においてFacebookを一層盛り上げていくために一緒に頑張りましょう」と、そういう思いです。
Crocos Engineers Information はじめます
それで、先日の発表の時にもあったのですが、世界一早くFacebookの障害を検知するだとか色々言ってましたら、そういう情報を共有してほしい、というご意見を多数いただきました。 世界一早くかどうかはわかりませんが ... Facecbook Platform Status に乗る前の情報、たとえば、先日で言えば connect.facbeook.net の DNS 障害などの情報は、公式にアナウンスがある前にこちらで気づいて調べていることも多いですし、このあたりは是非共有していきたいと思っていました。 もちろん、障害にかぎらず、アプリ開発に関するTipsなど、色々情報を共有することもできるのではないかと思い、Facebookページを立ち上げました!
是非いいね!してくださいね。
Crocos Engineers Information
外部リンク
fb.developers'+ さんのほうにもレポートが掲載されています。
Facebook Night vol.5 レポート | fb.developers'+
Vol.6 も今受付中のようです。
Facebook Night vol.6 レポート | fb.developers'+