[レポート] shop.LEGO.comでサーバーレスに移行した際のユースケースごとのアーキテクチャ #SVS320 #reinvent

2020.01.06

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CX事業本部の佐藤です。

re:Invent 2019 「The serverless journey of shop.LEGO.com」のセッションレポートです。shop.LEGO.comをサーバーレスアーキテクチャにマイグレーションした際のユースケースごとのアーキテクチャについて説明されています。現地で聞いていたんですが、とても面白かったのと、個人的にかなり参考になる部分があったため、改めてセッション動画を見直し、レポートとして記事にしました。

セッション概要

Connecting the LEGO play experience with millions of people requires an innovative platform. This has fueled the cloud migration of the legacy e-commerce application. In this session, we walk you through the principles, the approach, the learnings, and of course the serverless technologies that made the vision a reality. We cover multiple real-world use cases such as the integration of the e-commerce platform with the tax system, and the implementation of an event-streaming platform.

LEGOプレイ体験を何百万人もの人々と結び付けるには、革新的なプラットフォームが必要です。 これにより、レガシーeコマースアプリケーションのクラウド移行が促進されました。 このセッションでは、ビジョンを実現するための原則、アプローチ、学習、そしてもちろんサーバーレステクノロジーについて説明します。 電子商取引プラットフォームと税制の統合、イベントストリーミングプラットフォームの実装など、複数の実際のユースケースを扱います。

セッション動画

レポート

Agenda

How it all started

元々、shop.lego.comは以下のようなアーキテクチャで運用されていました。Elastic Beanstalkのコンテナワークロードの上で、Node.jsとReactを使い、VPCにオンプレミスバックエンドのゲートウェイ、オンプレミスのOracle ATG、SAP、TAXシステムがありました。

そして、ブラックフライデー2017の高トラフィックに耐えられず、バックエンドサービスが次々に落ち、503 Service UnAvailableになりました。ここから、アーキテクチャの見直しを考えるようになりました。

2018年9月に新しいアーキテクチャに移行しました。これは、バックエンドの一部を既存のオンプレミスから切り離し、API GatewayとLambdaを使用して税金システムを切り離しました。ここで初めて、プロダクション環境でLambdaを使用し始めました。これが私たちのサーバーレスの始まりでした。

そして、2018年のブラックフライデーが訪れました。これはアーキテクチャの見直しの甲斐があって、全てがうまく行きました。ここから、より多くのサーバーレスサービスを使用するプロジェクトが開始されました。

そして、2019年全てをサーバーレスに移行しました。

このサーバーレスアーキテクチャでは、以下のAWSサービスを使用しています。

これは、今回サーバーレスアーキテクチャに移行した際に、使っているAWSサービスの統計です。150以上のLambda関数、25以上のマイクロサービス、30以上のAPI Gatewayのエンドポイントがあります。

A journey through patterns

ここからは、ユースケースごとのサーバーレスのアーキテクチャを見ていきます。

ユースケース1: 商品をカゴに入れる機能

最初のユースケースです。これは、Eコマースでは基本的となる、エンドユーザーが商品を選択し、カゴに入れる機能です。

これは、シンプルなAPI GatewayとLambdaを使用した同期的なAPIです。ビジネスロジックはLambdaで実行し、カゴに追加します。

ユースケース2: 長時間実行する処理のステータスの確認

かごに商品を入れたあと、顧客は注文処理を行います。この場合、同期APIで注文を受け付け、その後、顧客は注文のステータスを確認する必要があります。

このユースケースには、以下のCQRS(コマンドクエリ責任分離)パターンを用います。コマンドでは、API Gatewayで注文を受けすぐに注文番号を返します。その後、注文を送信するLambda関数をInvokeします。その後、SQSに格納し、キューから実際に注文処理を実行するLambda関数を呼び出します。注文処理を実行するLambda関数は、注文のキャッシュをDynamoDBに保存します。これはTTLによって古い注文は削除されます。決済処理、新しいEコマースプラットフォームに対しての処理を実行します。そしてクエリでは、注文ステータスの確認をします。API GatewayからLambdaを呼び出し、DynamoDBから注文ステータスを取得します。この注文ステータスというのは、例えばクレジットカード支払いの失敗や、現在の注文状況などです。それはDynamoDBのテーブルに保存されています。

ユースケース3: バウチャーコードの生成と通知

3つ目のユースケースは、バウチャーコードの生成と通知です。これらは非常にシンプルに実現できました。

