every Tech Blog

株式会社エブリーのTech Blogです。

エブリー初のエンジニア新卒研修を開催しました!

はじめに

こんにちは。DELISH KITCHEN 開発部 SERS グループ兼、CTO 室 DevEnable グループ所属の池です。

SERS グループでは主に小売向けプロダクトの開発を行なっており、DevEnable グループでは社内開発組織活性化に向けた活動を行なっています。

DevEnable グループについては以下の記事で紹介しているので、よければご参照ください。

tech.every.tv

本記事では今年度から初開催となるエンジニア新卒研修の取り組みについてご紹介します!

エンジニア新卒研修を開催するに至った背景

エブリーでは、新卒社員全員を対象に内定者研修からはじまり、定期的な研修を行い、事業理解や業務におけるスキル獲得など早期の成長をサポートしています。

昨年までの研修ではエンジニアリングに特化した研修は行っておらず、スキル指導は配属後の OJT に依る部分が多いようなオンボーディング体制となっていました。これにより、配属後の実務において以下のような課題が生じていました。

  • マインドセット
    • エブリーのエンジニアとして働く上で期待されるマインド・スタンスがわからない
  • 領域外の自分ごと化
    • 全体像を把握できずに専門領域外のことを自分ごと化できない
  • 実務における前提知識の学習
    • インフラを体系的に学ぶ機会がない
    • テストやアーキテクチャに関して馴染みがない

そこで、今年度からエンジニア領域におけるオンボーディング体制を強化し、エンジニアとしての早期成長をサポートすべく、エンジニアを対象とした新卒研修を開催することにしました。

エンジニア新卒研修の目的と方針

目的

エンジニア新卒研修の主な目的は次の通りです。

『オーナシップを持ってプロダクト課題の解決に動けるエンジニアへの土台を作る』

上述した課題感を解消するとともに、この目的を達成するための施策を検討しました。

方針

目的に基づき、研修中と研修後に分けて次のような方針を決めました。

  • 研修中
    • エブリーのエンジニアとして求められるマインドを理解する
    • 専門領域を超えてエブリー全体で使われている技術スタックを理解する
  • 研修後
    • 内部のオリジナルコンテンツによる研修での支援が難しい領域については、配属後のスキル支援環境を提供する

エンジニア新卒研修のカリキュラム

DevEnable グループと開発部の役員・部長・マネージャーを中心にカリキュラムを策定しました。

今年からの取り組みであるため、コンテンツも役員・部長・マネージャーを中心にゼロから全て作成したものとなっています。

全体で実施期間 5 日です。

  • マインド研修
    • CTO からのメッセージ(1 時間)
    • インシデントへの向き合い方(30 分)
  • 技術スタックの把握(講義 + ハンズオン)
    • バックエンド/インフラ(1.5 日)
    • モバイル(1 日)
    • Web(1 日)
    • データ(1 日)
  • ランチ会
  • (研修後) AWS JumpStart 2024 for NewGrads

ここからは各講義について概要を説明します。

CTO からのメッセージ

この講義では、CTO が今までのキャリアを通じて大切にしているマインドセットを中心講義しました。

CTO の成功談や失敗談、どういう行動が評価されてきたかなど、エブリーの CTO ならではの経験談がふんだんに盛り込まれた内容となっており、配属に向けて大きな刺激となる講義でした。

CTO講義の様子

インシデントへの向き合い方

以前から、インシデント対応は新卒社員にとって精神的なハードルが高く、入っても何をやっていいかわからず、主体的に取り組みにくいという声が多く上がっていました。 また、新卒社員に限らず会社全体としても同様の課題感を持っていたということもあり、インシデント対応におけるマインド理解を研修の題材として選定しました。

