コネヒト開発者ブログ

コネヒト開発者ブログ

CakePHPの国際カンファレンス「CakeFest Virtual 2021」に登壇してきました!

こんにちは。CTOをやっている@itoshoです。コネヒトではここ1ヶ月でiOSDCやAWS Dev Day、PHPカンファレンス、PyCon JPと大きなカンファレンスへの登壇が続いています( ・∀・)イイ!!

というわけで、僕も負けじ?と「CakeFest Virtual 2021」に登壇してきたのでその様子をお届けしたいと思います。

CakeFest Virtual 2021とは?

CakeFestはPHPのフレームワークであるCakePHPの国際カンファレンスで、今年は昨年に引き続きオンラインで開催されました。構成としては初日にCakePHPのコアメンバーによるワークショップが開催され、2日目にカンファレンス形式で世界各国のエンジニアからの発表が行われました。

イベントの規模は、公式発表ではないので推測になりますが、2日目のカンファレンスは常時100人前後の方が参加していたと思います。雰囲気としては、オンラインということもあり参加者同士のコミュニケーションはそこまで多くはありませんでしたが、それでも2019年に日本で開催された時と同様にアットホームな空気が終始流れており、CakePHPのコミュニティの温かさを改めて感じることが出来ました。

tech.connehito.com

登壇内容「Components Reconsidered」

僕は「Components Reconsidered」(邦題: Component再考)というテーマで登壇をしました。CakePHPにはComponentと呼ばれるMVCモデルにおけるController層の共通処理をまとめたり、拡張したりする仕組みがあります。Component自体は非常に便利なのですが、CakePHP4.2からDIコンテナが実装されたこともあり、今後Componentの出番はより限定的になっていくのではないか?という問題意識がありました。

ですので、このセッションではComponentの機能やユースケースをおさらいしながら、代替方法の検討(具体的にはTraitやDIを取り上げました)を行い、その上でそれぞれの使い分けをどうするかといったことをお話させていただきました。

詳しくは以下のスライドをご覧ください。

speakerdeck.com

疎い英語で恐縮ですが、動画も公開されています。*1

www.youtube.com

登壇後の反応

内容的に明確な答えがあるテーマではなく、個々人のスタンスや環境に依る部分も大きいため、ドキドキしていたのですが、当日Slackに寄せられた反応をみる限り、概ね好評だったように思います。*2

特にCakePHPのLead DeveloperであるMark StoryさんとCore MemberであるAdmadさんからポジティブなフィードバックをいただけたのは自信になりました。

f:id:itosho525:20211016002114p:plainf:id:itosho525:20211016002046p:plain
コアコミッターからのフィードバック

英語での発表

英語での登壇では2年前のCakeFestで一度経験済みなのですが、今回は前回以上に緊張しました。前回は日本開催で翻訳者の方もいたので何とかなる感があったのですが、今回はそういった助け舟もない状態だったので、登壇が決まったタイミングから当日までずっと変なプレッシャーがありました。

その中でもCakePHPのコミュニティマネージャーの方が事前にリハーサルを丁寧に行ってくださったのと、同じ日本人である @yakitori009 さんが登壇仲間だったのは心強かったです。

準備としては、①サンプルコードの作成→②スライドの作成(英語)→③トークスクリプトの作成(日本語)→④トークスクリプトの翻訳(英語)→⑤ひたすら練習という流れで行いました。少なく見積もっても、国内のカンファレンスで登壇するときの5倍は準備に時間がかかりました。

ちなみにこのあたりは前回ブログにまとめたものがあるので、興味がある方はご覧ください。

itosho525.hatenablog.com

前回との差分で言うと、前回よりも技術的に突っ込んだ内容だったので、④の翻訳作業は識者(英語とソフトウェア開発双方に詳しい方)の方にご協力をいただきました。*3

また、英会話についてはコロナ渦ということもあり、数ヶ月からCAMBLYを頑張っています。あとは、DeepLに死ぬほどお世話になりました。ありがとうDeepL。

面白かったセッション3選

開催時間が日本時間の深夜ということもあり、全てのセッションをリアルタイムで観れたわけではないのですが、その中で個人的に特に面白かったセッションを3つ紹介します。

  • Workshop 3 - Mark Story
    • CakePHP4.3からfixtureの仕様が変わる(スキーマ管理とテストデータが分離される)のですが、具体的な変更内容を知ることが出来てよかったです
  • Fighting COVID-19 with Contact Tracing in Czech Republic
    • チェコでのCOVID-19対策事例の話で、CakePHP製の接触者追跡システムがダイレクトに社会の役に立っている話がエモくて最高でした
  • Our Standards and Why We Use Them
    • Jump24社で採用されている規約の話なのですが、コード例が多くて、1コントローラー1アクションにしてるんだとかリポジトリパターン採用してるんだとかが垣間みれて参考になりました

全てのセッションのスライドや動画はこちらにまとまっています。

cakefest.org

まとめ

正直、英語での発表は準備含めてとても大変ではあるのですが、その分学びも多かったです。やはり一人のエンジニアとして広い世界で勝負していきたいので、引き続きチャンスがあればどんどん登壇していきたいなと思っていますし、コロナが落ち着いたら実際に海外に行って、各国のエンジニアともっと対話したいなぁと改めて思いました。