最初にバウチャーコードを作成します。これはAPI Gatewayを使ってLambdaを起動します。少々時間がかかる処理なので、非同期にLambda関数をInvokeします。次に作成したバウチャーコードをDynamoDBに保存します。これには使用されていないバウチャーコードが保存されています。そして使用されたアイテムと完全なバックアップをS3に保存します。その後、S3のPresined-URlを作成します。このPresigned-URLをEメールで送信するためにSNSに通知します。SNSはサブスクライブしているEメールに対してメールを送信します。そしてバウチャーの管理者はEメールでバウチャーが格納されているS3のPresigned-URLを取得します。その後管理者は、このPresigned-URLを使ってバウチャーコードをダウンロードします。

ユースケース4: 異なるシステムのユーザーIDのルックアップ

4つ目のユースケースは、異なるシステムのユーザーIDの統合方法についてです。顧客は、顧客のIDを使って認証/認可しリワードを受け取ることができます。Eコマースプラットフォームには独自のIDがあり、LEGO IDとそのIDを結びつけ、リワードプラットフォームから報酬を受け取ることができます。

どのようにこの2つの資格情報を連携させるんでしょうか?顧客はクライアントアプリケーションにアクセスし、Eコマースプラットフォーム、LEGO IDシステムから2つのIDを受け取ります。その後、プロファイルAPIにアクセスしハンドラを呼び出し、リワードプラットフォームから報酬を受け取るという流れです。この時、リワードプラットフォームはこの2つのIDをどのように信用すればよいのでしょうか?

これの解決策は、API GatewayからLambda Authorizerを使います。Lambda AuthorizerはEコマースプラットフォームからセッショントークンを取得し、セッションが有効か、このユーザーに属するか、このプロファイルはこのユーザーにとって正しいリワードなのかという点をチェックします。これでOKならば、2つのIDセッションは正しいと言えます。

ユースケース5: オンデマンドの顧客データ移行

5つ目のユースケースは、顧客データの移行です。顧客が新サイトにアクセスし、新Eコマースにアクセスした場合に通知を行い、SAPからデータをフェッチし内部プロセスが、新Eコマースの顧客情報をアップデートします。

このユースケースのアーキテクチャです。新Eコマースは新しい顧客データを作成したら、SNSトピックに通知します。通知後、SNSに接続しているLambdaが、SAPシステムから顧客データを受け取り、新Eコマースの顧客データを更新します。

ユースケース6: 製品カタログのインポートと更新

6つめのユースケースは、製品カタログのインポートと更新です。これはSAPシステムで作成、更新された商品データをEコマースプラットフォームへ更新するユースケースです。

アーキテクチャは以下です。まず、商品データをS3に保存します。保存後、Lambdaがトリガーされ商品情報を変換し、商品データをSQSにプッシュします。SQSからLambdaがトリガーされ、Eコマースへ新しい商品データが連携されます。不正なデータはDLQにプッシュされ、NewRelicへ通知されます。

また商品の他にも、在庫更新パターン、価格変更パターンがあり同じアーキテクチャで動いています。価格変更パターンの優先度が一番高いため、キューを用いて各パターンの処理優先度の制御を行なっています。

ユースケース7: API駆動のデータ取り込み

モバイルアプリケーション及び、WebアプリケーションからイベントデータをAPI経由で保存します。

最初のアーキテクチャでは、API GatewayからLambdaを呼び出し、Kinesis FirehoseでバッファリングしたのちS3に保存していました。その際、Lambdaを使用してKinesis Firehoseにデータを送っていましたが、このLambdaは本当に必要でしょうか?この関数ではKinesis Firehoseにデータを送信しているだけなので、このLambdaは必要なさそうです。

Lambdaを削除し、API Gatewayの統合を使用して直接Kinesis Firehoseにデータを送信するようにしています。これにより、無駄なコードが削減され、コンピュートコストの削減ができました。Kinesis Firehoseに送信したデータはバッファされたのちS3に保存されます。S3に保存されたのをトリガーにLambdaが呼ばれます。この際、バッファしたのちにS3のオブジェクトに保存されているため、処理データを多くなってしまいます。このような場合にはFan-outパターンを適用することができます。Fan-outパターンとは、データを受け取るLambdaと処理するLambdaを別にするパターンです。まず、Kinesis FirehoseによってバッファされたデータをLambdaで受け取り、受け取ったデータを小さい単位に分割して後続のLambdaで並列に処理させます。並列処理させることで、高いスループットを実現することができます。

