[MindDiary]ホットスポット5会員登録時のEメール送信に失敗した場合のロールバックとトランザクションの処理


会員がAPIを入力したときに発生した問題.
会員登録時に、電子メールタグ付きurlを電子メールでユーザーに送信して電子メール認証を行います.
この場合、redisに電子メールトークンを保存して電子メールトークン検証を行うことができます.

再構築前のコード

 @Override
  @Transactional
  public void join(User user) {

    user.changeHashedPassword(passwordEncoder);

    isEmailDuplicate(user.getEmail());
    isNicknameDuplicate(user.getNickname());

    userRepository.save(user);

    redisStrategy.addEmailToken(user.getEmailCheckToken(), user.getId());
    emailStrategy.sendUserJoinMessage(user.getEmailCheckToken(), user.getEmail()); // 이메일 전송이 실패할 경우에는?
上記のコスト入力コードがあり、ロジックは以下の通りです.
1.ユーザーパスワードをhassed passwordに変更
2.メールとニックネームが重複しているか確認する
3.認証されていないロールをDBとして保存
4.redisキャッシュに電子メールタグを保存する
5.電子メールの送信
電子メールの転送に失敗すると、キャッシュに電子メールタグが蓄積されますが、これらのタグは使用されません.したがって、電子メールの転送に失敗した場合は、キャッシュ内の電子メールタグを再削除する必要があります.
スプリングでは、@Transactional宣言を使用してAOPでトランザクションをロールバックできます.@Transactionalを適用してメソッドを実行すると、データベースの変更に関連する論理がロールバックされます.
上記の会員入力コードでは、データベースにユーザが格納されているコードがロールバックされます.
ただし、redisなどの他のアレイにデータを追加または削除する場合は、手動でロールバックする必要があります.
スプリングでは、トランザクションの同期はトランザクションに関連付けられます.
トランザクション同期は、トランザクションを開始するために使用される接続オブジェクトを特定の同期リポジトリに保存し、必要に応じて取り出して書き込む技術です.
TransactionSynchronizeManagerというテクノロジーで、接続をインポートすることなくトランザクションの同期やトランザクションを管理するテクノロジーを提供します.
さらに、TransactionSynchronizationクラスを使用してトランザクションをロールバックおよび処理することもできます.このクラスでは、トランザクションコミット後(aftCommit)や完了後(aftCompletion)などの方法を使用して、トランザクション後にロジックを実行できます.
これらのテクノロジーを使用すると、トランザクションロールバック後の処理機能を適用して、準備中の電子メールタグを削除できます.

リファクタリングコード

//UsereServiceImpl.java
@Override
  @Transactional
  public void join(User user) {

...
    userTransactionService.removeCacheAfterRollback(user.getEmailCheckToken());
  }
//UserTransactionServiceImpl.java
 @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void removeCacheAfterRollback(String emailToken) {
    TransactionSynchronizationManager.registerSynchronization(
        new TransactionSynchronization() {
          @Override
          public void afterCompletion(int status) {
            if (status == STATUS_ROLLED_BACK) {
              redisStrategy.deleteValue(emailToken);
            }
          }
        });
  }
トランザクションステータス(status)がロールバックされている場合、コストロールバック時に上記after completionメソッドを使用してキャッシュ内のタグを削除します.
また、TransactionSynchronization Manager.registerSynchronizationメソッドで新しいトランザクション同期を登録します.登録されていない場合は、ロールバック後の処理方法が正常に動作しない可能性があります.

afterCompletion伝播属性をrequired newに設定


TransactionSynchronizationドキュメントでは、After Completionを使用するときに伝播プロパティをrequired newに設定して新しいトランザクションを作成することを示します.
NOTE: The transaction will have been committed or rolled back already, but the transactional resources might still be active and accessible. As a consequence, any data access code triggered at this point will still "participate" in the original transaction, allowing to perform some cleanup (with no commit following anymore!), unless it explicitly declares that it needs to run in a separate transaction. Hence: Use PROPAGATION_REQUIRES_NEW for any transactional operation that is called from here.
これは、after completionメソッドが呼び出されたときに、トランザクションに関連する既存のリソースがクリーンアップされていないためです.これは、既存のトランザクションに引き続き参加し、後でリソースを消去することで、トランザクション後の処理が正常に動作しないことを意味します.したがって、既存のトランザクションとは別にトランザクションを作成して、後続の処理を既存のリソースではなく新しいリソースとして実行できるようにする必要があります.

クラスの分離


以下に、ユーザー・サービスとユーザー転送サービスを分離する理由を示します.
@TransactionalはエージェントのAOPを使用します.したがって、ユーザServiceImplの接続が@Transactionalと宣言されている場合、ユーザServiceImplのプロキシオブジェクトは、ターゲットオブジェクトUserServiceImplの接続方法の代わりになり、トランザクションが設定されます.
ターゲット・オブジェクトからターゲット・オブジェクトの他のメソッドを直接呼び出すと、エージェントを介してターゲット・メソッドを直接呼び出さないため、トランザクションは有効になりません.
joinメソッドとremoveCacheAfterRollbackメソッドは同じクラスにあります.
「トランザクションに必要なnew」に設定された伝播プロパティは無視されます.
既存のjoinメソッドのトランザクションに参加するだけです.
(既存のjoinメソッドのトランザクション伝播プロパティが必要なため、既存のトランザクションに参加します.)
したがって、ユーザー・サービスとユーザー・トランスポート・サービスを別々にして、新しいトランザクションを作成できます.
上記の再構築により、トランザクションの後で処理できます.

あとで


最初は、メールの転送に失敗した場合、登録コストも失敗するべきだと思います.
しかし、多くのサービスでは、電子メールの送信に失敗しても会員収入は失敗しますか?実はそうではありません.Eメールの送信に失敗したために会員をロールバックしないでください.
Eメール送信に失敗した場合は、再送信のためにEメール送信の可用性を取得する必要があります.

コメントリンク


トビーのスプリング5枚、6枚
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/TransactionSynchronizationManager.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/TransactionSynchronization.html