エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

グローバルサービスの開発における技術的な意思決定

これは エムスリー Advent Calendar 2022 Advent Calendar 2022 の4日目の記事です。 前日は id:abouch による、Reactで管理画面(SPA)を作った時の技術選定とか。 でした。

エムスリーエンジニアリンググループ AI・機械学習チームの笹川です。 趣味はバスケと筋トレで、このところは、NBAが開幕して前評判と全然違うチームの躍進に毎日ワクワクしています。

今回は、2022年4月にリリースし、これまで運用しているEUの医療従事者向けメディアサービスの開発における開発の工夫と、技術的な意思決定について紹介します。 なおこちらは、Google Cloud Next 2022 で同じ内容を発表しており、今回はそれを少し深堀りして紹介します。

嬉しそうにおもちゃを破壊する犬氏(かわいい)

サービス開発の背景

エムスリーは、アメリカ、イギリス、フランス、中国などグローバルへ積極的に事業展開しています。 今回は、フランスのグループ会社である Vidal と協力し、EUの会員アクティビティ向上のため、医療従事者向けニュースサービスを新規に開発することになりました。

体制としては、以下の分担で開発を進めました。

エムスリー(日本)

  • 会員アクティビティ向上のための施策ノウハウの提供
  • 開発全般

Vidal(フランス)

  • サイトに掲載するコンテンツの制作
  • 各種規制への対応サポート
  • 会員基盤の提供

開発に際して重視したポイント

新規サービスの開発に際して特に重視したポイントは以下の3点です。

  1. 素早くPDCAを回すための高速な開発
  2. 限られた予算に対応するための開発コストの削減
  3. GDPR をはじめとした規制への対応

1つ目の高速な開発については、会員獲得や、定着を進めるため、施策を打ちつつ、その結果から学ぶというループを数多く高速に回す必要があります。 そのため運用負荷を極力下げつつ、変更を容易にしておくことが重要です。

2つ目の開発コストの削減については、新規開発に限らないことではありますが、特にサービス開発序盤では、限られた予算の中から最大の成果を出すため、開発コストを少しでも減らす必要があります。

3つ目の規制への対応については、今回サービスを展開するEUではGDPR へ対応することが必須であり、そのための調査・開発が必要になります。 また、今回は、フランス独自の規制への対応も必要になったため、GDPR対応と併せてフランスチームとの協力がとても重要でした。

以下では、上記のポイント押さえつつ開発を進めた際の工夫について紹介します。

高速な開発

高速な開発のために、今回のプロジェクトでは以下の技術選定を実施しました。

  1. GKE Autopilot の利用
  2. GraphQL のエンドポイントを用意し、フロントとバックエンドを分離
  3. マイクロサービスにはしない

はじめに、サービスが動く基盤としてGKE Autopilotを利用することを決めました。 「新規サービス開発にKubernetesの利用は悪手では?」と思われるかもしれません。 この技術選定の背景として、筆者を含め開発を担当するエンジニア全員が、普段から日本でのサービス開発にGKEを既に利用しており、習熟していたことが挙げられます。 手慣れた物を使うことで、既存の資産(Terraformや、CIの実装など)を流用しつつ、いいスタートダッシュを切れたと思います。 また、今回はAutopilotを初めて導入してみました。 AutopilotはKubernetesクラスタを構成するノードの管理をGoogleに任せることができるGKEのモードで、これによりノードの管理の手間が削減され、開発者はアプリケーションの開発に集中することができます。 Autopilotの導入に伴うトラブルは特になく、とても安定しているので、このノウハウを日本の開発にフィードバックし、導入を順次進めているところです。

次にGraphQLの導入と、フロントエンドと、バックエンドの分離です。 これは、昨今メジャーな開発方針ではありますが、フロントとバックを分離して独立して開発することによる開発の高速化を狙ったもので、 GraphQLのスキーマを前提として相互のやりとりを規定することで、スムーズに開発することができました。 今回のプロジェクトでは、エンジニア3名、デザイナ1名で開発を進めたのですが、この構成にしたことでデザイナのメンバーにもフロントの開発の一部を担当してもらうことができました。

最後にマイクロサービスにはしないという意思決定についてです。 この体制でサービスを分割して開発を進めても、おそらく起こることは、分割したサービスを修正し、GraphQLスキーマを変更し、対応するフロントを変更するということを1人が行うということであり、特にサービスを分割するメリットがありません。 なので、ほとんどのサービスは分割せず、1つのアプリケーションとして実装を進めました。 ただし、将来的なサービスの分割はありうることと考え、分割するであろう部分を想定した開発を意識しました。

以下が今回開発したアプリケーションのアーキテクチャ図です。 Dataと書いてあるBigQueryのコンポーネント以外は全てGKE Autopilotのクラスタ上で動いています。 中央のBackendの中のGraphQL + Contentとある部分が、将来分割するであろうニュースコンテンツを配信するコンポーネントとGraphQLのコンポーネントが合わさった部分です。

アーキテクチャ図

開発コストの削減

開発コストの削減には、以下の取り組みを実施しました。

  1. Spot Podの利用
  2. DBインスタンスを1つに集約

1のSpod Podは、 Google Cloudの予備のコンピューティングリソースを利用して、一定時間後にPodが強制終了してしまう代わりに、起動するPodの料金が大きく割引されるという仕組みです。 特に開発環境の全てのPodに適用することで、大幅にコストを削減できました。

次に、DBインスタンスの集約についてです。DBインスタンスはクラウドコンポーネントの中でも特にコストがかかるコンポーネントの1つです。 今回の開発ではその料金の節約のため、1つのインスタンスに集約して開発しました。 ただし、先述した通りサービス分割を意識した構成にするため、DB内部のデータベースはサービス単位で分割しました。 こうすることで、不意に分割予定のサービス間のテーブル同士のjoinなどが発生しないようにし、将来的なサービス分割の際に剥がしやすいように設計しました。

