🎁

Momento Topics でマイクロサービス間の状態管理を行い、React アプリケーションで処理状態をプログレスバーで表示する

2024/08/29に公開

マイクロサービス間の状態管理

ユーザーの一括 CSV インポートといったような時間がかかる重たい処理を非同期的に実行するために、マイクロサービスに切り離したとします。

次の図でいうと、フロントエンドである React アプリケーションからリクエストが送信され、
最終的に Lambda が重たい処理を非同期で実行します。

この場合、フロントエンドで処理の状態を把握するにはどうするか...

いくつか方法が考えられると思います。

例えば、フロントエンドから数秒ごとに定期的にリクエストを送り、
処理状況を確認し続けるというポーリングする仕組みでも実現できるかと思います。

ただ、今回は Momento Topics を使用したアーキテクチャでの実現を模索します!!!!

Momento Topics を使用した状態管理

Momento Topics とは

Momento Topics は Momento クラウドインフラストラクチャ上に構築されたサーバーレスのパブリッシュ/サブスクライブ(pub/sub)メッセージングサービスです!

詳細については、以下の記事や Momento について紹介した LT の登壇資料 で触れているので、ここでは割愛します。

https://zenn.dev/collabostyle/articles/73d933b31af204

Momento Topics を使用したアーキテクチャ

Lambda とフロントエンドを繋ぐ形で Momento Topics を導入します🐿️✨

Lambda から処理状態を Momento Topics にメッセージをパブリッシュして、
フロントエンドからサブスクライブすることで処理状態を把握することができるというわけです!

プロトタイピングで Momento Topics を試す

実装したアプリケーションは Github で公開しています。

https://github.com/codemountains/momento-topics-microservices

Momento Topics のセットアップ

以下の記事を参照してください。

難しい作業は一切ありません!

Publisher の実装

decentralized-app は Lambda で実行されることを想定した処理を実装しています。
(実際に Lambda にはデプロイしません。)

Momento の SDK を使用してメッセージをパブリッシュする処理を実装します。

decentralized-app/index.ts

import { CredentialProvider, TopicClient, TopicConfigurations } from "@gomomento/sdk";

const publish = async () => {
    // Create a new TopicClient instance
    const topicClient = new TopicClient({
        configuration: TopicConfigurations.Default.latest(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
            environmentVariableName: 'MOMENTO_API_KEY',
        }),
    });

    const cacheName = process.env.MOMENTO_CACHE_NAME;
    if (!cacheName) {
        throw new Error('MOMENT_CACHE_NAME environment variable is required.');
    }

    const topicName = process.env.MOMENTO_TOPIC_NAME;
    if (!topicName) {
        throw new Error('MOMENT_TOPIC_NAME environment variable is required.');
    }

    // Publish a message to a topic
    for (let i = 0; i <= 100; i += 10) {
        console.log(`progress: ${i}%`);
        await topicClient.publish(cacheName, topicName, i.toString());

        // Wait for 2.5 seconds
        if (i < 100) {
            await new Promise(resolve => setTimeout(resolve, 2500));
        }
    }
}

// Execute the publish function
publish();

やっていることは非常に簡単で 0 から 100 までをカウントアップしています。
これを処理状態の進行状況(%)に見立てています。

また、カウントアップに時間がかかるように 2.5 秒の待機時間を設定しています。

Subscriber の実装

frontend-app は文字通りフロンエンドアプリケーションです。

Momento の SDK を使用してメッセージをサブスクライブする処理を実装します。

frontend-app/src/features/subscriber.tsx

import { Configurations, CredentialProvider, TopicClient } from "@gomomento/sdk-web";
import { useCallback, useEffect, useRef, useState } from "react";

export function Subscriber() {
    const authToken = import.meta.env.VITE_MOMENTO_API_KEY;
    const cacheName = import.meta.env.VITE_MOMENTO_CACHE_NAME;
    const topicName = import.meta.env.VITE_MOMENTO_TOPIC_NAME;

    // 処理状態の管理
    const [progressValue, setProgressValue] = useState<number>(0);
    const topicClientRef = useRef<TopicClient | null>(null);

    const updateProgress = useCallback((value: string) => {
        setProgressValue(Number(value));
    }, []);

    useEffect(() => {
        async function startSubscription() {
            if (topicClientRef.current) return;

            // Momento Topics のクライアントを生成する
            const topicClient = new TopicClient({
                configuration: Configurations.Browser.v1(),
                credentialProvider: CredentialProvider.fromString({ authToken }),
            });

            topicClientRef.current = topicClient;

            // サブスクライブ
            await topicClientRef.current.subscribe(cacheName, topicName, {
                onItem: (data) => {
                    console.log(data);
                    updateProgress(data.valueString());
                },
                onError: (err) => console.log(err),
            });
        }

        startSubscription();
    }, [authToken, cacheName, topicName, updateProgress]);

    return (
        <div className="container mx-auto max-w-2xl px-4">
            <div className="w-full flex justify-center items-center">
                <h2 className="p-2 text-lg">Progress</h2>
            </div>
            <div className="w-full flex justify-center">
                <progress className="progress progress-primary w-96" value={progressValue} max="100"></progress>
            </div>
            <div className="w-full flex justify-center items-center mt-8">
                <h2 className="p-2 text-lg">Radial progress</h2>
            </div>
            <div className="w-full flex justify-center">
                <div 
                    className="radial-progress text-success m-2"
                    style={{ "--value": progressValue, "--size": "10rem", "--thickness": "0.8rem" }}
                    role="progressbar"
                >
                    {progressValue} %
                </div>
            </div>
        </div>
    );
}

処理状態の変更を検知して、Progress や Radial progress に反映しています。

サブスクライブという一見難しそうな処理も SDK が実装してくれているため、
自前で実装する必要はありません!

ちなみに、UI ライブラリは daisyUI を使っています。

テスト実行

.env.local.env を整備して、以下のコマンドを実行します。

なお、.env.local.env の整備は README を参照してください。

frontend-app:

cd ./frontend-app
npm run dev

decentralized-app:

.env ファイルを扱うために @dotenvx/dotenvx を使うコマンドで実行します。

cd ./decentralized-app
npx dotenvx run -- npx tsx index.ts

実行すると以下のような動作を確認することができます。

異なるサーバー間のアプリケーションで処理状態を共有することができました🎉🎉🎉

まとめ

Momento Topics を使用して、React アプリケーションで処理状態をプログレスバーで表示することができました!

Momento のサービスは本当に簡単に導入することができるので、開発者体験が素晴らしいです...。

Momento Topics の活用してコスト削減とパフォーマンス向上を実現しましょう🔥

わたくし、Momento の回し者ではございませんよ?
ただのファンです😘😘笑

それにしても、マイクロサービスアーキテクチャは難しい!!!!

コラボスタイル Developers

Discussion