コネヒト開発者ブログ

コネヒト開発者ブログ

ECS Scheduled Taskの管理をecscheduleでGitOps化しました

こんにちは。コネヒトのテクノロジー推進グループでインフラエンジニアをしている laughk です。 今回は定期実行バッチで利用しているECS Schedule Taskの管理に Songmu/ecschedule を導入し、GitOps化した話をまとめます。

サマリ

  • ecscheduleを導入する前の定期実行バッチの管理状況と課題
  • 技術選定 - ecshceduleを選定した理由
  • 導入プロセス
  • GitOps化
  • 導入後どうなったか

ecscheduleを導入する前の定期実行バッチの管理状況と課題

コネヒトでは提供するサービスのWeb基盤にAmazon ECSをフル活用しており、定期実行バッチにおいてもECS Scheduled Taskを利用しています。ECS Scheduled TaskはECS Taskを cron のように定期実行でき、とても便利なものです。一方でその管理においては利用する ECS Task の定義、EventBridge のルールなどの複数のAWSリソースを組み合わせる必要があり、煩雑になりがちです。

ECS Scheduled Taskの導入に関しましては別途記事がありますので、興味がある方はそちらも参照いただければうれしいです。

tech.connehito.com

ecschedule導入以前もECS Scheduled Taskのインフラの一部をTerrafrormによって管理をしている状況ではありました。管理対象はバッチ実行に利用するTask Definitionのみで、1つのバッチに対し1つのTask Definitionを用意し、スケジュール部分は手動で設定を行うという運用です。

しかしながらこの方法では次のような問題がありました

  • 各バッチで利用するコンテナイメージは同一のものであり、違いは実行コマンドのみであるのにそれぞれ別のリソースとして定義する必要があった
  • Task Definitionの container_definitions は JSON を文字列で記載する必要があるため、似たようなコピペが量産されている状況だった
  • スケジュールはコメントアウトでメモ程度に書かれているだけで、実際に適用する際は手作業で行っていた

特にTask Definitionのコードは次のような形で、container_definitioncommand 以外ほぼ同じもののコピペがほとんどを占めている状況で、開発者にとってもわかりやすいものではありませんでした。

image

技術選定 - ecshceduleを選定した理由

この状況を打開する手段はいくつか考えられましたが最終的に次の理由でecscheduleを採用しました。

github.com

  • ECS Scheduled Taskの管理に特化しているので、余計な機能がなくシンプル
  • ECSクラスタ名を指定し、その中のTask Definitionを指定するという思想は今のコネヒトのECSクラスタの状況と相性がよかった
  • タスクの単発実行にも対応している ( ecschedule run -conf conf.yaml batchName1 みたいな実行)
  • containerOverrides も含め、一気通貫YAMLで書ける

ほかの手段として Ansible の AWSモジュール を利用することも検討しました。

docs.ansible.com

こちらでもかなり効率的にECS Scheduled Taskの管理が行えるとみていました。しかしながらcontainerOverrideはJSON文字列を渡す必要がある点や、社内でAnsibleがバリバリ使われている状況ではありませんでした。開発者も巻き込んで使えるツールとしてはシンプルさでecshceduleに劣ると判断し、今回は利用を見送りました。

導入プロセス

ecscheduleにはECSクラスタ名を指定して、既存のECS Scheduled Taskの状態をYAML形式で出力するdump機能があります。そのため使い始めること自体はとても簡単にできました。次のようにコマンドを実行するだけでOKでした。

$ ecschedule dump -cluster cluster1  --region ap-northeast-1 > cluster1/event-rules.yaml

ですが、コネヒトの場合は既存のECS Scheduled Taskを管理するだけではTask DefinitionのTerrafrorm管理からは解放されませんでした。そのためバッチを実行する専用のTask DefinitionをECSクラスタにつき1つ用意し、EventBridge RuleのContainerOverrideでコマンドを指定するように構成を変更しました。

before after
image image

コネヒトではdev環境(開発環境)とprd環境(本番環境)でそれぞれ3つのECSクラスタでECS Scheduled Taskを利用している状況で、最終的に次のようなファイル構成に落ち着きました。

.
|-- cluster1/
|   |-- dev-events-rules.yaml
|   |-- dev-task-definition.json
|   |-- prd-events-rules.yaml
|   `-- prd-task-definition.json
|-- cluster2/
|   |-- dev-events-rules.yaml
|   |-- dev-task-definition.json
|   |-- prd-events-rules.yaml
|   `-- prd-task-definition.json
`-- cluster3/
    |-- dev-events-rules.yaml
    |-- dev-task-definition-ec2.json
    |-- dev-task-definition-fargate.json
    |-- prd-events-rules.yaml
    |-- prd-task-definition-ec2.json
    `-- prd-task-definition-fargate.json

ecschedule向けのYAMLファイルを (クラスタ名)/(環境名)-event-rules.yamlとしています。一方で、バッチで利用するTask Definitionの管理はecscheduleではできません。そのため執筆時点ではaws cliで適用可能なJSONを(クラスタ名)/(環境名)-task-definition.jsonにおいています。一つだけEC2とFargateを併用しているクラスタがあるため、そちらはEC2向けとFargate向けで別々に用意しています。

これらのファイルをGitHubリポジトリに乗せ、PRベースでECS Scheduled Task上のバッチ追加、変更をコード管理できるようにしました。PRがApproveされ、マージされたらEventBridge Ruleについてはecscheduleのapplyを実行するだけで変更の適用のほとんどが完結するようになり、運用負荷を大幅に減らすことができました。1

GitHub ActionsでGitOps化

