KINTO Tech Blog
Development

テスト用システム日付変更にRedisのPub/Subの導入

Cover Image for テスト用システム日付変更にRedisのPub/Subの導入

はじめに

こんにちは!
KINTOテクノロジーズの新車サブスク開発グループに所属している劉(ユ)です。

完璧ではないかもしれませんが、少しずつ問題をより良い方向に改善していくことを目指して日々努力しています。
この記事ではSpring BootにおいてRedisのPub/Subを導入してシステム日付を変更する内容について共有したいと思います。

導入に至った背景

システムのQAやテストを実施する際、システムの日付を変更しないと確認できないケースが多くあります。
特に、サブスクリプションサービスにおいては特定の日付に依存するビジネスロジックをテストする必要があります。
例えば、期間開始日や期間終了日、月額料金、中途解約の精算金、メンテナンス点検・車検などに基づいた処理の検証が求められます。
これまではシステム日付が環境設定ファイルで定義されていたため、日付を変更するたびにコンテナの再デプロイが必要でした。
その結果、テストやQAを行うたびに再デプロイに5分以上の時間がかかるという問題が発生していました。
このような状況における課題を解決した内容を紹介したいと思います。

これを入れたことでどういうメリットがあったか?

RedisのPub/Subの概念を導入することで、テスト環境でのシステム日付変更がより効率的になり、迅速な対応が可能となりました。
これにより、テストやQAなどの工数を削減できるようになり、作業効率が向上しました。
具体的にコンテナの再デプロイは不要になり、変更したい設定項目(トピック)に対するメッセージ(変更したい設定値)を発信するだけで、各コンテナはリアルタイムで変更内容を受信し、設定値の変更ができるようになりました。
また、複数のコンテナが構成されている場合でも、すべてのSubscriberがメッセージを受信しているため、複数のコンテナでも再起動せずに設定値を変更できます。

さらに、システム日付変更のログも出力できるため、変更履歴を追跡することが可能となりました。
他にもSpring BootのProfileの設定で、指定したテスト環境のみでこの機能を有効にし、本番環境や他の環境へ誤って適用することを防げます。
※Profileについてはこちら

Redis Pub/Subとは

Redis Pub/Subは、メッセージキューのメッセージングパターンの1つです。
メッセージキューとは、サーバーレスやマイクロサービスアーキテクチャにおける非同期通信の手法の1つで、分散システムにおいてリアルタイムのイベント通知を実現します。
この仕組みは、異なるソフトウェアモジュール間で拡張かつ安定した通信をサポートするため、データベースやキャッシュとしての利用に加え、メッセージブローカーとしても広く使用されています。

主な構成

  • Topic(主題):購読する対象となる主題やテーマです。
  • Publisher(発行者):特定のTopicに関するメッセージを発信します。
  • Subscriber(受信者):購読したTopicに対して、発行者のメッセージを受信します。

Keyspace Notifications

何らかの方法でRedisデータセットに影響を与えるイベントを受信し、Redisキーおよび値の変更内容をリアルタイムでモニタリングします。

ではどういう実装か?

システム日付変更の仕組み

トピックに対するメッセージを送信するPublisherとしてAPIを実装しました。
購読しているトピック(キー)に対するイベントが発生すると複数のコンテナ(Subscriber)がメッセージを受信し、リアルタイムで設定値を変更します。
システム日付変更のアーキテクチャ

システム構成

JavaとSpring Bootを使用して構築されています。アプリケーションはコンテナ化され、クラウド環境で稼働しています。

build.gradleにlibraryの追加

implementation 'org.springframework.data:spring-data-redis'

RedisConfigクラスの実装

@AllArgsConstructor
@Configuration
public class RedisTemplateConfig {
    private final RedissonClient redissonClient;

    @Bean
    public RedisTemplate<String, String> redisTemplate() {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(new RedissonConnectionFactory(redissonClient));
        template.setDefaultSerializer(new StringRedisSerializer());
        return template;
    }

    @Bean
    public RedisMessageListenerContainer redisContainer() {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(new RedissonConnectionFactory(redissonClient));
        return container;
    }
}

Publisherの実装

トピックに対するメッセージを送信するAPIを実装します。

@RestController
public class SystemTimeController {
    private final SomeService service;

    @PostMapping("/update")
    public void updateSystemTime(@RequestParam String specifiedDateTime) {
        service.publish(specifiedDateTime);
    }
}

@Service
@RequiredArgsConstructor
public class SomeService {
    private final RedisTemplate<String, String> redisTemplate;
    // Topicのキーを定義
    private static final String FOO_TOPIC = "foo-key";

    public void publish(String specifiedDateTime) {
        // Topicに対するメッセージを送信する
        redisTemplate.opsForValue().set(FOO_TOPIC, specifiedDateTime);
    }
}

Subscriberの実装

購読しているトピック(キー)に対するイベントが発生すると、メッセージを受信します。

@Slf4j
@Component
@Profile("develop1, develop2") // 指定されたテスト環境のプロファイルのみ有効
public class FooKeyspaceEventMessageListener extends KeyspaceEventMessageListener {
    private final RedisMessageListenerContainer listenerContainer;
    private final RedisTemplate<String, String> redisTemplate;
    private static final String FOO_TOPIC = "foo-key";

    @Override
    public void init() {
        doRegister(listenerContainer);
    }

    public FooKeyspaceEventMessageListener(
            RedisMessageListenerContainer listenerContainer,
            RedisTemplate<String, String> redisTemplate) {
        super(listenerContainer);
        this.listenerContainer = listenerContainer;
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doHandleMessage(Message message) {
        // Redisからシステム日付を取得する
        String systemTime = updateSystemTimeConfig(redisTemplate.opsForValue().get(FOO_TOPIC));
        // システム日付を反映するメソッドを作成してコールする
        updateSystemTimeConfig(systemTime);
        log.info("Receive a message about FOO_TOPIC: {}", message);
    }
}

さいごに

今回の記事を最後までお読みいただき、ありがとうございます。
まだまだ至らない点が多いですが、毎回少しずつ問題を改善しながら成長していこうと努力しています。
完璧な構造や実装ではありませんが、少しずつより良い方向へ進んでいくことが重要だと思っています。
今後もこうした小さな進歩が集まり、より良い結果を生み出せるよう、引き続き学び続けていきます。
共に成長する旅路を歩んでいければと思います。ありがとうございました。

Facebook

関連記事 | Related Posts

We are hiring!

【フロントエンドエンジニア(コンテンツ開発)】新車サブスク開発G/東京

新車サブスク開発グループについてTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』のWebサイトの開発、運用をしています。​業務内容トヨタグループの金融、モビリティサービスの内製開発組織である同社にて、自社サービスである、クルマのサブスクリプションサービス『KINTO ONE』のWebサイトコンテンツの開発・運用業務を担っていただきます。

【PjM】プロジェクト推進G/東京

プロジェクト推進グループについてプロジェクト推進グループでは、​TOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 』をはじめ、国内向けサービスのプロジェクト立ち上げから運用保守に至るまでの運営管理を行っています。

イベント情報

【さらに増枠】AWSコミュニティHEROと学ぶ!Amazon Bedrock勉強会&事例共有会
製造業でも生成AI活用したい!名古屋LLM MeetUp#4