

DDDではトランザクション管理をアプリケーション層で行うという認識でいます。

例えばUserとCompanyという2つのドメインモデルが存在し、  
それぞれマスタ画面でDBへの登録処理があるとします。  
登録処理をアプリケーションサービスクラスで行うときは

[code]

    class UserRegisterService {
        public function register(User $user)
        {
            // トランザクション開始
            try {
                // ユーザー登録処理
    　          $this->userRepo->register($user);
                commit
            } catch {
                rollback
            }
        }
    }
    
    class CompanyRegisterService {
        public function register(Company $company)
        {
            // トランザクション開始
            try {
                // 会社登録処理
                $this->companyRepo->register($company);
                commit
            } catch {
                rollback
            }
        }
    }
    
[/code]

のように書くのではないかと思っています。

ここで更にUserとCompanyの2つを同じタイミングで登録する業務Xが存在した場合  
Xサービスクラスで「UserRegisterService」と「CompanyRegisterService」  
を呼んでしまうと、Userを登録したあとCompanyの登録でエラーが発生しても  
rollbackするのは、当然CompanyServiceだけということになってしまいます。

かといって、Xサービスクラスで2つのサービスクラスと全く同じ処理を書いても  
冗長なコードになってしまいます・・・。  
こういった場合どうすればよいのでしょうか？

## 回答を受けての追記

あとわいと様、Vietta Toe様

サンプルのコードがだいぶ省略していて大変申し訳ないのですが  
実際のクエリの発行はRepositoryというクラスが行うようになっています。（サンプルコードを編集しておきました）  
つまり以下のような処理の流れになります。  
View → Controller → Service → Repository → DB

サンプルのコードぐらいのシンプルなものだと  
Xサービスクラスのregisterメソッドの中で、userRepoとcompanyRepoを呼び出せばいいだけの話だと思うのですが  
実際の業務では登録をするためのデータの加工などをサービスクラス内で行った後、  
加工したデータを引数にリポジトリを呼び出すということが多々あります。

その場合、Xサービスクラスのregisterメソッドで、データ加工処理を書いてしまうと  
XサービスとUserサービスで同じ処理を書いてしまうことになってしまいます。

なので、できるだけサービスクラスを呼び出すだけにしたいのですが  
このような場合でもサービスクラスの上のレイヤーにトランザクション管理を任せればよいのでしょうか？

その際、Vietta Toe様のご指摘のとおり、コントローラーでトランザクション管理をするのか、  
それとも、コントローラーとサービスの間に1つレイヤーを作り、そこでトランザクションを行うのか  
どうすればよいでしょうか？

説明が下手で申し訳ありません。伝わればいいのですが。。。


CC BY-SA 4.0
2018-06-14T07:24:07.257
44770
2021-09-26T05:54:43.247
2018-06-26T07:57:33.877
19110
28946
question
php database
DDDのトランザクション管理について


ＤＤＤ自体に知見がないので、一般論で

エンティティにたいする処理の分割が不十分なことに起因していませんか？

例では、クエリーの発行とトランザクションの開始、終了が同一のレベルで取り扱われているので、お困りのようなことになります。トランザクションmanagerはトランザクションスコープを管理するべきです。例の場合はregisterの関数ごとにスコープをとらえています。スコープについての方針が間違いなければこれでもいいのですが、実際にお困りなので、スコープが適切ではなかったということです。  
やり様としては、トランザクション管理する一段上のクラスを作ること等があります。(通常、フレームワークではこういう存在が定義されています。)  
トランザクションmanagerで、トランザクションを開始し、query発行をここで言うregisterで行う(呼び出す)、なにも問題なければトランザクションmanagerがコミットする。勿論ロールバックの責任もこいつが一括します。という感じです。


CC BY-SA 4.0
2018-06-14T23:10:23.673
44772
2018-06-14T23:10:23.673
10174
44770
answer


DDDですよね？  
DDDの開発設計思想を踏襲されているのであれば、MVCorRESTを採用されているのでしょうか。

まぁどちらにしても、

**あとわいと** さんのいう通り

> トランザクション管理する一段上のクラスを作ること等があります。

こういった設計を施す場合は、  
ルーティングとコントローラーとモデルもしくは、コントローラーとモデルと言ったように２レイヤーで設計をすることが前提となります。

この場合、トランザクションを発行するのは、いずれもコントローラーがいいです。

コントローラーは、モデルの作成や、編集、更新をコントロールし、  
モデルはコントロールの定義を行う。

