3月24日、サイバーエージェントのエンジニア・クリエイターによる技術カンファレンス「CyberAgent Developer Conference2022」を開催しました。本記事では、「ABEMAモバイルアプリ開発のDevOps戦略」の様子をお届けします。

目次

■新しい未来のテレビ「ABEMA」について
■「ABEMA」の開発体制
■Delivery Performance
■DevOps戦略
■Conclusion

■新しい未来のテレビ「ABEMA」について

「ABEMA」は新しい未来のテレビとして展開する動画配信サービスで、ニュースチャンネルをはじめ、アニメ、スポーツ、オリジナルのドラマや恋愛番組などを24時間365日放送しています。常時3万本以上のオンデマンド配信、オンラインライブも展開しており、順調にメディア規模を拡大しています。

「ABEMA」が大事にしているのは「無料・同時性」と「時間・空間からの解放」です。無料の手軽さと、みんなで同時に見るテレビの良さを残しながら、テレビという体験の再発明を目指しています。

2016年の本開局時は、iOSとAndroidのモバイルアプリとWebのPC・モバイルでスタートしました。以降、タブレット、テレビデバイス、ゲーム機など徐々に対応デバイスを拡大し、今後も更なる拡大を視野に入れています。

「ABEMA」では、ユースケースごとに対応デバイスをグルーピングしてUI/UXや仕様を考えています。ユースケースは、WebのPC・モバイル、iOS、Androidのモバイルアプリ、テレビ、スマートディスプレイ、スマートスピーカーで、技術スタックはiOS、Android、Web、Unityで開発しています。

クライアントアプリケーションの開発体制は、Cross Device、Native、Webの3つに分かれており、その中の枠がユースケースのグルーピングになります。本セッションでお話しするモバイルアプリの取り組みは、iOS、Android、Fireタブレットのモバイルアプリの開発についてです。

■「ABEMA」の開発体制

現在の開発組織はプロダクト、データ、プラットフォームの3つに区分し、その中でチームを組んでいます。プロダクトはプロダクトマネージャー、Web、ネイティブ、クロスデバイスのクライアントアプリケーション開発チームと、バックエンド、品質保証(QA)から構成されています。データはデータ分析、データ基盤、データ管理活用などです。プラットフォームはコンテンツ管理、コンテンツ配信、ストリーミングクライアントやクラウドプラットフォーム、SREなどです。

さらにビジネスバーティカルで最適化しており、リソースごとに配置し、各ビジネス施策を迅速に開発可能な体制としています。クライアントアプリケーションの開発体制はWeb、iOS、Androidだったものを、iOSとAndroidをNativeに統合し、現在はWeb、Native、Cross Deviceとしてそれぞれのユースケースグループで設計を共通化して、生産性の向上を目指しています。

Native Teamは現在20~25名程度です。ビジネスバーティカルに合わせてそれぞれチームビルドしており、その他基盤としてSRE、アーキテクチャ改善、コード共通化、iOSとAndroidのプラットフォーム進化への追従をミッションに活動するメンバーが在籍しています。

■Delivery Performance

Native Teamでは、Delivery performanceを意識して戦略的に動いています。参考にしているのが、DevOps Research and Assessmentが定義したDelivery performanceの指標とレベルです。デプロイ頻度、変更リードタイム、修復時間、変更失敗率の4つの指標が定義され、LowからEliteまでのレベル分けされています。

Native Teamでは、本指標を参考にDelivery performanceを計測しています。下記は、2019年と2020年中頃から2022年1月のパフォーマンスを比較したものです。

2019年と現在で比較すると、Androidのリリース頻度が高まり、iOS、AndroidともHighパフォーマーです。変更リードタイムと平均修復時間はあまり変化ありませんが、変更失敗率はEliteパフォーマーとなり、品質が大きく向上しました。計測可視化が出来るようになったのも大きな前進です。

■DevOps戦略

ここからはDelivery performanceといった大きな指標をもちつつ、開発プロセスにおいてどのような課題があってどのように実践・解決しているかについてお話します。まず、開発におけるプロセスを整理し、それぞれのフェーズで抱えている課題を洗い出しました。これらの課題に対して、Delivery performanceに寄与する度合いでインパクトを定義し、優先度を決めて対応を進めています。

Code

まずCodeについてです。開局から6年近く経ち、長く続くサービスとしてありがちではありますが、様々な負債を抱えながら日々開発しており、大きく下記の課題を抱えていました。