ここまででもGitHubリポジトリ上のYAMLでバッチの管理が可能になり、とても便利になりました。しかしながら、変更が発生した際に人の手でYAMLの内容を適用する必要があることに変わりはなく、どのタイミングで変更が適用したのかということ人が意識しておく必要がありました。

そこでecscheduleのYAMLを編集する際に発生する実行コマンドをMakefileを用意してTask化し、その内容をもとにGithub Actions経由で自動適用できるところまで対応しました。

Makefileは次のような内容です。

ENV=dev

.PHONY: diff-all
diff-all: diff-cluster1-api diff-cluster2 diff-cluster3

.PHONY: apply-all
apply-all: apply-cluster1-api apply-cluster2 apply-cluster3

.PHONY: diff-cluster1-api
diff-cluster1-api:
  ecschedule diff -conf ./cluster1-api/$(ENV)-events-rules.yaml -all

.PHONY: diff-cluster2
diff-cluster2:
  ecschedule diff -conf ./cluster2/$(ENV)-events-rules.yaml -all

.PHONY: diff-cluster3
diff-cluster3:
  ecschedule diff -conf ./cluster3/$(ENV)-events-rules.yaml -all

.PHONY: apply-cluster1-api
apply-cluster1-api:
  ecschedule apply -conf ./cluster1-api/$(ENV)-events-rules.yaml -all

.PHONY: apply-cluster2
apply-cluster2:
  ecschedule apply -conf ./cluster2/$(ENV)-events-rules.yaml -all

.PHONY: apply-cluster3
apply-cluster3:
  ecschedule apply -conf ./cluster3/$(ENV)-events-rules.yaml -all

このMakefileを利用することを前提に、次のような .github/workflow/apply.yaml を用意しました。

name: apply
on:
  push:
    branches:
      - main

jobs:
  apply:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: setup ecschedule
        env:
          ECSCHEDULE_VERSION: 0.3.1
          ECSCHEDULE_MD5_HASH:  28bdd174e4f012e0345d701ac46f1038
        run: |
          sudo apt-get install -y make
          curl -L https://github.com/Songmu/ecschedule/releases/download/v${ECSCHEDULE_VERSION}/ecschedule_v${ECSCHEDULE_VERSION}_linux_amd64.tar.gz -o ecschedule.tgz
          echo "${ECSCHEDULE_MD5_HASH}  ecschedule.tgz" | md5sum -c -
          tar vxf ecschedule.tgz
          sudo mv -v ecschedule_v${ECSCHEDULE_VERSION}_linux_amd64/ecschedule /usr/local/bin/.

      - name: apply (dev)
        env:
          AWS_ACCESS_KEY_ID:  ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          make apply-all ENV=dev

      - name: apply (prd)
        env:
          AWS_ACCESS_KEY_ID:  ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          make apply-all ENV=prd

AWSを利用するためのsecretsはあらかじめ登録しておくことが前提です。applyはmainブランチの内容は毎回適用しても問題ないだろうと判断しているため、細かい条件は指定せずすべて適用するようにしています。

最後にApproveなしのコードがmainブランチに入ることを防ぐため、リポジトリの「Require pull request reviews before merging」を有効にします。執筆時点ではインフラエンジニアのレビューを必須にするため「Require review from Code Owners」も有効にしています。

image

これで人の手を介さずコードの内容の適用が可能になり、バッチ追加・変更のほとんどはGitHubのPullRequestで完結するようになりました。

導入後どうなったか

ecscheduleによるGitOps化ができてからは、開発者がローカルにecscheduleの実行環境を持つ必要もなくなり、バッチの更新・追加についてはGitHubのPullRequest上のやり取りで完結するようになりました。ecscheduleの仕様上自動削除には対応できてはいませんが、それでもほとんどの運用がYAMLの編集のみで完結するようになりました。

image

また計画停止が発生する際にバッチごとに影響範囲をPullRquest上で確認しながらYAMLに落とし込むことができ、とても効率的かつ、チームの議論の過程を残しながら対応ができるようになりました。参考までに、最近実際にPullRequest上で確認しながら計画停止を行った様子を紹介します。

image

image

image

合意をとった状態で対象のブランチをローカルに持ってきてecscheduleのapplyコマンドを実行すれば、その通りに確実にバッチを停止させられるため確実にオペミスを避けることができ、とても安心して計画停止を実施できるようにもなりました。

さいごに

ecscheduleを導入しGitOpsの実現によってECS Scheduled Task上の定期実行バッチの運用負荷を下げた取り組みを紹介しました。

今回紹介した話は来月3/17(水)13:30~18:00に開催される「KGDC Tech Conference #0 通信インフラだけじゃないKDDIグループの多彩な技術」というオンラインイベントでもお話いたします。ぜひ興味ある方、「直接話を聞いてみたい!」という方はご参加いただけるとうれしいです。

kgdc.connpass.com