また、冒頭にも述べた通り、CakePHPのコミュニティの温かさは本当に素晴らしく、海外カンファレンスの参加や登壇はCakeFestからはじめるのが一番よいのでは?と思えるぐらいですので、皆さんも是非来年のCakeFestに参加してみてください!

そして、僕も微力ながら、CakePHPのコミュニティに貢献していければと思っています。

技術コミュニティに貢献したいエンジニア募集中!

コネヒトでは「Beyond a Tech Company」というテックビジョンを掲げているのですが、テックカンパニーの条件の一つとして「技術コミュニティの発展に貢献している」ということを重要視しています。

というわけで、コネヒトではプロダクト開発だけではなく、OSS貢献やカンファレンス登壇などアウトプットが好きなエンジニアを大大大募集しております!

1mmでも興味がありましたら、まずはカジュアルにお話させてください。

meety.net

*1:機会があれば、日本語でも再演したいなと思っています…!

*2:CakePHPの公式Slackは http://cakesf.herokuapp.com から参加出来ます。また、今回のイベントは #cakefest チャンネルからその様子を伺うことが出来ます。

*3:前回も社内外からご協力をいただいたのですが、前回はどちらかというと英語のレビューが主でした。

PHP Conference Japan 2021に登壇してきました

皆さん、こんにちは。バックエンドエンジニアの山田(@rukiadia)です。

10/2、3に開催されたPHP Conference Japan 2021で参加・登壇してきたので、登壇内容の紹介とイベントの参加メモを書かせてもらいました。

登壇について

弊社からはスポンサーツアーでの登壇を含め、3名が登壇しました。

スポンサーツアーでは、コネヒトの会社のことや作っているサービスのこと、技術組織について紹介させていただきました。残念ながら資料の公開は難しいのですが、カジュアル面談などでお話できると思いますので、お気軽にご連絡ください!

今こそ深堀りする、PHPのDockerイメージ

speakerdeck.com

こちらは私の登壇資料です。コネヒトではCakePHPやPHPのバージョンアップに注力しており、その過程で学んだ内容を盛り込んだ内容となります。

PHPerがISUCON11にチャレンジした時のあれこれ

speakerdeck.com

こちらはチームメンバーの@takobaが登壇したLTです。@takobaと私を含めた3名でISUCON11の予選に参加してきたので、そのふりかえりをした内容となります。

資料でも振れられていますが、ISUCONはコードを見ているだけではダメで、アプリケーションエンジニアの総合力を問われる内容でした。普段の業務ではなかなか味わえないスピード感と緊張感を体験できる、とても楽しい経験となりました。

カンファレンスの感想

PHP Conferenceでは参加者のコミュニケーションの場として、公式のdiscordが提供されています。PHP Conferenceには去年も参加させていただきましたが、discordを介したコミュニケーションが去年よりも活発だった印象です。表では登壇がおこなわれつつ、舞台裏で内容に関するディスカッションが盛り上がるという構図はdiscordならであり、お祭りっぽい雰囲気を感じることができました。

また、今年からYouTubeのコメント欄が無効になったのも印象的ですね。YouTube Liveはラグがあって登壇中に見るのが難しいのですが、今年はdiscordに集中するだけで良くなり登壇側の負担が減りました。

補足ですが、PHP ConferenceはYoutubeのアーカイブが配信されているので、気になるセッションがあれば見返すことが可能です。

www.youtube.com

まとめ

まずはカンファレンスの運営に携わっている皆様、今年も本当にお疲れ様でした!登壇前の接続テストもあったおかげで、登壇に対する不安は全くない状態で登壇ができました。

来年のPHP Conferenceがどういう形式(オンライン or オフライン)になるかは分かりませんが、次回も登壇側からイベントを盛り上げられるように日頃からのインプットを頑張っていこうと思います。

AWS Dev Day Online Japan 2021に登壇してきました!

f:id:taxa_program:20211004165400p:plain

みなさんこんにちは。 MLエンジニアのたかぱい(@takapy0210)です。

本エントリでは、先日行われたAWS Dev Day Online Japan 2021にインフラエンジニアの永井(@shnagai)と私が登壇してきましたので、簡単に内容を補足しながら紹介しようと思います。

Step Functions × AWS SAMで実現する家族ノートの底運用コストETL基盤

永井からは、弊社の新サービスである「家族ノート」で運用しているETL基盤の構築・運用方法について話しました。

データが価値となる新サービスにおいてデータ基盤の意思決定から、属人化しないためのAWS SAMを使ったコード管理など、実際のコードなどを交えて紹介しています。

データ基盤を構築する上でアーキテクチャに悩んでいる方や、AWS SAMを使ったサーバレスリソースの管理方法について知りたい方の参考になる発表内容でした。

自分も新しいETL処理を追加する際にコード管理のありがたみを体感しました!(とても簡単に処理を追加することができました)

SageMaker StudioとStep Functionsを用いてMLOpsへの一歩を踏み出そう

私からは、AWSのサービスを組み合わせて、MLOps基盤を構築してきた事例について話しました。

PoCフェーズ・運用フェーズそれぞれの関心事や、各フェーズでMLOpsに求められる役割、課題を整理したのち、それぞれの課題についてAWSのサービスを用いて一歩ずつ解消してきた方法を紹介しています。

MLOpsやりたいけど最初どこから手をつけたら良いだろう、と悩んでいる方の参考になれば良いなと思います!

電笑戦が面白かった(お笑い的にも、技術的にも)

