QUO CARD Digital Innovation Lab Tech Blog

クオカード デジタルイノベーションラボの技術ブログです

PagerDuty と Sentryを用いて自動で緊急連絡するシステムを構築しました

デジタルイノベーションラボのゴルゴです。

先日、PagerDutyとSentryを活用して、バッチ処理で特定のアラート条件を満たした際に、担当の営業にオートコールを行うシステムを構築しました。今回はそのシステムの紹介をしたいと思います。

背景

ラボでは以前から、特定のアラートが発生すると営業部に対してSlackで通知を行っていたのですが、Slack通知だけでは担当者が自ら確認する必要があります。すぐに気付いて対応してもらいたいので、電話でもコールされるようにしたかったのです。

ラボの運用チームがアラートを検知して、手動で営業部にコールする方法も考えられましたが、それでは運用チームの負担が大きくなってしまうので、できるだけ運用チームを介さずに、直接営業部にオートコールを行うことを目指しました。

幸い、ラボ内では既にシステム障害を検知して運用チームにオートコールを行う仕組みがあったので、今回はその既存の仕組みを応用し、特定のアラート時には営業部に直接オートコールを行うようシステムを構築しました。

利用サービス

Sentry https://sentry.io/welcome/

アプリケーションに組み込んでおくことで、エラーの発生検知と追跡を支援してくれるサービスです。 今回は特定の条件を満たした場合にアラートを発生させ、PagerDutyに通知するために使用します。

PagerDuty https://www.pagerduty.co.jp/

インシデント対応の自動化を提供するクラウドサービスで、システム障害や重要なイベントが発生した際に、適切なメンバーにリアルタイムでアラートを送信してくれます。 今回はSentryからのアラートを受け取り、受電担当者にオートコールするために使用します。

通知の流れ

今回実現したシステムでの通知の流れです

通知の流れ

導入までの流れ

1.営業部門との調整

まず最初に行ったのは担当部署との調整です。課題の説明と、オートコールの受電担当者を決めて貰うよう相談し、担当者が受電したあとにどのような対応をしてもらうかも予め調整しておきます。

2.PagerDutyとSentryのIntegration設定

2.0.サービスの登録

今回はPagerDutyとSentryいずれのサービスもラボ内で導入済みだったのでアカウント作成はしていませんが、必要に応じてそれぞれのサービスのアカウント作成から行ってください。

2.1.PagerDuryにサービスの作成

公式ガイドのサービスを作成を参考にして、今回のシステム用に新規のサービスとエスカレーションポリシーを作成します。 エスカレーションポリシーには最終的には担当部門のメンバーを登録しますが、この時点では開発メンバ等を登録しておくといいでしょう。

2.2.PagerDuty側にインテグレーション設定

サービスを作成したら今度はSentryとのインテグレーション設定を既存のサービスにインテグレーションを追加するを参考に行います。 設定すると Integration Key が生成されますが、このKeyはあとでSentry側に設定する必要があるので控えておきます。

2.3.Sentry側にIntegration設定

今度はSentryの管理画面で、PagerDutyへのIntegration設定を追加します。 Sentryの公式ガイドを参考に行ってください。

3.バッチ設定

続いて実際にバッチの改修に入ります。

3.1.バッチへのSentry組み込み

まずはバッチからSentryへメッセージを送れるようにSentrySDKの組み込みと設定が必要です。今回のバッチはMicronautで実装しているのでJavaの設定手順に従ってSDK組み込みと設定を行いましたが、プラットフォームによって設定方法は異なるので、こちらから調べて設定してください。

3.2.アラートメッセージ送信

設定ができたら、実際にバッチで条件を満たした際に、Sentryへイベントメッセージを送るようにコードを修正します。 ここで設定したメッセージが最終的にPagerDutyからの通知でも使用されるので、ある程度内容がわかるようにしておきます。

  // 通知条件を満たした場合の処理
  Sentry.captureMessage("XXXでエラーが発生しました。至急Slackを確認して対応してください", SentryLevel.ERROR)

