ObjectManager { /** * Gets the UnitOfWork used by the EntityManager to coordinate operations. * * @return UnitOfWork */ public function getUnitOfWork(); }
Unit of Work が何をすべきかを決定することであ る。トランザクションを開き、同時実行チェックを 実行し(悲観的ロック、または楽観的ロックを使 用)、変更をデータベースに書き出す。” Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler)) P184(※機械翻訳後に意訳) 2. ActiveRecord vs DataMapper
function createMembers(Member $entity): void; public function updateMembers(Member $entity): void; public function deleteMembers(Member $entity): void; public function commit(): void; } Interface class UowService implements UowServiceInterface { public function __construct( private readonly EntityManagerInterface $entityManager ) { } public function createMembers(Member $entity): void { $this->entityManager->persist($entity); } public function updateMembers(Member $entity): void { $this->entityManager->persist($entity); } public function deleteMembers(Member $entity): void { $this->entityManager->remove($entity); } public function commit(): void { // 永続化 $this->entityManager->flush(); } } 具象 3. Unit of Workパターン サンプル
function createMembers(Member $entity): void; public function updateMembers(Member $entity): void; public function deleteMembers(Member $entity): void; public function commit(): void; } Interface class UowService implements UowServiceInterface { public function __construct( private readonly MemberRepositoryInterface $memberRepository ) { } public function createMembers(Member $entity): void { $this->memberRepository->add($entity); } public function updateMembers(Member $entity): void { $this->memberRepository->update($entity); } public function deleteMembers(Member $entity): void { $this->memberRepository->remove($entity); } public function commit(): void { // 永続化 $this->memberRepository->save(); } } 具象 3. Unit of Workパターン サンプル リポジトリ使ってもいい (こうすればInterfaceでも注入できるはず) // ServiceProvider... public function register(): void { $this->app->bind( MemberRepositoryInterface::class, fn (Application $app) => $app["em"]->getRepository(\App\Entities\Member::class) ); }
function createMembers(Member $entity): void; public function updateMembers(Member $entity): void; public function deleteMembers(Member $entity): void; public function commit(): void; } Interface class UowService implements UowServiceInterface { public function __construct( private readonly MemberRepositoryInterface $memberRepository ) { } public function createMembers(Member $entity): void { $this->memberRepository->add($entity); } public function updateMembers(Member $entity): void { $this->memberRepository->update($entity); } public function deleteMembers(Member $entity): void { $this->memberRepository->remove($entity); } public function commit(): void { // 永続化 $this->memberRepository->save(); } } 具象 3. Unit of Workパターン サンプル リポジトリに処理を委譲できる
Microsoft Learn https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2009/june/the-unit-of-work-pattern-and-persistence-ignorance 3. Unit of Workパターン サンプル
Unit of Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase; interface UnitOfWorkInterface { public function markDirty($entity); // Entityを受け取るように public function markNew($entity); public function markDeleted($entity); public function commit(); public function rollback(); } 3. Unit of Workパターン サンプル
Unit of Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\Service; use App\Example\UseCase\UnitOfWorkInterface; use Doctrine\ORM\EntityManagerInterface; class UnitOfWork implements UnitOfWorkInterface { public function __construct( private readonly EntityManagerInterface $entityManager ) { } public function markDirty($entity) { // EntityManagerはEntityの変更を自動的に監視しているから // persistする必要はないけども $this->entityManager->persist($entity); } 3. Unit of Workパターン サンプル public function markNew($entity) { $this->entityManager->persist($entity); } public function markDeleted($entity) { $this->entityManager->remove($entity); } public function commit() { $this->entityManager->flush(); } public function rollback() { $this->entityManager->rollback(); } }
Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase\Command; use App\Example\Entities\Invoice; use App\Example\UseCase\UnitOfWorkInterface; interface InvoiceCommandInterface { public function execute(Invoice $invoice, UnitOfWorkInterface $unitOfWork); } 3. Unit of Workパターン サンプル
Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase\Command; use App\Example\Entities\Invoice; use App\Example\UseCase\UnitOfWorkInterface; interface InvoiceCommandInterface { public function execute(Invoice $invoice, UnitOfWorkInterface $unitOfWork); } 3. Unit of Workパターン サンプル UnitOfWorkInterfaceを利用して Invoiceに対する操作を行う何か
Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\DiscountForLoyalCustomerCommand; use App\Example\UseCase\Command\LateInvoiceAlertCommand; class InvoiceUseCase { public function __construct( private readonly InvoiceCommandProcessorInterface $invoiceCommandProcessor ) { } public function handle(Invoice $invoice) { $commands = [ new DiscountForLoyalCustomerCommand(), new LateInvoiceAlertCommand() ]; $this->invoiceCommandProcessor->runCommands($invoice, $commands); } } 3. Unit of Workパターン サンプル
Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\DiscountForLoyalCustomerCommand; use App\Example\UseCase\Command\LateInvoiceAlertCommand; class InvoiceUseCase { public function __construct( private readonly InvoiceCommandProcessorInterface $invoiceCommandProcessor ) { } public function handle(Invoice $invoice) { $commands = [ new DiscountForLoyalCustomerCommand(), new LateInvoiceAlertCommand() ]; $this->invoiceCommandProcessor->runCommands($invoice, $commands); } } 3. Unit of Workパターン サンプル Invoiceに対する処理が増えても、 コマンド処理インターフェイスを 満たすものを渡せばいいだけ
Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\DiscountForLoyalCustomerCommand; use App\Example\UseCase\Command\LateInvoiceAlertCommand; class InvoiceUseCase { public function __construct( private readonly InvoiceCommandProcessorInterface $invoiceCommandProcessor ) { } public function handle(Invoice $invoice) { $commands = [ new DiscountForLoyalCustomerCommand(), new LateInvoiceAlertCommand() ]; $this->invoiceCommandProcessor->runCommands($invoice, $commands); } } 3. Unit of Workパターン サンプル 拡張に対して開かれている (Open-Closed Principle: 開放閉 鎖の原則)
Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\DiscountForLoyalCustomerCommand; use App\Example\UseCase\Command\LateInvoiceAlertCommand; class InvoiceUseCase { public function __construct( private readonly InvoiceCommandProcessorInterface $invoiceCommandProcessor ) { } public function handle(Invoice $invoice) { $commands = [ new DiscountForLoyalCustomerCommand(), new LateInvoiceAlertCommand() ]; $this->invoiceCommandProcessor->runCommands($invoice, $commands); } } 3. Unit of Workパターン サンプル Invoiceに対する処理を色々やって くれているんだろうなぁという抽 象具合。 気づけばいい感じに永続化されて いる。
Architecture ◦ https://www.amazon.co.jp/-/en/Martin-Fowler/dp/0321127420 • ちょうぜつ Advent Calendar 2022 6日目 ◦ https://qiita.com/tanakahisateru/items/b0c441c4540e84fe6dea • P of EAA: Unit of Work ◦ https://martinfowler.com/eaaCatalog/unitOfWork.html • Unit of Work パターンと永続性の無視 | Microsoft Learn ◦ https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2009/june/the- unit-of-work-pattern-and-persistence-ignorance 5. まとめ
2022 ◦ https://qiita.com/advent-calendar/2022/php-doctrine-orm-vs-eloquent • How is Doctrine 2 different to Eloquent? | Culttt ◦ https://culttt.com/2014/07/07/doctrine-2-different-eloquent • GoによるRepositoryパターンとUnit of Workを組み合わせたトラン ザクション処理 ◦ https://zenn.dev/hacobell_dev/articles/0ae114500cf974 ◦ Unit of Work に興味を持つきっかけになった記事 • その他たくさん 5. まとめ