他のセッションは時間の関係であまり覗くことができなかったのですが、2日目の17時55分〜に行われた「ボケて電笑戦 ~AIは人を笑わせられるのか~」がとても面白かったです。

"ボケて"に関しては、こちらの動画を見ていただけるとイメージしやすいかと思います。

www.youtube.com

ボケる対象となる画像を入力として、その画像に対する「ボケ」を出力するのですが、人間が思いつく様なオーソドックスなボケ(この時点でも結構すごいと思いました)から、人間では思いつかない様な壮大なボケまで登場していて、技術の進化はすごいな、と楽しく参加させていただきました。

当時の状況やどんなボケが披露されたのかは、#電笑戦 でTwitterを検索するといくつか見れるので、興味のある方は見てみてください!

個人的には、特急ラピート*1という電車の画像がお題として与えられた時の

「車検の代車がこれ」

というボケが最高でした(笑)

最後に

オンラインカンファレンスは、現地での交流や雰囲気を味わいづらい反面、居場所にとらわれずに参加できるのがメリットかなと思っています。そのおかげもあってか、今回は登録者数・参加者数ともに過去最大のAWS Dev Dayだったようです。
(個人的にはオンサイト開催の方が好きなので、来年あたりはどこかの会場で現地開催しつつ、リモート配信も同時に行えるような情勢になっていれば嬉しいな〜と思います)

また、動画のアーカイブも1か月後程度を目処に公開されるようなので、当日視聴できなかった〜〜という方はこちらの公開をお待ちいただければと思います。

当日ご視聴いただいた方、素敵なカンファレンスを主催してくださったAWSの運営の皆様、ありがとうございました!

ML Test Scoreを使って現状の機械学習システムをスコアリングしました

皆さん,こんにちは!機械学習エンジニアの柏木(@asteriam)です.
コネヒトでは,テクノロジー推進部に所属し,組織横断的に機械学習(ML)施策の実施・推進を通してサービスグロースする役割を担っています.

はじめに

MLチームでは,少人数ながらレコメンドエンジンの開発*1やカテゴリ類推*2などの機械学習を用いたサービス開発を実施しています.一方でプロダクション環境に投入するMLシステムの数が増えると,それら1つ1つが属人的になったり,テストが不十分だったり,運用が疎かになったり,それ以外に技術的にも負債が蓄積するケースがあります.私たちのチームでもこれらが課題の1つとなっています.

f:id:connehito-mkashiwagi:20210928015818p:plain

上図はよく目にするMLシステムの技術的負債の図*3ですが,MLシステムはモデル開発だけでなく,MLシステムを支える周辺のインフラや各種メトリクスのモニタリングなど考慮すべき項目が多くあります.加えてMLシステムの特性でもあるリリース後の継続的な学習・デプロイにも対応していく必要があり,これに対して場当たり的な対応をするとますます負債が溜まっていくことが考えられます.これらを解消していくためにもMLOpsが重要になってきます.

今回は,現状のMLシステムの技術的負債を定量化するために,ガイドラインとしてML Test Scoreを用いてスコアリングした内容になります.これを実施した目的は,MLOpsを実践するために何が足りていないか(AS-IS),そして何が必要なのか(TO-BE)を把握するためです.本来はMLシステムの技術的負債を可視化することを目的にしたものですが,これをMLOps実践の道標へと転用しました.

MLOpsを進めることで,少ない人数でも効率的かつ技術的負債を少なくしてMLシステムを回すことができるので,より施策を検討する時間やユーザーにとって良いサービスを作っていく部分に注力して行きたいと思っています.

※現在,このスコアリングした内容を元にMLOpsをAWSのサービスを用いて進めていますが,その内容については別の機会にご紹介出来たらと思います.


目次


MLOpsについて

最近色々とMLOps関連の話がありますが,包括的な内容としては,「MLOps Principles」や「ゆるふわMLOps入門」がとても参考になります.ここで述べられているMLOpsとは,MLシステムの構築運用に伴い生じる以下の課題を,効率的かつ継続的に取り組んでいくためのフレームワークや考え方のことになります.(MLOpsの定義は文脈により様々なのでご注意下さい)

  • バージョン管理
    • データセット,モデル,パラメータ,設定ファイルやその他生成物のバージョン管理など
  • テスト
    • データのバリデーションテスト,パイプラインテスト,単体/結合テスト,パフォーマンステストなど
  • 自動化
    • モデル作成のワークフローの自動化,CI/CDを使ったシステムの自動化など
  • 再現性
    • 特徴量生成の再現性,モデルの再現性,開発環境とプロダクション環境の環境差分など
  • デプロイ
    • プロダクション環境への継続的なデプロイなど
  • モニタリング
    • データドリフト,モデルの劣化,推論のパフォーマンスなど

加えて,組織的な問題や上記開発プロセスのイテレーティブな実行などもあります.
かなり広範囲でやるべきことが色々とあるなという印象ですが,MLOpsにもレベルがあるのでそちらを参考にしても良いかもしれません.
レベルの参考になるものとして,Microsoftが定義しているMachine Learning Operations maturity modelというMLOpsの成熟度を表すものがあります.これによると下図のレベル0~4までの5段階にまとめられています.

f:id:connehito-mkashiwagi:20210929223356p:plain
MLOps maturity model