4.Sentry管理画面でのアラート設定

次はSentryの管理画面で設定を行います。 Sentryにログインし、Alerts > Create Alerts でアラート条件の作成をします。

特に大切なのがSet Conditionsで、次のように WHEN, IF, THEN の設定をします。

WHEN イベントを受信のたびにオートコールしたいので、毎回アラートが作成されるように条件は全て消しておきます。 初期状態だとA new issue is createdが設定されていますが、これだと最初の一回しかアラートが発動しないので注意してください。 しばらく条件を無しにできることに気付かず苦労しました。

IF 今回はイベントの message に 先ほど設定したメッセージを含んでいることを条件にします。

THEN PagerDutyへ通知を送りたいので、Send a notification PagerDuty を選択し serviceにはIntegration設定しておいたサービスを選びます。

Set Conditionsの設定画面

5.設定確認テスト

ここまでの設定を終えたら、バッチからアラートメッセージ送信→Sentryでのアラート検知→PagerDutyで検知しオートコール、という一連の流れを確認できるはずなので、検証環境等を使って動作確認しておきます。

6.受電担当者のPagerDutyアカウント作成

動作確認がうまくいったら、PagerDutyのエスカレーションポリシーに登録するため、受電担当者のPagerDutyアカウントを作成します。 ユーザーの追加ガイドを参考に、先に決めておいた受電担当者に招待メールを送り、登録作業をしてもらいます。

7.エスカレーションルール設定

複数の受電担当者がいる場合には通知の順序や、一人目が応答しない場合に次の人に連絡するまでの間隔等を設定します。

8.テストコール

エスカレーションルールへの設定順の確認や、実際に受電する人に慣れてもらうためにもテストコールをします。 PagerDutyではオートコールが掛かってきた際にダイヤルボタン回答で、Acknowledge(対応する)や、Resolved(解決済み)にステータスを設定できるのですが、着信に出なかったり応答してもステータスを回答しなければ、次の受電者へ通知がされます。 テストコールを実施してエスカレーションルールどおりになっていることを確認できれば、全ての設定が完了です。

お疲れ様でした。

終わりに