この記事はShodo (https://shodo.ink) で執筆されました。


  1. Task Definitionについては、latestタグのDockerイメージを利用している関係で更新はほとんど発生していません。

レコメンデーション機能を実装するまでの "not 技術的" な取り組みについて

こんにちは。テクノロジー推進グループで機械学習周辺を担当しているたかぱい(@takapy0210)です。

最近観葉植物を購入しました。デスクに緑があると仕事が捗る気がし(ry

f:id:taxa_program:20210224093540j:plain:w600
仕事を捗らせてくれる魔法の植物

今回は質問のレコメンデーションをアプリに実装*1するまでの中で行ってきた、チームビルディングやプロジェクトの進め方についてお話しようと思います。
(技術的な話は、2021年02月25日に開催するイベント*2でお話しできればと思います!)


目次


はじめに

2020年11月頃から、テクノロジー推進グループはデザイングループと共創し、アプリ内にレコメンデーション機能を導入するプロジェクトを始めました。

本エントリでは、

  • 初めて一緒に仕事をする(デザイナーと一緒に仕事をするのが初めてだった)
  • ステークホルダーが多い(アプリに新しい機能を実装するので、CTO, PdM, デザイナー, エンジニアなどが関係している)

というプロジェクトの中で、どのようなことに取り組みながらプロジェクトを進めていったのかについてご紹介できればと思います。

取り組んだこと

以下、時系列に実践してきたことを紹介していきます。

ドラッカー風エクササイズ

冒頭で述べた通り、テクノロジー推進グループとデザイングループで1つのプロジェクトを行うのは初めてでした。
もちろんランチなどで話をしたことはあるものの、知っているようで知らないことも多いのでは?という思いもありました。

そこで、まずは仲間のことを知る時間を作ろうということで、ドラッカー風エクササイズを行いました。

ドラッカー風エクササイズとは?

  • チームビルディングの手法
  • チーム全員がそれぞれお題の質問に答え、共有し合うワーク
  • 相互理解の促進と期待のすりあわせという効果がある
  • 特にプロジェクトの開始時や新メンバーを迎えるときに効果的

と言われています。

今回は以下のような効果を見込んでワークを行いました。

  • メンバーの得意なことを確認・発見し、チームの力を最大限引き出す
  • 期待値を擦り合わせて、円滑なコミュニケーションとチームの成長を促す
  • その期待は重い…という場合はベビーステップを考えられる状態になる

f:id:taxa_program:20210223230136p:plain
エクササイズの様子。Miroで行いました。

このワークを通じて、「何が得意なのか」ということはもちろん、「私はこんな仕事の進め方は苦手」などといった共通認識を取ることもできました。
また、「〇〇さんにはこんなことを期待しているけど、どう?」といった議論がカジュアルにできたりと、非常に有用なワークだったと思います。

我々はなぜここにいるのか(インセプションデッキの作成)

インセプションデッキ*3とは、アジャイル開発のプラクティスの1つで「プロジェクトを始める前に明らかにしておくべき大切なことを知るための活動」と言われています。

インセプションデッキは10個の質問と答えから構成されていますが、今回は「我々はなぜここにいるのか」とう1つの質問に焦点を当ててワークを行いました。
この頃はコロナが一時的に落ち着いていたので、日程調整してオフィスでワークをしていました。懐かしい。

f:id:taxa_program:20210223232209p:plain
インセプションデッキ「なぜ我々はここにいるのか?」についてアイディアを出している様子

このワークではチームの北極星となるような、何かあったら立ち返る場所を作ることを目的として行いましたが、別の観点から「バケーションの写真」としての効果もあるようです。(弊社のエンジニアが教えてくれました)

バケーションの写真とは、旅行で撮った1枚の写真を見れば、その旅行で何があったか/どんな感情だったか、ということが頭の中に蘇ってくるように、全員で決めた「我々はなぜここにいるのか」の標語を見れば、そこにたどり着くためにどんな議論があったか、ということを全員が思い出せる状態になる、という効果のことのようです。

実際、ここで決まった標語は毎朝slackに流れる(毎日目に入る)ようにしたのですが、「なんでこれやってるんだっけ?」「誰のためにやってるんだっけ?」などを毎日思い返すことができ、チームの士気も上がっていたように感じます。

サービスの要求理解

バケーションの写真を撮るとの同時に、PdMの人とサービスの要求についても整理しました。
具体的には、下記のような図に対してそれぞれの項目を埋めることで、お互いの齟齬を減らしつつ、「なぜやるの?」を明文化しました。

f:id:taxa_program:20210223234101p:plain
要求理解に使用した図

UIの検討

ママリの世界観を壊さないように、また、体験が大きく変わりすぎないように、デザイナー主導でいろんなアイディアを出しながらUIを決めていきました。

個人的には、UIってこんな感じに決めていくんだなぁ〜という体験ができたとの同時に、「このUIでレコメンドされたら、ユーザーはどう思うだろう?」といった思考を巡らす機会にもなり、とても楽しかったし勉強になりました。

レコメンデーションは裏側のロジックも大切ですが、「それをどのようにユーザーに届けるのか」といった部分でも結果が大きく左右されると思っています。
ロジックとUI・UXを掛け合わせて、どうすれば効果を最大化できるのか、ということを改めて考える良い機会になりました。

f:id:taxa_program:20210224002352p:plain
MiroでUIのアイディア出しを行っている様子

先行指標やA/Bテストの進め方をPdMと握る

これは今回に限った話ではありませんが、開発を始める前に施策のメトリクスを定義しました。
具体的には下記項目について叩き台を作り、PdMと議論しながら最終的なメトリクスを決定しました。

  • Optimizing for OEC metrics: 施策のゴールとしておく指標
  • Protecting Guardrail metrics: サービスとして最低限維持したい指標
  • Understanding the Why. Local feature, debug, and operational metrics: 施策に関係しそうなユーザー行動をみる指標

また、A/Bテストの割合や実施期間についても予め決めておきました。
開発を始める前に上記の事柄を定義しておくことで、A/Bテストの前にドタバタすることもありませんし、明確な指標をイメージして開発することができるので、よりコトに向かって開発することができました。

反省点

取り組んだ中で「もっとこうした方が良かったな、どうにかできたかもな」と思う点もいくつかありました。

PdMからの権限移譲

今回の施策では、ユーザー体験が大きく変わるかもしれないということで、意思決定の多くはPdMを巻き込みながら行いました。
しかし、PdMとの日程調整は結構難しく、モノによってはこの意思決定がボトルネックとなり予定が遅れてしまう、といったこともありました。

これに対しては、「メンバー層でどこまで意思決定をしても良いか、線引きを明確にする」といったことがポイントだったかな、と感じています。
といっても、いきなり明確な線引きをするのは難しいと思うので、まずは意思決定を下す上で大切・不安に思っているポイントをヒアリングしたりなど、PdMの頭の中を徐々に理解しながら、自分たちで意思決定できる幅(信頼残高)を増やしていければ良いのでは、と思っています。

こういった信頼形成は一朝一夕ではできないことなので、日頃から積み重ねを意識していこうと改めて思いました。

深いコラボレーション

今回はデザイナーと初めての共創ということで、自分自身もワクワクしていましたが、当初想定していたよりもうまくコラボレーションできなかった感覚がありました。
もちろん、インセプションデッキの作成や、UIの検討といった部分では、ママリの世界観に精通しているデザイナーと議論することで得るものは大きかったです。しかし、当初は「サービスの体験設計など一歩踏み込んだ部分でコラボレーションしていけるのでは?」と思っていたので、少し物足りなさを感じていました。
(このあたりはリソースや他業務の優先度などとの兼ね合いもあり、ゴリゴリ推進することができませんでした)

ここで感じたモヤモヤは「当初イメージしていたものとのギャップ」も影響しているとは思うので、最初の期待値調整・リソース配分などをもう少し明確にしておけば結果は違ったかもしれません。

まとめ

本日は機械学習プロジェクトを進める上で、0→1の機能を作る際にやったことをピックアップしてお伝えしました。

機械学習に限らず、他プロジェクトでも流用できる取り組みが多いと感じているので、本エントリが同じような状況にある人の一助になれば幸いです。

最後に

コネヒトでは、プロダクトを成長させたいMLエンジニアを募集しています!!

  • ライフイベント,ライフスタイルの課題解決をするサービスに興味がある方
  • 機械学習の社会実装,プロダクト開発に興味のある方

是非お話できれば嬉しいです!
カジュアル面談では答えられる範囲でなんでも答えます!(特に準備はいりません!)

自分のTwitter宛てにDM送っていただいてもOKですし、下記リンクから5分で申し込むこともできます!

sokumenkun.com

選考はこちらから応募できます。

hrmos.co

iOS/Android 開発Tips共有会 potatotips #72 をオンライン開催しました

本年もよろしくお願い致します。コネヒトのテクノロジー推進グループというところでアプリケーションエンジニアをしているaboyです。

2021年もコネヒトは技術コミュニティに貢献していくぞ、ということで、昨年12月ではありますが iOS/Android 開発Tips共有会 potatotips #72 を主催させていただきました。

potatotips.connpass.com

12月は師走、お忙しい中参加してくださった方々、本当にありがとうございました。

ちなみに、コネヒトにとって12月は再出発の月でした。新しいビジョンを掲げ、コーポレートサイトもリニューアルしました。また、コネヒトにおけるテクノロジーへの「態度」を表明した羅針盤 Connehito Tech Vision を公開したタイミングでもあったため、その紹介もさせていただきつつ。

connehito.com

以降は発表スライドのまとめと、簡単な感想を書いています。

なお当日の様子は togetter してくれた方がいました。ありがとうございます!

togetter.com

発表スライドと一言感想

僕は CI サービスを Travis CI から Bitrise へ移行したことについて発表しました。元々 fastlane で自動化されていたところをそのまま活用し、ワークフローは fastlane ステップを中心に組み立てるだけ。移行コストは低かったです。


同僚の @tommykw さんは Kotlin Compiler Plugin の作り方を解説してくれました。ドキュメントが無いそうですが代わりとなりうるオススメの動画を紹介してくれました。

www.droidcon.com


iOS 14 における Universal Links の変更点について。 Apple CDN 経由に変わり、クローラーからアクセスできるサイトで app-site-association を配布する必要があるみたいです。


はじめての発表ということでしたがとてもスムーズでした! あとで書く


HorizonCalendarというカレンダー UI ライブラリの tips を共有してくれました。僕はこのライブラリを知らなかったのですが、自前で実装すると色々と面倒な UI を実現できるので便利そうでした。メソッドをチェーンさせて書けるのが Flutter や SwiftUI ぽくて個人的に好きです(Flutterは Dart のカスケード記法のことです)。

github.com


Flutter や Kotlin Multiplatform Mobile 等のマルチプラットフォーム開発フレームワークを使うにしても、プラットフォームごとの知識はあるに越したことはないので、入門する際の参考にしたいと思いました。


Twillio 使ったことないので新鮮でした。主題から逸れますが CMTime と TimeInterval のインターフェースが用意されていますがどう使い分けるのがいいのでしょう?コネヒトで動画再生まわりを実装したときは、CMTime を使って再生時間を取得していました。


Flutter における状態管理手法が、僕の中では「Google も使ってるし BLoC でおk」のイメージからアップデートできていなかったので、久しぶりに状態管理について聞けて良かったです。アーキテクチャの選定/命名理由の言語化も丁寧だなーと思って聞いてました。


下から出てくるメニューに限らず、いわゆるアコーディオン UI も簡単に実装できたりするんですかね。この方の著書『1人でアプリを作る人を支えるSwiftUI開発レシピ』も読みつつ SwiftUI 勉強したいと思いました!


Android は全くわかりませんが広く捉えるとプログラミングにおいて重要なエラーハンドリングの話だ...と思いながら聞いていました。スライドの最後に書いてある「Lint は無視しない」は大事です。

参加者の皆さんありがとうございました!

最後に、イベント中流していたハワイの BGM はこちらです。

www.youtube.com


コネヒトでは Android エンジニアなど募集していますので少しでも気になった方は一緒にお話しましょう!

hrmos.co

RxSwift 6.0の主な変更点

こんにちは!コネヒトでiOSエンジニアをやっていますyanamuraです。

RxSwiftの6.0が公開されました。

公式のWhat's new in RxSwift6はこちらです。

What's new in RxSwift 6 ? - DEV Community

細かいdiffはこちらをご覧ください。 https://github.com/ReactiveX/RxSwift/compare/5.1.1...6.0.0

------------------------------✂--------------------------------------

ここでは、RxSwift6.0にアップデートすると影響受けそうなところから見ていきたいと思います。

Breaking Changes

deprecatedだったものが消されました

変更はこちら

Deprecated

以下のものの命名が変更されました。コンパイルは通りますがwarningになります。

  • Rename catchError(:) to catch(:).
  • Rename catchErrorJustReturn(:) to catchAndReturn(:).
  • Rename elementAt(_:) to element(at:).
  • Rename retryWhen(_:) to retry(when:).
  • Rename takeUntil(:) to take(until:) and takeUntil(behavior::) to take(until:behavior:).
  • Rename takeWhile(:) to take(while:) and takeWhile(behavior::) to take(while:behavior:).
  • Rename take(_:) duration overload to take(for:) (e.g. take(for: .seconds(3))).
  • Rename skipWhile(_:) to skip(while:).
  • Rename takeUntil(_:) to take(until:).
  • Rename observeOn and subscribeOn to observe(on:) and subscribe(on:).

Single

RxSwift5まではSingleはsubscribeするとSingleEventという独自のResultみたいなものを返していましたが、これがResultに変わりました。

// RxSwift 5

public enum SingleEvent<Element> {   
    /// One and only sequence element is produced. (underlying observable sequence emits: `.next(Element)`, `.completed`)  
    case success(Element) 

    /// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`) 
    case error(Swift.Error)   
}
// RxSwift 6

public typealias SingleEvent<Element> = Result<Element, Swift.Error>

これまでは .error, onError としていたところを .failure, onFailure に変更しないとwarningになります。

変更のコミットはこちら

New

全てのReactiveCompatibleなオブジェクトのプロパティでBinderの実装が不要に

これまではclassのプロパティを

class MyView: UIView { 
    var title: String
}

このようにbind(to:)できるようにするには

viewModel.title.bind(to: myView.rx.title)

下記のような記述を書く必要がありました。

// RxSwift 5

extension Reactive where Base: MyView {
    var title: Binder<String> {
       Binder(base) { base, title in 
           base.title = title
       }
    }

RxSwift6ではReactiveCompatibleなオブジェクト(.rxの使えるオブジェクト)ではこの記述は不要になります。

変更のコミットはこちら

withUnretained

ObservableTypeに withUnretained が追加されました。

これまでは[weak self]をいちいち書く必要がありましたが

viewModel.importantInfo
    .subscribe(onNext: { [weak self] info in 
        guard let self = self else { return }
        self.doImportantTask(with: info)
    })
    .disposed(on: disposeBag)

withUnretainedを使うことでoptional bindingが不要になります。

viewModel.importantInfo
  .withUnretained(self)
  .subscribe(onNext: { owner, info in 
    owner.doImportantTask(with: info)
  })
  .disposed(by: disposeBag)

変更のコミットはこちら

decode(type:decoder:)

Observabledecode(type:decoder:) が追加されました。

decodeがメソッドチェーンですっきりとかけるようになりました。

func test() {
    let rawJSON = """
        [
          {"id": 1, "name": "Foo", "country": "Foo"},
          {"id": 2, "name": "Bar"}
        ]
        """.data(using: .utf8)!

    Observable
              .just(rawJSON)
              .decode(type: [FakeObject].self, decoder: JSONDecoder())
}

private struct FakeObject: Equatable, Decodable {
  let id: Int
  let name: String
  let country: String?
}

変更のコミットはこちら

driveとemitで複数のobserverにbinding

driveとemitでは一つにしかbindingできませんでしたが、複数できるようになりました。

viewModel.string.drive(input1, input2, input3)
viewModel.number.emit(input4, input5)

変更はこちらこちら

Infallible

RxSwiftにInfallibleが追加されました。

errorを流さないObservableです。似たようなものにRxCocoaのDriverやSignalがありますが、これらと違うのはメインスレッドは保証されないのとresourceをshareしたりreplayしない点です。

実装はこちら

ReplayRelay

ReplayRelayが追加されました。

PublishRelay, BehaviorRelayと同様にReplaySubjectのwrapperでerrorやcompleteを流しません。

変更のコミットはこちら

distinctUntilChanged(at:)

distinctUntilChanged(at:) が追加されdistinctUntilChangedにkeyPathを使うことができるようになりました。

        struct TestObject: Equatable {
            let value: Int
            let other = ""
        }

        Observable<TestObject>
            .create { observer in
                observer.onNext(TestObject(value: 1))
                observer.onNext(TestObject(value: 1))
                observer.onNext(TestObject(value: 2))
                return Disposables.create()
            }
            .distinctUntilChanged(at: \.value)
            .subscribe(onNext: {
                print($0.value)
            })

変更のコミットはこちら

UIApplication.rx

UIApplicationのLife cycle Eventなどが追加されました。

追加されたものはこちらです。

  • didEnterBackground
  • willEnterForeground
  • didFinishLaunching
  • didBecomeActive
  • willResignActive
  • didReceiveMemoryWarning
  • willTerminate
  • significantTimeChange
  • backgroundRefreshStatusDidChange
  • protectedDataWillBecomeUnavailable
  • protectedDataDidBecomeAvailable -userDidTakeScreenshot

変更のコミットはこちら

サービスとシステムの健全性に保つためのAndroidチームの取り組み色々

こんにちは!2017年11月にAndroidエンジニアとしてjoinした関根です。気づけば入社4年目に突入しました。 さて今回は、弊社サービスのママリ改善を担当するAndroidチームでやっていることや始めていることを、年末の棚卸しを兼ねて紹介してみようと思います。

この記事はコネヒト Advent Calendar 2020 16日目の記事です。

やっていること

まずは今年までに既にやっていることの中から2つの動作チェックサポート対象のアプリバージョンやminSdkVersionの運用について紹介をさせていただきます。

2つ動作チェック

言わずもがなですが、サービスやシステムを健全に保つためには、意図した挙動が守られていることを事前に確認しておく必要があります。 Androidチームでは大きく分けて2つ動作チェックのタイミングがあります。

プルリクエストでの確認

ひとつめはプルリクエストを送るタイミングです。 下記のようなテンプレートを用意して、レビュアー/レビュイーの間の共通認識をとり、認識の齟齬を減らしミスを減らす工夫をしています。

## 目的
今回のPRの目的を書く。関連issueを貼ることが多い

## やったこと
このPRの作業内容を書く

## テスト項目
動作確認する内容や手順を書く

## チェック項目
- [ ] File Changedのセルフレビュー
- [ ] 6系で動作が問題ないこと
- [ ] 7系で動作が問題ないこと
- [ ] 8系で動作が問題ないこと
- [ ] 9系で動作が問題ないこと
- [ ] 10系で動作が問題ないこと
- [ ] デザインが崩れていないこと
- [ ] Activity破棄設定にして動作が問題ないこと
- [ ] 不要なリソース削除。`$ ./gradlew removeUnusedResources`

## その他
特に見てほしいポイントや、その他レビュワーに伝えたいことなどがあれば記載

テンプレートのチェック項目を全て確認をするのはレビュイーが多いですが、レビュアーも必要に応じて動作確認をしています。

リリース前チェック

もう一つはリリース前のリグレッションテストです。 こちらはプルリクエストの場合と違い、既にある機能を守れているかを確認するテストとなります。

確認する項目

サービスの重要な機能にしぼり、約70の項目を確認しています。 プロダクトマネジメントを担当するメンバーと相談し、ユーザーやクライアントに与える価値の大きいものを抜粋してテストをしています。

確認するタイミング

アプリリリース前に実施しています。かかる時間は一時間弱です。

機能が増えるタイミングで項目が追加されることも多いため不定期で棚卸しを実施しています。今は人力での動作確認をしていますが、今後は自動化検討して、効率化をしていきたいと考えています。

サポート対象のアプリバージョンやminSdkVersionの運用

Android開発ではminSdkVersionとの向き合い方に悩まされることが多いと思います。コネヒトでも長い間明確な運用ルールは決めていなかったのですが、2019年度のシステム安定化という部署の目標をきっかけにルールの整備が始まりました。

導入の背景

それ以前は、ルールがないことで全ての環境の全ての問題を同じように対応することが多く、サービス改善で起こるシステムの問題に対して「過度な萎縮」が起こり始めていました。 しかし、ユーザーに価値を提供し続けるにはシステム改修に萎縮せず気軽にリリースをし続けることが大事だと考えています。他方、ビジネス数値からみると、サポート範囲を狭めることはネガティブに捉えられがちでなので、「守るべき範囲」を明確に定義し過度な萎縮を防ぐことが「ユーザーに価値を提供し続ける」ことに繋がるという前提で議論を進め、導入に至っています。現在は以下のルールで運用されています。

基準

ルール名 対応内容 基準値
非推奨環境 設計や実装時の考慮から外してOK
不具合やエラー発生時に通常issueとして扱い対応可否を決める
DAU500人以下の環境
レガシーアプリver. バージョンアップを誘導する リリースから2年経過したアプリVer.
レガシーOS minSdkVersionの対象外にする
※2~3週間前にお知らせでアナウンスをする
OS毎MAUが2%以下のOS

見直し時期

  • クオータ毎
    • 3月、6月、9月、12月

見直し事項

  • サポートバージョンの基準値の見直し
  • サポートバージョンの環境の更新

実施ステップ

  1. 開発部でサポート対象外となる環境を計測する
    • 特定期間のMajorバージョンシェア
    • 特定期間でDAU1000以下のMajorバージョン
    • 2年前のバージョン番号
  2. 社内に共有してスケジュールを組む
    • ex). レガシーOS/レガシーアプリver対応:事前に対象ユーザーのお知らせに案内を掲出
  3. スケジュールに沿って遂行する

あくまでも一定の基準であるため、閾値を下回ったから直ぐに対応を実施するわけではなく、開発効率のバランスやサービスに与える影響をプロダクトマネジメントを担当するメンバーと相談しながら進められるように工夫をしています。

はじめている事

最後に、これからはじめていくことの中からrenovateの導入について紹介します。今月に入ってから着手し始めたもので、試行錯誤の真っ最中です。

依存ライブラリのアップデートを効率化

renovateを一言で紹介すると、依存ライブラリのアップデートを検知してPRを投げてくれるOSSです。 Androidの依存ライブラリは更新も早く、人力でアップデートしていくのは、至難の技だと感じており、 この課題を解決するために、renovateで自動化をし、運用を楽にすることを考えています。 余談ですが、以前は gradle-use-latest-versions-pluginをGithub Actionsで実行し、PRを出す運用の準備をしていましたが、別のプロジェクトで仕様していたrenovateでも同じことが実現できることがわかり乗り換えをしています。 この取り組みの中から1つの工夫を紹介します。

PRをまとめる

renovateではデフォルト設定だとパッケージごとにPRが送られてきます。 依存ライブラリが多いとPRが多くなりすぎることが懸念されたので、minorとpatchアップデートは1つにまとめるようにルールを決めています。 renovateの設定ファイルに下記の設定を追加することで実現ができます。

"packageRules": [
        {
            "packagePatterns": [
                "*"
        ],
            "updateTypes": [
                "minor",
                "patch"
        ],
            "groupName": "all non-major dependencies",
            "groupSlug": "all-minor-patch"
        }
]

なお、majorバージョンは変更が大きく、プロダクトへの影響も大きいため、個別のPRでしっかりと検証する必要があると判断し、対象をminor、patchのみにしぼっています。

最後に

いかがでしたでしょうか?コネヒトでは今回紹介したように、各々のメンバーが工夫をしながら業務改善を楽しんでいます。そんなコネヒトでは積極的にエンジニアを募集しているので、是非一度話を聞きにきてください!

hrmos.co

Bitrise を触ってみた所感

こんにちは!アプリケーションエンジニアのあぼです。

コネヒトでは iOS アプリの CI ツールとして Travis CI を使っていましたが、先日プランが変わったこともあり、定額の Bitrise に移行しました。元々 fastlane で自動化していたので、Bitrise ではキャッシングなどのワークフローを組み立ててメインの処理は fastlane のコマンドに任せるという感じで動かしています。ですので移行自体もそこまで苦ではなかったなという印象です。

今回ははじめて Bitrise を触ったので自分の所感を簡単に書いていこうと思います。コネヒト Advent Calendar 2020の記事です。

導入初期の色々試したいときに GUI でガリガリいじれる

Bitrise はワークフローの設定などが書かれた bitrise.yml を Bitrise.io で管理する方法とリポジトリ管理する方法が選べますし、後からでも変更可能です。個人的に導入初期は yml のこと考えずに一気にガリガリ弄って試したいので、その点でラクでした。そしてある程度動くことが確認できて正式にチームに展開する際に GitHub のリポジトリ管理に切り替えています。GUI で弄った bitrise.yml をそのままダウンロードしてリポジトリに追加すれば良いので切り替えも簡単にできました。

f:id:aboy_perry:20201214162452p:plain
bitrise.ymlの管理方法

bitrise.yml を自分で書く場合にドキュメントが必要なのですが、探してみた感じだと Bitrise CLI のドキュメントを参照すればだいたい網羅されてそうな気がします。CLI 自体 OSS なのでソースコードを読んで調べることもできますね。Go で書かれているようです。

devcenter.bitrise.io

スタック(イメージ)の更新・削除が早い

スタックの更新と削除ポリシーを見る限り、他の CI と比べて古いスタックが deprecated になるタイミングや、削除されるタイミングが早そうです。例えば直近だと Xcode11.x は 11.7 以外のマイナーバージョンが順次 deprecated になり、削除されていきます。Circle CI の場合 Xcode11.x は Xcode13 のリリースが始まってから順次 deprecated になっていきます。一方で、Bitrise は新しいバージョンのスタックが使えるようになるのも早い印象*1なので、そこは嬉しいですね。

Travis CI に関しては更新と削除ポリシーについてそれらしい記述が見つけられなかったのですが、現状用意されている環境一覧を見る限り Xcode7.3 があり、かなり古いバージョンまで保持しているようです。(ちなみに前述のリンク先のページでは Xcode11.7 が無い…のですが、チェンジログには対応したと書いてありました)

会社や個人の状況によって、スタックの更新・削除ポリシーが要件と合わず使いたくても使えないということも起こりそうですが、コネヒトの場合は運用していけそうと判断しました。

ビルドの結果がステップごとに表示されて見やすい

ステップごとに折り畳まれてたり、かかった時間が一覧で見れるのでパッと見で見やすいと感じます。ビルド時間を削減しようと思った時にステップを細かくわけてボトルネックを探ったりするのに使えそうですね。

f:id:aboy_perry:20201214165209p:plain
ビルドの結果がステップごとに表示される

キャッシュが便利

Bitrise のキャッシュはブランチごとに特定のディレクトリを保存しておけるもので、Bitrise.io Cache:PullステップとBitrise.io Cache:Pushステップを組み込んで簡単に使うことができました。該当のブランチに有効なキャッシュが無ければ Bitrise 上でデフォルトブランチに指定しているブランチのキャッシュを取りに行く仕様です。Travis CI ではプルリクエストトリガーの際にターゲットブランチのキャッシュを取りにいく点が違っていますが、ほとんど同じ感覚で使うことができそうですね。

コネヒトでは現在 ruby (rbenv)、Gem、CocoaPods で管理しているライブラリ、Swift Package Manager 管理しているライブラリをキャッシュしています。

devcenter.bitrise.io

おわりに

というわけで今回は Bitrise を触ってみて感じたことを書きました!まだ細かい課題ややりたいこともあるので色々試していきたいなと思います!


そして宣伝です!12/12 (火) に potatotips (iOS/Android のオンラインLT会) を開催予定です。空いている枠は先着順で入れるのでご予定合う方はぜひご参加ください〜!

potatotips.connpass.com

*1:Bitrise の Twitter をウォッチしているとそんな印象があります、あくまで印象

CakePHPを使った実装で悩んだときに見ている情報源

こんにちは! @fortkle です。
この記事は コネヒト Advent Calendar 2020 8日目 の記事です。

f:id:fortkle:20201208110038p:plain
https://cakephp.org/jp

はじめに

コネヒトではサーバーサイドの言語として主にPHP、フレームワークでいうとCakePHPが採用されています。 公式のドキュメントにもある通り、CakePHPは「設定より規約」的な思想が随所に感じられるフレームワークです。

私たちは「設定より規約」(convention over configuration) という考え方に賛成です。 CakePHP の規約を習得するには少し時間がかかりますが、長い目で見ると時間を節約していることになります。 規約に従うと自由に使える機能が増えますし、設定ファイルを調べまわってメンテナンスするという悪夢からも 開放されます。 規約によって開発が統一感を持つため、開発者が加わってすぐに手伝うということがやりやすく なります。
CakePHP の規約 - 4.x

日々の実装の中で「どうやって書けばスマートかな?」や「Cakeだとどうやって書くのがCakeWayっぽいかな?」だったりを考えながら手を動かすことが多いため、今日はそういった際に役に立つ情報源をまとめてみます。

ちなみに私が探しに行く順番で、上から順に紹介していきます。

情報源

公式ドキュメント(Cookbook)

最初というか、実装に悩む以前にCakePHPを触るのであれば一通り公式ドキュメントを流し見しておくことをおすすめします。 「設定より規約」という思想のフレームワークにおいて、「知っていること」が何よりも重要になるからです。

CakePHPの公式ドキュメントは内容の充実度もさることながら、ボランティアベースで行われている日本語への翻訳作業も継続的に実施されていて大変素晴らしい・・というかお世話になっております。

ドキュメントはcakephp/docsで管理されています。翻訳に興味がある人は以下の記事を参考にしてみてください。

公式APIリファレンス

CakePHPは公式のドキュメントであるCookbookの他に、APIリファレンスをHTMLの形で公開しています。これも実装に悩む以前に読んでいてほしいものですね。英語で言えばAPIは英単語、英会話を楽しむ前に最低限の語彙力を鍛えましょう!

エンジニアなら全員読んだことがありそうな『リーダブルコード』にも以下のような記述があります。

 プログラマというのは、既存のライブラリで問題を解決できることを知らないことが多い。あるいは、ライブラリで可能なことを忘れていることが多い。ライブラリの機能を熟知して、実際に活用することが大切だ。
 ここでささやかな提案だ。たまには標準ライブラリのすべての関数・モジュール・型の名前を15分かけて読んでみよう。標準ライブラリというのは、C++標準テンプレートライブラリ(STL)やJavaのAPIやPythonの組み込みモジュールなどのことだ。
13.4 身近なライブラリに親しむ 『リーダブルコード』 P.172

厳密にはフレームワークのAPIリファレンスよりは低いレイヤーの話だと思いますが、伝えたいことは同じです。全てを覚えるのではなく、頭の中にインデックスを作り、実装で悩んだときに参照できるようにしておきましょう、ということです。

社内の類似技術スタックのリポジトリ

(全員が参考にできないのでサクッといきますが)社内に同じような技術スタックのリポジトリがある場合はそちらの実装も参考にしてみましょう。 大抵の悩みは先行しているリポジトリで解決されていたりします。

cakephp/cakephpリポジトリ

特におすすめなのは「テストコードを読むこと」です。
公式ドキュメントを読んでも使い方が分からなかったり、自分たちがやりたいカスタマイズ方法が分からなかったりする場合はあると思います。 そんなときにテストコードを読むと、テスト対象であるSUTが「一体何を目的に実装されたコードなのか?」を明示されながら使い方まで理解することができるからです。

個人的には、テストコードのスマートな書き方を求めてコアコードを見に行くことが多いです。

(コラム) 思想を理解する

フレームワークの思想を理解するにはより詳細にissueなど実装当時の情報を深ぼるのもおすすめです。特にlabel:RFCだけでも見ておくとよいかもしれません。
例えば、CakePHP3でガラッと変わったバリデーション周りの変更はこのissueに背景が書いてありますし、Traitの使い方の見直しについてもこのissueで議論を垣間見ることができます。

他にもwikiにあるロードマップを見たり、コアコントリビューターのmark storyのwikiには実装の草案・アイデアが公開されていたりします。

FriendsOfCake/awesome-cakephp リポジトリ

CakePHPの良さそうなライブラリやシステムのリンク集です。
用途別に分類されているので、関係がありそうなセクションのリポジトリを眺めにいくことが多いですね。 弊社に関係するライブラリもいくつか記載されています💪

ちなみに、 FriendsOfCake はCakePHPの中の人達が中心となっている開発者グループで、品質の比較的高いCakePHPプラグインやリソースが提供されているため、比較的安心して実装を参考にできるかなと思います。

CakePHP Slackグループ

これまで紹介した情報源を調べても分からなかったり、数時間悩んだりするなら「人に聞く」というのもおすすめです。
周りに詳しい人がいない場合は、CakePHPコミュニティのSlackグループがあるのでそちらを活用してみるのはどうでしょうか。

#japanese チャンネルがあるので日本語で相談もできますね!
CakePHP Slack から誰でもjoinできます。

コアデベロッパーの関連サイト(2020/12/08 追記)

CakePHPのコアデベロッパーが個人でCakePHPに関する情報発信をしているケースが多々あるのでそちらも参考になります。いくつか例をあげます。

おわりに

ここまで参考になりそうな情報源を紹介してきました。
実際にはもちろんQiitaやZenn、個人ブログなども参考に見ることが多いですが、参考に値する確かな情報源を持っておくことは重要です。 いろいろなコードを読み、理解して、よりスマートな実装ができるように鍛錬していきましょう!

宣伝! イベントやります!

コネヒトでは12/17にオンラインイベント開催予定です。 サービス開発が大好きなエンジニアはもちろん、事業会社で働いてみたいエンジニアやtoC向けサービスに興味がある方はぜひご参加ください〜!

connehito.connpass.com