ContractS開発者ブログ

契約マネジメントシステム「ContractS CLM」の開発者ブログです。株式会社HolmesはContractS株式会社に社名変更しました。

Pub-Subモデルを活用して通知基盤を改善した話

というタイトルで、12/13(金)に開催されたDisruptors Tech Meetに登壇しました。
こんにちは。ContractSの友野です。

Disruptors Tech Meetとは

株式会社ディスラプターズのグループ会社に所属するエンジニアが

  • 各社事例を見て・聞いて刺激をもらう
  • 普段触れない技術の話を聞いてスキルアップに繋げる
  • 発表や意見交換の経験を積む

といったことを目的に、日頃の成果を発表・共有する勉強会です。
ContractSはディスラプターズ(キャリアインデックスから商号変更)グループの一員として、継続的なアウトプットの場として活用しています。

同会には、キャリアインデックス社、マージナル社のエンジニアが参加しており、毎度ワイワイと楽しんでいます。

Disruptors Tech Meetは、グループ会社間のクローズドな勉強会ではあるものの、クオーター毎に継続開催され、そろそろ3年目に突入します。
いずれパブリックな勉強会にしていく狙いもあります。お楽しみに。
(今回は登壇側でしたが、筆者は普段は運営側の人間です)

Springにおけるアプリケーションイベント

登壇資料では具体的な技術スタックに触れておりませんが、Java/Spring Boot環境で基盤は構築しています。
Springでは、アプリケーションイベントの発行はApplicationEventPublisherで行います。

ApplicationEventPublisher (Spring Framework API) - Javadoc

余談ですが、数年前まで(Spring 4.2以前)はイベント発行のためにApplicationEvent(Javadoc)というクラスを継承する必要がありましたが、今現在はPOJOのデータモデルが使えるので、今回の通知基盤だったり、ドメインイベントへの適用だったり、活用はかなりしやすくなったと感じています。

発行されたイベントは、TransactionalEventListenerアノテーションを付与したメソッドでリッスンします。評価されるタイミングはデフォルトはトランザクションコミット後(AFTER_COMMIT)です。

TransactionalEventListener (Spring Framework API) - Javadoc

サンプル

以下、簡単なコードイメージです(イメージしやすさ優先で、適切な責務分割ではないです…)。

// イベント発行側
public class ApplicationEventNotifier {
    // 通知用文面ファクトリ
    private final NotifiableContentsFactory notifiableContentsFactory;
    private final ApplicationEventPublisher applicationEventPublisher;

    // タスクが作成されたことをユーザーに通知する
    public void taskCreated(Set<UserId> destinations) {
        destinations.stream()
                .map(userRepository::findById)
                .map(notifiableContentsFactory::taskCreated)
                .forEach(this::notify);
    }

    private void notify(Notifiable event) {
        applicationEventPublisher.publishEvent(event);
    }
}


// タスク作成イベント
public record TaskCreated(MailAddress destination, String subject, String body) implements Notifiable {
}


// イベント購読側
public class NotifiableEventListener {
    private final MailClient mailClient;

    // タスク作成時に呼ばれるリスナー
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onTaskCreated(TaskCreated event) {
        mailClient.send(event.destination(), event.subject(), event.body());
    }
}

このサンプルだけ見ると効果は感じにくいかもしれませんが、実際には通知イベントインタフェースの統合やリスナーの共通化によって、フレームワークのような形で使えるようにしています。
今回はアプリケーションの構造を整理したのみで、メッセージブローカーの導入などまでは踏み込んでいません。いずれこのあたりも拡張後、記事にまとめていこうと思います。

最後に

簡単ではありますが、登壇資料の公開と、Springにおけるアプリケーションイベントの実装サンプルをまとめました。今回の文脈では通知のみですが、また別の形でドメインイベントへ適用も進めています。こちらも機会を見つけてまとめておこうと思います。

最後に、ContractSでは一緒にプロダクトを進化させていくエンジニアを募集中です。少しでも興味がある方、「CLM?なにそれ美味しいの?」という方もカジュアル面談から可能ですので、以下のページからお気軽にお声掛けください。

recruit.jobcan.jp

recruit.jobcan.jp

recruit.jobcan.jp