Slide 1

Slide 1 text

Unit of Workパターンで 永続化とトランザクション を制御する 2023/09/16 ペチコン沖縄2023 by しまぶ@shimabox 


Slide 2

Slide 2 text

自己紹介

Slide 3

Slide 3 text

コンフリクトを恐れない アジェンダ
 自己紹介

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

1. Unit of Workパターンとは Martin Fowler氏によると、 “ビジネストランザクションによって影響を受 けるオブジェクトのリストを保持し、変更の 書き込みと同時実行の問題の解決を調整す る。” Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler)) P184(※機械翻訳後に意訳)

Slide 7

Slide 7 text

1. Unit of Workパターンとは なるほど、ちょっとわからん 😇

Slide 8

Slide 8 text

1. Unit of Workパターンとは ちょうぜつ Advent Calendar 2022 6日目 UnitOfWork https://qiita.com/tanakahisateru/items/ b0c441c4540e84fe6dea

Slide 9

Slide 9 text

1. Unit of Workパターンとは ● DomainModelの変更を都度DBに反映せず、 ビジネスロジック完了後にまとめて反映 ● 変更は監視され、最小のSQLで同期される ● ActiveRecordとUnit of Workは排他的 ● DataMapperはUnit of Workと自然に共存する

Slide 10

Slide 10 text

1. Unit of Workパターンとは なんとなく、わかるぞ 😀

Slide 11

Slide 11 text

1. Unit of Workパターンとは たぶん、ActiveRecord ディスられている (偏見) 😀

Slide 12

Slide 12 text

1. Unit of Workパターンとは ここまでで分かったこと ● ビジネスロジックでの変更内容を覚えておいて(ここではDBに関し て無関心)、最後にDBへ反映させるもの ● つまり、単純にトランザクションが短くなる ○ DBへ反映させるときだけだから ○ パフォーマンスが良くなる ● DataMapper が肝っぽい ● ActiveRecord と Unit of Workは排他的

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

2. ActiveRecord vs DataMapper ActiveRecord ● みんな大好き(?) ● 好きなときにDB触れる ○ 行をオブジェクトとして表現し、そのオブジェクトにDB操作のロジックを持つ ○ CRUD操作するためのメソッドや機能がついている ● ドメインのビジネスロジックとデータベース操作のロジックが混在する ● DBのことを知っている

Slide 15

Slide 15 text

2. ActiveRecord vs DataMapper DataMapper ● DBのレコードを表すもの(Entity)と、それらのライフサイクルを管理するもの(エン ティティマネージャ)に分かれている ● Entityはビジネスロジックやアプリケーションのドメインモデルを表現する ● DataMapperは、EntityとDBとの間のマッピングや操作を担当する ○ デザインパターンのひとつ ○ インピーダンスミスマッチ とかいうやつ ■ 現実世界とアプリケーションとのうんたらかんたら ○ エンティティマネージャは裏方、仲介役 ● Entity自体はDBのことを知らない/依存していない (諸説あり) ○ アノテーションは書かれているが

Slide 16

Slide 16 text

2. ActiveRecord vs DataMapper ActiveRecord ● 「何をすべきか」「どのように」を一つのオブジェクト(ActiveRecord)が担当 ● 自分が主 DataMapper ● 「オブジェクト(Entity)が何をすべきか」に集中でき、「どのように」の部分は エンティティマネージャ(リポジトリ)が担当 ● Entityはエンティティマネージャ(リポジトリ)がいないとDBに対して何もできない

Slide 17

Slide 17 text

2. ActiveRecord vs DataMapper サンプルを書いてみよう (PHP 8.2.10, Laravel 10.21.0 使う)

Slide 18

Slide 18 text

2. ActiveRecord vs DataMapper ActiveRecord ● Eloquent DataMapper ● Doctrine ○ laravel-doctrine

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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 神

Slide 21

Slide 21 text

2. ActiveRecord vs DataMapper Eloquent (ActiveRecord) アッコちゃん Doctrine (DataMapper) ドクくん

Slide 22

Slide 22 text

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) ドクくん

Slide 23

Slide 23 text

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) ドクくん

Slide 24

Slide 24 text

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) ドクくん

Slide 25

Slide 25 text

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) ドクくん

Slide 26

Slide 26 text

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) ドクくん

Slide 27

Slide 27 text

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;