ソースコードの各コンポーネントで疎結合が担保されていないことでテスタビリティ、メンテナビリティの低いコードを生み、開発効率の低下を招いていました。これはアーキテクチャの変更によって解決を図っています。

またUI開発の安全性と生産性の低下も課題でした。UIに近いレイヤーにビジネスロジックが含まれていたり、密結合になっていたり、変更の多いUI実装が不安定でした。これについては、先程のリアーキテクチャとVisual Regression Testingによって解決を目指しています。

最後はAndroidとiOSの仕様、実装差異の拡大です。人員的なリソースの差や、不完全な仕様書、経営判断による開発優先度などの様々な要因がありました。これに対しては、リアーキテクチャとコードの共通化によって解決します。Kotlin Multiplatformを採用しビジネスロジックのコードを共通化することで実装の差異を少なくし、人員的なリソースの問題にもアプローチしています。

「ABEMA」のモバイルアプリのアーキテクチャは、開局当初の2016年からFluxをベースに独自カスタマイズして採用していましたが、コンポーネント間が密結合となっており共通ビジネスロジックの分離が困難な状況だったり、ビルド時間の増加や個別ビルドが難しいといった課題が存在していました。現在はClean Architectureをベースに再構築して、責務でコンポーネントを分割し、各コンポーネントの将来的な変更に柔軟に対応できるような設計を目指しています。iOSはFluxからMVVMを取り入れ、AndroidはFluxでしたが、どちらも合わせてClean Architectureベースのアーキテクチャへと移行中です。

全体のモジュール構成は下記の通りです。

コンポーネント(iOSだとフレームワーク、Androidだとライブラリーモジュール)で見ると、UIComponent、UILogic、UseCase、Domain、Repository、APIServiceがあります。基本的にClean Architectureベースですが、ControllerとPresenterをUILogicでまとめ、リポジトリはデータソースを抽象化するのではなく、APIServiceで分けているところが特徴です。

UIComponentはView、ViewController、Activity、Fragmentなどです。UILogicは ControllerとPresenterの役割があります。コンポーネント間の依存をなくすために、UILogic InterfaceとUILogicでコンポーネントを分けています。UILogicはUI ComponentからのイベントをハンドリングしてUseCaseを実行し、その結果をUI Componentに反映する役割があります。

UseCaseはアプリケーション固有の仕様を実装します。こちらも依存を切るため、UseCaseInterfaceとUseCaseを分けています。ユーザーがアプリケーションを操作し、結果を得るまでの一連を1つのUseCasとして実装しています。

Domainはプラットフォームやアプリケーションに依存しないドメインモデルと、そのビジネスロジックを実装する役割を担います。このコンポーネントにはアプリケーション層やデータ層で使用されるデータモデルや、ドメインモデルに属するビジネスロジック、アプリケーションに依存しないビジネスロジックを含みます。

APIServiceは外部APIからデータを取得するコンポーネント、Repositoryはドメインモデルのキャッシュ・永続化、OS/Deviceイベントをハンドリングするコンポーネントです。RepositoryでAPIを抽象化する設計ではなく、キャッシュ制御、リトライ、エラーハンドリングはアプリケーション仕様として実装する方針なので、Repositoryでの抽象化は行なわないようにしています。App Targetは全てのコンポーネントに依存して、DIを行いアプリケーションを構成します。以上が新しいアーキテクチャの概要になります。

UI開発の生産性、安全性の低下の課題に対してはVisual Regression Testingの導入を進めています。View単位ではUIModel、ViewController/Activity/FragmentではUILogicのスタブをDIして、データパターンを作成してUIパターンを網羅するようにしています。reg-suitというツールを使用しており、コード変更前後のUI差分をチェックしています。プルリクエストをフックとしてiOSはCircleCI、AndroidはFirebase Test Labでスナップショットを書き出しています。

プルリクエストで差分のコメントがつくので、レビュイー・レビュアーは意図した変更か、デグレかを確認できます。

軽量なUI開発用カタログアプリも作成しています。UIComponentとUILogicInterfaceの依存のみで構成しています。Storybookのようなものですが、これによってデザイナーとの意思疎通、スタイルガイド、UI開発効率化を図っています。iOSに関してはXcode Previewsも併用しており、プレビューしながら開発できるようにしています。

iOSとAndroidの仕様・実装差異の拡大に対しては、生産性向上も含めてコードの共通化に取り組んでいます。これはNative Teamだけでなく、Web、Cross Deviceチームと連携を取りながら共通化を検討しています。対応デバイスの拡大については、最初に汎化できる部分と、特化でするべき部分を検討して、方針を定めています。