今回の仕組みの導入により、運用の負荷を上げずに担当部署に緊急連絡することができるようになりました。 導入しておよそ1ヶ月ですが「休日にアラートのSlack通知が発生しても気付けないかも。。」といった心配事が減り、こころなしか眠りが深くなった気がします(笑。 これからも、充実した眠りのために?自動化をすすめていきたいと思います。

この記事がどなたかの助けになれば幸いです。

子育て中の方へ: 便利な職場制度のご案内

デジタルイノベーションラボの齋藤です。

子育てをしながら働くことは大変なことです。そこで、特に育児中の方々に役立ちそうな会社の制度をいくつかご紹介します。

※以下は2023年11月時点のものになり、今後変更となる可能性があります。

  • 裁量労働制(中抜けOK)

  • フルリモート可

  • 育児時間

  • 時間単位の有給休暇

  • 企業主導型保育園事業への対応

  • 子の看護休暇(子ども1人につき年5日※上限10日:有給)

  • 積立有給休暇

  • 配偶者の出産

  • 出産祝い

  • 夜間・休日の対応

  • デジタルイノベーションラボの子育て中のメンバーの割合

裁量労働制(中抜けOK)

裁量労働制です。基本的に朝5時から夜22時の間で勤務いただくルールとしています。

フルリモート可

新型コロナウイルス感染症の流行以前から、リモート勤務を導入しています。遠隔地に移住したスタッフも在籍しています。

育児時間

1歳未満のお子様をお持ちの社員は、1日に2回、各30分以上の育児時間を取得可能です。この時間は1回30分までは通常勤務と同様に扱われます。

時間単位の有給休暇

1年について5日の範囲内で時間単位の年次有給休暇が付与されます。

企業主導型保育園事業への対応

社員が企業主導型保育園を利用したい場合、提携手続きをサポートします。

子の看護休暇(小学校入学前までの子ども1人につき年5日上限10日:有給)

法的には無給で問題ないものの、当社では有給休暇としています。

積立有給休暇

使用しきれなかった有給休暇は、病気や子どもの看護のために積み立てることができます。最大60日までです。通常の有給休暇上限40日とは別で積み立てることができます。

配偶者の出産

配偶者の出産予定日の7日前から出産日の7日後までの間に3日有給を取れます。

出産祝い

会社とレッツ中央(福利厚生)両方からそれぞれ支給があります。

夜間・休日の対応

柔軟な勤務時間を心がけている一方で、夜間や休日の緊急対応をお願いする場合があります。予期せぬトラブルを避けるため、休日の前日や夜間にシステム更新は避け、業務時間内でのバッチ処理を行う(オンラインにできるものはそうする)などの取り組みを進めています。

参考までに、2023年1月1日から2023年11月10日までの夜間・休日対応の実績は以下になります。

障害対応:2件

深夜メンテナンス:2件

デジタルイノベーションラボの子育て中のメンバーの割合

デジタルイノベーションラボの2023年11月時点の子育て中のメンバーの割合は約37%になります。

スクラムマスター研修を受講してきました

デジタルイノベーションラボPayチームのゴルゴ&鳥海です。 先日、スクラムマスター研修を受講してきたので、その感想をお届けします。

受講の背景ですが、私達は3年前からスクラム開発を導入していて複数の開発チームが有ります(Payチームもその一つ)が、専任のスクラムマスターはいません。そこでプロダクトオーナーからの提案で、スクラム開発への理解を深めるためにスクラムマスター研修を受講することにしました。

https://www.jp.agilergo.com/online-csm-coplien-202303

研修は4日間で、講師のJames.O.Coplien氏はデンマークからのリモート授業。同時通訳で理解できるか多少心配だったのですが、実際受けてみたところ、あまり問題はありませんでした。 講義の内容はスクラムガイドの細かい解説や、スクラム開発に必要な心構えなどが中心で、時折演習も交えて行われました。以下が印象に残った点のメモです。

  • コミットメントの意味 - 約束や確約と捉えられがちだが、チームがゴール達成に向けて最善を尽くすという気構え。想定と異なり満たせないことは当然ある。その場合は検査と適応で学習していく必要がある。

  • 適切なリファインメントの量 - 2〜3スプリント分がすぐに取り組める状態にまでリファインメントが終わっていると良い。先のタスクをあまり詳細に詰めても無駄になる。リファインメントが不足していると、予想より早く計画したタスクを終えた場合に、次に取り掛かる作業が不足して無駄になる。

  • PBIのタスク化は必要な無駄 - PBIのタスク化は必要だが無駄な作業。短時間で終わらせて実際の開発を行う時間を確保することが重要。
  • デイリースクラム - 目的は課題の見える化。課題について話し込まずに短時間で終わらせる。課題への対応は別途時間をとって行う。
  • スクラム的に正しいかを問うスクラム警察は必要無い」。現場の状況に応じて改善を続けていくことが大切。
  • スクラムマスターに適した人 - よく話を聞く人、信頼関係を作る、円滑なコミュニケーションを確立する、対立が発生した場合は仲介をする、タフになる必要がある。オープンになる必要がある。オープンというのは正直ということでもある。
  • ベロシティ - ベロシティを高めることに意味はない、あくまで指標と考えたほうが良い。ベロシティを調整する、というのはよくない考え方。ベロシティは計測するのみ。フィーチャー工場にならないように!それよりも何が価値のあることなのかを分かっていることが重要。
  • スプリントゴール - スプリントゴールは一つに絞るほうが、チーム全体としてのまとまりが出る。私たちのゴールは何だろうか、ということをもっとみんなで言語化してみるとよいかもしれない。
  • スクラムチームの信頼 - スクラムチームは日頃スプリントゴールを満たしていくことでステークホルダーからの信頼を積み重ねていく必要がある。この信頼は超大事。

全般的に、従来からの理解とは大きなズレはなかったものの、普段スクラム開発に取り組む中でぼんやりと疑問に感じていた点がいろいろ腹落ちし、得るものが多かった研修でした。今後自信を持って開発に取り組んでいけそうです。

また、研修の仕上げとして後日スクラムマスター認定試験を受験する必要があります。合格率はかなり高いようなので逆にそれがプレッシャーですが、しっかりと研修内容を復習して臨みたいと思います。

以上、スクラムマスター研修の感想でした。今後もよりよい開発を行えるよう、日々改善を重ねていきたいと思います。

Four Keysの取得をはじめました

少し前からFour Keysの取得をはじめました。今回はそれについて書きます。

背景

Four KeysとはLeanとDevOpsの科学でも紹介されていますが、GoogleのDevOps Research and Assessment (DORA) チームが確立したソフトウェア開発チームのパフォーマンスを示す指標です。

https://cloud.google.com/blog/ja/products/gcp/using-the-four-keys-to-measure-your-devops-performance?hl=ja

クオカードでも開発生産性の向上は一つの重要なテーマですが、現状を定量的に把握しないことには改善しているのかどうかの判断ができないため、Four Keysを取得する事にしました。実はかなり以前から取得しないといけないという意識はあったのですが、手が回っておらずこのタイミングになってしまったという状況です。

今回の対応

計測にはSwarmiaというツールを利用しています。 結果は各チーム単位にSwarmiaの管理画面で各指標の値を確認し、スプレッドシートに書き込むという運用をしています。 可視化はダッシュボード化も検討しましたが、他に優先度が高いバックログがあるため、まずは大きな工数を掛けずに始めることにしました。

実際の計測について

4つの指標のうち、変更のリードタイム とその他(デプロイの頻度、変更障害率、サービス復元時間)でSwarmia上での確認方法や必要な設定が異なったので、それぞれどのようにして確認できるようにしたか記載します。

変更のリードタイム

変更のリードタイムは、SwarmiaとGithub Organizationを連携させることで、Githubに作成してあるチーム単位での指標が表示されるようになりました。

デプロイの頻度、変更障害率、サービス復元時間

こちらの指標を確認するには、まずはSwarmiaが用意しているDeployments APIを通してデプロイ情報を送る必要があります。 https://help.swarmia.com/sending-deployment-information-to-swarmia

Github Actionsでデプロイ情報を送信

開発チームのリリース作業では主にGithub Actionsのワークフローを使ってデプロイしているので、デプロイに成功したあとに上記APIを呼び出すステップを追加することで、Swarmiaにデプロイ情報を送るようにしました。 中には、1つのワークフロー内でFrontend/Backend等、複数のアプリをデプロイするケースもあるのですが、そういったケースでは全てのデプロイが成功した場合のみ送るようにしました。

一部情報は手動で設定

ここまででデプロイ頻度がわかるようになりますが、変更障害率、失敗率を算出するには、さらに追加の情報も必要です。 何らかの意図通り動かない失敗デプロイがあったとして、その修正のためにデプロイを行った場合には、どのデプロイに対する修正であるかわかるようバージョン番号を指定する必要があります。

この指定は、APIでデプロイ情報を送る際にオプションパラメータ(fixesVersion)で指定もできるのですが、修正バージョンは人が指定する必要があることから、ワークフローの入力パラメータを増やしたり、ワークフローを起動するSlackBotに手を加えたりと工数がかかってしまいます。

そこで他の方法がないか調べたところ、通常のデプロイとして送った情報に対し、あとからSwarmiaの管理画面上でfixesVersionを指定できることがわかったので、修正デプロイを行った場合には手動で設定するような運用手順としました。

連携した結果

これらの対応を行うことで、 デプロイの頻度、変更障害率、サービス復元時間 の指標についても確認できるようになりました。

現状

現在の各指標の値ですが、変更障害率はHigh(変更失敗が少ない),その他はMediumが多いという状況です。何を改善すれば良いか明らかになった為、顧客に価値を提供しつつそれぞれの値を改善していきたいと思います。

Kibanaでユーザ単位の権限管理をできるようにしました

デジタルイノベーションラボのIです。 QUOカードPayのElasticsearch/Kibana環境でユーザ単位の権限管理をできるようにした件について書こうと思います。

背景

AWSのALBのログをElasticsearchに取り込み、Kibanaで可視化しています。

マネージドサービスであるOpenSearch Serviceを使用しているのですが、VPC外部からアクセスするためにnginxをプロキシとしてKibanaにアクセスしています。そのプロキシECSのALBリスナーにおいてCognito認証を組み込んでいました。下記のような構成です。

今回、あるユーザにおいて特定のフィールドを閲覧できないようにしたいという要求が出ました。

何か方法はないか探したところ、OpenSearch Serviceにおいて「きめ細かなアクセスコントロール」を有効化しCognito認証を行い、ユーザごとに別のIAM Roleを割り当てることで実現できそうでした。

記事①

記事②

基本的にはこの2つの記事の通りに作業を実施したのですが、いくつか考慮すべき点があったのでそのあたりを中心に書きます。

変更点

プロキシのHTTPS化

OpenSearch Serviceできめ細かなアクセスコントロールを有効化する際はトラフィックHTTPS化が必須です。

既存構成ではALBより先ではHTTP(80)で通信していたので、ここをHTTPS(443)化する必要がありました。

記事①を参考にしたのですが、この記事においてはnginxをEC2でホストしています。プロキシのためだけにEC2を運用したくはないので、既存構成と同様にECS(Fargate)+ALBを使うことにしました。つまり下記のような構成です。

nginx公式のDocker imageをベースとし、nginx confファイル差し替えとSSL証明書の設置を行ったimageを作成しました。

confファイルは以下です。基本的には記事①に記載されているとおりですが、2つ差異があります。

まず、Elasticsearch バージョン7.10 を使用しているため、_dashboards ではなく _plugin/kibana エンドポイントを指定しています。

また、nginx 1.15.0 以降ではsslディレクティブはdeprecatedとなっているため削除し、listenディレクティブにsslを追加しています。

server {
    listen 443 ssl;
    server_name $host;
    rewrite ^/$ https://$host/_plugin/kibana redirect;

    ssl_certificate           /etc/nginx/conf.d/ssl/cert.crt;
    ssl_certificate_key       /etc/nginx/conf.d/ssl/cert.key;

    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers on;

    location /_plugin/kibana {
        # Forward requests to Dashboards
        proxy_pass https://${PROXY_PASS}/_plugin/kibana;

        # Handle redirects to Cognito
        proxy_redirect https://${COGNITO_DOMAIN} https://$host;

        # Update cookie domain and path
        proxy_cookie_domain ${PROXY_PASS} $host;
        proxy_cookie_path ~*^/$ /_plugin/kibana/;

        # Response buffer settings
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }

    location ~ \/(log|sign|fav|forgot|change|saml|oauth2) {
        # Forward requests to Cognito
        proxy_pass https://${COGNITO_DOMAIN};

        # Handle redirects to Dashboards
        proxy_redirect https://${PROXY_PASS} https://$host;

        # Handle redirects to Cognito
        proxy_redirect https://${COGNITO_DOMAIN} https://$host;


        # Update cookie domain
        proxy_cookie_domain ${COGNITO_DOMAIN} $host;
    }
}

既存構成ではALBリスナーにおいてCognito認証を設定していましたが、OpenSearch Serviceで設定するため削除しました。

また前述のnginx設定だとKibanaへ302リダイレクトされるため、ターゲットグループのヘルスチェックのSuccess codesには302を設定しました。プロトコルHTTPSです。

特定フィールドのマスク

記事②の手順24~31にあたる、IAMLimitedUserRoleを割り当てたCognitoユーザーグループ: limited-user-group のKibana操作権限設定の部分です。

今回は読み取り権限のみかつ、request_uri, request_uri_path, request_uri_query, redirect_url というフィールドを閲覧できないようにしたかったので、インデックス許可を以下の様に設定しました。

これにより、limited-user-groupに所属したユーザにおいては、このフィールドのデータが以下のようにマスクされます。

終わりに

繰り返しになりますが、概ねAWS公式ドキュメント通りに進めてやりたいことが実現できました。

そもそもOpenSearch Service側ではなくALBでCognito認証をするというやや邪道な構成だったので、この機会によりよい構成に変更できてよかったです。

脱社内外注を進めています

デジタルイノベーションラボの齋藤です。

少し前から、社内で脱社内外注という話をしています。社内外注の状態でもうまく行っている組織もあると思いますが、クオカードの場合は色々と課題を感じた為、状況を変えようとしています。今回は社内外注にはどのような問題があると考えているか、またどのように脱却しようとしているかを説明します。

本記事内での社内外注、用語の定義は以下とします。

  • ビジネス側が構築するシステムの要件定義を行い、リリース時期と合わせて開発側に依頼(発注)する
  • 開発側は依頼された通りのシステムを構築し納品する
  • 開発側:デジタルイノベーションラボ所属のエンジニア、デザイナー
  • ビジネス側:エンジニア、デザイナー以外

内容的にビジネス側と開発側に対立があるように受け取られる部分があるかもしれませんが、クオカードはかなり協力的に進める事ができている組織だと思います。今回の件もビジネス側に相談したところ、ではどのように進めたら良いか教えて欲しいという申し出を頂きこの記事を書く事にしました。

社内外注の問題

正しいものを作るのが困難

DX 実践手引書 IT システム構築編 で、以下の外部委託の問題が挙げられていますが、この問題は外部委託が社内外注になった場合でもそのまま当てはまると考えています。

DX に取り組む上での課題を設定する際には、現場の人材の課題意識やニーズ、自社の強みを深堀することが重要であるが、それらの本質的な理解を外部の人材に求めることはそもそも難しい

ビジネス側が要件を作成する場合、作成したものを開発側に連携する形になりますが、必要なものを開発前の段階で漏れなく記載するのは不可能に近いと考えています。

要件だけが渡される場合、開発側がそれぞれの要件の背景や文脈を理解するのが難しいというものがあります。その為、要件に問題があったとしても開発中に気づくことができず、問題があったものがそのまま作られる事があります。

また要件だけが渡され、背景や文脈、現在の運用内容などの情報が連携されないと開発側に業務知識が蓄積されず、長期間開発を続けてもビジネス側に確認しないと何を作って良いかわからない状態が続いていきます。

エンジニア/デザイナーの専門知識が生かされにくい

通常システムを開発する際、機能要求に加え、性能やセキュリティなど、非機能要求も合わせて検討して行く事になります。

非機能要求の実現にかかる時間/コストは機能要求により変わる事がありますが、それらはシステム開発の専門家でないとわからず、社内外注の形だと非効率な設計になる事があります。

本来であれば要件の精査を行うところですが、社内外注の場合依頼を受けた時点でリリース時期が決まってしまっているなど、非効率なまま開発に着手せざるを得ない状況もあります。

また設計と実装は不可分なものと考えています。実装中に当初の設計に無理があることに気づいたり、より良い設計を思いつくことがあります。

またデザインもスキルが必要ですが、一方で専門職以外の人にそれが伝わりにくい領域だと感じます。こちらも言われたものをただ作っていくのではなく、必要に応じスキルを持っているメンバーがユーザーテスト等を実施し、正しいものを作るようにしていきたいと思います。

非効率

DX 実践手引書 IT システム構築編 で、以下の外部委託の問題が挙げられていますが、この問題も外部委託が社内外注になった場合でもそのまま当てはまると考えています。

外部委託開発はトライアルからのフィードバック、サービス実装者へのニーズの正確な伝達や設計実装などに時間的オーバーヘッドがかかりすぎることが問題となる。

目指す姿

エンジニアはシステムを作る存在ではなく、顧客(利用者)の課題を解決する存在

こういったものを作ってほしいという依頼が来た場合でも、それをそのまま構築するのではなく、依頼者の抱えている問題を把握し、場合によっては違うソリューションを提案した方が良いケースがあると思います。

例えば運用を変更する事で問題が解決するなら開発期間ゼロで問題が解決するため、ROIは高くなります。また時間をかけずに問題が解決するので顧客の満足度も高くなります。

現在までの取り組み

アナウンス&説明

まずはビジネス側、開発側両方に現在どのような問題があるか、どう改善していきたいかのアナウンスや説明を行いました。

一度伝えたら理解してもらえるというものでもないので、何度か同様の説明を行なっています。

具体的にはビジネス側には要件ではなく背景・課題を共有するようにして欲しい、開発側にはもし要件を受け取ってもそれをそのまま実装するのではなく、背景・課題を確認し、運用フローを作成した上で開発に着手して欲しいと伝えています。

フィードバック

目指したい方向とずれた進め方になっていることに気づいた場合は、できるだけ早めにその旨フィードバックするようにしています。

開発側が運用フローを作成してから開発に着手

課題を明らかにするため、また焦点がずれた機能を開発するのを避ける為、開発側が運用フローを作成してから開発に着手するようにしています。この取り組みにより、開発側もビジネス面の理解が進みました。

上記の取り組みはまだ道半ばですが、できるだけエンジニアがその専門性を生かし、ビジネスに貢献できる組織としたいと考えています。逆に技術だけに興味があるもしくは顧客の問題を解決することに興味が無い、または機能要求、非機能要求などを全て決めて欲しいという人にはマッチしない会社になっていると思います。

クオカードでは、顧客の問題を解決したい、もしくはビジネス側と一緒に要件を考えるなど裁量を持って働きたいエンジニアを募集しています。

採用サイト quo-digital.jp

出典

DX 実践手引書 IT システム構築編

AndroidアプリのDIライブラリを KoinからHiltに移行しました

デジタルイノベーションラボPayチームのゴルゴです。 先日Androidアプリで利用するDIライブラリをKoinからDagger-Hilt(移行Hilt)へ移行したので、今回はその件について書こうと思います。

背景

Androidアプリの初期実装は主に2名のエンジニアで行ったのですが、2018年の当時はまだどちらもDaggerの利用経験がなくDaggerの学習コストが高いことがネックとなり、導入が容易なKoinを採用しました。 Koinを採用してから最近まで特に大きな問題はなかったものの、

  • 近年Hiltがリリースされたことで導入コストが下がった
  • Koinのバージョン変更に伴いコード改修を必要とした
  • Koinが実行時に依存性解決するのに対し、Hiltはビルド時に解決するため問題の検出が早く安全に使える

などを考慮してHiltに移行することにしました。

進め方

準備

移行にあたってはまずはスクラムのスパイクタスクとして試験的に1画面の移行を試し、その中で課題の洗い出しと調査を実施することにしました。 Developersガイドに従って作業すると共に、Hiltのコンポーネントの分け方やライフサイクル、Koinで特殊な使い方をしていた箇所の移行方法についてまとめを行いチーム内で共有しました。 途中で躓いた点もあったのですがそれは後述しています。

本対応

2週間の1スプリントで気合を入れて一気に移行作業を実施しました。 Hiltのコンポーネントファイルは、アプリがMVVM+UseCase/Repository/DataStoreという構成なので、UseCase/Repository/DataStoreの各レイヤ毎に作成し、移行作業を進めました。 ただFirebaseAnalyticsのLoggerのような特定レイヤに属さず全般で使うようなオブジェクトもあるので、そういったものはApplicationModulesを作成してそこに属させることにしました。

また作業中に特に気をつけた点として、途中でリファクタリングやDeprecatedの対応をしたくなったのですが、これをしてしまうと万一問題があった際に原因切り分けに時間を要してしまうので、やむを得ない箇所を除きぐっとこらえて進めました。

躓いた点

途中躓いて調査を要したポイントです

ContextからEntryPoint取得

ActivityやFragment等のHilt既定のAndroidEntryPointと、依存オブジェクトを使いたい箇所が離れていてオブジェクトを引き回すのが難しいケースというのがどうしても出てきました。そのようなケースのためにContextさえあれば独自に定義したEntryPointを取得し、依存オブジェクトを取得する方法が用意されています。

https://dagger.dev/hilt/entry-points

例えば任意の場所でFooBarを欲しい場合は

@EntryPoint
@InstallIn(SingletonComponent::class)
interface FooBarEntryPoint {
    fun get(): FooBar
}

という定義をしておけば、

val fooBar = EntryPointAccessors.fromApplication<FooBarEntryPoint>(application).get()

でオブジェクトを取得することができます。

ActivityとFragmentでViewModelを共有

Activityとその内部に配置しているFragmentで同じViewModelを参照し、Activityからの操作をFragmentの画面に反映したい場面がありました。 このケースではActivity/Fragmentそれぞれで通常通りby viewModels()でオブジェクトを取得すると、別インスタンスになってしまいActivityからの操作が画面に反映されない事態が発生してしまいます。

こういった場合、Fragmentからは

// Fragment内
private val viewModel: FooBarViewModel by activityViewModels()

で取得することで、Activity側と同じインスタンスを共有することができます。

https://developer.android.com/kotlin/ktx?hl=ja#fragment

ViewModelのコンストラクタで動的なパラメータを引き渡し

例えば、一覧から詳細画面に遷移する際に、詳細画面のViewModeにIDを渡すようなケースがあると思います。ViewModelにミュータブルなプロパティやセッターメソッドを用意し、Activity/Fragmentからセットしてもいいのですが、次のようにSavedStateHandleを使うことでイミュータブルにすることができます。

@HiltViewModel
class DetailViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    val id = requireNotNull(savedStateHandle.get<ItemId>("id"))
   …
}

なお、savedStateHandle にはHiltによって activity.getIntent().getExtras()あるいはfragment.getArguments()が渡されますが、任意の値を設定したい場合にはAbstractSavedStateViewModelFactoryを継承したFactoryクラスをつくり、このクラスを使ってViewModelを生成することで実現できました。

fun provideFactory(
    owner: SavedStateRegistryOwner,
    defaultArgs: Bundle? = null,
    foo: Foo
): AbstractSavedStateViewModelFactory = object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
        handle.set("key_foo", foo)
        return TopicViewModel(handle) as T
    }
}

やってみてどうだったか

移行作業の中で単純な記述ミスも時折あったのですが、コード内にEntryPointを定義した時点で依存解決できずにビルドエラーとなる、つまりHiltのメリットとして期待していたとおり、コード実行せずとも設定ミスがわかるので効率的にすすめることが出来ました。 動かしてから初めてエラーになるようなミスはなかったと記憶しています。 実はHiltに移行したアプリをリリースしてから既に数ヶ月経過しているのですが、今のところ移行に起因した問題は出ていません。広範囲の修正をおこなったのでリリース時は不安はありましたが、無事に移行できてよかったです。

この記事がどなたかの助けになれば幸いです。