トランザクションは機能に含ませず、コントローラーがモデル操作を始める前にトランザクションを発行し、モデルの編集後にトランザクションをチェックしロールバック/コミットなどの処理を行い、基本処理を終了します。

なので、２つ以上のレイヤーが必要になります。

**コントローラー▼**  

[code]

    use model\Company;
    use model\User;
    
    class Controller { 
    
        public function New($db) {
            // トランザクション発行
            $db->Begin();
    
            // Companyデータ作成
            $cm = Company :: Register("HOGE-0000", "HOGE株式会社");
            $company = $cm->Create($db);
            if ($company["Error"]) {
                $db->Rollback();
                printf($company["Error"]);
                return false;
            }   
    
            // Userデータ作成
            $um = User :: Register("fuga000", "府賀 普顔", "FUGA-981738271");
            $user = $um->Create($db);
            if ($user["Error"]) {
                $db->Rollback();
                printf($user["Error"]);
                return false;
            }  
    
            // コミット
            $db->Commit();
            return true;
        }
    }
    new Controller();
    ?>
    
[/code]

**モデル▼**

[code]

     <?php namespace model;
    class Company {
    
        // Struct
        public $id;
        public $name;
        public $createdAt;
        public $updatedAt;
        public $deletedAt;
    
        // Struction Regist
        public function Register($id, $name) {
            $this->id           = $id;
            $this->name         = $name;
            $this->createdAt    = date("Y-m-d H-i-s");
            $this->updatedAt    = date("Y-m-d H-i-s");
            $this->deletedAt    = null;
        }
    
        public function Create($DB) {
            // 作成の処理（与えられた暗号化なり、DML操作なりを記述）
            $resp = $DB->Set("tbl_company_mst")->Query(
                array($this->id, $this->name, $this->createdAt, $this->updatedAt, $this->deletedAt)
            )->Do();
    
            if ($resp->Error()) {
                return array("Company" => new Company(), "Error" => $resp->Error());
            }
    
            $c = Company :: Register($this->id, $this->name);
    
            return array("Company" => $c, "Error" => null);
        }
    
        public function Read($DB) {
            // 作成の処理（与えられた暗号化なり、DML操作なりを記述）
        }
    
        public function Update($DB) {
            // 作成の処理（与えられた暗号化なり、DML操作なりを記述）
        }
    
        public function Delete($DB) {
            // 作成の処理（与えられた暗号化なり、DML操作なりを記述）
        }
    }
    ?>
    
    <?php namespace model;
    class User {
        public $id;
        public $name;
        public $employNo;
        public $createdAt;
        public $updatedAt;
        public $deletedAt;
    
        public function Register($id, $name, $employNo) {
            $this->id           = $id;
            $this->name         = $name;
            $this->employNo     = $employNo;
            $this->createdAt    = date("Y-m-d H-i-s");
            $this->updatedAt    = date("Y-m-d H-i-s");
            $this->deletedAt    = null;
        }
    
        public function Create() {
            // 作成の処理（与えられた暗号化なり、DML操作なりを記述）
        }
    
        public function Read() {
            // 作成の処理（与えられた暗号化なり、DML操作なりを記述）
        }
    
        public function Update() {
            // 作成の処理（与えられた暗号化なり、DML操作なりを記述）
        }
    
        public function Delete() {
            // 作成の処理（与えられた暗号化なり、DML操作なりを記述）
        }
    } 
    ?>
    
[/code]

※ この例は、独自ORMを例として使用した為、コピペで動作しない。


CC BY-SA 4.0
2018-06-15T11:37:46.603
44784
2018-06-15T11:37:46.603
28961
44770
answer


どのようなフレームワークをお使いかわかりませんので適切かどうかわかりませんが、  
〇〇Service
で記述してある内容はアプリケーション層ではなくてドメイン層の記述になっていると思います。エリック・エバンズの「ドメイン駆動設計」をお持ちであれば、この書籍の「サービス」の節に相当するものになっています。  
私の場合はアプリケーション層には画面やページ単位でユースケースを網羅する内容を書きますので
UserとCompanyの両方が必要なのであれば両方を記述します。〇〇Model という名前にしています。  
レイヤーは下記のとおりです。

Controller → ApplicationModel →
DomainModel(Entity,ValueObject,Service,Repository) → DataAccess

年数がたった質問への回答で失礼します。


CC BY-SA 4.0
2021-09-26T05:54:43.247
82691
2021-09-26T05:54:43.247
18755
44770
answer