Slide 28

Slide 28 text

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) ドクくん

Slide 29

Slide 29 text

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) ドクくん

Slide 30

Slide 30 text

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への書き込 み、トランザクションと...

Slide 31

Slide 31 text

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) ドクくん トランザクションで囲って みました

Slide 32

Slide 32 text

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) ドクくん 少し想像してみよう...

Slide 33

Slide 33 text

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) ドクくん

Slide 34

Slide 34 text

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) ドクくん

Slide 35

Slide 35 text

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) ドクくん

Slide 36

Slide 36 text

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) ドクくん トランザクションから脱出 します

Slide 37

Slide 37 text

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) ドクくん

Slide 38

Slide 38 text

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) ドクくん

Slide 39

Slide 39 text

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) ドクくん 配列のメモリが心配

Slide 40

Slide 40 text

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,,,] って何?

Slide 41

Slide 41 text

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) ドクくん これが、みんな大好き連想 配列というやつ!?

Slide 42

Slide 42 text

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 がうまれやすい...!?

Slide 43

Slide 43 text

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) アッコちゃん

Slide 44

Slide 44 text

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) アッコちゃん

Slide 45

Slide 45 text

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) アッコちゃん

Slide 46

Slide 46 text

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とか

Slide 47

Slide 47 text

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) ドクくん

Slide 48

Slide 48 text

2. ActiveRecord vs DataMapper いい具合とは?

Slide 49

Slide 49 text

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) ● メンバーを登録して ● なんやかんやして ● メンバーの情報を更新する ● なんやかんやして ● 情報に不適切なものがあればメンバーを 削除する ユースケース

Slide 50

Slide 50 text

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) 実行されるクエリ 何も実行されない

Slide 51

Slide 51 text

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回

Slide 52

Slide 52 text

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 } } 実行されるクエリ 自然に書けそう

Slide 53

Slide 53 text

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回

Slide 54

Slide 54 text

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回

Slide 55

Slide 55 text

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 実行されるクエリ 無駄なクエリを流さないた めに、工夫が必要。 その工夫が煩雑さをうむ?

Slide 56

Slide 56 text

2. ActiveRecord vs DataMapper Doctrine (DataMapper) 一連のビジネスロジックが完了した後に、 最終的な結果をまとめて いい具合に反映させる

Slide 57

Slide 57 text

1. (再掲) Unit of Workパターンとは ● DomainModelの変更を都度DBに反映せず、 ビジネスロジック完了後にまとめて反映 ● 変更は監視され、最小のSQLで同期される ● ActiveRecordとUnit of Workは排他的 ● DataMapperはUnit of Workと自然に共存する

Slide 58

Slide 58 text

2. ActiveRecord vs DataMapper お気づきになったであろうか...

Slide 59

Slide 59 text

2. ActiveRecord vs DataMapper DoctrineがUnit of Workの パターンを組み合わせて 実装されているのである

Slide 60

Slide 60 text

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(); }

Slide 61

Slide 61 text

2. ActiveRecord vs DataMapper UnitOfWorkも持っている😎 namespace Doctrine\ORM; class UnitOfWork implements PropertyChangedListener { }

Slide 62

Slide 62 text

2. ActiveRecord vs DataMapper Doctrine is Unit of Work.

Slide 63

Slide 63 text

2. ActiveRecord vs DataMapper みんなDoctrineを使おっ!? そしたらUnit of Workだぞっ💛 😍

Slide 64

Slide 64 text

2. ActiveRecord vs DataMapper 完

Slide 65

Slide 65 text

2. ActiveRecord vs DataMapper 完 もうちょいやります 一 一

Slide 66

Slide 66 text

2. ActiveRecord vs DataMapper 監視しておいて最後に保存

Slide 67

Slide 67 text

2. ActiveRecord vs DataMapper 並行処理は? 最後にあぼーんしない?

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

1. Unit of Workパターンとは
 ちょうぜつUnit of Work によると、 “トランザクションの ACID 性と、複数の並行処理が データの矛盾を発生させないようにするロックと は、別の問題です。並行処理のロックについては、 別のパターンで述べられています。” ちょうぜつ Advent Calendar 2022 6日目 2. ActiveRecord vs DataMapper

Slide 70

Slide 70 text

2. ActiveRecord vs DataMapper Unit of Workが解決することではない 別のパターンで解決