規制への対応

EU圏でのサービス運営には、GDPRはもちろん、サービスを展開する各国の規制に対する対応が必須です。今回は以下の対応を実施しました。

  1. GDPRへの対応
  2. フランス国内の規制への対応

まず1つ目のGDPRの対応については、サービス規約の作成や、各種同意画面のフロントへの実装を実施しました。 開発にあたっては、Vidalのデータ部門の担当者と密に連携し、規約への記載内容の調整、同意取得箇所の洗い出し、デザインの調整などを細かく実施しました。

次にフランス国内の規制への対応についてです。 開発当初、ユーザの行動ログデータ取得のためにGoogle Analyticsを利用する想定でフロントエンドおよびデータ連携の実装を進めていました。 リリース直前の2月にフランス当局からGoogle Analyticsの利用は違法である旨の発表がありました。

www.cnil.fr

これにはかなり困ったのですが、対応しないわけにはいかないため、メンバーで相談し、ログ記録ツールを自作することにしました。 バックエンドには、リクエストに含まれるpayloadをロギングするだけのシンプルなAPIを作成し、そのログをCloud LoggingからLog Sinkを用いてBigQueryへ連携する構成にしています。 フロントからそのAPIを呼ぶ際に、トラッキングする内容を含めてリクエストを送るようにすることで行動ログデータの取得を実現します。 APIの実装のうち主要な実装は以下のようになっています(説明のために不要な部分を省略しています)。

func handle(w http.ResponseWriter, r *http.Request) {
    var request map[string]interface{}
    err := json.NewDecoder(r.Body).Decode(&request)
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        _, _ = w.Write([]byte(err.Error()))
        logger.Error().Err(err).Send()
        return
    }

    logger.Info().Labels(
        commonLabel,
        zerodriver.Label("type", fmt.Sprint(requestType)),
    ).
        Interface("request", request).
        Send()

    w.WriteHeader(http.StatusOK)
}

実装には拙作のzerodriverという構造化ログ出力のためのロギングライブラリを利用し、requestの内容を以下の部分でCloud Loggingに出力するのみのシンプルな実装です。

   logger.Info().Labels(
        commonLabel,
        zerodriver.Label("type", fmt.Sprint(requestType)),
    ).
        Interface("request", request).
        Send()

出力したログはマネージドサービスのみでBigQueryまで連携される構成になっており、実装負荷はもちろん、運用負荷の低減にも貢献しています。

Google Analyticsから自前ログ記録ツールに変更した後のアーキテクチャは以下のようになっています。

Google Analyticsを自作のログ記録ツールに変更した後のアーキテクチャ

技術的なチャレンジを考慮した意思決定

ここから少し視点を変えて、このプロジェクトで取り組んだ「技術的なチャレンジ」について、その考えの背景を中心に簡単に紹介したいと思います。

エムスリーでは、技術選定は実装を担当するチーム / メンバーに任せられています。 それが社内に技術的な多様性を生んでおり、個人的にも良い文化だなと思っています。

エムスリーの多様な技術スタック

普段の技術選定は、プロジェクトの性質、最近のトレンド、学習コストなどを総合的に考慮して行われます。

しかし、今回のプロジェクトの技術選定では、メンバー全員が締切とクオリティに責任を持つ前提で、積極的にチャレンジしていく方向としました。 「チャレンジする」という言葉は、ここでは、単に新しい技術を選ぶということではなく、開発するメンバーが慣れていないということを理由にそれを選択しないという判断をせず、 メンバーが「面白そう」と感じる技術を採用する、ということを意味しています。

今回の例では、GKE Autopilot、GraphQLの採用の他、ORMとしてent、フロントエンドにReactを採用したことなどが挙げられます。 GKE Autopilotはリリース当時まだ比較的新しいサービスでしたが、GraphQLや、Reactは既にメジャーで採用事例も多いものであると思います。

このチャレンジは、メンバーが、より楽しんで開発することができるようになり、そのことが良い成果につながるという狙いのもと実施しました。

例えば、Reactの採用では、リリース当時、社内での利用は若干Vueが優勢というところであり、デザイナの大月もVueの経験がありました。 順当にいけばVueを採用するような状況の中ではあるものの、メンバー内で議論の結果Reactの方が「面白いだろう」となり、Reactを採用することになりました。 キャッチアップには大月も含めメンバー全員が多少苦労した部分もありましたが、技術選定の背景も後押しし、楽しんで開発することができました。 結果として問題なく締め切りに間に合わせることができたのはもちろんのこと、手前味噌ではありますがクオリティ的にも良いものができたと思います。

もちろんチャレンジにはいいことばかりではなく、リスクがあることも知っておく必要があります。 リスクのコントロールを誤ると、不慣れな技術ゆえに思ったより多くの時間を使ってしまい、品質を上げきれない、締切に間に合わないなどが発生することは想像に難くありません。

いささか生存バイアスが強めにかかった事例ではありますが、このような技術選定の方向も面白いのではないでしょうか。

まとめ

今回は、グローバル向けメディアサービスの開発における工夫と、技術的な意思決定について紹介しました。 このプロジェクトは現在もエンハンス開発をガンガン進めているところです。 サービスの横展開など、さらなる大きな成果につなげるために頑張っていきたいと思っています。

We are hiring!

エムスリーでは、世界各地のメンバーと協力しながら、医療を前進させるプロダクトを生み出すエンジニアを広く募集中です。

グローバルに大きなインパクトを与える機会が多数ありますので、我こそは!という方はカジュアル面談、ご応募お待ちしています!

jobs.m3.com