
2025.04.02
働く人が増えても、日本の「人手不足」問題は解決しない “労働力=人手”という捉え方の盲点
Listing Service モノリスからマイクロサービスへ(全1記事)
リンクをコピー
記事をブックマーク
森國泰平氏(以下、森國):こんにちは。私はメルカリでマイクロサービスの開発をしている、森國と申します。今日は私が担当しているListing Serviceというサービスについて、どのようにマイクロサービス化を進めているかをお話いたします。
まず最初に、Listing Serviceについて簡単にご紹介します。
Listing Serviceは、メルカリのなかでも出品機能を担当するマイクロサービスとなっています。現在メルカリが進めているPHPのモノリスからGoのマイクロサービスへ移行する、最初のプロジェクトとなっています。
現在、Listing Serviceでは、1つのHTTPエンドポイントを対象として、マイクロサービス化を進めています。Listing Serviceが提供するのはHTTPのエンドポイントなんですが、Listing Serviceが他のサービスと通信する際には、gRPCを使用しています。
次に、メルカリのマイクロサービス化の進め方の戦略について、2つのポイントをお話します。
1つ目が、APIの互換性を保ちながらサービスを分割していくということです。APIの互換性を保つことによってカナリアリリース、つまりは段階的にPHPからGoのコードに移行していくことができます。
カナリアリリースをすることによって、万が一バグなどが最初に見つかった場合にも、PHPのコードに戻すこともできますし、そもそも影響範囲を少なくすることができるので、お客さまに対する負担というものも低下することができます。
2つ目の点として、チームとして独立した開発を可能にするということがあります。マイクロサービス化の目的は、ただコードを分割するということではなくて、長期的にスケール可能な組織を作り上げるということです。
そのため、Listing Serviceの開発を進めるなかでも、ただただ目の前の問題にとらわれすぎるのではなく、長期的な組織の在り方などを意識しながら、開発を進めていく必要があります。
これに対して、Listing Serviceがどのように実際に開発を進めているのかを紹介します。
まず、APIの互換性を保つということについては、3つのテストによって、互換性が保たれていることを保証しようとしています。
1つ目が、既存のAPIに対するアプリ、要するにiOSやAndroidの自動テストがあります。これをListing Serviceにもそのまま適用することによって、クライアントからの動作が変わっていないことを確認します。
2つ目のテストとして、QAチームによる手動の確認を行います。1つ目のアプリの自動テストだけではカバーしきれない範囲や細かいチェックなどを、QAチームの人の手によって行います。
3つ目は、AQAチームによるインテグレーションテストツールの開発と、それを使ったAPIの自動テストの構築を計画しています。
2つ目のチームとして独立した開発を可能にするという点については、そもそもListing Serviceで扱いづらいものは、出品に関するロジックだけなので、現在のエンドポイントに含まれているすべてのコードをListing Serviceに持ってくるというわけではなく、別のサービスに配置しなおしたりしています。
あるいは、そのままPHPのコードに残しておいて、Listing ServiceからWebhook経由でコードを実行するようなことにしています。そして、このPHPに残ったコードについては、適切なマイクロサービスが誕生した時に、コードの移行を考えています。
次に、Listing Serviceの開発を進めるにあたって直面した、4つの点について紹介します。
1つがGCPからさくらのMySQLへの接続、2つ目がトランザクションの分割、3つ目が機能追加・修正への追従、4つ目がPHPの例外との互換性です。それぞれについて、少し詳しく話していきます。
1つ目のGCPからさくらのMySQLへの接続についてです。
まず前提として、マイクロサービスというのは、ぞれぞれのサービスが独立したデータベースを持つべきだとは思います。ただし、今回のメルカリのようにモノリスからマイクロサービスに移行するという過程においては、どうしても一時的にデータベースの共有をしなければならない場合もあると思います。
メルカリのマイクロサービスは、基本的にはGCPの上で動いているんですが、既存のメルカリのPHPのコードであったりMySQLというのは、さくらのクラウド上で動いています。このGCPとさくらのMySQL間の接続をどうするかということが、課題になりました。
もちろんMySQLのポートを外部のインターネットに公開してしまえば、GCPから接続することは可能なんですが、それはセキュリティなどの要件的にあまりやりたくはないということで、課題となりました。
そこで我々がどのように進めているかというと、MySQLをgRPC経由で触るためのSQL on gRPCのようなサービスと、それを使うためのGoのクライアントライブラリを開発しています。gRPCを使ってMySQLを触ることによって、既存のgRPC上の認証・認可の仕組みに乗ることができるので、セキュアに通信を行うことができます。
この作成しているサービスでは、基本的にはCRUDの基本的な操作のみを提供し、JoinやTransactionといった複雑な処理は対応していません。実際にコードなどを見ていきます。
まず、こちらにはSELECT文に対応するProtocol Buffersのリクエストとレスポンスを載せています。
リクエストにはSQLで指定するカラム名だったりテーブル名、あるいは、WHERE句などが指定できます。
これをサービスに投げることによって、レスポンスにRowの配列が返ってきます。
このRowというのは、データベースの1行に相当する、カラム名とその値の配列のようなものとなっています。そして、このメッセージをうまく扱うために、Goのライブラリを開発しました。
Goのライブラリは、メソッドチェーンを使って、SQLのようにプロトバフのリクエストを組み立てることができます。
そして、このクライアントのもう1つの機能として、レスポンスのProtocol BuffersをGoのstructにマッピングするための、O/Rマッパーのような機能も備えています。これを使うことによって、Protocol Buffersというものをあまり意識せずに、SQLに近いかたちでMySQLを扱うことができるようになっています。
次に、トランザクションの分割についてお話します。Listing Serviceでは、基本的には出品に関する商品のデータの更新をメインとしたいんですけども、既存のAPIのコードには商品だけでなく、例えば配送情報だったり、分析用のデータなどが1つのトランザクションで更新されていました。
最終的にマイクロサービス化が進んでいくと、それぞれのサービスが独立したデータベースを持つことになるので、もちろんトランザクションは使えなくなります。そのため、この段階からトランザクションの分割っていうものを意識しながら、開発を進めていく必要があります。
そこで我々がどうしたかというと、まずどうしても商品に関連するようなデータについては、トランザクションが必要だと判断しました。そのため、さくら上にItem Serviceというサービスを作って、このItem Serviceはトランザクションを扱うようにしました。
それ以外についても、お客さまの目に直接触れるようなデータについては、できるだけリクエスト内で同期的に処理をするようにしています。それ以外の内部データなどについては、Cloud PubSubを使った非同期の結果整合性を受け入れるようにしています。
具体的に見ていくと、例えば、出品に関する機能があった時に、そのなかでトランザクションを貼って、例えば商品や画像、配送情報、分析用データなどが更新されていたとします。
その時に、商品についてはトランザクションが必要なので、Item Serviceが担保する。画像についてはお客さまの目に直接触れるものなので、トランザクションからは出すものの、できるだけ同期的に更新をする。
配送情報などについては、商品が売れるまでは配送されることもないので、例えば非同期の結果整合性を受け入れるようなかたちにする。そして、内部データについても、お客さまには意識してもらう必要はないので、非同期のデータ整合性を受け入れる、というふうに進めていきました。
次に、機能追加・修正への追従についてです。
Listing Serviceは出品を扱うのですが、出品はメルカリのなかでもコアな機能の1つとなっているので、このマイクロサービス化を進める過程においても、機能の追加だったり修正が行われる場合があります。新しいマイクロサービスではAPIの互換性を保とうとしているため、この機能の追加や修正に対しても追従していく必要があります。
そこで我々がどうしたかというと、もとのPHPのリポジトリ上に変更追跡用のブランチというものを作成しました。そして、Goのコードに移行したコードについては、そのブランチ上から削除しています。
これによって、例えばPHPのmasterブランチに新しい変更が加えられた場合に、その変更追跡用のブランチにマージした際にコンフリクトが発生するので、新しい変更が行われたことに気付くことができます。そして、この変更追跡用ブランチからすべてのコードを削除することができれば、マイクロサービス化のコードの移行が完了したと判断することができます。具体的に見ていきます。
例えば、この左側にあるPHPのコードを、右にあるGoのコードに移行していくようなことを考えます。例えば、最初にif文のところをGoのコードに移したとします。
すると、左のPHPのブランチからは、対応するコードを削除します。
同様にreturn文についても、例えばGoに移したとすると、PHPからは削除します。
そして、この左の状態のPHPのように、すべてのコードが削除された状態になれば、Goへの移行が完了したと判断することができます。
例えば、機能の追加や修正が行われたことを考えてみます。
例えば、新しくPHPでif文に変更があった場合を考えると、これはmasterブランチをこのブランチに対してマージしてくる時に、コンフリクトが発生するので新しい変更に気付くことができて、それをGoのコードに反映することができます。
あるいは、完全に新しい機能が追加された場合を考えると、これはもしかしたらコンフリクトしないかもしれないんですけども、最後に移行が終わったかどうかを確認する際に、対応する箇所だけがPHPのコードに現れることになるので、その部分を新しくGoに移しなおせば、互換性を保つことができます。
最後に、PHPの例外との互換性についてお話します。
これはたぶんメルカリ特有の課題にあたる部分だとは思うんですけども、既存のメルカリのAPIでは、レスポンスの一部にエラーコードとしてPHPの例外名が含まれている場合がありました。
さらに、この例外名というのをクライアントがハンドリングしている部分があったため、互換性を保つGoのAPIからもPHPの例外名を返さなくてはならない、という状況にありました。
さらには、この例外情報というのは、例外の種類によってレスポンスのJSONのかたちが一部異なっていたり、あるいはマイクロサービス化によって、この例外が発生する位置というもの自体が複数のサービスに分割されていくようなことになったため、各サービスがこの例外情報っていうのを意識する必要が出てきました。
そこで我々がどうしたかというと、このPHPの例外情報を扱うためのGoの共有ライブラリを開発しました。
このライブラリを使うことによって、例えばサービス内でエラーが発生した際に、それに対応するPHPの例外情報をgRPCのStatus.detailsっていうところに付与して、レスポンスを返すことができます。
そして、PHP互換のHTTPエンドポイントでは、このgRPCのレスポンスに含まれる例外情報から、直接メルカリのPHP互換のエラー情報のレスポンスを生成することができます。具体的にコードで見ていきます。
これがgRPCサーバーのコードの例です。
例えばエラーが発生した際に、通常のgRPCのエラーコードであるNotFoundに加えて、ここではPHPのNotFoundExceptionのようなものを一緒にメッセージにくっ付けて、エラーのレスポンスを返すことができます。
そして、PHPと互換性を保つHTTPエンドポイントでは、この後ろのサービスから返されてきたエラーからExceptionの情報を取り出して、レスポンスのJSONを直接生成することができます。
これによって、後ろのサービスで発生した例外情報というのを、等価的にクライアントに返すことができるので、各サービスは自分のサービスのなかで発生についてだけ意識すればよくなりました。
以上、マイクロサービス化を進めるにあたって出てきた4つの課題について、ご紹介いたしました。最後にまとめです。
Listing Serviceでは、長期的な組織の課題を解決できるように、かつ、短期的な負債を生んでしまわないようにすることを意識しながら、マイクロサービス化を進めています。
Listing Serviceはメルカリのマイクロサービス化の最初のプロジェクトであるため、資産だったり知識、経験などがそろっていなくて、なかなか大変な部分が多いところはあります。
ただし、正解がない中で、「自分が信じる設計を信じろ!」という精神で、ひたすらただしい姿を目指して全員で開発を進めていくのは、なかなか楽しいものがあるなと思いながら、開発を進めています。以上で、Listing Serviceについての発表を終わります。ありがとうございました。
(会場拍手)
関連タグ:
2025.03.25
減点を恐れてモチベ低下、果ては離職も… あらゆる“会社の害虫”を大繁殖させる「ラスボス」の正体
2023.02.13
小6で「ヤマギシ会」に入り、23歳まで子どもだけで集団生活 「お金が存在しない」コミューン育ちの青年が社会に出て知ったこと
2025.03.25
ムダな仕事がなくならない“マッチョな職場”を変えるには 近年の過度な「KPI主義」が組織に与えた影響
2025.03.24
最悪の場合、組織を死に至らせる“会社の害虫”とは 誤った意思決定や品質不祥事を招く要因
2025.03.27
交渉で「落としどころを探る」という考えは捨てるべき プロが教える、チャンスを逃さない条件交渉のコツ
2025.03.21
査定時期に上司から1年前の失敗を指摘される理不尽 変えられない過去を議論する「成果主義」の弊害
2025.03.27
組織のホワイト化で「優秀じゃない人」ほど居心地の良い会社に… 「退職者の質」の変化から見る、アルムナイが注目されるわけ
2025.01.07
1月から始めたい「日記」を書く習慣 ビジネスパーソンにおすすめな3つの理由
2025.03.26
ブラック企業の次に来る「透明企業」の問題点 良い人ばかりの均質化された組織で失われつつあるもの
2025.03.19
組織をダメにする“害虫”の正体は間違った思い込み AIやDXなど手段のみにこだわるダメ上司の見極め方
2025.03.25
減点を恐れてモチベ低下、果ては離職も… あらゆる“会社の害虫”を大繁殖させる「ラスボス」の正体
2023.02.13
小6で「ヤマギシ会」に入り、23歳まで子どもだけで集団生活 「お金が存在しない」コミューン育ちの青年が社会に出て知ったこと
2025.03.25
ムダな仕事がなくならない“マッチョな職場”を変えるには 近年の過度な「KPI主義」が組織に与えた影響
2025.03.24
最悪の場合、組織を死に至らせる“会社の害虫”とは 誤った意思決定や品質不祥事を招く要因
2025.03.27
交渉で「落としどころを探る」という考えは捨てるべき プロが教える、チャンスを逃さない条件交渉のコツ
2025.03.21
査定時期に上司から1年前の失敗を指摘される理不尽 変えられない過去を議論する「成果主義」の弊害
2025.03.27
組織のホワイト化で「優秀じゃない人」ほど居心地の良い会社に… 「退職者の質」の変化から見る、アルムナイが注目されるわけ
2025.01.07
1月から始めたい「日記」を書く習慣 ビジネスパーソンにおすすめな3つの理由
2025.03.26
ブラック企業の次に来る「透明企業」の問題点 良い人ばかりの均質化された組織で失われつつあるもの
2025.03.19
組織をダメにする“害虫”の正体は間違った思い込み AIやDXなど手段のみにこだわるダメ上司の見極め方