Clean Architectureベースで整理すると、サービスの強みとしてUI/UX、視聴体験を追求するために、プレゼンテーション層は各デバイスに最適化し、デバイス・プラットフォームに依存しないユースケースとエンティは共通化を進めています。

特化する部分は、UIプレゼンテーション層、プラットフォーム固有のユースケース、汎化する部分は汎化可能な外部インターフェースとプラットフォームに依存しないエンティティ・ユースケースとなります。

コード共通化にあたり、マルチプラットフォーム技術を検討しました。UIを含む共通化は独自UI、ネイティブUI、WebベースのUIがあり、後はUIを含まないロジックのみの共通化があります。選定基準には、プラットフォームに最適なUI/UXが提供できることを重視し、その他にはプラットフォームAPIへの追従性、開発コミュニティの将来性、快適な開発体験(DX)を基準に選定しました。

UIを含むコード共通化は各プラットフォームに最適なUI/UXの提供が難しく、動画再生周りの技術がネックとなる部分もあり要求を満たせないと判断しました。開発やコミュニティ状況、将来性と開発体験を鑑みて、現時点ではビジネスロジックの共通化を目的にKotlin Multiplatform Mobile(以下KMM)を採用しています。クライアント全体を見据え、他チームと連携しつつ、まずはNative Teamで共通化を進めています。

UI、UILogic、プラットフォーム固有のUseCaseと、RepositoryはiOSとAndroidでそれぞれ実装しますが、それ以外はKMMで共通化していっています。

移行コストがかかるRepositoryはインターフェースをKMMから提供し、各プラットフォームでインターフェースに適合させて実装します。既存資産を活用しつつ、KMMに依存しすぎないようにexpect/actualは基本的に使わない方針です。KMM側にRepositoryを実装するケースで、KMMのライブラリがないSDKはAdapterを各プラットフォームで実装して、KMM側のRepositoryにDIします。

KMMのモジュール構成ですが、共通機能のコアモジュール、機能ごとのフィーチャーモジュールをサブプロジェクトで管理しています。UseCase、Domain、Repository、API Serviceなどはライブラリーモジュール単位です。コアモジュールは後方互換を維持して最新を参照して開発するようにし、フィーチャーモジュールはフィーチャーごとにバージョン管理するようにしています。

アプリ本体とのインターフェースでは、ビジネスロジックの実行は基本的にフィーチャーモジュールのUseCaseInterfaceがアプリとのインターフェースになります。移行過程で、プラットフォーム固有のUseCaseから、コアモジュールのServiceを実行するケースもあります。

iOSはObjective-Cに変換されるため、インターフェース変換が必要になるケースがあります。iOSはRxSwiftを使っているのですが、FlowをObservableに、suspend functionをSingleに変換してます。逆にiOSをKMMに適合させる場合はObservableをFlowに、Singleをsuspend functionに変換しています。変換にボイラープレートコードが多くなるので、Kotlin Symbol ProcessingというCompiler Pluginを使ってAdapterの自動生成をしています。

現状はiOS、Android、KMMでRepositoryが分かれていますが、バージョン管理の煩雑さやKMMモジュールが反映されるまでのリードタイムがあるため、開発効率のためにMonorepoも検討しています。

リアーキテクチャやコードの共通化はコストもかかりますが、コードの内部品質は継続的な生産性の維持、向上の土台となるので、やる意義があると考えています。

Build

ビルドについては、デバッグやCI待ちなどのビルド時間が、積み重ねで開発効率や開発体験の観点から大きくなってくると考えています。基本的にはプラットフォーム純正のビルドツールを使用して、最新バージョンへの追従、コンパイラやビルドオプションの見直しを定期的に実施しています。さらに差分ビルドによる高速化、ビルド・テストの並列化、マシンスペック、CIプランの見直しもしています。リアーキテクチャによる恩恵として、マルチモジュール化の差分ビルドによる高速化も大きなポイントになります。

AndroidはGCEを用いたリモートビルド環境を構築し、活用しています。rsyncでソースコードをアップロードして、リモートマシンでビルドし、アーティファクトをダウンロードして、ローカルマシンで実行する流れです。ローカルマシンのリソースを使わず作業が快適になるため、おすすめです。

CI/CDツールのプランの見直しだけではなく、ツール自体も変えています。Jenkinsから2017年にBitriseに移行し、2020年にはCircleCIとGitHub Actionsに移行しています。

●Test

テストは手動QAに大きく依存しており、リードタイムが長くなる要因の一つです。また全体としてテストポリシーがなく、どのフェーズに何を担保するのか曖昧であることが課題でした。テストピラミッドで整理すると以下の通りです。