Slide 71

Slide 71 text

1. Unit of Workパターンとは 楽観的ロックと悲観的ロック(ざっくりと) ● 楽観的ロック(早いもの勝ち) ○ Doctrineだと、@Versionアノテーションをエンティティのフィールドに付ける ○ Eloquentだと、ひと工夫が必要そう ● 悲観的ロック(俺のもの) ○ Doctrineだと、EntityManagerのlockメソッドを使用 ■ LockMode::PESSIMISTIC_READ(読み取り), LockMode::PESSIMISTIC_WRITE(書き込み) ○ Eloquentだと、sharedLock()(共有)、lockForUpdate()(専有) ■ うっ...頭痛が... 2. ActiveRecord vs DataMapper

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

3. Unit of Workパターン サンプル 永続化処理をユースケース層から分離 ● 永続化、トランザクションを効率的に行えるのは分かった ● が、今のままだとユースケース層に永続化の知識が漏れている ○ persist(); , flush(); とか ○ Laravelだと、 DB::transaction(function(){}); しているところ ● ユースケース(アプリケーションサービス)層では永続化の知識に依存したくないはず ○ 原理主義者 ● Laravel ○ Eloquent → Eloquent/UoW風 → Doctrine/UoW

Slide 75

Slide 75 text

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 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); }); } }

Slide 76

Slide 76 text

2. ActiveRecord vs DataMapper
 これを参考にやってみる https://martinfowler.com/eaaCatalog/Unit of Work.html 3. Unit of Workパターン サンプル

Slide 77

Slide 77 text

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パターン サンプル


Slide 78

Slide 78 text

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 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(); } }

Slide 79

Slide 79 text

2. ActiveRecord vs DataMapper
 
 Doctrineだとどうだろう
 
 3. Unit of Workパターン サンプル


Slide 80

Slide 80 text

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パターン サンプル

Slide 81

Slide 81 text

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) ); }

Slide 82

Slide 82 text

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パターン サンプル リポジトリに処理を委譲できる

Slide 83

Slide 83 text

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(); } }

Slide 84

Slide 84 text

2. ActiveRecord vs DataMapper もういっちょう 3. Unit of Workパターン サンプル

Slide 85

Slide 85 text

2. ActiveRecord vs DataMapper コマンドパターン 3. Unit of Workパターン サンプル

Slide 86

Slide 86 text

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パターン サンプル

Slide 87

Slide 87 text

2. ActiveRecord vs DataMapper これのサンプルを PHPで書き直してみた (動くとは言っていない) 3. Unit of Workパターン サンプル

Slide 88

Slide 88 text

3. Unit of Workパターン サンプル コマンドパターンのユースケース ● 請求書(Invoice)に対する処理 ● 請求書に対して一連の処理がある ○ 色々なEntityに対しての処理がある ○ 処理は今後も増えたり減ったり ● その処理を順に実行していく

Slide 89

Slide 89 text

2. ActiveRecord vs DataMapper Unit of Workインターフェイス ● UnitOfWorkInterface 4. Unit of Workパターンが解決すること

Slide 90

Slide 90 text

2. ActiveRecord vs DataMapper Unit of Work具象 ● UnitOfWork 4. Unit of Workパターンが解決すること 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(); } }

Slide 91

Slide 91 text

2. ActiveRecord vs DataMapper コマンド処理インターフェイス ● InvoiceCommandInterface 4. Unit of Workパターンが解決すること

Slide 92

Slide 92 text

2. ActiveRecord vs DataMapper コマンド処理インターフェイス ● InvoiceCommandInterface 4. Unit of Workパターンが解決すること

Slide 93

Slide 93 text