この講義は、そのような課題を解消すべく、エブリー開発部におけるインシデントに対する向き合い方・マインドを教える講義です。 内容は次の通りで、インシデントにおける行動指針を学べる内容となっています。

  • インシデントが起きたらまずどうすればよいか
    • 関係ありそうな人間を巻き込む
  • インシデントが起こってそう
    • 野次馬でも参加しましょう
  • 別の部署でインシデントが発生している
    • 関係なくても参加しましょう
  • 小さくてもインシデントはインシデント
    • インシデントかどうかは上長が判定するのでとりあえず報告しましょう(割れ窓理論
  • インシデントは終わってからも大事
    • ポストモーテム

バックエンド/インフラ

バックエンド/インフラ講義では、次のような目標を設定しました。

  • エブリーで共通的に用いられる技術や知識について、一通り触れて理解する
  • 自分たちが開発するシステムが具体的にどのような環境で動いているかを理解する
  • なぜ今の構成になっているかを理解する
  • パフォーマンス観点で取り組み方について理解する

ハンズオンを通して上記目標の内容を理解できる形式となっています。

ハンズオンはいくつかのパートに分かれています。

  • Go を利用した簡易的な API サーバをもとにシステム開発を体験
    • API を操作・改修
    • テストコード実装
    • パフォーマンス改善
  • デプロイ
    • 手動でプログラムを AWS 上のサーバに配置し、インターネット上に公開された状態を構築する手動デプロイ
    • ECS を用いた半手動デプロイ
    • terraform や CI/CD を用いた自動デプロイ

モバイル

モバイル講義は座学とハンズオンを通して学べる形式となっており、次のような内容を行いました。

  • 環境構築およびパッケージ構成、マルチターゲットの説明
  • 画面遷移
    • UIKit, SwiftUI
    • ViewController, ViewModel, View を作成し、画面遷移できるようにする
  • API 接続
    • Network と Model、非同期処理
    • API に接続して情報を取得し、Model 変換を行う
  • View 作成
    • Figma を参考に View を作成。ViewModel と接続し、Model を View に反映
  • 分析
    • アプリログ収集、データフロー、ログ設計、Crashlytics

モバイル講義の様子

Web

Web 講義では、次のような目標を設定しました。

『フレームワークによらない web の基礎知識を理解し、今後の web 開発のベースにする』

  • (座学)web 開発でベースとなる知識を身につける
  • (座学)web 開発で意識するポイントを理解する
  • (ハンズオン)実際の web 開発のイメージをつける

以下のような講義内容となっています。

  • エブリーでの web 開発
  • エブリーの web 開発で利用される技術スタック
  • Web 開発の歴史
  • Web 開発で知っておきたい基礎知識
  • Web 開発で意識されるポイント
  • ハンズオン:仮想の簡易的な DELISH KITCHEN アプリを用いて、デザインをもとに画面を作成

Web講義の様子

データ

データ講義では、データ領域の各分野毎に講義を行うようなカリキュラムとなっています。

  • データエンジニア
    • データエンジニアとは
    • エブリーで扱うデータ
    • 一般のデータベースとの違い
  • データサイエンティスト
    • [業務理解]エブリーのデータサイエンティストが何をやっているか知る
    • [協業の視点]エンジニアリングとデータサイエンスの違いを知る
  • データストラテジスト
    • データストラテジストとは
    • データストラテジストの具体的な業務内容
  • ハンズオン
    • Databricks
    • SQL

ランチ会

研修期間中は、以下の目的のもと、毎日各領域ごとのエンジニア社員とのランチ会を行いました。

  • 人的ネットワークの構築
  • 実務イメージを深める

各領域の社員とのランチ会を行うことで、全ての領域において気軽に話せるようなネットワークを作ることができました。

受講者の声

今後のエンジニア新卒研修の改善に向けて、受講後のアンケートを通じて、受講者からのフィードバックを収集しました。

全体の満足度に関する質問項目では、受講者全員から最も高い評価を得ることができました。

続いてポジティブな声の一例を紹介します。

  • マインドセット
    • 新卒でもインシデントを発見した場合は報告する
    • 自分が苦手だと思う部分があったので、配属してから実際その技術を使用するまでに、しっかりと自習しておく
  • 領域外の自分ごと化
    • クライアントチームやデータチームが何をしているか理解できたことはかなり良かった。今後一緒に仕事するときに相手のことを考えながら業務に取り組めるので、よりスムーズに業務が進められると思う。
  • 実務に必要な前提知識の学習
    • tfstate の知識などが早速タスクで役に立った
    • 利用している技術・ツールについての全体像が掴めた

研修の目的としていた課題感の解消に関連するようなコメントがあり、大枠の目的は達成できたと思っています。

しかし、一方で次のようなネガティブな声もあり、改善点も見つかりました。

  • 最低限必要な知識などを事前に共有することで、もう一段階踏み込んだ講義内容になると感じた。
  • 研修で使用する各種ツールの使い方についての研修か資料があると取り組みやすいと思いました。

今回の研修は、受講者の専門領域外を含めて全ての領域を学ぶような研修だったこともあり、初めて扱う技術やツールが多く出てきます。 また、運営側で受講者の前提知識の基準を高く設定していた部分もあったため、講義に必要な前提知識を持っていないと理解が困難な内容が一部ありました。

次回開催時にはアンケート結果を踏まえてより新卒社員にとって学びの多い研修になるように改善していく予定です。

おわりに

本記事では初開催となったエンジニア新卒研修について紹介しました。

初めての取り組みで改善すべき点も多くありましたが、全体的には開催して意義のある取り組みだったと思います。 プロダクト開発全体に関する解像度を高める機会になったことや、新卒自身が技術スキルにおける課題を把握できたこと、ランチ会で各領域のエンジニアとの接点を構築できたことなど、配属に向けたエンジニアとしての早期成長の土台作りに繋がったと感じています。

エンジニア新卒研修を含め、今後もスキルアップのためのり組みや体制を整えていく予定です。

他の取り組みを開催した際には同様にレポートをお届けできればと思うので、ご期待ください。

簡単に高品質なドキュメントスキャナーが実装できる、ML Kit Document Scanner API がリリースされたので使用してみた。

はじめに

トモニテでAndroid開発を担当している岡田です。

先日、ML Kit Document Scanner API のベータ版がリリースされました。
公式ドキュメントやサンプルアプリを参考に、今回はAndroidでの実装方法・内容をご紹介したいと思います。
以下に参考にしたサイトのリンクを示します。是非覗いてみてください。

android-developers.googleblog.com

developers.google.com

github.com

ML Kit とは

Googleの機械学習の機能を、Android/iOSアプリとして提供するモバイルSDKです。
例えば顔検出やバーコードスキャンなどの機能を簡単に実装することができます。

ML Kit Document Scanner API とは

紙の資料をカメラでスキャンして、デジタル資料として読み込むことができる、ドキュメントスキャナーSDKのAPIです。
用意されたスキャナーは自動キャプチャ・切り抜き・自動回転検出機能だけでなく、フィルター機能など編集もできます。
つまり、すごいリッチなスキャナーを簡単に実装できるということです。
スキャナーの使用感については公式のサンプルアプリでご確認ください。

実装の簡単な流れは以下の通りです。

  1. スキャナーのオプションを決める
  2. スキャナーを呼び出す
  3. スキャナーから結果を取得する

主なクラスについて

主に以下の3つのクラスで構成されています。

  • GmsDocumentScannerOptions
  • GmsDocumentScanning
  • GmsDocumentScanningResult

それぞれ名前の通り、スキャナーのオプション、スキャン開始、スキャン結果に関するクラスになっています。
これらについて、実際に確認してみたいと思います。

GmsDocumentScannerOptions (オプション)

GmsDocumentScannerOptions でスキャナーに関してのオプションを設定できます。
公式ドキュメントのコードでは、以下のように紹介されています。

val options = GmsDocumentScannerOptions.Builder()
    .setGalleryImportAllowed(false) // フォトギャラリーからのインポートの可否
    .setPageLimit(2)                // 最大ページ枚
    .setResultFormats(RESULT_FORMAT_JPEG, RESULT_FORMAT_PDF)    // 結果のフォーマット
    .setScannerMode(SCANNER_MODE_FULL)  // スキャナのモード
    .build()

設定できるオプションは以下の通りです。

  • スキャナーのモード
  • 最大ページ枚
  • 結果のフォーマット
  • フォトギャラリーからのインポートの可否

それぞれ見ていきます。

スキャナーのモード

setScannerMode() を用いて、スキャナーの設定ができます。 現段階では3種類のモードが要されています。

public static final int SCANNER_MODE_BASE = 3;
public static final int SCANNER_MODE_BASE_WITH_FILTER = 2;
public static final int SCANNER_MODE_FULL = 1;

SCANNER_MODE_BASE
基本的な編集機能(ページの切り抜き、回転、並べ替えなど)が使用できます。

SCANNER_MODE_BASEのプレビュー画像

SCANNER_MODE_BASE_WITH_FILTER
SCANNER_MODE_BASEモードに画像フィルタ(グレースケール、自動画像補正など)が追加されます。

SCANNER_MODE_BASE_WITH_FILTERのプレビュー画像

SCANNER_MODE_FULL(デフォルト)
SCANNER_MODE_BASE_WITH_FILTERモードの機能に加えて画像クリーニング機能(汚れや指の消去など)が追加されます。

SCANNER_MODE_FULLのプレビュー画像

最大ページ数

setPageLimit() を用いて、最大のページ数を指定できます。int型で指定します。
以下は最大ページ数を2とした場合のスクリーンショットです。最大数に達すると、ページを追加する "+" アイコンが出ないことがわかると思います。

最大ページ数のプレビュー画像

フォトギャラリーからのインポートの可否

フォトギャラリーからのインポートの可否を設定できます。
setGalleryImportAllowed() を用い、Booleanで指定します。

結果のフォーマット

setResultFormats() を用いて、出力結果のフォーマットを指定できます。
現段階では、JPEGかPDF、またはその両方を選択できるようです。

public static final int RESULT_FORMAT_JPEG = 101;
public static final int RESULT_FORMAT_PDF = 102;

GmsDocumentScanning (スキャナーの開始)

スキャナーはGmsDocumentScanningを用いて、以下のように記述できます。
コードは公式のドキュメントとサンプルを参考にしました。

GmsDocumentScanning.getClient(options)  // optionsは先ほど紹介したGmsDocumentScannerOptions
  .getStartScanIntent(activity)
  .addOnSuccessListener { intentSender ->
     scannerLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
   }
  .addOnFailureListener {
    // 失敗した際の処理
  }

メソッドチェーンでわかりにくいので、順を追って説明します。

はじめに、GmsDocumentScanninggetClient()を呼び出します。 getClient()GmsDocumentScannerOptionsを引数にとり、GmsDocumentScannerを返します。

public final class GmsDocumentScanning {
    @androidx.annotation.NonNull
    public static com.google.mlkit.vision.documentscanner.GmsDocumentScanner getClient(@androidx.annotation.NonNull com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions options) { /* compiled code */ }

    private GmsDocumentScanning() { /* compiled code */ }
}

返ってくるGmsDocumentScannerはInterfaceです。getStartScanIntent()というメソッドが用意されています。
こちらはActivityを引数にとり、Task<IntentSender>を返します。

Taskが返されるので、addOnSuccessListeneraddOnFailureListenerが使えます。
成功時にIntentSenderが返ってきます。IntentSenderはスキャナーを起動するために使用します。

public interface GmsDocumentScanner extends com.google.android.gms.common.api.OptionalModuleApi {
    @androidx.annotation.NonNull
    com.google.android.gms.tasks.Task<android.content.IntentSender> getStartScanIntent(@androidx.annotation.NonNull android.app.Activity activity);
}

scannerLauncherは後述する、ActivityResultLauncher<IntentSenderRequest>型の変数です。 こちらは終了したActivityの結果を受け取り、処理します。

GmsDocumentScanningResult (結果処理)

GmsDocumentScanningResultを用いて、結果を処理できます。
公式ドキュメントにて、以下のように記述されています。

val scannerLauncher = registerForActivityResult(StartIntentSenderForResult()) {
  result -> {
    if (result.resultCode == RESULT_OK) {
      val result =
        GmsDocumentScanningResult.fromActivityResultIntent(result.data) // ここで結果を受け取る
      result.getPages()?.let { pages ->
        for (page in pages) {
          val imageUri = pages.get(0).getImageUri()
          // imageUriを用いた処理
        }
      }
      result.getPdf()?.let { pdf ->
        val pdfUri = pdf.getUri()
        val pageCount = pdf.getPageCount()
        // pdfUriやpageCountを用いた処理
      }
    }
  }
}

GmsDocumentScanningResultfromActivityResultIntent()を用いて、結果を受け取ります。 Intentを引数に取り、GmsDocumentScanningResultとして返してくれます。

@androidx.annotation.Nullable
public static com.google.mlkit.vision.documentscanner.GmsDocumentScanningResult fromActivityResultIntent(@androidx.annotation.Nullable android.content.Intent data) { /* compiled code */ }

返されるGmsDocumentScanningResultですが、PagePdfを持っています。

public abstract class GmsDocumentScanningResult implements android.os.Parcelable {   
    ...
    public static abstract class Page implements android.os.Parcelable {
        @androidx.annotation.NonNull
        public abstract android.net.Uri getImageUri();

        public Page() { /* compiled code */ }
    }

    public static abstract class Pdf implements android.os.Parcelable {
        public abstract int getPageCount();

        @androidx.annotation.NonNull
        public abstract android.net.Uri getUri();

        public Pdf() { /* compiled code */ }
    }
}

現時点でpageは画像のUriPdfはページ数とUriを取得できるようです。
それぞれ結果に合わせて、処理を記述できます。

説明については、以上になります。

まとめ

ML Kit Document Scanner API を用いると、簡単に高品質なドキュメントスキャナーが実装できました。
実装の簡単な流れは以下の通りです。

  1. スキャナーのオプションを決める
  2. スキャナーを呼び出す
  3. スキャナーから結果を取得する

余談

未実装のキャプチャーモード

com.google.mlkit.vision.documentscanner.GmsDocumentScannerOptions のコードには CaptureMode なるものが存在しました。

public static final int CAPTURE_MODE_AUTO = 1;
public static final int CAPTURE_MODE_MANUAL = 2;

現在はオートのみだが、今後はマニュアルで選択できるような機能が追加されるかもしれない……?
アップデートが楽しみです。今後も追っていきたいと思います!

資格試験(AWS Certified)の受験を通じた業務での活用

こんにちは 開発本部データ&AIチームでデータエンジニアを担当している塚田です。

今回は、AWS Certified Data Engineer - Associateを受験しましたので、準備と感想とエブリー(業務)でどのように活かせそうかをまとめたいと思います。

なお、AWS 認定プログラムアグリーメントに則り試験内容については触れませんのでご承知おきください。

本試験について

AWS Certified Data Engineer - AssociateAWS(トレーニングと認定)で以下のように説明されています

AWS Certified Data Engineer - Associate は、コアデータ関連の AWS サービスに関するスキルと知識や、データの取り込みと変換、プログラミングの概念を適用しながらのデータパイプラインのオーケストレート、データモデルの設計、データライフサイクルの管理、データ品質の確保といった能力を検証します。

データ分析というよりかはデータパイプライン全般に対してのスキルを評価するものだと感じました。

AWSで適切なサービスを利用してスケールのしやすさや保守運用・セキュリティなどの能力を評価すると試験ガイドにも記載されているので、体験したものと差異はないように感じています。

なぜ、受験しようと思ったか

現在の業務範囲に通じるものがあることが一番大きな理由ですが、他に理由があるとすれば以下が上げられます。

  • AWS Certified Data Engineer - Associateは2024年4月現在、比較的新しい試験であること
  • 今まで取得したすべてのAWS Certifiedの有効期限が切れてしまったこと

試験準備

  • 公式問題集を解く
    • ある程度AWSサービスを利用したことがあったため事前準備なしで回答しました(何度でも受験可能です)
      • 悩んだ問題や間違えたものは解説を読んで理解できたかを再度解くことで確認しました
    • どのような粒度の問題が出るかなどイメージすることができとても有用でした
    • 上記以外にも各資格試験で公式問題集があるので合わせて解いて周辺知識の補完に利用しました
  • 試験ガイドを読む
    • 聞いたことがない・実際利用するとしたときに手が動かせないと思ったサービスはサービス別資料から該当のサービス資料を確認し、実際にAWSコンソール上で操作してみました

あまり特筆する部分はありませんが、今までの経験と未経験の部分を勉強によって補足するイメージで準備を行いました。

受験・受験後

  • 余裕を持って予約した時間・会場で受験しました
    • 80分程度で問題を一周し再確認などして20分残して退出しました
  • その場では結果は受け取れず、後日連絡が来る形でした
    • 結果としては合格はしたものの、まだまだ経験が必要だなと感じるものでした

業務で活かせるか

何かインプットしたらアウトプットしていきたいと思うところですが、 今回は本試験を通じて得たものについてどのように活かせるかを考えたいと思います。

現在、エブリーのデータ&AIチームではDatabricksを活用したデータフローの作成やML・ABテストの運用を行なっています。

tech.every.tv

AWSをフルに活用している構成ではありませんが、 セキュリティやコストについてはどのプラットフォームを利用したとしても意識するべきことだと感じていて 知識としてアップデートでき、そういった重要性について再認識することができました。

まとめ

データエンジニアとしてはデータを安全に使いやすく必要なセキュリティを担保していくことはもちろんですが、 データ&AIチームではAI・MLをプロダクト導入していくことも推進しています。 tech.every.tv tech.every.tv

今回はAWSの資格試験を切り口に記事を書かせていただきましたが、 ここで得た知識以外にも新しい技術、考え方を柔軟に取り入れていきデータをより利活用できる環境を作っていきたいと考えています。

FlutterでiOS、AndroidアプリをWebで動かせるようにする

DELISH KITCHEN 開発部で小売向き合いでFlutterのアプリ開発をしている野口です。

本記事では、弊社で開発しているFlutterのアプリをFlutter Webでリリースできるかどうかの調査を行った時の知見についてお話しします。

FlutterアプリをWebで動かすとは

Flutterはマルチプラットフォーム開発できるので、Android / iOS / Web / Windows / macOS / Linuxで同じソースコードで開発できます。なので、iOS、Android用に作成したアプリでもリリースできます。 一般的なWebサイトを作るときは、HTMLやCSS、JavaScriptを使用しますが、FlutterはiOS、Androidと同じ見た目になるように、HTML、CSS、Canvasなどを使用して描画してくれます。また、FlutterはDartという言語で書かれていますが、それをJavaScriptに変換してくれています。

ただ、パッケージを使用した場合、モバイル特有の機能(ネイティブコードでないと実現できないもの)などDartで書かれてない可能性があるため、パッケージの公式ドキュメントを見てWebに対応しているか確認する必要があります。ここにWebの記載があればWeb対応しているパッケージだと判断できます。

https://pub.dev/packages/flutter_riverpod

riverpod

FlutterアプリをWebで動かすにあたっての課題

まとめると以下のような課題がありました。

  • パッケージがWebに対応しているか
  • Platform.isAndroid Platform.isIOSの分岐エラー

具体的な対応

パッケージがWebに対応しているか

対応していないもの

そもそもWebに対応してないパッケージがあるので、その場合は代替を探すか、Javascriptで書くか、Webではその機能を諦めるかをしないといけません。 今回は以下のパッケージが使用できませんでした。 - firebase_crashlytics(そもそもWebはクラッシュしないのでいらない) - path_provider - flutter_html - adjust_sdk - flutter_appauth - dart_jsonwebtoken

対応していたが、途中で動かなくなったもの

isar

isarはv3ではエラーが出て動かなくなっていました。

エラー内容

Error: The integer literal 288085404374050446 can't be represented exactly in JavaScript. Try changing the literal to something that can be represented in JavaScript. In JavaScript 288085404374050432 is the nearest value that can be represented exactly. id: 288085404374050446,

https://pub.dev/packages/isar

issueも出ており、v4では動くようになっているようですが、公式ドキュメントにISAR V4 IS NOT READY FOR PRODUCTION USEとあるので本番環境で使用するのは避けたほうが良さそうです。

https://pub.dev/documentation/isar/4.0.0-dev.14/

対応としては、 isarはローカルデータベースを扱うためのパッケージなので代替になるパッケージに書き換えるが良いかと思います。

v4がstableになるのが待てるのであれば待った方がいいですが、、、v4のPrereleaseが出てから時間が経っており、いつstableになるかわからない状態なので、一旦考えない方針にしています。

バージョンを上げれば対応されるもの

flutter_secure_storage

エラー内容

Unsupported operation: Platform._operatingSystem

使用しているバージョンではWebが対応していないため、5.0.0に上げれば解決します。

Platform.isAndroid Platform.isIOSの分岐エラー

エラー内容

Unsupported operation: Platform._operatingSystem

Webで実行時にPlatform.isAndroid Platform.isIOSがあると起こるようです。 この記事のように、Webの分岐を入れるか、universal_platform(https://pub.dev/packages/universal_platform)を使用することで対応できるかと思います。

https://zenn.dev/ryo_ryukalice/articles/140a64f894afad

Flutte Webを採用して開発運用を行う上でのビジネス上のリスク(考慮事項)

ビジネス上のリスクは以下が挙げられるかなと思います。アプリの複雑度によってリスクの重みは変わるかもしれないですが、これらが許容できればいい選択肢かなと思います。

  1. やりたいことを実現するためのWebに対応しているパッケージがない
  2. isarのようにWebに対応していたパッケージが、更新されなくなりWebが動かなくなる
  3. 2が原因でflutterのバージョンを上げづらくなる

iOS、AndroidアプリをFlutter Webで動かす

iOS、Androidアプリ

Flutter Webアプリ

Flutter Webを動かした結果、画像のようになりました。 見た目としてはiOS、Androidアプリがブラウザのサイズに合わせてそのまま大きくなっています。 このままでも見た目はそんなに悪くないかなと思いますが、商品情報が大きすぎるなどの場合はレスポンシブ対応か、モバイルのサイズに統一するなどすると良くなると思います。 動作が重くなる様子はなかったのでリリースはできるかなと思いました。

まとめ

Flutter Webで開発をする際の主な考慮点は使用するパッケージがWebに対応しているかどうかということがわかりました。 ただ、Webに対応していても動かなくリスクがあるので、Webだけは使えない機能が出る可能性もありそうですね。

個人的にはシンプルなアプリであれば基本的には動きそうなので用途によっては良い選択なのではと思いました。

認証・課金の共通基盤

はじめに

エブリーでソフトウェアエンジニアをしている本丸です。
先日、弊社からヘルスケアアプリ「ヘルシカ」がリリースされたのはご存知でしょうか?ヘルシカは弊社のサービスであるDELISH KITCHENのヘルスケア機能を切り出したサービスなのですが、ヘルシカの裏側で認証・課金の共通基盤が動いています。
今回はこの認証・課金の共通基盤(社内でDAPと呼んでいるため、以降はDAPと表記します)についてお話しできればと思います。なお、実装の詳細には触れず概要の説明に留める予定です。

システムの概要

DAPとは

DAPとは、認証・課金の共通基盤で、IdP(IDプロバイダー: ユーザーIDを保存および検証するサービス)としての役割と、課金を管理する役割を持っています。
DAPという名称は、一般的に使われるものではなくいわゆる造語なのですが、社内やチーム内で認識を合わせるために命名されたという経緯があります。
DAPの目的は、複数サービスでのユーザーの管理を一元化することです。

下図はDAPとそれに関わるものを表した概要図です。
DAPではSNSを用いた認証をサポートしているので、LINEやAppleといった外部のプラットフォームを利用します。以降は、DAP内の認証サーバーのことをInternal IdP、認証に利用する外部のプラットフォームのことをExternal IdPとします。
矢印は、依存の方向を示していてPaymentはInternal IdPに依存しているという関係になっています。DAPはExternal IdPや外部の課金プラットフォームに依存しており、弊社のサービスがDAPを利用するという形になっています。

認証サーバとしての役割

認証サーバーとしての役割は、ユーザーがどのアカウントと紐づくのかの認証を行うというのが主な役割になります。サインアップの時は図のようなフローになるのですが、全て説明すると長くなってしまうので要点だけお話しします。

Internal IdPとExternal IdPの間の認証情報

Internal IdPはExternal IdPに認証を委譲しています。Internal IdPはExternal IdPからIDトークンを受け取るのですが、このIDトークンの中にIDトークンの発行者や一意の識別子などが含まれており、それをもとにどのユーザーなのかの判断を行います。

Internal IdPとApplication ServerとClientの間の認証情報

ClientからApplication ServerのAPIを呼び出す時にはAccess Tokenを認証済みかどうかの判定に利用します。このAccess TokenはInternal IdPで発行しています。Application ServerはClientからAccess Tokenを受け取った時に、Internal IdPを通して認証を行い、認証が成功した場合に後続の処理を行うことになります。

Web View

図の中で、web viewに言及している箇所があるのですが、これはClientがスマホのアプリの時の挙動を示したものです。ClientとExternal IdPの間で直接認証する場合は、External IdPが用意してくれているSDKを利用した方が便利ではあるのですが、Internal IdPを経由させる目的でweb viewから認証を行うようにしています。

課金サーバとしての役割

課金サーバとしての役割は主に2つです。

1つ目はユーザーが商品を購入した際にappleから受け取ったレシートを検証して、商品の有効性を確かめることです。

2つ目はレシートとユーザー状態の管理・更新です。DAPではappleからレシートの情報が更新された時に通知を受け取り、それをトリガーとしてユーザーの状態の更新を行なっています。

レシートとユーザー状態の管理については、弊社ブログの過去記事にもありますのでよければご覧ください。
https://tech.every.tv/entry/2022/04/07/170000

RFCに則った実装

少し、話は逸れるのですがDAPの中のInternal IdPに関してはRFCやOIDCのドキュメントに則った実装が基本方針になっています。
社内用にカスタマイズされたドキュメントではないので、一見とっつきにくくもあるのですが、Internal IdPに関しては下記の理由などでドキュメントに則った方が良いという判断になったようです。 - IdPなので社内特有のロジックが入り込みにくい - 社内ドキュメントよりドキュメントのメンテナンスが維持されやすい - セキュリティ的な要件も満たせる

動作確認の困難さ

要件として、従来のDAPの仕様に則っていないサービスと新規のDAPの仕様に則っているサービスでユーザーのアカウント・課金状態を紐づける必要がありました。
この連携パターンが、従来のサービスでSNS連携されているか、新規サービスでSNS連携されているかなどに加えて課金状態の確認まで必要だったため、かなり複雑に感じました。
動作確認の段階でどのようなパターンがあるか洗い出してテストをしたのですが、動作確認の段階で考慮漏れなどが見つかり修正に追われるといったこともありました。複雑なシステムを作るときは想定されるパターンをあらかじめ洗い出してから開発すべきだったかなというのが反省です。

まとめ

記事にしたこと以外でもリリースまでに色々と大変なこともあったのですが、なんとか致命的なバグはなく動いているようなので一安心といったところです。 複雑なシステムなので概要を話すだけの形にはなってしまいましたが、認証・課金基盤でどのようなことをやっているのかの導入になれば幸いです。

最後に宣伝になりますが、このDAPを裏で利用しているヘルシカというサービスがリリースしたのでよければ使ってみてください。

参考資料