ユニットテストは基本的に書いているものの、書けてない部分も多くあります。UI部分は先述した通り、Visual Regression TestingによるPRごとの差分確認をしています。インテグレーションテストはほぼ存在せず、単体の挙動は担保できていても、結合した際の挙動を担保できていない状態です。

E2Eテストは機能テスト、リリース前の機能網羅テスト(通しテスト)、フリーテスト(探索的テスト)を実施しています。自動化されたテストはほぼ存在せず、手動QAで品質を担保しており、QAコストも増加しつづけています。

現在リアーキテクチャと並行してテストポリシーの策定を進めています。どのテストで、どのコンポーネントを担保するかを整理するとこちらの図になります。

ユニットテストでは担保すべき品質と網羅性を定義し、重要度でテスト実施優先度を決めています。UIComponentについては、先程の通りVisual Regression Testingによる担保を進めています。カタログを充実させてカバー範囲を広げ、レビュイー・レビュアーの責務を明確化して効果を高めていっています。

インテグレーションテストとE2Eテストの自動化は現状ほぼありませんが、手動テストで担保している粒度が細か過ぎるので、各テストレベルで担保すべき品質を定義し、ユニットテストとインテグレーションテストを拡充し、E2Eテストで担保するべきシナリオを明確化していこうと考えています。またE2Eテストの自動化はコストが大きくなりがちなので、手動テストとのバランスを見極めながら進めようと考えています。

●Release

リリースにおいては、改善サイクルのリードタイムが長いことや、変更失敗の影響が大きいこと、障害復旧時間の長さに課題がありました。

開発フローとしては、週1リリースするフローを導入しています。水曜夜のmasterブランチの状態でタグを切り、木曜にリリース前テスト、金曜にストア申請、大体月曜に申請が通るので段階リリースを開始し、水曜に100%適用といった流れを繰り返しています。

開発のパターンは大きく3つ。1つ目はQAを必要としないケースで、つなぎ込みをしてないコンポーネントや、OS/端末バリエーションテストが必要ないものです。ロジックのみの変更でユニットテストで担保できるものはコードレビューのみで、QAなしでmasterへmergeします。

2つ目はFeature Flagなしで開発するケースです。機能に影響がある変更はフィーチャーブランチでQA後にmasterへmergeしています。可能な限り機能に影響がないよう、小さい単位でmergeすることを推奨しています。

3つ目はFeature Flagを使用して開発するケースです。小さい単位でmasterへmergeして、masterのモジュールでテスターのみ機能をオンにしてQA実施するか、Feature Flag差し替えのフィーチャーブランチでQAを実施します。積極的にFeature Flagを使用して、デプロイとリリースを切り離し機能のライフサイクルを管理していますが、全てのケースでFeature Flagを入れると複雑になるケースがあるのと、コストもかかるので案件により判断しています。

Feature Flagの使い方は大きく分けて4つ、Release Flag、Ops Flag、Permission Flag、Experiment Flagです。フラグの生存期間と変化の度合いに特徴があります。

Release Flagは開発中でテストされていないコードをプロダクションコードにリリースするとき、絶対にオンにさせない機能として使用し、Trunk-Based Developmentを可能とします。開発中に使うのは、生存期間は短く、変化は小さいフラグです。Feature Flagを使う場合は、基本的にはWorkInProgress Flagとして定義し、開発していきます。

Ops Flagはシステム動作を変更するためのフラグで、生存期間が長く、変化は中程度です。「ABEMA」では低遅延配信有効フラグ、広告配信有効フラグ、広告配信のbtirateやタイムアウト、KMM有効フラグなどに利用しています。KMMフラグは現在削除していますが、Kotlin Multiplatform導入初期に、問題があればフォールバックできるようにしていました。

Permission Flagは特定ユーザーだけに機能を提供したり、変更したりするフラグとして定義しており、生存期間は長く、変化も大きいフラグになります。このフラグはメトリクスサンプリング対象ユーザーや、VoiceOver機能の限定配布で利用しています。

Experiment FlagはA/Bテストで使用するフラグで、生存期間は短く、変化は大きいフラグです。「ABEMA」ではデザインやリコメンド、チュートリアルや訴求ポップアップ、UIモジュール表示・非表示、アンケート表示回数など、様々なA/Bテストで使用しています。

開発中はWorkInProgressですが、機能が完全に実装され、テストされているものは、用途によりOps、Permission、Experimentに適切に差し替えて運用します。
またWorkInProgressはFeature Flag管理画面から変更できないので、デバックやQA用にデバックメニューからoverrideできるメニューを用意して、手軽に変更と確認ができるようにしています。