2. ActiveRecord vs DataMapper 長期の顧客に対して割引を適用するコマンド ● DiscountForLoyalCustomerCommand 4. Unit of Workパターンが解決すること isLoyalCustomer($invoice->getCustomer())) { $invoice->applyDiscount(10); // 10%の割引を適用する $unitOfWork->markDirty($invoice); } } 3. Unit of Workパターン サンプル private function isLoyalCustomer(Customer $customer): bool { // 顧客がロイヤル(長期)顧客であるかの判定ロジックを実装 return true; } }

Slide 94

Slide 94 text

2. ActiveRecord vs DataMapper 長期の顧客に対して割引を適用するコマンド ● DiscountForLoyalCustomerCommand 4. Unit of Workパターンが解決すること isLoyalCustomer($invoice->getCustomer())) { $invoice->applyDiscount(10); // 10%の割引を適用する $unitOfWork->markDirty($invoice); } } 3. Unit of Workパターン サンプル private function isLoyalCustomer(Customer $customer): bool { // 顧客がロイヤル(長期)顧客であるかの判定ロジックを実装 return true; } } Entityの変更を監視

Slide 95

Slide 95 text

2. ActiveRecord vs DataMapper 請求書が遅延している場合にアラートを作成するコマンド ● LateInvoiceAlertCommand 4. Unit of Workパターンが解決すること 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(); } }

Slide 96

Slide 96 text

2. ActiveRecord vs DataMapper 請求書が遅延している場合にアラートを作成するコマンド ● LateInvoiceAlertCommand 4. Unit of Workパターンが解決すること 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の追加を監視

Slide 97

Slide 97 text

2. ActiveRecord vs DataMapper 一連処理インターフェイス ● InvoiceCommandProcessorInterface 4. Unit of Workパターンが解決すること

Slide 98

Slide 98 text

2. ActiveRecord vs DataMapper 一連処理インターフェイス ● InvoiceCommandProcessorInterface 4. Unit of Workパターンが解決すること

Slide 99

Slide 99 text

2. ActiveRecord vs DataMapper 一連処理の実行 ● InvoiceCommandProcessor 4. Unit of Workパターンが解決すること execute($invoice, $this->unitOfWork); } $this->unitOfWork->commit(); } catch (Exception $e) { $this->unitOfWork->rollback(); throw $e; } } }

Slide 100

Slide 100 text

2. ActiveRecord vs DataMapper 一連処理の実行 ● InvoiceCommandProcessor 4. Unit of Workパターンが解決すること execute($invoice, $this->unitOfWork); } $this->unitOfWork->commit(); } catch (Exception $e) { $this->unitOfWork->rollback(); throw $e; } } } ここでコマンドが呼ばれて Entityが諸々監視されて

Slide 101

Slide 101 text

2. ActiveRecord vs DataMapper 一連処理の実行 ● InvoiceCommandProcessor 4. Unit of Workパターンが解決すること execute($invoice, $this->unitOfWork); } $this->unitOfWork->commit(); } catch (Exception $e) { $this->unitOfWork->rollback(); throw $e; } } } ここでまとめてドーン

Slide 102

Slide 102 text

2. ActiveRecord vs DataMapper 一連処理の実行 ● InvoiceCommandProcessor 4. Unit of Workパターンが解決すること execute($invoice, $this->unitOfWork); } $this->unitOfWork->commit(); } catch (Exception $e) { $this->unitOfWork->rollback(); throw $e; } } } 監視されていたEntityが、 ここでよしなに永続化され る

Slide 103

Slide 103 text

2. ActiveRecord vs DataMapper ユースケース ● InvoiceUsecase 4. Unit of Workパターンが解決すること invoiceCommandProcessor->runCommands($invoice, $commands); } } 3. Unit of Workパターン サンプル

Slide 104

Slide 104 text

2. ActiveRecord vs DataMapper ユースケース ● InvoiceUsecase 4. Unit of Workパターンが解決すること invoiceCommandProcessor->runCommands($invoice, $commands); } } 3. Unit of Workパターン サンプル Invoiceに対する処理が増えても、 コマンド処理インターフェイスを 満たすものを渡せばいいだけ

Slide 105

Slide 105 text

2. ActiveRecord vs DataMapper ユースケース ● InvoiceUsecase 4. Unit of Workパターンが解決すること invoiceCommandProcessor->runCommands($invoice, $commands); } } 3. Unit of Workパターン サンプル 拡張に対して開かれている (Open-Closed Principle: 開放閉 鎖の原則)

Slide 106

Slide 106 text

2. ActiveRecord vs DataMapper ユースケース ● InvoiceUsecase 4. Unit of Workパターンが解決すること invoiceCommandProcessor->runCommands($invoice, $commands); } } 3. Unit of Workパターン サンプル Invoiceに対する処理を色々やって くれているんだろうなぁという抽 象具合。 気づけばいい感じに永続化されて いる。

Slide 107

Slide 107 text

2. ActiveRecord vs DataMapper なんかめっちゃいい気がする 3. Unit of Workパターン サンプル