MLOps maturity modelは,人/カルチャー・プロセス/構造・オブジェクト/技術を定性的に評価するもので,各レベルで上記課題に関連する内容が整理されています.これを確認することで自分達のチームがどこに位置して何が出来ているのか,また逆に何が出来ていないのかを把握することができます.
ちなみに僕たちのチームは現状レベル2で一部テストに関する項目は不十分なところが存在しています.対応出来ていない部分を把握することで次にどこに手を付ければ良いかを判断したり,もしくは何から手を付けたら良いかわからない場合には一度こちらを参考にして,自分達のチームに足りていない部分を整理し,優先順位を立てて取り組むのが良いと思います.

ML Test Scoreとは

今回のメイントピックになります.MLOps maturity modelと似た内容がこちらにも含まれていますが,MLOps maturity modelが定性的に評価するものに対して,ML Test ScoreはMLシステムを定量的に評価できるものになります.

これはGoogleが発表したThe ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reductionという論文で紹介されているものになります.また日本語訳された記事([抄訳] The ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reduction)もあるので,参考にしてみて下さい.

ここでは,「オンラインで継続的に学習され,推論を行う教師ありの機械学習システム」を前提にしています.論文で言及されているテスト項目は大きく以下の4つになります.

  1. 特徴量とデータのテスト
  2. モデル開発のテスト
  3. 機械学習インフラのテスト
  4. 機械学習のモニタリングテスト

各項目に7つの検査項目があり,スコアリング方法は「結果をドキュメント化し,テストを手動実行している」場合は0.5ポイント,「テストを自動的に繰り返し実行できるシステムがある」場合は1.0ポイントとなっています.4つのテスト項目毎にポイントを合計(7つの検査項目の合計)し,4つのテスト項目のポイントの最小値が最終的なML Test Scoreになります.これはどれか1つだけ高くても良くなく,4つのテスト項目すべてが重要であることを意味しています.ML Test Scoreを上げるためには,4つのテスト項目すべてのポイントを上げる必要があります.

Googleのいくつかのチームを対象にした調査では,80%以上のチームが上記4つのテスト項目の多くを実施出来ていなかったということです.下図は調査したチームの4つのテスト項目のスコア平均値になりますが,Googleでも高得点が取れていないことがわかります.やはりMLシステムの構築運用時に生じる課題をクリアすることが難しいと改めて感じます.

f:id:connehito-mkashiwagi:20210930013350p:plain
調査したチームのスコア平均値

また,ML Test Scoreの解釈も記載されています.(下記表は[抄訳] The ML Test Score: A Rubric for ML Production Readiness and Technical Debt Reductionからの引用)

ポイント 解釈
0 プロダクションシステムというよりは研究プロジェクト
(0, 1] 総合的にテストはされていないが,可能な限り信頼性向上に努めている
(1, 2] 基礎的なプロジェクトの要求事項は通過した.しかし,信頼性向上のためのさらなる投資が必要とされる
(2, 3] 適切なテストがされている,だが更に自動化の余地が残っている
(3, 5] 信頼性の高い自動化されたテストとモニタリングレベル.ミッションクリティカルな状況でも問題はない
> 5 卓越したレベルの機械学習システム

どのようにML Test Scoreをスコアリングしたのか

まず代表となる機械学習プロジェクトを選択し,それをベースにスコアリングをするようにしました.実際のプロジェクトに当てはめてスコアを付けていくのがイメージも付けやすいし良いと思います.
スコアリングは日本語訳をしてくれているShunya Uetaさんが公開してくれているGoogle Spread Sheetsを用いて行いました.簡単に計算できるようになっているので,非常にありがたかったです!

各検査項目については論文を見て頂ければわかりますが,検査項目とその内容,さらにどのようにすればいいか(How)が書かれています.しかし,それだけだと少しわかりづらい部分もあるので,検査項目の内容をもう少し噛み砕いて自分達のケースだとどうなるかを議論しました.

例えば,特徴量とデータのテスト項目にある「Data 2: 有効な特徴量かどうかを判別できていること」では,以下のように考えています.

  • 「Data 2: 有効な特徴量かどうかを判別できていること」とは?
    • 不要な特徴量を使わないようにする
    • どういう特徴量かを把握することが大切
    • タスク毎に有効/無効な特徴量がある
    • 自動化するイメージとしては,GBDTのFeature Importanceを確認して,閾値以下の値を切り捨てる

こちらは一例ですが,全ての項目に対して内容を深掘りをして,具体的にイメージしやすいようにしました.また,スコアを付ける上で手動・自動の定義は先に述べたように定義がありますが,この部分もどういった状態であれば自動化された状態なのかと再定義をしています.

機械学習インフラのテストについての手動・自動の定義をサンプルとして載せておきます.いざ実際に自動化をしていく時に,どういった状態になればいいかわからないと進んで行かないと感じたので,チーム内で議論しながら整理して行きました.

f:id:connehito-mkashiwagi:20210930161227p:plain
参考: 機械学習インフラのテスト

項目については,自分達のチームに必要だと思うものは追加したり,中にはピンと来ない項目もあって削除しようかと考えるものもありました,スコアリングはあくまで一つの指標なので拘りすぎなくても良いかなと考えています.最初に書いた目的のためにより良くなるのであればアップデートしていくことが大事だと思っています.(本来は検査項目の数を合わせないとスコアが本来の値と乖離してしまうので,ご注意下さい)

ML Test Scoreでスコアリングした結果

