Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unit of Workパターンで永続化とトランザクションを制御する

shimabox
September 16, 2023

Unit of Workパターンで永続化とトランザクションを制御する

PHPカンファレンス沖縄2023 Track B での発表資料です。

shimabox

September 16, 2023
Tweet

More Decks by shimabox

Other Decks in Programming

Transcript

  1. 1. Unit of Workパターンとは 2. ActiveRecord vs DataMapper 3. Unit

    of Workパターンサンプル 4. 所感 5. まとめ アジェンダ
  2. 1. Unit of Workパターンとは 2. ActiveRecord vs DataMapper 3. Unit

    of Workパターン サンプル 4. 所感 5. まとめ 1. Unit of Workパターンとは
  3. 1. Unit of Workパターンとは ちょうぜつ Advent Calendar 2022 6日目 UnitOfWork

    https://qiita.com/tanakahisateru/items/ b0c441c4540e84fe6dea
  4. 1. Unit of Workパターンとは 2. ActiveRecord vs DataMapper 3. Unit

    of Workパターン サンプル 4. 所感 5. まとめ 2. ActiveRecord vs DataMapper
  5. 2. ActiveRecord vs DataMapper ActiveRecord • みんな大好き(?) • 好きなときにDB触れる ◦

    行をオブジェクトとして表現し、そのオブジェクトにDB操作のロジックを持つ ◦ CRUD操作するためのメソッドや機能がついている • ドメインのビジネスロジックとデータベース操作のロジックが混在する • DBのことを知っている
  6. 2. ActiveRecord vs DataMapper DataMapper • DBのレコードを表すもの(Entity)と、それらのライフサイクルを管理するもの(エン ティティマネージャ)に分かれている • Entityはビジネスロジックやアプリケーションのドメインモデルを表現する

    • DataMapperは、EntityとDBとの間のマッピングや操作を担当する ◦ デザインパターンのひとつ ◦ インピーダンスミスマッチ とかいうやつ ▪ 現実世界とアプリケーションとのうんたらかんたら ◦ エンティティマネージャは裏方、仲介役 • Entity自体はDBのことを知らない/依存していない (諸説あり) ◦ アノテーションは書かれているが
  7. 2. ActiveRecord vs DataMapper ActiveRecord • 「何をすべきか」「どのように」を一つのオブジェクト(ActiveRecord)が担当 • 自分が主 DataMapper

    • 「オブジェクト(Entity)が何をすべきか」に集中でき、「どのように」の部分は エンティティマネージャ(リポジトリ)が担当 • Entityはエンティティマネージャ(リポジトリ)がいないとDBに対して何もできない
  8. 2. ActiveRecord vs DataMapper DataMapper • Doctrine ◦ laravel-doctrine/orm ▪

    ▪ 自分が試したときは、-W をつけないと入らなかった ▪ `Downgrading doctrine/lexer (3.0.0 => 2.1.0)` された ◦ laravel-doctrine/migrations ▪ $ composer require laravel-doctrine/orm:^2.0@dev -W $ composer require laravel-doctrine/migrations:^3.0@dev
  9. 2. ActiveRecord vs DataMapper DataMapper • Doctrine ◦ laravel-doctrine/orm ▪

    ▪ 自分が試したときは、-W をつけないと入らなかった ▪ `Downgrading doctrine/lexer (3.0.0 => 2.1.0)` された ◦ laravel-doctrine/migrations ▪ $ composer require laravel-doctrine/orm:^2.0@dev -W $ composer require laravel-doctrine/migrations:^3.0@dev 神
  10. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  11. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) Doctrine (DataMapper) 楽すぎて草 (無邪気) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  12. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  13. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); // EntityManager $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) EntityManagerを呼ぶ Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  14. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); // EntityManager $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); // 関連付け $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) 関連付けはEntityに対して アノテーションを定義 Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  15. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); // EntityManager $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); // 関連付け $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) こういうやつ Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん namespace App\Entities; /** * @ORM\Entity * @ORM\Table(name="members") * @ORM\Entity(repositoryClass=MemberRepository::class) */ class Member { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ private $id; // 〜 略 /** * @ORM\OneToMany(targetEntity="Post", mappedBy="member") */ private $posts;
  16. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); // EntityManager $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); // 関連付け // この時点では、まだデータベースには何も保存されていない $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) EntityManagerが変更を マーキング(監視) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  17. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); // EntityManager $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); // 関連付け // この時点では、まだデータベースには何も保存されていない $em->persist($member); $em->persist($post); // flush()を呼び出すと、監視されている全てのエンティティの変 更が一度にデータベースに適用される $em->flush(); Doctrine (DataMapper) ここでしかデータベースへ の保存(トランザクション) は発生しない Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  18. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; $member =

    new Member(); $member->name = "Alice"; $member->save(); // この時点でデータベースに保存 $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); // この時点でデータベースに 保存 Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); // EntityManager $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); // 関連付け // この時点では、まだデータベースには何も保存されていない $em->persist($member); $em->persist($post); // flush()を呼び出すと、監視されている全てのエンティティの変 更が一度にデータベースに適用される $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん ふむふむ、DBへの書き込 み、トランザクションと...
  19. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; use Illuminate\Support\Facades\DB;

    DB::transaction(function () { $member = new Member(); $member->name = "Alice"; $member->save(); $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); }); Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); // EntityManager $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); // 関連付け // この時点では、まだデータベースには何も保存されていない $em->persist($member); $em->persist($post); // flush()を呼び出すと、監視されている全てのエンティティの変 更が一度にデータベースに適用される $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん トランザクションで囲って みました
  20. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; use Illuminate\Support\Facades\DB;

    DB::transaction(function () { $member = new Member(); $member->name = "Alice"; $member->save(); $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); }); Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); $member = new Member(); $member->setName("Alice"); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん 少し想像してみよう...
  21. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; use Illuminate\Support\Facades\DB;

    DB::transaction(function () { $member = new Member(); $member->name = "Alice"; $member->save(); // ここの間に、なにか長い処理があった場合 // Postの内容をどこか別の場所から取るとか // sleep(3); $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); }); Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); $member = new Member(); $member->setName("Alice"); // ここの間に、なにか長い処理があった場合 // Postの内容をどこか別の場所から取るとか // sleep(3); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  22. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; use Illuminate\Support\Facades\DB;

    DB::transaction(function () { $member = new Member(); $member->name = "Alice"; $member->save(); // ここの間に、なにか長い処理があった場合 // Postの内容をどこか別の場所から取るとか // sleep(3); $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); }); Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); $member = new Member(); $member->setName("Alice"); // ここの間に、なにか長い処理があった場合 // Postの内容をどこか別の場所から取るとか // sleep(3); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) ここでしかトランザクショ ンが発生しない Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  23. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; use Illuminate\Support\Facades\DB;

    DB::transaction(function () { $member = new Member(); $member->name = "Alice"; $member->save(); // ここの間に、なにか長い処理があった場合 // Postの内容をどこか別の場所から取るとか // sleep(3); $post = new Post(); $post->title = "A post by Alice"; $member->posts()->save($post); }); Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); $member = new Member(); $member->setName("Alice"); // ここの間に、なにか長い処理があった場合 // Postの内容をどこか別の場所から取るとか // sleep(3); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん え、3秒はトランザクショ ンがはられてしまう...? Doctrine (DataMapper) ドクくん
  24. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; use Illuminate\Support\Facades\DB;

    $member = new Member(); $member->name = "Alice"; // 長い処理 $post = new Post(); $post->title = "A post by Alice"; DB::transaction(function () use ($member, $post) { $member->save(); $member->posts()->save($post); }); Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); $member = new Member(); $member->setName("Alice"); // ここの間に、なにか長い処理があった場合 // Postの内容をどこか別の場所から取るとか // sleep(3); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん トランザクションから脱出 します
  25. 2. ActiveRecord vs DataMapper use App\Models\Member; use App\Models\Post; use Illuminate\Support\Facades\DB;

    $member = new Member(); $member->name = "Alice"; // 長い処理 $post = new Post(); $post->title = "A post by Alice"; // この間で、$member, $postはDBに気軽に触れる DB::transaction(function () use ($member, $post) { $member->save(); $member->posts()->save($post); }); Eloquent (ActiveRecord) use App\Entities\Member; use App\Entities\Post; $em = app("em"); $member = new Member(); $member->setName("Alice"); // ここの間に、なにか長い処理があった場合 // Postの内容をどこか別の場所から取るとか // sleep(3); $post = new Post(); $post->setTitle("A post by Alice"); $member->addPost($post); $em->persist($member); $em->persist($post); $em->flush(); Doctrine (DataMapper) 無邪気なActiveRecordが うろつきだしたわね... Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  26. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  27. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん 配列のメモリが心配
  28. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん そもそも、 ["name" => $xxx,,,] って何?
  29. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん これが、みんな大好き連想 配列というやつ!?
  30. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん データを作るための、やん ちゃな連想配列, stdClass がうまれやすい...!?
  31. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) ここでEntityに対する処理 をして、persistしている だけ。 ちゃんと会話していそう 👀 Doctrine (DataMapper) ドクくん Eloquent (ActiveRecord) アッコちゃん
  32. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) この中がもっと複雑であっ ても、ビジネスロジックだ けに集中してモデルを変更 → persistするだけ Doctrine (DataMapper) ドクくん Eloquent (ActiveRecord) アッコちゃん
  33. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) 何をしようとしているのか わかりやすい Doctrine (DataMapper) ドクくん Eloquent (ActiveRecord) アッコちゃん
  34. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん 永続化関連のクラスは特定 の層でしか使えないような ルールもかけやすいかも。 deptracとか
  35. 2. ActiveRecord vs DataMapper use App\Models\Member; use Illuminate\Support\Facades\DB; $members =

    []; $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $members[] = [ "name" => $prop->name, "email" => $prop->mail_address, // ... ]; } DB::transaction(function () use ($members) { (new Member())->insert($members); }); Eloquent (ActiveRecord) use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; $em = app()->make(EntityManagerInterface::class); $memberProps = /** どこかからか取ってきた */; foreach ($memberProps as $prop) { $member = new Member(); $member->setName($prop->name); $member->setEmail($prop->mail_address); // ... $em->persist($member); } $em->flush(); Doctrine (DataMapper) 最後にflushして確定して あげると、いい具合にして くれる (もちろんメモリ量の心配 はある) Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん
  36. 2. ActiveRecord vs DataMapper use App\Entities\Member; $em = app("em"); //

    登録 $member = new Member(); $member->setName("taro"); $em->persist($member); // なんやかんや // 更新 $member->setProfile("XXXXX"); $em->persist($member); // なんやかんや // 不適切!削除 $em->remove($member); // 確定 $em->flush(); Doctrine (DataMapper) • メンバーを登録して • なんやかんやして • メンバーの情報を更新する • なんやかんやして • 情報に不適切なものがあればメンバーを 削除する ユースケース
  37. 2. ActiveRecord vs DataMapper use App\Entities\Member; $em = app("em"); //

    登録 $member = new Member(); $member->setName("taro"); $em->persist($member); // なんやかんや // 更新 $member->setProfile("XXXXX"); $em->persist($member); // $memberはマーク済みなのでしなくても いい // なんやかんや // 不適切!削除 $em->remove($member); // 確定 $em->flush(); Doctrine (DataMapper) 実行されるクエリ 何も実行されない
  38. 2. ActiveRecord vs DataMapper use App\Entities\Member; $em = app("em"); //

    登録 $member = new Member(); $member->setName("taro"); $em->persist($member); // なんやかんや // 更新 $member->setProfile("XXXXX"); $em->persist($member); // $memberはマーク済みなのでしなくても いい // なんやかんやあって、削除しない // 確定 $em->flush(); Doctrine (DataMapper) { "sql": "INSERT INTO members (name, profile) VALUES (?, ?)", "params": { "1": "taro", "2": "XXXXX" }, "types": { "1": 2, "2": 2 } } 実行されるクエリ 1回
  39. 2. ActiveRecord vs DataMapper use App\Entities\Member; $em = app("em"); //

    登録 $member = new Member(); $member->setName("taro"); $em->persist($member); // なんやかんや // 更新 $member->setProfile("XXXXX"); $em->persist($member); // $memberはマーク済みなのでしなくても いい // なんやかんやあって、削除しない // 確定 $em->flush(); Doctrine (DataMapper) { "sql": "INSERT INTO members (name, profile) VALUES (?, ?)", "params": { "1": "taro", "2": "XXXXX" }, "types": { "1": 2, "2": 2 } } 実行されるクエリ 自然に書けそう
  40. 2. ActiveRecord vs DataMapper use App\Models\Member; // 登録 $member =

    new Member(); $member->name = "taro"; $member->save(); // なんやかんや // 更新 $member->profile = "XXXXX"; $member->save(); // なんやかんや // 不適切!削除 $member->delete(); Eloquent (ActiveRecord) insert into `members` (`name`) values (taro) update `members` set `profile` = XXXXX where `id` = 1 delete from `members` where `id` = 1 実行されるクエリ 3回
  41. 2. ActiveRecord vs DataMapper use App\Models\Member; // 登録 $member =

    new Member(); $member->name = "taro"; $member->save(); // なんやかんや // 更新 $member->profile = "XXXXX"; $member->save(); // なんやかんやあって、削除しない Eloquent (ActiveRecord) insert into `members` (`name`) values (taro) update `members` set `profile` = XXXXX where `id` = 1 実行されるクエリ 2回
  42. 2. ActiveRecord vs DataMapper use App\Models\Member; // 登録 $member =

    new Member(); $member->name = "taro"; $member->save(); // なんやかんや // 更新 $member->profile = "XXXXX"; $member->save(); // なんやかんやあって、削除しない Eloquent (ActiveRecord) insert into `members` (`name`) values (taro) update `members` set `profile` = XXXXX where `id` = 1 実行されるクエリ 無駄なクエリを流さないた めに、工夫が必要。 その工夫が煩雑さをうむ?
  43. 2. ActiveRecord vs DataMapper こんなインターフェイスもあるし😎 namespace Doctrine\ORM; interface EntityManagerInterface extends

    ObjectManager { /** * Gets the UnitOfWork used by the EntityManager to coordinate operations. * * @return UnitOfWork */ public function getUnitOfWork(); }
  44. 1. Unit of Workパターンとは
 Martin Fowler氏によると、 “Unit of Work の重要な点は、コミットの時に、

    Unit of Work が何をすべきかを決定することであ る。トランザクションを開き、同時実行チェックを 実行し(悲観的ロック、または楽観的ロックを使 用)、変更をデータベースに書き出す。” Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler)) P184(※機械翻訳後に意訳) 2. ActiveRecord vs DataMapper
  45. 1. Unit of Workパターンとは
 ちょうぜつUnit of Work によると、 “トランザクションの ACID

    性と、複数の並行処理が データの矛盾を発生させないようにするロックと は、別の問題です。並行処理のロックについては、 別のパターンで述べられています。” ちょうぜつ Advent Calendar 2022 6日目 2. ActiveRecord vs DataMapper
  46. 1. Unit of Workパターンとは 楽観的ロックと悲観的ロック(ざっくりと) • 楽観的ロック(早いもの勝ち) ◦ Doctrineだと、@Versionアノテーションをエンティティのフィールドに付ける ◦

    Eloquentだと、ひと工夫が必要そう • 悲観的ロック(俺のもの) ◦ Doctrineだと、EntityManagerのlockメソッドを使用 ▪ LockMode::PESSIMISTIC_READ(読み取り), LockMode::PESSIMISTIC_WRITE(書き込み) ◦ Eloquentだと、sharedLock()(共有)、lockForUpdate()(専有) ▪ うっ...頭痛が... 2. ActiveRecord vs DataMapper
  47. 1. Unit of Workパターンとは ここまでで分かったこと • Doctrine(DataMapper)がUnit of Workだった •

    ActiveRecordで同じことをやろうとすると、意識することが多い ◦ ActiveRecordのよさが消えそう • Unit of Workは管理者がいて、そいつが管理する ◦ ActiveRecordは自由 ◦ 排他的なの分かる • 並行処理のロックについては、別のパターンで解決 2. ActiveRecord vs DataMapper
  48. 1. Unit of Workパターンとは 2. ActiveRecord vs DataMapper 3. Unit

    of Workパターン サンプル 4. 所感 5. まとめ 3. Unit of Workパターン サンプル
  49. 3. Unit of Workパターン サンプル 永続化処理をユースケース層から分離 • 永続化、トランザクションを効率的に行えるのは分かった • が、今のままだとユースケース層に永続化の知識が漏れている

    ◦ persist(); , flush(); とか ◦ Laravelだと、 DB::transaction(function(){}); しているところ • ユースケース(アプリケーションサービス)層では永続化の知識に依存したくないはず ◦ 原理主義者 • Laravel ◦ Eloquent → Eloquent/UoW風 → Doctrine/UoW
  50. 2. ActiveRecord vs DataMapper Doctrine (DataMapper) ユースケース • CSVから行を読み込んで、メンバー情報 を更新する

    • 登録、削除、更新のケースが考えられる • この例はリポジトリの中でEloquentを 使っている リファクタポイント • 永続化の知識が見えているところ ◦ 別に悪いことじゃないけど ◦ 原理主義でいくのなら 3. Unit of Workパターン サンプル class UseCase { public function __construct( private readonly FileInterface $file, private readonly MemberRepositoryInterface $memberRepository ) { } public function __invoke(FileParameterInterface $parameter) { /** @var Generator<Row> CSVを読み込んでオブジェクト化したやつ */ $csv = $this->file->read($parameter->getPathName()); foreach ($csv as $row) { if ($row->shouldDelete()) { $deleteMembers[] = $row->toArray(); continue; } if ($shouldUpdate) { $updateMembers[] = $row->toArray(); continue; } $insertMembers[] = $row->toArray(); } // 永続化 DB::transaction(function () use ($insertMembers, ...) { $this->memberRepository->insert($insertMembers); $this->memberRepository->delete($deleteMembers); $this->memberRepository->update($updateMembers); }); } }
  51. 2. ActiveRecord vs DataMapper
 interface EloquentUowServiceInterface { public function createMembers(array

    $member): void; public function updateMembers(array $member): void; public function deleteMembers(array $member): void; public function commit(): void; } // arrayですまんな... Interface class EloquentUowService implements EloquentUowServiceInterface { private array $createMembers = []; private array $updateMembers = []; private array $deleteMembers = []; public function __construct( private readonly MemberRepositoryInterface $memberRepository ) { } public function createMembers(array $member): void { $this->createMembers[] = $member; } public function updateMembers(array $member): void { /** 割愛 */ } public function deleteMembers(array $member): void { /** 割愛 */ } public function commit(): void { // 永続化 DB::transaction(function () { $this->memberRepository->insert($this->createMembers); $this->memberRepository->update($this->updateMembers); $this->memberRepository->delete($this->deleteMembers); }); } } 具象 3. Unit of Workパターン サンプル

  52. 2. ActiveRecord vs DataMapper
 リファクタ後
 • 永続化の知識が見えなくなった 
 ◦ どこかにcommitしているんだろうなぁと

    いう抽象具合
 • 更新するためのデータを溜め込んで渡すの は変わらない
 ◦ 配列をどこかにひきづりまわしたくない 
 • ラッパーが増えただけ?
 3. Unit of Workパターン サンプル
 class UseCase { public function __construct( private readonly FileInterface $file, private readonly MemberRepositoryInterface $memberRepository, private readonly EloquentUowServiceInterface $eloquentUowService, ) { } public function __invoke(FileParameterInterface $parameter) { /** @var Generator<Row> CSVを読み込んでオブジェクト化したやつ */ $csv = $this->file->read($parameter->getPathName()); foreach ($csv as $row) { if ($row->shouldDelete()) { $this->eloquentUowService->deleteMembers($row->toArray()); continue; } if ($shouldUpdate) { $this->eloquentUowService->updateMembers($row->toArray()); continue; } $this->eloquentUowService->createMembers($row->toArray()); } $this->eloquentUowService->commit(); } }
  53. 2. ActiveRecord vs DataMapper use App\Entities\Member; interface UowServiceInterface { public

    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パターン サンプル
  54. 2. ActiveRecord vs DataMapper use App\Entities\Member; interface UowServiceInterface { public

    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) ); }
  55. 2. ActiveRecord vs DataMapper use App\Entities\Member; interface UowServiceInterface { public

    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パターン サンプル リポジトリに処理を委譲できる
  56. 2. ActiveRecord vs DataMapper Doctrineでのリファクタ後 • 更新するためのデータを溜め込んで渡さ なくてよくなった ◦ Entityは渡す必要がある

    • Entityを渡せばよしなにやってくれる ◦ おまかせするだけ ◦ リレーションが増えたときも対応 してくれる 3. Unit of Workパターン サンプル class UseCase { public function __construct( private readonly FileInterface $file, private readonly UowServiceInterface $uowService, ) { } public function __invoke(FileParameterInterface $parameter) { $csv = $this->file->read($parameter->getPathName()); foreach ($csv as $row) { if ($row->shouldDelete()) { $deleteMember = (new Member())->factory($row->toArray()); $this->uowService->deleteMembers($deleteMember); continue; } if ($shouldUpdate) { $updateMember = (new Member())->factory($row->toArray()); $this->uowService->updateMembers($updateMember); continue; } $createMember = (new Member())->factory($row->toArray()); $this->uowService->createMembers($createMember); } $this->uowService->commit(); } }
  57. 2. ActiveRecord vs DataMapper 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パターン サンプル
  58. 3. Unit of Workパターン サンプル コマンドパターンのユースケース • 請求書(Invoice)に対する処理 • 請求書に対して一連の処理がある

    ◦ 色々なEntityに対しての処理がある ◦ 処理は今後も増えたり減ったり • その処理を順に実行していく
  59. 2. ActiveRecord vs DataMapper Unit of Workインターフェイス • UnitOfWorkInterface 4.

    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パターン サンプル
  60. 2. ActiveRecord vs DataMapper Unit of Work具象 • UnitOfWork 4.

    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(); } }
  61. 2. ActiveRecord vs DataMapper コマンド処理インターフェイス • InvoiceCommandInterface 4. Unit of

    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パターン サンプル
  62. 2. ActiveRecord vs DataMapper コマンド処理インターフェイス • InvoiceCommandInterface 4. Unit of

    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に対する操作を行う何か
  63. 2. ActiveRecord vs DataMapper 長期の顧客に対して割引を適用するコマンド • DiscountForLoyalCustomerCommand 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase\Command; use App\Example\Entities\Customer; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\InvoiceCommandInterface; use App\Example\UseCase\UnitOfWorkInterface; /** * 長期の顧客に対して割引を適用する */ class DiscountForLoyalCustomerCommand implements InvoiceCommandInterface { public function execute(Invoice $invoice, UnitOfWorkInterface $unitOfWork) { if ($this->isLoyalCustomer($invoice->getCustomer())) { $invoice->applyDiscount(10); // 10%の割引を適用する $unitOfWork->markDirty($invoice); } } 3. Unit of Workパターン サンプル private function isLoyalCustomer(Customer $customer): bool { // 顧客がロイヤル(長期)顧客であるかの判定ロジックを実装 return true; } }
  64. 2. ActiveRecord vs DataMapper 長期の顧客に対して割引を適用するコマンド • DiscountForLoyalCustomerCommand 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase\Command; use App\Example\Entities\Customer; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\InvoiceCommandInterface; use App\Example\UseCase\UnitOfWorkInterface; /** * 長期の顧客に対して割引を適用する */ class DiscountForLoyalCustomerCommand implements InvoiceCommandInterface { public function execute(Invoice $invoice, UnitOfWorkInterface $unitOfWork) { if ($this->isLoyalCustomer($invoice->getCustomer())) { $invoice->applyDiscount(10); // 10%の割引を適用する $unitOfWork->markDirty($invoice); } } 3. Unit of Workパターン サンプル private function isLoyalCustomer(Customer $customer): bool { // 顧客がロイヤル(長期)顧客であるかの判定ロジックを実装 return true; } } Entityの変更を監視
  65. 2. ActiveRecord vs DataMapper 請求書が遅延している場合にアラートを作成するコマンド • LateInvoiceAlertCommand 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase\Command; // use 略; /** * 請求書(Invoice)が遅延している場合にアラートを作成する */ class LateInvoiceAlertCommand implements InvoiceCommandInterface { public function execute(Invoice $invoice, UnitOfWorkInterface $unitOfWork) { // 遅延が無ければ何もしない if (!$this->isTheInvoiceLate($invoice)) { return; } $alert = $this->createLateAlertFor($invoice); $unitOfWork->markNew($alert); // 遅延アラートEntityを保存 } 3. Unit of Workパターン サンプル private function isTheInvoiceLate(Invoice $invoice): bool { // ここにinvoiceが遅延しているかどうかを判定するロジックを実装 return true; } private function createLateAlertFor(Invoice $invoice): PaymentDelayAlert { // ここに遅延アラートEntityを作成するロジックを実装 return new PaymentDelayAlert(); } }
  66. 2. ActiveRecord vs DataMapper 請求書が遅延している場合にアラートを作成するコマンド • LateInvoiceAlertCommand 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase\Command; // use 略; /** * 請求書(Invoice)が遅延している場合にアラートを作成する */ class LateInvoiceAlertCommand implements InvoiceCommandInterface { public function execute(Invoice $invoice, UnitOfWorkInterface $unitOfWork) { // 遅延が無ければ何もしない if (!$this->isTheInvoiceLate($invoice)) { return; } $alert = $this->createLateAlertFor($invoice); $unitOfWork->markNew($alert); // 遅延アラートEntityを保存 } 3. Unit of Workパターン サンプル private function isTheInvoiceLate(Invoice $invoice): bool { // ここにinvoiceが遅延しているかどうかを判定するロジックを実装 return true; } private function createLateAlertFor(Invoice $invoice): PaymentDelayAlert { // ここに遅延アラートEntityを作成するロジックを実装 return new PaymentDelayAlert(); } } Entityの追加を監視
  67. 2. ActiveRecord vs DataMapper 一連処理インターフェイス • InvoiceCommandProcessorInterface 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\InvoiceCommandInterface; interface InvoiceCommandProcessorInterface { /** * @param Invoice $invoice * @param InvoiceCommandInterface[] $commands */ public function runCommands(Invoice $invoice, array $commands); } 3. Unit of Workパターン サンプル
  68. 2. ActiveRecord vs DataMapper 一連処理インターフェイス • InvoiceCommandProcessorInterface 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\UseCase; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\InvoiceCommandInterface; interface InvoiceCommandProcessorInterface { /** * @param Invoice $invoice * @param InvoiceCommandInterface[] $commands */ public function runCommands(Invoice $invoice, array $commands); } 3. Unit of Workパターン サンプル Invoiceに対する操作(コマンド)を 持つものたちを受け取り、順にそ のコマンドを実行させる何か
  69. 2. ActiveRecord vs DataMapper 一連処理の実行 • InvoiceCommandProcessor 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\Service; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\InvoiceCommandInterface; use App\Example\UseCase\InvoiceCommandProcessorInterface; use App\Example\UseCase\UnitOfWorkInterface; use Exception; class InvoiceCommandProcessor implements InvoiceCommandProcessorInterface { public function __construct( private readonly UnitOfWorkInterface $unitOfWork ) { } 3. Unit of Workパターン サンプル /** * @param Invoice $invoice * @param InvoiceCommandInterface[] $commands */ public function runCommands(Invoice $invoice, array $commands) { try { foreach ($commands as $command) { $command->execute($invoice, $this->unitOfWork); } $this->unitOfWork->commit(); } catch (Exception $e) { $this->unitOfWork->rollback(); throw $e; } } }
  70. 2. ActiveRecord vs DataMapper 一連処理の実行 • InvoiceCommandProcessor 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\Service; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\InvoiceCommandInterface; use App\Example\UseCase\InvoiceCommandProcessorInterface; use App\Example\UseCase\UnitOfWorkInterface; use Exception; class InvoiceCommandProcessor implements InvoiceCommandProcessorInterface { public function __construct( private readonly UnitOfWorkInterface $unitOfWork ) { } 3. Unit of Workパターン サンプル /** * @param Invoice $invoice * @param InvoiceCommandInterface[] $commands */ public function runCommands(Invoice $invoice, array $commands) { try { foreach ($commands as $command) { $command->execute($invoice, $this->unitOfWork); } $this->unitOfWork->commit(); } catch (Exception $e) { $this->unitOfWork->rollback(); throw $e; } } } ここでコマンドが呼ばれて Entityが諸々監視されて
  71. 2. ActiveRecord vs DataMapper 一連処理の実行 • InvoiceCommandProcessor 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\Service; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\InvoiceCommandInterface; use App\Example\UseCase\InvoiceCommandProcessorInterface; use App\Example\UseCase\UnitOfWorkInterface; use Exception; class InvoiceCommandProcessor implements InvoiceCommandProcessorInterface { public function __construct( private readonly UnitOfWorkInterface $unitOfWork ) { } 3. Unit of Workパターン サンプル /** * @param Invoice $invoice * @param InvoiceCommandInterface[] $commands */ public function runCommands(Invoice $invoice, array $commands) { try { foreach ($commands as $command) { $command->execute($invoice, $this->unitOfWork); } $this->unitOfWork->commit(); } catch (Exception $e) { $this->unitOfWork->rollback(); throw $e; } } } ここでまとめてドーン
  72. 2. ActiveRecord vs DataMapper 一連処理の実行 • InvoiceCommandProcessor 4. Unit of

    Workパターンが解決すること <?php declare(strict_types = 1); namespace App\Example\Service; use App\Example\Entities\Invoice; use App\Example\UseCase\Command\InvoiceCommandInterface; use App\Example\UseCase\InvoiceCommandProcessorInterface; use App\Example\UseCase\UnitOfWorkInterface; use Exception; class InvoiceCommandProcessor implements InvoiceCommandProcessorInterface { public function __construct( private readonly UnitOfWorkInterface $unitOfWork ) { } 3. Unit of Workパターン サンプル /** * @param Invoice $invoice * @param InvoiceCommandInterface[] $commands */ public function runCommands(Invoice $invoice, array $commands) { try { foreach ($commands as $command) { $command->execute($invoice, $this->unitOfWork); } $this->unitOfWork->commit(); } catch (Exception $e) { $this->unitOfWork->rollback(); throw $e; } } } 監視されていたEntityが、 ここでよしなに永続化され る
  73. 2. ActiveRecord vs DataMapper ユースケース • InvoiceUsecase 4. Unit of

    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パターン サンプル
  74. 2. ActiveRecord vs DataMapper ユースケース • InvoiceUsecase 4. Unit of

    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に対する処理が増えても、 コマンド処理インターフェイスを 満たすものを渡せばいいだけ
  75. 2. ActiveRecord vs DataMapper ユースケース • InvoiceUsecase 4. Unit of

    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: 開放閉 鎖の原則)
  76. 2. ActiveRecord vs DataMapper ユースケース • InvoiceUsecase 4. Unit of

    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に対する処理を色々やって くれているんだろうなぁという抽 象具合。 気づけばいい感じに永続化されて いる。
  77. 1. Unit of Workパターンとは 2. ActiveRecord vs DataMapper 3. Unit

    of Workパターン サンプル 4. 所感 5. まとめ 4. 所感
  78. 2. ActiveRecord vs DataMapper Unit of Workパターンが解決すること • データベースとのやり取りを効率的に行ってくれる •

    EntityはDBのことを知らないのでビジネスロジックに集中しやすい • サービス層などでUnit of Workを使って永続化処理を隠蔽 4. 所感
  79. 2. ActiveRecord vs DataMapper Unit of Workパターンが解決すること • データベースとのやり取りを効率的に行ってくれる ◦

    データベースへの操作(挿入、更新、削除)を一括で管理してくれる ◦ `かたまり`をきちんと見てくれる ▪ 集約みたいな概念をきちんとやっているところと相性が良さそう ▪ 適宜、flush(), clear() して、結果整合性に倒すとか • clear()は監視しているEntityを開放する ▪ DDD風味がある ◦ flush() のときにしかトランザクションは発生しない ▪ エンティティマネージャからDBコネクションを取れるので beginTransaction, rollback, commit は書ける 4. 所感
  80. 2. ActiveRecord vs DataMapper Unit of Workパターンが解決すること • EntityはDBのことを知らないのでビジネスロジックに集中しやすい ◦

    会話したらうっかりDB更新しちゃったとかがない ◦ 実際のDB操作はエンティティマネージャ、リポジトリにお任せ ◦ きっちりと分かれているのでリポジトリパターンと相性が合うのが分かる 4. 所感
  81. 2. ActiveRecord vs DataMapper Unit of Workパターンが解決すること • サービス層などでUnit of

    Workを使って永続化処理を隠蔽 ◦ 基本的には、Entity取得, 作成, 更新して、flush() ◦ コマンドパターンおすすめ 4. 所感
  82. 2. ActiveRecord vs DataMapper 逆に足かせになりそうなこと • データアクセスする際は必ずエンティティマネージャが必要なので、手間がかかる ◦ その分、制約を設けられる ◦

    例えば deptrac 使ったり • お任せできると言っても、Entityに書くアノテーション(マッピング)による ◦ やはりそのへんの学習コストはある • その点、ActiveRecordは気軽にアクセスできる ◦ リーン、早く進めたいならActiveRecord ◦ かっちりやりたいならDataMapper 4. 所感
  83. 2. ActiveRecord vs DataMapper 気になりポイント • 複数の変更を効率的にデータベースに反映するとあったので、たくさんのレコードを 作る際はバルク処理もしてくれるのかなぁと思ってログを見たけどそうでもなかった ◦ 例えば、insert用にpersistを100回してから、flush

    ◦ きっちり1行1行insert文を発行していた • 監視対象のEntityが多すぎてもメモリを食うらしい(そりゃそうか) • 対処方法はこの記事あたりが参考になるかも ◦ DoctrineとEloquent比較大全14: バッチ更新 ▪ https://qiita.com/77web@github/items/bbdac4c1a373577cbda2 ◦ DoctrineとEloquent比較大全15: バッチ削除 ▪ https://qiita.com/77web@github/items/57d1129acddf10909c4a 4. 所感
  84. 2. ActiveRecord vs DataMapper
 気になりポイント • 複数の変更を効率的にデータベースに反映するとあったので、たくさんのレコードを 作る際はバルク処理もしてくれるのかなぁと思ってログを見たけどそうでもなかった ◦ 例えば、insert用にpersistを100回してから、flush

    ◦ きっちり1行1行insert文を発行していた • 監視対象のEntityが多すぎてもメモリを食うらしい(そりゃそうか) • 対処方法はこの記事あたりが参考になるかも ◦ DoctrineとEloquent比較大全14: バッチ更新 ▪ https://qiita.com/77web@github/items/bbdac4c1a373577cbda2 ◦ DoctrineとEloquent比較大全15: バッチ削除 ▪ https://qiita.com/77web@github/items/57d1129acddf10909c4a 4. 所感 神 PHPのORM: DoctrineORMとEloquentの比較大全をひとりでやる Advent Calendar 2022
  85. 2. ActiveRecord vs DataMapper TIPS • ログの吐かせ方 ◦ `src/vendor/laravel-doctrine/orm/config/doctrine.php`を、 `src/config/`以下に配置

    ▪ 自分はなぜかpublishできなかった ◦ Doctrine\DBAL\Logging\Middleware::class のコメントを外せばOK 4. 所感 // src/config/doctrine.php 'managers' => [ // 略 'middlewares' => [ Doctrine\DBAL\Logging\Middleware::class // コメントを外す ] ]
  86. 2. ActiveRecord vs DataMapper
 TIPS • デバッグ ◦ `APP_DEBUG` を

    true に変える ◦ `ddd()`とかで見れる 4. 所感 'managers' => [ 'default' => [ 'dev' => env('APP_DEBUG', true), // trueに
  87. 2. ActiveRecord vs DataMapper
 TIPS • Entityの名前空間 ◦ デフォルトは、`App\Entities` ◦

    変更したければconfigを修正 ◦ 他にも、`repository`、`DOCTRINE_METADATA`とか変えられる 4. 所感 'managers' => [ 'default' => [ // 〜 'paths' => [ base_path('app/Entities') // ここ変える ],
  88. 1. Unit of Workパターンとは 2. ActiveRecord vs DataMapper 3. Unit

    of Work パターンサンプル 4. メリット / デメリット 5. まとめ 5. まとめ
  89. 2. ActiveRecord vs DataMapper Doctrineが Unit of Work でした 4.

    メリット / デメリット 5. まとめ
  90. 1. Unit of Workパターンとは 参考 • Patterns of Enterprise Application

    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. まとめ
  91. 1. Unit of Workパターンとは 参考 • PHPのORM: DoctrineORMとEloquentの比較大全をひとりでやる Advent Calendar

    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. まとめ