LoginSignup
1
0

More than 1 year has passed since last update.

Scalar DBでの例外処理とは

Posted at

TL;DR

Scalar DB と Spring Boot を使った API 開発を行う際の例外処理の設計およびその処理方法をまとめてみました。

この記事は、以下の記事の続きになります。Scalar DBとSpring Bootを使ったAPIの開発方法について知りたい方は以下の記事をご覧ください。

Spring BootとScalar DBを用いたAPIの作り方①
Spring BootとScalar DBを用いたAPIの作り方②
Spring BootとScalar DBを用いたAPIの作り方③

目次

  1. 例外処理の設計
    1. Scalar DBでスローされ例外の種類
    2. Spring Boot のレイヤーごとに処理するべき例外
      1. Repositoryクラスで処理すべき例外
      2. Serviceクラスで処理すべき例外
  2. まとめ

例外処理の設計

Scalar DBでスローされる例外の種類

Scalar DB は、次の種類の例外をスローします。

  • トランザクションの失敗
  • トランザクションが失敗したか成功したか不明
  • コミットの失敗
  • コミットのコンフリクト
  • トランザクションの中止処理の失敗
  • CRUD処理の失敗
  • CRUD処理のコンフリクト

Spring Boot のレイヤーごとに処理するべき例外

Scalar DB は Bussinessレイヤーでトランザクション処理を、PersistenceレイヤーでCRUD処理を実装します。

このため、CRUD処理に関する例外はPersistenceレイヤーの実装クラスであるRepositoryクラスで、
トランザクションに関する例外はBussinessレイヤーの実装クラスであるServiceクラスで、例外処理を記述します。

Repositoryクラスで処理すべき例外

Repositoryクラスでは以下の2つに関する例外をキャッチします。

例外 説明 
CrudException  CRUD処理が失敗した場合にスローされる例外
CrudConflictException  CRUD処理のコンフリクトが発生した場合にスローされる例外

Repostoryクラスでの例外処理の実装例

以下の例では、ReposiotryConflictEception と RepositoryCrudException は、RuntimeException をラップしており、例外チェーンを構築しています。これにより、ラップされた例外として処理できます。

UserRepository.java
public String createUser(DistributedTransaction tx, CreateUserDto createUserDto, String userId) {
  try {
    Key pk = createPk(userId);
    getAndThrowsIfAlreadyExist(tx, createGet(pk));
    Put put =
        new Put(pk)
            .forNamespace(NAMESPACE)
            .forTable(TABLE_NAME)
            .withValue(User.EMAIL, createUserDto.getEmail())
            .withValue(User.COMMON_KEY, COMMON_KEY);
    tx.put(put);
    return userId;
  } catch (CrudConflictException e) {
    throw new RepositoryConflictException(e.getMessage(), e);
  } catch (CrudException e) {
    throw new RepositoryCrudException("Adding User failed", e);
  }
}

Serviceクラスで処理すべき例外

Serviceクラスではキャッチした例外の種類によって、処理を分けていきます。

以下の3つの例外をキャッチした場合は、トランザクションの再実行を行い、一定回数以上失敗する場合はトランザクションを中止させます。

例外 説明 
UnKnownTrasactionException トランザクションのコミットに成功したか失敗したか不明な状態のときにスローされる例外
CrudConflictException CRUD処理のコンフリクトが発生した場合にスローされる例外
CommitConflictException コミットにコンフリクトが発生した場合にスローされる例外

以下の3つの例外をキャッチした場合は、直ちにトランザクションを中止させます。

例外 説明 
TransactionException トランザクションが失敗したときにスローされる例外
CrudException CRUD処理が失敗した場合にスローされる例外
CommitException コミットに失敗した場合にスローされる例外

以下の例外をキャッチした場合はログに書き込みを行います。

例外 説明 
AbortException トランザクションの中止処理に失敗した場合にスローされる例外

Serviceクラスでの例外処理の実装例

以下の例では、変数retryCountにトランザクションの実行回数を格納し、3回トランザクション処理に失敗した場合に処理を中止しています。

また、トランザクションの再実行に100ミリ秒の間隔をあけています。

UserService.java
@Service
@Slf4j
public class UserService {
  private final UserRepository userRepository;
  private final DistributedTransactionManager manager;
  private DistributedTransaction tx;

  int retryCount = 0; // トランザクションの実行回数

  @Autowired
  public UserService(
      UserRepository userRepository,
      DistributedTransactionManager manager) {
    this.userRepository = userRepository;
    this.manager = manager;
  }

  public String createUser(CreateUserDto createUserDto) throws InterruptedException {
    while (true) {
      if (retryCount > 0) {
        if (retryCount == 3) { // 3回トランザクションの実行に3回失敗した場合、処理を中止し、RuntimeExceptionをラップした例外に情報を付加してスロー
          throw new ServiceException("An error occurred when adding a user");
        }
        TimeUnit.MILLISECONDS.sleep(100);
      }

      try {
        tx = manager.start();
      } catch (TransactionException e) { // トランザクションのスタートに失敗した場合は、処理を中止し、RuntimeExceptionをラップした例外に情報を付加してスロー
        try {
          tx.abort();
        } catch (AbortException ex) { // トランザクションの中止に失敗した場合は、ログに書き込む
          log.error(ex.getMessage(), ex);
        }
        throw new ServiceException("An error occurred when adding a group", e);
      }

      try {
        String userId = UUID.randomUUID().toString();
        userRepository.createUser(tx, createUserDto, userId);
        tx.commit();
        return userId;
      } catch (CommitConflictException | RepositoryConflictException| UnknownTransactionStatusException e) {
        try { // これらの例外をキャッチした場合は、トランザクションを中止し、retryCountを増やし、トランザクションを再実行させる
          tx.abort();
        } catch (AbortException ex) {
          log.error(ex.getMessage(), ex);
        }
        retryCount++;
      } catch (CommitException | RepositoryCrudException e) {
        try { // これらの例外をキャッチした場合は、直ちにトランザクションを中止させ、RuntimeExceptionをラップした例外に情報を付加してスロー
          tx.abort();
        } catch (AbortException ex) {
          log.error(ex.getMessage(), ex);
        }
        throw new ServiceException("An error occurred when adding a user", e);
      }
    }
  }
}

まとめ

以上が、Scalar DB と Spring Boot を使った API 開発を行う際の例外処理の設計およびその処理方法になります。

ご覧いただいた方のご参考になればと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0