Slide 108

Slide 108 text

3. Unit of Workパターン サンプル クラス図

Slide 109

Slide 109 text

3. Unit of Workパターン サンプル クラス図 神 https://github.com/smeghead/php-class-diagram

Slide 110

Slide 110 text

2. ActiveRecord vs DataMapper こういうの書けるようになりたい 3. Unit of Workパターン サンプル

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

2. ActiveRecord vs DataMapper Unit of Workパターンが解決すること ● サービス層などでUnit of Workを使って永続化処理を隠蔽 ○ 基本的には、Entity取得, 作成, 更新して、flush() ○ コマンドパターンおすすめ 4. 所感

Slide 116

Slide 116 text

2. ActiveRecord vs DataMapper 逆に足かせになりそうなこと ● データアクセスする際は必ずエンティティマネージャが必要なので、手間がかかる ○ その分、制約を設けられる ○ 例えば deptrac 使ったり ● お任せできると言っても、Entityに書くアノテーション(マッピング)による ○ やはりそのへんの学習コストはある ● その点、ActiveRecordは気軽にアクセスできる ○ リーン、早く進めたいならActiveRecord ○ かっちりやりたいならDataMapper 4. 所感

Slide 117

Slide 117 text

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. 所感

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

2. ActiveRecord vs DataMapper 気になりポイント ● 大量データの扱いに関して少し調べてみると、やはりそこは生のクエリを書くか、 バッチ処理にするか、キューなどを使うかなどであった ● それはそれで別のソリューションを考える必要がある ● いきつくところは結局そこなのか...という思いがある ● が、それをひっくるめても勉強を続けて実務で使いたいお気持ち 4. 所感

Slide 120

Slide 120 text

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 // コメントを外す ] ]

Slide 121

Slide 121 text

2. ActiveRecord vs DataMapper
 TIPS ● デバッグ ○ `APP_DEBUG` を true に変える ○ `ddd()`とかで見れる 4. 所感 'managers' => [ 'default' => [ 'dev' => env('APP_DEBUG', true), // trueに

Slide 122

Slide 122 text

2. ActiveRecord vs DataMapper
 TIPS ● Entityの名前空間 ○ デフォルトは、`App\Entities` ○ 変更したければconfigを修正 ○ 他にも、`repository`、`DOCTRINE_METADATA`とか変えられる 4. 所感 'managers' => [ 'default' => [ // 〜 'paths' => [ base_path('app/Entities') // ここ変える ],

Slide 123

Slide 123 text

2. ActiveRecord vs DataMapper
 余談 ● Unit of WorkパターンをLaravelにもどうだ?というプロポーザルが挙げられていた ○ 2017/03 頃 ● https://github.com/laravel/ideas/issues/495 4. 所感

Slide 124

Slide 124 text

2. ActiveRecord vs DataMapper
 余談 ● Unit of WorkパターンをLaravelにもどうだ?というプロポーザルが挙げられていた ○ 2017/03 頃 ● https://github.com/laravel/ideas/issues/495 4. 所感

Slide 125

Slide 125 text

1. Unit of Workパターンとは 2. ActiveRecord vs DataMapper 3. Unit of Work パターンサンプル 4. メリット / デメリット 5. まとめ 5. まとめ

Slide 126

Slide 126 text

2. ActiveRecord vs DataMapper Doctrineが Unit of Work でした 4. メリット / デメリット 5. まとめ

Slide 127

Slide 127 text

2. ActiveRecord vs DataMapper 永続化、トランザクションは エンティティマネージャが よしなにやってくれます 4. メリット / デメリット 5. まとめ

Slide 128

Slide 128 text

2. ActiveRecord vs DataMapper Unit of Workを使って 永続化の処理をうまく隠蔽して 書こうと思えば書けます 4. メリット / デメリット 5. まとめ

Slide 129

Slide 129 text

2. ActiveRecord vs DataMapper Doctrine(DataMapper) 使ってみよう 4. メリット / デメリット 5. まとめ

Slide 130

Slide 130 text

2. ActiveRecord vs DataMapper 自ずとUnit of Workが分かります (きっと) 4. メリット / デメリット 5. まとめ

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

2. ActiveRecord vs DataMapper ご清聴ありがとうございました 4. メリット / デメリット 5. まとめ