リリース周りでは、リリースフロー改善やフラグ活用、カナリアリリースのプラクティスで、リリースの安全性を高めていっています。

●Monitor

モニタリングにおいては「最低限の監視しかできてないこと」「素早く障害検知ができていないこと」「障害対応基準が曖昧で属人化していること」「影響範囲を把握できてないこと」という4つの課題がありました。

よりObservabilityを向上させて障害検知やアクションにつなげたいと考え、いくつかソリューションをPOCした結果、New Relicを採用してモニタリング強化を図っています。

クライアントアプリケーションでSLI/SLOの定義は特に難しいのですが、まず機能が提供できているといえる条件を定義して、計測対象を明確化します。計測実装して、ベースラインを確認して、そこからSLO設定する形です。導入では現状のパフォーマンスを参考にベースラインを定めて、SLO調整しています。クラッシュはセッション数のクラッシュ率を見ていますが、その他は基本ユースケースのAvailabilityとLatencyをSLIとして定義して、SLOを設定しています。リリースでお話したカナリアリリース期間で、視聴品質とプロダクト品質をチェックします。

ここで基準を下回る場合には、リリースを停止して原因調査を行います。チェック用にダッシュボードも用意していますが、毎回見に行くのは手間なので、botを作成して、リリース判定を簡単にチェックできるようにしています。SLI、SLO導入はまだ主要な部分だけですが、重要度が高い部分から順次導入しています。

アラートは高い精度で検出時間が短いことが望ましく、これを実現するためにバーンレートという概念を導入しています。バックエンドでは高い水準を定義することが多いものの、フロントエンドではノイズや端末状態の影響を受けるため、SLOは低めに設定しています。レベルを分けて、ワーニングはslack通知し、クリティカルならPagerDutyでOn-Call設定をしています。

障害レベルは開発局全体で定義しており、共通の障害レベルを定め、各レベルに応じた障害フローの構築を進めています。障害時のトラブルシューティングはモニタリングシステムによる検知、障害連絡、事象確認によるインシデント生成でwarroomという専用の障害対応部屋がslackチャンネルに作られ、対応開始します。

Native Teamの障害対応フローでは、PagerDutyのAcknowledge開始後、原因調査を開始します。リリース対応が必要か不要か、緊急を要するか、Feature Flagの運用で回避できるかなどが定められています。

モニタリングの取り組みでは、アプリのObservabilityを向上し、素早く障害検知、影響範囲を把握できるようにして、障害対応時のフローを明確化して、誰もが素早く対応できる体制の構築を進めています。

●Documentation

開発プロセス全般に関わるところではドキュメンテーションにも力を入れてます。さまざまな取り組みの中で、ドキュメントは非常に重要な要素です。なぜやるか、どのようにやるかまでしっかり残して、新規メンバーがすぐにキャッチアップでき、振り返りによる改善をしていけるようにしています。

ドキュメントはなるべく対象のコードの近くにおき、コードと同じように管理して、レビューで品質を担保しています。iOSとAndroidのTech Radarでは、技術選定状況、選定基準、選定理由を可視化・追跡可能として継続的に進化できるようにしています。ドキュメンテーションを重要なアウトプットとして位置付けて組織にナレッジがたまるようにして、開発サイクル改善を促進できるように取り組んでいます。

■Conclusion

開発体制、デリバリー品質、開発プロセスの課題とプラクティスについて幅広くご紹介しました。高速に改善サイクルを回し、素早くユーザーのニーズに答えて、品質を維持しながらも継続的に改善していくことで、変化の激しい環境でビジネス競争力の向上につながると考えています。

まずは開発プロセスのどこに課題があるかを明確にして、インパクトを考えながら取り組んでいくことが大切だと思っています。組織やチーム、フェーズによって課題になっている部分は異なってくると思いますので、本セッションも参考に一つになれば幸いです。また、本セッションで紹介しきれなかった部分も、チームメンバーがさまざまなところで発信していますので、ぜひお時間があるときにご覧ください。

「CyberAgent Developer Conference 2022」のアーカイブ動画・登壇資料は公式サイトにて公開しています。ぜひご覧ください。


https://cadc.cyberagent.co.jp/2022/

 

■採用情報

新卒採用:https://www.cyberagent.co.jp/careers/special/students/tech/?ver=2023-1.0.0
キャリア採用:https://www.cyberagent.co.jp/careers/professional/