4項目についてスコアリングした結果,OVERALL ML TEST SCORE = 1.5(参考値)になりました.各項目のスコアは以下になります.

  1. 特徴量とデータのテスト: 合計スコア = 2.5
  2. モデル開発のテスト: 合計スコア = 2.0
  3. 機械学習インフラのテスト: 合計スコア = 1.5
  4. 機械学習のモニタリングテスト: 合計スコア = 2.5

解釈によると「基礎的なプロジェクトの要求事項は通過した.しかし,信頼性向上のためのさらなる投資が必要とされる」というレベル感になります.結果を見ると,インフラの部分がまだまだ足りていないことがわかります.また検査項目毎で見ていくと0の項目も多く,まずは手動でも実践しないといけないなと感じる部分が多くありました.

伸びしろも多くあるので,これをベースに足りていない部分を補強して1つずつMLOpsを進めて行きたいと思います.

おわりに

今回は,ML Test Scoreを使って現状の機械学習システムのスコアリングを実施しました.現状把握をすることが出来たので,ようやくスタートラインに立てたかなと思います.まだまだ足りていない部分は多いですが,出来ることが多くあるのでツールなどを活用しながら進めて行きたいと思っています!これからが楽しみですね!!

最後に,私たちのチームではデータを活用したサービス開発を一緒に推進してくれるデータエンジニアを募集しています. 少しでも興味を持たれた方は,ぜひ一度お話させてもらえるとうれしいです.

hrmos.co

参考

iOSDC2021で「知られざる課金ステータス」というタイトルで発表してきました

こんにちは!コネヒトでiOSアプリの開発をしている @ohayoukenchan です。
2021.9.17-9.19に開催されたiOSDC2021で発表してきました。

iOSDCとは?

iOS関連技術をコアのテーマとしたオンラインカンファレンスです。

今回はオンラインということで、トークは事前収録されニコ生で配信されました。 また、トークセッション後のAsk The SpeakerはDiscordにて行なわれました。 事前収録のおかげでトーク中もDiscordで質問にお答えする余裕があったりオンラインイベントならではの良さがありました。

コネヒトはシルバースポンサーとして協賛させていただきました!

自分のトーク内容

お試しオファーの無料期間中にプロモーションオファーを適用したり、プロモーションオファーの無料期間中にプロモーションオファーを適用したりすると、ユーザーの課金状態がどのようになるかについてお話しました。

オファーの比較

それぞれのオファーについて簡単な表にしてみました。 オファーコードに関しては省略しております

お試しオファー プロモーションオファー
主な用途 新規サブスクリプション利用者の獲得 既存のサブスクリプション利用者の維持と過去の利用者の再登録
利用資格 App内の新規サブスクリプション利用者 App内の既存または過去のサブスクリプション利用者
利用の限度 サブスクリプショングループごとに1件のお試しオファーを利用可能 デベロッパはユーザーが何件までオファーを利用できるか決定 する
オファーの限度 サブスクリプションごと、地域ごとに、1件のオファー サブスクリプションごとに、10件のアクティブなオファー
互換性(iOS) iOS 10以降 iOS 12.2以降

同一サブスクリプションのプロモーションオファーを適応する場合

プロモーションオファーはサブスクリプションに紐づく形で作成できます。
オファー適用前のサブスクリプションが適用するオファーに紐づく場合は、現在の無料期間終了後にプロモーションオファーが適用されます。

お試しオファー無料中の場合、お試しオファー終了後にプロモーションオファーが適用される

f:id:ohayoukenchan:20210928180731p:plain
プロモーションオファーが課金前のサブスクリプションに紐づく場合

課金中の場合、契約期間終了後にプロモーションオファーが適用される

f:id:ohayoukenchan:20210928181857p:plain
プロモーションオファーが課金前のサブスクリプションに紐づく場合(課金中)

クロスグレードが発生する場合

クロスグレードはユーザーが、同等のレベルのサブスクリプションに切り替えることを指します。詳しくはこちらをご確認ください。

プロモーションオファーが紐付いていない別のサブスクリプションからプロモーションオファーを適用させることも出来ます。 その場合、グレードと契約期間からクロスグレードの条件が決まります。 今回はすべて同一グレードのみの検証となっています。 グレードが異なる場合アップグレードやダウングレードの対象となりますのでご注意ください。

別のサブスクリプション(期間が同じ)のプロモーションオファーを適用する場合

別のサブスクリプションで期間が同じサブスクリプションからプロモーションオファーを適用する場合は現在の契約は即時終了します。

お試しオファー無料中の場合、お試しオファーは即終了しプロモーションオファーの無料期間になる

f:id:ohayoukenchan:20210928180836p:plain
プロモーションオファーが課金前のサブスクリプションに紐づかない場合(お試しオファー中)

課金中の場合、現在の契約は終了し、プロモーションオファーの無料期間になる。そして契約期間の差額分返金される

App 内課金が同じ期間のものである場合、前の App 内課金から比例配分された金額は、元の支払い方法で返金されます。

f:id:ohayoukenchan:20210928181239p:plain
プロモーションオファーが課金前のサブスクリプションに紐づかない場合(課金中)

別のサブスクリプション(期間が異なる)のプロモーションオファーを適用する場合

同じグレード、同じ期間の場合は現在適用中のオファーは即破棄されていましたが、期間が異なる場合は条件が異なります。 期間が異なる場合、オファー適用は現在の契約が終了した後の適用となります。