このアーキテクチャの詳細は以下のLEGOのブログ記事に詳しくまとめられていますので、参考にしてください。

https://medium.com/lego-engineering/dont-wait-for-functionless-write-less-functions-instead-8f2c331cd651

ユースケース8: ユニークな注文IDの生成

ユニークな注文IDを作成するために、何らかのマイクロサービスがシークエンスナンバーAPIを呼び出します。これは、Lambdaを使わずにDynamoDBを直接呼び出してアトミックカウンタを実装しました。

以下は、アトミックカウンタの実装方法です。

ユースケース9: URLの変更に伴うWebsiteの移行

サーバーレスに移行する際に、WebサイトのURLが変更されました。顧客は古いURLにアクセスする場合があるので、古いWebsiteにアクセスした際は新しいURLにリダイレクトさせる必要がありました。

CDNによってキャッシュされるURLリダイレクトのアーキテクチャを構築しました。CDNによってキャッシュされている場合は、CDNからWebサイトが返されます。キャッシュされていない場合は、アクセスはApplication Load Balancerにいきます。その後、Lambda関数を呼び出します。これはAPIではないため、API Gatewayを使用しませんでした。最初の関数の呼び出しでは、コンテンツストアからURLルールを取得します。取得したルールに基づいて、新しいWebサイトを返します。例えば、301が返された場合はCDNによってキャッシュされます。その後のアクセスはCDNから取得します。

ユースケース10: ウェブサイトのサイトマップを最新に保つ

大規模なWebサイトでは、SEQの最適化などでサイトマップを最新の状態に保つ必要がありました。サイトマップとはXMLファイルのことです。

このアーキテクチャは、CloudWatch Logs Eventsを用いて毎日定期実行しています。CloudWatchのトリガルールを用いて、StepFunctionsのワークフローを実行します。

StepFunctionsのワークフローの詳細です。まずは、カテゴリのサイトマップと、商品のサイトマップを作成し、ワーク用のS3バケットに保存します。保存されたサイトマップからインデックスを作成します。作成したインデックスをリリ- ス用のS3バケットに保存し、それをCDNを使って公開しています。

ユースケース11: チェックアウトイベントプロセッシング

最後のユースケースです。チェックアウトの管理です。チェックアウトは異なるマイクロサービスと連携する必要があります。最初のアーキテクチャでは、SNSトピックをたくさん使用して各マイクロサービスと連携させていました。このサービスは大きく成長していき、SNSトピックが増えていくにつれて開発が困難になっていきました。

そこで、イベントバスを使ったルーティングをするアーキテクチャに変更することにしました。ちょうどこの時に、AWSからEventBridgeという新サービスが発表されました。

このEventBridgeは、まさにLEGOのこのアーキテクチャに必要なものであり、すぐに導入しました。現在開発中でまだ本番環境にはなっていません。現在マイクロサービスをEventBridgeに移行中です。各マイクロサービスをEventBridgeで連携しています。注文や顧客情報のアップデートをLambdaが受け取り、EventBridgeにイベントを送信します。EventBridgeは受け取ったイベント情報を元に処理をルーティングさせます。例えば、顧客情報のアップデートの場合、処理はData Syncマイクロサービスへルーティングされます。注文の場合は、まず支払マイクロサービスに処理が渡り、成功した場合は注文送信のマイクロサービスに処理が渡り、注文のステータスを更新し、イベントを配送マイクロサービスに渡します。その後出荷が開始されます。EventBridgeを用いることで、各マイクロサービスの連携が容易になります。

TakeAways

まずはシンプルに始めましょう

LEGOは、最初はAPI Gateway + LambdaでシンプルなAPIからはじめました。シンプルなものから徐々に移行していきました。

自動統合テストを実装しましょう

サーバーレスでアーキテクチャを組む以上、各コンポーネントはマイクロサービスになっていきます。マイクロサービスにおいて、統合テストはとても重要なので自動化する必要があります。変更を行うたびに自動的に統合テストが行われ再チェックされます。

dev, test, prodで環境を分けましょう

AWSでは複数のアカウントを簡単に作成できるので、各環境ごとにAWSアカウントを作成しましょう。

まとめ

LEGOのサーバーレス事例のセッションでした。モノリスのアーキテクチャからフルサーバーレスに移行するという、サーバーレスの知見に満ちたとても参考になるセッションでした。特に最後のユースケースのEventBridgeを使った各マイクロサービスの連携については、早く実際の開発で使用してみたいなと思いました。