3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CleanArchitectureとLaravelとTransaction

Posted at

【概要】

CleanArchitectureにおいてデータの整合性を担保するためにUseCaseでトランザクション処理を行うと、Application Business RulesがFramework & Driversに依存することになります。
これは図の依存の矢印とは逆向きに依存してしまうため違反することとなります。
この記事では実際にUseCaseでトランザクション処理を行うサンプルコードと違反することなくデータの整合性を担保する方法を提示します。なおサンプルコードにはLaravelを用います。

image.png

【対象読者】

  • CleanArchitectureを学習中の方
  • CleanArchitectureにおけるデータの整合性に関してお悩みの方

【例題】

[題材]

店舗の口コミサイトを例にします。このサイトでは口コミを投稿することでポイントがもらえるものとします(他の仕様を考え出すと本筋から脱線してしまうため非常に簡易ですが、ここではこの仕様だけ考えます)
口コミとポイントのテーブル設計は下記の通りだとします。

  • 口コミテーブル
review_id shop_id content
1 101 良かった
2 102 イマイチ
3 102 悪かった
  • ポイントテーブル
point_id review_id point
201 1 50
202 2 50
203 3 100

[クラス図]

口コミ投稿におけるクラス図を作成しました。

Untitled Diagram-Page-1 (1).png

[サンプルコード]

PostReviewInteractor.php
use Illuminate\Support\Facades\DB;

class PostReviewInteractor implements PostReviewUsecase
{
	private $reviewRepository;
	private $pointRepository;
	private $presenter;

	public function __construct(IReviewRepository $reviewRepository, IPhotoRepository $pointRepository, IPostReviewResultPresenter $presenter)
	{
		$this->reviewRepository = $reviewRepository;
		$this->pointRepository = $pointRepository;
		$this->presenter = $presenter;
	}

	public function handle(PostReviewInputdata $inputData)
	{
		try {
			DB::beginTransaction();
			$review = $this->reviewRepository->save($inputData->getShopId(), $inputData->getContent());
			$point = $this->pointRepository->save($inputData->getShopId(), $inputData->getPath());
			$outputData = new PostReviewSuccessOutputData($review->getId(), $point->getId());
			$this->presenter->complete($outputData);
			DB::commit();
		} catch (\Exception $ex) {
			DB::rollBack();
			$outputData = new PostReviewFailedOutputData($ex);
			$this->presenter->failed($outputData);
		}
	}
}

[問題点]

コード動いてそうだし問題なくね?と思いきや、

DB::beginTransaction();
DB::commit();
DB::rollBack();

ユースケースでDBトランザクション制御を行なっていることに気づきます。
概要に記載したCleanArchitectureの図を見るとUse Casesが所属するApplication Business RulesはDBが所属するFrameworks & Driversの内側に位置することが分かります。依存の矢印は外から内へ向いているためユースケースがDBトランザクション処理を行うのは違反していると考えられます。

【解決案】

トランザクション以外の方法を用いたデータの整合性を担保を考えます。まず整合性に関してですが、ACID特性とBASE特性の2通りがあります。ACID特性とBASE特性に関しての詳細は こちらの記事 が分かりやすかったので、参考にしていただけると幸いです。ここではACID特性の一貫性とBASE特性の結果整合性に関して触れます。それぞれの概要は下記の通りです。

  • 一貫性
  • DB内のデータに矛盾のない事を常に保証します。
  • 結果整合性
  • 最終的に整合性とれてればいい。

UseCaseにおいてトランザクション処理を出来ないということで、一貫性は妥協して結果整合性を担保することを考えることになると思います。

結果整合性を担保する手法の1つとしてキューを用いた手法を提示します。
例題のサンプルコードでは口コミ投稿とポイント登録を同一サービスにて行なっていましたが、口コミサービスとポイントサービスに分割します。口コミサービスでは口コミ投稿とポイントキューへのエンキューを行い、ポイントサービスではポイントキューからデキューしDBに登録を行います。
クラス図は下記の通りです。

  • 口コミサービス
    Untitled Diagram-Page-2.png
  • ポイントサービス
    Untitled Diagram-Page-3.png

【まとめと謝辞】

UseCaseにおいてトランザクション処理を行うと違反してしまうため、トランザクション処理によるデータの整合性の担保ではなく他の方法を用いる必要がある。しかしトランザクション処理が出来ないためACID特性ではなくBASE特性を考える必要がある。
なお実際にキューを用いた手法を考えると冪等性など他にも考えなければいけないことが出てきます。これに関してまとめきる事が出来ませんでした。 こちらの記事 を参考にすると良いと思います。
他の記事に丸投げしてしまい申し訳ありません。

3
4
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
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?