お試しオファー無料中の場合、お試しオファー終了後にプロモーションオファー適用になる

f:id:ohayoukenchan:20210928181315p:plain
プロモーションオファーが課金前のサブスクリプションに紐づかない場合(お試しオファー中)

課金中の場合、契約期間終了後にプロモーションオファー適用になる

App 内課金が異なる期間のものである場合、クロスグレードは、カスタマーの次の更新日に有効になります。 同一サブスクリプションのプロモーションオファーを適用する場合と同じ挙動となります。

f:id:ohayoukenchan:20210928181408p:plain
プロモーションオファーが課金前のサブスクリプションに紐づかない場合(課金中)

プロモーションオファー無料期間中にプロモーションオファー

プロモーションオファーの無料期間中に再度プロモーションオファーを適用させようとすると、現在適用中のオファーとは別に1件までペンディングさせることができます。ペンディング中のオファーはレシート情報のpending_renewal_infoに記録され、次回更新日に有効化することができます。 pending_renewal_infoは常に最新のオファーしか記録しないので複数のプロモーションオファーを適用待ちにできないので、プロモーションオファーを何回かけても最新のオファー一件のみしかペンディングさせることが出来ないことに注意してください。

f:id:ohayoukenchan:20210928181445p:plain
プロモーションオファー無料期間中にプロモーションオファーを適応させ、 再度プロモーションオファー

話さなかったこと

7日間無料状態で課金を試すときは鍛錬が必要

sandboxでの7日間は5分しかないので無料期間内にオファーを追加するのが大変だったりしました。 sandboxアカウント名を短くしたり、事前に課金開始画面を開いておいたり。DBでレシート検証するユーザーの情報を事前に用意したりすることでいかに早く処理を完了するかを鍛えることができました。

sandbox環境で何度やっても想定通り行かない日がある

私はexpire_dateを過ぎても全く更新されない地雷を踏みました 別の日に試すとすんなりうまくいったので、sandbox調子悪いなとおもったら別の日に試したほうがよさそうです。

ハマったこと

サーバー側で現在の自動継続課金の有効期限を、レシート内の一番未来のexpire_date_msでソートして取得していたが、お試しオファー1年無料の後に、6ヶ月無料を適用させた場合、有効期限が6ヶ月後を想定しているのに対して、破棄された1年無料の有効期限を取得してしまっていた。サーバー側で保持した有効期限はクライアント側で表示ロジックの制御に使用していたので、有効期限が異なり、ユーザーに適切な体験を提供できなくなりそうだった。最新のレシートの有効期限を取得することで回避しました。

終わりに

オファーの切り替えなどはユーザーが意図しないケースも多く、ユーザー体験を損ねてしまっているのではないかと感じています。 ママリでは購読開始前に現在の課金ステータスから、返金対象だったり契約が消えてしまう可能性のあるユーザーに注意文言を表示していますが、そもそも確認されているのかも怪しいと思っています。 StoreKit2の発表により、アプリ内で現在の課金状態を表示することが出来るようになったり、返金APIが追加されたりするのでユーザー体験を損ねないようなユーザー体験を提供できそうですね。

コンポーネントカタログ・ユニットテストを停滞させないための開発フロー設計

こんにちは、リードエンジニアの @dachi_023 です。4ヶ月前に Storybookを利用した開発フローの設計 という記事を書いてから今も引き続き上手くやれてますよ、という話とテスト運用についても改善を入れたことなどについて書きます。プロダクトコード外の運用ができていないなと感じる方向けの記事になればいいなと思っています。

運用し続けることが大事

テストがあることで仕様を担保しながら安心して実装することができます。コンポーネントカタログがあることでコンポーネントのドキュメントを簡単に生成でき、また UI に関する話をする際に実際に動くものとして提示できる材料になります。

といったようにテストもコンポーネントカタログも特定のシーンで大きく効果を発揮するものですが、導入しただけでは意味がなく運用し続けるための工夫が必要です。例えば特定のケースが担保できていないテストや実装時にコンポーネントカタログに追加し忘れたコンポーネントがある状態が放置され続けていればその効果も薄まりますし、それを解消する方向に導かなければ足りない箇所がどんどんと増えていくかもしれません。

これらを達成するために意図通りの仕様で動作しているかを検査する仕組みを作ったり、日常的に取り組めるよう開発フローの一部として組み込んだりすることが重要だと考えています。入れたら勝手に運用が回っていく、ということはそうそうないと思います。

コンポーネント単位で PR を作る

(冒頭に貼り付けた記事 に経緯などが書かれていますので是非読んでみてください)

私達のチームではいきなりページ全体を実装する、といった方法は取らずコンポーネントごとに実装しています。ページごと丸っと誰かをアサインして開発していくといったこともできなくはないですがページによって実装難易度も大きく変わってきますし、何日もかかったりすると他の作業をブロックしたり小さくリリースすることが難しくなります。また、コード量や対象範囲が大きくなることでレビュー時にレビュアー・レビュイーの双方に負担をかけかねません。

そういったことを回避するためにまずはページ内で新規実装する必要があるコンポーネントの実装を GitHub Issue 化して個別に対応 → Storybook に追加・レビュー → ひと通り揃ったところでページを組み立て、というフローにしています。

f:id:dachi023:20210831120416p:plain
1つの機能を実装するために複数の Issue に分解しカンバン上で管理する

上記を実践するためのツールとして Storybook を採用しています。コンポーネントを個別に実装していった際の「実際に動く場所がなくてレビューできない」という問題を解消するために利用しています。これによって小さく実装し、小さくリリースしていくことができています。また、コンポーネントを1つずつ PR にしていくことでレビュー時の観点が絞られるので (コンポーネントとしての設計・コード・UI の挙動) 指摘や修正が広範囲にならずに済みますし、ページを組み立てた際のレビューもページとしての仕様が担保できているか、を中心にレビューすれば良くなります。

非同期コミュニケーションの回数が増えると作業が中断されたりボールが返ってくるまでに時間がかかったりするので分解できるものは分解して、というのが基本方針です。

テストケースの考慮不足に気づく

テストに関しても同様で「開発フローに組み込む」「レビューで活用する」といった考えでやっています。これを実現するためにチームでは Codecov を利用しています。テストは実装されているか、どのケースを考慮してテストを書いたか、テスト済み以外のコードでテストすべき点はないか、などを見つけるために有効です。

f:id:dachi023:20210830151249p:plain
テストが当該行を通過していない時に出るメッセージ

網羅率に関するルールなどは特に明記してありませんが「アプリケーション全体としてある程度書けていること」をチェックするために全体の網羅率が 90% を切った時に CI が赤くなるよう設定しています *1

機械的に判定 (カバレッジが担保できているかチェック) するだけでなく「仕様に対して適切なテストができているか」をレビューするところまでをセットとして捉えています。

まとめ

本記事で挙げたようなツールの運用がストップしてしまうのは「プロダクト開発に必須でない (= プロダクションで動かない)」という意識なのではないかと思っています。

短期的に考えたらあまり効果的ではないかもしれないです。しかし、チームとして過去のコードを活かしながら何年も開発し続けるためには重要な要素です。入れるだけではなく、しっかりと活用するところまでイメージしながら開発していきましょう。

*1:厳密に設定しているわけではなく、都度様子見ながら運用しています

Slack上での出欠確認を楽にする「点呼さん」というbotをつくりました

点呼さんのイメージ
点呼さんのイメージ

こんにちは。CTOをやっている @itosho です。コネヒトでは「Beyond a Tech Company」というテックビジョンを掲げているのですが、テックカンパニーの条件の一つとして「社内の人がテクノロジーの恩恵を受けている」ということを重要視しています。ですので、以前からインナーコミュニケーションを盛り上げるための開発社内ツールの開発を積極的に行っています。というわけで、この記事では最近開発したSlack Botである「点呼さん」の紹介をしたいと思います。

点呼さんとは?

コネヒトでは昔からSlack上でイベントの出欠をとることが多く、こんな感じで「参加」「不参加」のリアクションをつけてもらうことで出欠確認を行ってきました。

f:id:itosho525:20210730133947p:plain
出欠確認の様子

社員の数が少ないうちはこのやり方で問題なかったのですが、嬉しいことに社員の数がどんどん増えてきた結果、誰がリアクションしていないかを確認してリマインドを送ったり、誰が参加者なのかを把握したりする手間が増えてきました。解決策として、tmpチャンネルを用意して、対応を行った人から抜けるという方法も一部では行っていますが、ものによってはToo Muchなケースも多くありました。もちろん、このまま人力で頑張るのはスケールしないやり方ですし、何よりテックカンパニー感がありません。

そこで開発したのが「点呼さん」です。点呼さんはSlackのリアクションで出欠をとる時のリマインドや集計のサポートをしてくれるSlack Botです。

使い方

基本的な使い方

まずは点呼さんを出欠確認を行いたいチャンネルにinviteします。

f:id:itosho525:20210730135510j:plain
点呼さんをinviteする

その後、こんな感じのフォーマットで点呼さんにmentionを送ります。(最後のモザイクがかかっている部分はSlack内のメッセージURLです)

f:id:itosho525:20210730135934j:plain
点呼さんにmentionを送る

そうすると、点呼さんから対象のSlackメッセージにリアクションをまだつけていない人のリストが返ってきます。(モザイクがかかっている部分はユーザーIDの一覧です)

f:id:itosho525:20210730140428j:plain
点呼さんからリストが届く

あとはコードブロックの部分をコピペしてもらえば、リマインドなど自由にメッセージを送ることが出来ます。

ちなみに点呼さんの返事をDMにしている点とそのまま点呼さんがリマインドしないようにしている点は意図的で、人によっては自分のユーザー名をキーワード通知している可能性があるのと時間帯を気にせず使えるようにしてもらいたいというのが理由の一つです。その代わり、コピペですぐメッセージを送れるようにしています。

発展的な使い方

@点呼さん 教えて!◯◯ SlackURL が基本的なフォーマットになっていて、リアクションをしていない人のリストを作る以外にも…

  • @点呼さん 教えて!参加する人 SlackURL とすると参加する人のリスト
  • @点呼さん 教えて!不参加の人 SlackURL とすると不参加の人のリスト
  • @点呼さん 教えて!リアクション名 SlackURL とすると特定のリアクションをした人のリスト
  • 例えば、doneリアクションをした人みたいな使い方を想定

といったようなリストの確認も出来ます。

また、抽出する対象をチャンネルにjoinしている人ではなく、特定のSlackグループで絞り込むことも出来ます。ユースケースとしては、たくさん人がいるチャンネルだけど、開発組織の歓迎会の出欠をエンジニアやデザイナーにリマインドしたい時なんかを想定しています。

f:id:itosho525:20210730142746j:plain
グループで絞り込みを行いたい時

点呼さんの実装

特殊なことはしておらずSlack Botをつくって、そこから上述の仕様を実現するためにSlack APIを適宜叩いています。Goで書いているのでSlackのAPI連携やメンション時のイベントハンドリングなどは slack-go/slackを利用させてもらっています。

コネヒト独自の仕様のところは参考にならないと思いますが、SlackのAPI連携している箇所はもしかしたら参考になる部分もあるかもしれないので、コード片を晒しておきます。プロダクションコードではないので、ゆるく書いている箇所もありますが、NYSL的な感じで自由に使ってください。

// slack-go/slack をimportしている前提です

// BotのSlackクライアントとuser APIのSlackクライアントが存在するのでこんな書き方になっています
type User struct {
    Client *slack.Client 
}

//  全ユーザーを取得する処理
func (u User) GetAllUsers() (users map[string]string, err error) {
    res, err := u.Client.GetUsers()
    if err != nil {
        return users, err
    }

    users = map[string]string{}
    for _, v := range res {
        if !v.IsBot {
            users[v.ID] = v.Profile.DisplayName
        }
    }

    return users, nil
}

// 指定したチャンネルにjoinしているユーザーを取得する
func (u User) GetUserIDsInChannel(channelID string) (userIDs []string, err error) {
    params := &slack.GetUsersInConversationParameters{ChannelID: channelID, Limit: 1000}
    res, _, err := u.Client.GetUsersInConversation(params)
    if err != nil {
        return userIDs, err
    }

    return res, nil
}

// 指定したSlackグループ名(人間が利用するもの)からグループID(APIで利用するもの)を取得する
func (u User) GetGroupIDs(groupHandles map[string]bool) (groupIDs []string, err error) {
    res, err := u.Client.GetUserGroups()
    if err != nil {
        return groupIDs, err
    }

    for _, v := range res {
        if _, ok := groupHandles[v.Handle]; ok {
            groupIDs = append(groupIDs, v.ID)
        }
    }

    return groupIDs, nil
}

// 指定したグループIDに所属するユーザーID一覧を取得する
func (u User) GetUserIDsInGroups(groupIDs []string) (userIDs []string, err error) {
    for _, v := range groupIDs {
        res, err := u.Client.GetUserGroupMembers(v)
        if err != nil {
            return userIDs, err
        }
        userIDs = append(userIDs, res...)
    }

    return sliceUnique(userIDs), nil
}

// 指定したSlackメッセージ(チャンネルIDとタイムスタンプ)から指定したリアクションをしたユーザーID一覧を取得する
func (u User) GetActionedUserIDs(channelID string, timestamp string, reactionName string) (userIDs []string, err error) {
    item := slack.ItemRef{Channel: channelID, Timestamp: timestamp}
    params := slack.GetReactionsParameters{Full: true}
    res, err := u.Client.GetReactions(item, params)
    if err != nil {
        return userIDs, err
    }
    if len(res) < 1 {
        return userIDs, errors.New("unexpected length of GetConversationReplies")
    }

    if reactionName == "" { // The target is all reaction.
        for _, reaction := range res {
            userIDs = append(userIDs, reaction.Users...)
        }
        userIDs = sliceUnique(userIDs)
    } else {
        for _, reaction := range res {
            if reaction.Name == reactionName {
                userIDs = reaction.Users
                break
            }
        }
    }

    return userIDs, nil
}

実装時に意識をした点

カッとなって勢いで作った部分もあるのですが、その中でもユーザーに入力を求める類のSlack BotはUNIXコマンドに似たところがあると思うので、Unixの哲学を意識し、出来る限りシンプルさを保つようにしました。

具体的には、先ほど述べたようにリマインド機能やDMではないチャンネルで結果を返すことなども検討したのですが、それを実現しようとするとオプションがどんどん増えていってしまいインターフェイスも複雑になります。そうすると、結果的に何でも出来るけど何が出来るかわからないツールになるリスクがあるので、オプションは最小限に留めるようにしました。そして、出欠確認のリマインドを楽にするという課題にフォーカスし @点呼さん 教えて!まだの人 SlackURL だけ覚えれば、まずはBotが使えるという状態を作り、そこでこのBotの利便性を感じてもらってから発展的な使い方をしてもらえるような工夫を行いました。

実際、利用してくれる方はちょくちょくいるので、ある程度のその設計は上手くいったのかなと思っています…!

最後に

会社が大きくなると、事業だけではなく、組織の面でも様々な課題が生まれます。そういった課題もテクノロジーやエンジニアリングの力で解決出来る会社が、冒頭にも述べたようにテックカンパニーだと僕は考えているので、引き続き、技術で勝っていくことで、プロダクトのユーザーだけではなく、社内の人も少しでも幸せにしていくぞ!

というわけで、コネヒトでは自分の書いたコードで社会や組織を良くしていきたいエンジニアを大大大募集しております! 1mmでも興味がありましたら、下記のWantedlyから「話を聞きに行きたい」ボタンをポチっと押していただくか、僕にDMを雑に送っていただいても構いませんので、まずはカジュアルにお話させていただければと思います!

hrmos.co