はいさい(N回目)
というわけで、9/16に PHPカンファレンス沖縄2023 で登壇してきました。
オフラインでの登壇は初めての経験だったということもあり、せっかくなので記憶が薄れる前に記録に残しておこうと思います。
登壇する前までマジで地獄を見たのですが、今となってはそれもいい思い出です。はい。
発表資料
発表資料はこちらになります。(Google Slidesをpdfにするとフォントの問題なのか崩れたり見づらくなるのどうにかならんのか)
Unit of Workパターンで永続化とトランザクションを制御する – Speaker Deck
動画はこちら。Track B の3人目くらいです。
LIVE配信始まります!#phpcon_okinawa
【Track A】https://t.co/53focwRkqk
【Track B】https://t.co/vlbDT4Rk1h— PHPカンファレンス沖縄 (@phpcon_okinawa) September 16, 2023
発表時の様子はこちら。
Unit of Workパターンで永続化とトランザクションを制御する
しまぶ様#phpcon_okinawa #track_b pic.twitter.com/FMsDZmCAPj
— PHPカンファレンス沖縄 (@phpcon_okinawa) September 16, 2023
X(旧Twitter)での紹介。
本日の発表資料です!https://t.co/GhxQrQgmIl
※ ネタバレするのでリアルタイムで見ていただけると#phpcon_okinawa #track_b— しまぶ (@shimabox) September 16, 2023
神が息づいている島。
神が息づいている島 #phpcon_okinawa #ちょうぜつエンジニアめもりーちゃん pic.twitter.com/1NBUtECS4p
— 田中ひさてる (@tanakahisateru) September 16, 2023
なぜ登壇しようと思ったのか
去年の春頃に転職をしたのですが、そこでは息を吐くようにプロポーザルを出して登壇する人が一定数存在しておりました。
自分はこういったカンファレンスで登壇する人って雲の上の人なんだろうなぁと正直思っていたのですが、身近にそういった人がいたというのが一番大きかったと思います。あれ?気軽にチャレンジしてもいいのでは!?、というところですかね。
そこで思い切って、ペチコン福岡にプロポーザルを出してみたのですが撃沈。いやあれは無理や。
結局自分には縁のない話なんだよなぁと思っていたところ、まさかのペチコン沖縄開催決定。魂の置いてある場所。これは魂を拾いに行かねばなるまいと意気込んでプロポーザルを出したところ採択された感じです。
※ 魂の置いてある場所とありますが、わたし沖縄生まれなのです
あと、内弁慶でいるのも何か違うんだよなぁと常々思っていたのもあります。いつかは外で発信してみたいなぁとひっそり夢見ていたので自らチャレンジできたのは良かったと思っています。
登壇してよかったこと
1. 違う風景を見れたこと
生粋の引きこもり/ボッチ気質である自分にとって、外部に向けて、ましてやオフラインでの発表をするのはかなり勇気のいる行動だったのですが、それと引き換えに今まで見たことのなかった風景を見ることが出来ました。
どんな風景なんだ?と聞かれるとうまく説明できませんが、登壇する前の緊迫感、登壇中の緊張感、登壇した後の開放感、それぞれの場面での自分の目を通して見える風景というんですかね、そこでの感じ方とも言うんでしょうか、そういう普段とは違う体験・経験が出来たと思っています。
はじめて経験することに対して一歩踏み込むのは勇気がいりましたが、この一歩を踏み出せたのは今後に活きるかなぁと。
つーか、踏み出してみればなんとかなるんで、大したことないっすわ。なので、みんなも踏み込んでいこっ!?
2. コミュニティに参加できたこと
前夜祭と懇親会にも思い切って参加してみましたが、それも良かった。ネット上でしか観測できていなかった著名な方々があちらにもこちらにもいる状態でした。
ボッチになるのは覚悟していて前夜祭もほぼボッチだったのですが、しめじ(@smeghead)さん が話しかけてくれてマジで助かりました感謝です。
懇親会では他の方たちと積極的に話すことは出来ませんでしたが、話を盗み聞いているとナチュラルに技術的な話をずっとしていて、おぉ!これがコミュニティってやつか!と、ひとりで感動していました。
(@katzchumさんとはActiveRecordの話、ちゃまほり@tyamahoriさんとは髪型がめちゃくちゃ被っている話ができました)
@tyamahori さんと髪型が完全に一致 https://t.co/BWX8PyERRI
— しまぶ (@shimabox) September 16, 2023
ただ、基本ボッチというのには変わりなかったので、こういうところで積極的に飛び込んでいくというのは今後の課題かもしれません。ことみん(@kotomin_m)さんに昼飯時カジュアルに声かけてもらった記憶があるけど、あれくらいのコミュ力があればなぁと思います。
いやー、無理かなぁ。無理だなぁ。
3. 声をかけてもらえたこと
登壇を終えた後の休憩中だったかな?トイレで「発表、めちゃくちゃ勉強になりました」と声をかけてもらえました。
めちゃくちゃ嬉しかったです。名前とか聞けなかったのですが、その節はありがとうございました。
あと、X(Twitter)でも、めちゃくちゃ分かりやすかったとメンションをもらえたりもしました。
DataMapperの説明がめちゃくちゃ分かりやすかったです!
・Doctrine(DataMapper)がUnit of Workだった
・並行処理のロックについては、別のパターンで解決
・大量データの時は愚直にbatch, 生query, queue etc#phpcon_okinawa https://t.co/Z2UkenuqcN— basi (@84basi) September 16, 2023
本当に資料を作るのに苦労したので、こうやって褒めてくれる、声をかけてくれる人がいてマジで救われました。感謝感激雨嵐。
4. ソウルフードをめちゃくちゃ食べた
沖縄そば、タコス、ポーク玉子、油みそ、ブルーシール、さんぴん茶は絶対に飲み食いしようと決めてたので、それらをちゃんと食べれたのは良かったです。
ソールフード pic.twitter.com/2ODilavHC8
— しまぶ (@shimabox) September 15, 2023
今日くらいこれだけぜいたくしていいよね、ハム太郎? pic.twitter.com/8VIYc141VF
— しまぶ (@shimabox) September 16, 2023
豪雨にめげずにブルーシールアイス食ってる pic.twitter.com/lU7CpIkmpF
— しまぶ (@shimabox) September 17, 2023
タコス食いにきた pic.twitter.com/AjcKzbQgxk
— しまぶ (@shimabox) September 17, 2023
なかでも、てぃしらじそば (汀志良次) – 首里/沖縄そば | 食べログはマジで美味かった。優勝でした。
優勝ーーーーー!!!!! pic.twitter.com/839HVJA6Ti
— しまぶ (@shimabox) September 17, 2023
反省点
今となっては反省するところなどないと思っているのですが、強いて挙げるとするならば、やはりスライドを完成させるのが遅かったというところですかね〜。
この先、ちょっと雑に書いていきます。
構想〜初稿完成まで
まずは@hanhan1978さんに手助けしてもらったときの図。
【緩募】同僚が Unit of Work わかんね〜ってなってました。PoEAA以外に参考になるリソースを知っている方、教えてください。
— Ryo Tomidokoro (@hanhan1978) August 30, 2023
現実逃避をずっとしていて、そろそろ取り掛かるかぁと思ったのが9月に入ってから。
さて、現実を直視するか
— しまぶ (@shimabox) September 2, 2023
そこからはネタのためのコードを書きまくってました。こういうことを喋れたらいいなぁというのは考えつつ、それを裏付けるためのコードを書いていった感じです。
これは、Doctrineを試しだした時の図。
Laravel10.21.0にて、composer require laravel-doctrine/orm:^2.0@dev -W でdoctrineを入れることができてホッとしている。
Downgrading doctrine/lexer (3.0.0 => 2.1.0) がキモっぽい。 pic.twitter.com/vQShNHGMfX— しまぶ (@shimabox) September 4, 2023
で、大体これくらいのネタがあれば書けるかなぁと思ったのが9/7。
頭の中では、これを喋ろうと思っていたのはあったが、いざスライドに落とそうと思うと何をどう書いていけばいいのかまったくわからんの図。
ここから進まぬ。。。 pic.twitter.com/q3V10KyzKJ
— しまぶ (@shimabox) September 7, 2023
なんとなく構想が固まってきたところの図(自分はこんな感じで発表資料は付箋とかでアウトラインとか概要を決めています)。
構想できてきた。後は書くだけだっだっ。 pic.twitter.com/JG3VeJoYDA
— しまぶ (@shimabox) September 7, 2023
でだ、弊社隔週の金曜日に勉強会(カタリバっていうんだけどね)がありまして、9/8に素振りの場を自ら用意していたのもあり、なんとしてでもいったんは仕上げないといけない状況だったので、ここから徹夜ですよ。はい。
おじさん久しぶりに徹夜しちゃった。つらかったーーー。徹夜つれぇーーー。
発表の素振り
そして、まぁまぁ話す内容もグダグダのまま素振りを実行。
昨日のあのスタート時点から内容はどうであれ素振り場で発表出来るところまで書けたのはかなり頑張ったんじゃないかな〜って思っている。マジで0だったからね。素振り場で発表出来てめちゃくちゃ良かった〜。内容はどうであれ。大事なことだから2回。
— しまぶ (@shimabox) September 8, 2023
素振りって本当に大事で、話しを実際にしてみて、伝えたいことがこれだと伝わらない、何か足りない、みたいなことが分かるわけですよ。ほいで、ここからがある意味本番で、後はひたすら足りなさそうな部分、逆に削ったほうがいい部分を調整していった感じです。
(なので、絶対に素振りはしたほうがいいです。もしそういう場がなくても、誰にでもいいから実際に実演してみるのが大事だと思います。)
完成
なんやかんやで、9/14にだいたい出来上がって、それを社内の人たちに見てもらいました。前乗りで15日に沖縄出発だったのでガチで直前。
(こういう素振りの場所とか壁打ちができる環境があって弊社素晴らしいなって思います)
直前に頂いていたフィードバックを、空港や飛行機の中で反映してフィニッシュ。
※ フィードバック自体は、内容をゴロッと変えるとかではなく、こういう見せ方/書き方のほうがいいのでは?みたいなやつで良かった
※ 直前にも関わらずフィードバックをくれた同僚には感謝しかない!!
飛行機の中と空港で昨日会社でもらったフィードバックをスライドに反映したぜ。すべて予定通りだ。
— しまぶ (@shimabox) September 15, 2023
前夜祭に参加してホテルに戻ってから、2〜3回練習して当日を迎えるというなかなかハードな感じでした。
ここで良かったこととしては、練習時点で大体30分前後で終わっていたこと。奇跡。これはなかなか良かった。実際の本番でも時間を守れた。こればかりは褒めてやりたい。
勇気を持って自分の発表見返してみたけど、大体29分だった!ここは褒めてやりたい。https://t.co/Vs8cJCzTXv
— しまぶ (@shimabox) September 17, 2023
という感じで、ほんとうに直前でスライドが完成したので本番は当たって砕けろ精神で挑みましたね。いぇい。
(だからこそ、開き直って思い切ってやれたという説がある)
なので、また何か登壇のチャンスがあれば
- 1ヶ月前にはスライドを書き出す
- 2週間前には何かしらの素振りでフィードバックをもらう
- 1週間前にはスライド完成させる
- 後は本番まで練習、調整
みたいな大人のスケジュールってやつを組んで挑みたいですね。いや、絶対に無理だな。
あと、最後の最後チェックインに失敗したのでこれは反省。
最後にしくじってチェックイン最終時刻に間に合わずCAさんに強行突破で案内してもらいました。申し訳ありません。(深く反省している顔)
— しまぶ (@shimabox) September 17, 2023
スライドに書ければよかったこと
時間がなさすぎて、スライドに書ければよかったことを補足しておきます。
Identity Map
IdentityMap – Qiita
うーん、これは書ければ良かったかもしれない。気づくのが遅くて入れられなかった。
これ、Doctrineは体現していて、いい具合にクエリを実行してくれるのくだりで入れられたら良かったかも。そうすればコマンドパターンの説得感もあがったかもしれない。
How is Doctrine 2 different to Eloquent? | Culttt にもあるのですが、要は一度エンティティを生成しておくと何度問い合わせても、そのインスタンスを再利用して返してくれるというものになります。
簡単なサンプルを書いてみます。
サンプル
<?php declare(strict_types = 1); use App\Entities\Member; use Doctrine\ORM\EntityManagerInterface; // EntityManager $em = app()->make(EntityManagerInterface::class); // 取得 $member = $em->find(Member::class, 1); echo $member->getProfile(); // 'hoge' と出力 // 更新 $member->setProfile('updated'); // $memberは監視済みなのでpersistを明示する必要はない // $em->persist($member); // 再取得 $updatedMember = $em->find(Member::class, 1); echo $updatedMember->getProfile(); // 'updated' と出力 // 保存 $em->flush($member);
これを行うと流れるクエリは以下の2本になります。
Select1回
{ "sql": "SELECT t0.id AS id_1, t0.name AS name_2, t0.profile AS profile_3 FROM members t0 WHERE t0.id = ?", "params": { "1": 1 }, "types": { "1": 1 } }
Update1回
{ "sql": "UPDATE members SET profile = ? WHERE id = ?", "params": { "1": "updated", "2": 1 }, "types": { "1": 2, "2": 1 } }
つまり、再取得のところで行っているfindではクエリは実行されていないということになります。
// 再取得 $updatedMember = $em->find(Member::class, 1); // ここでクエリは実行されていない
一度生成されたEntityが再利用されていることが分かるかと思います。
更新の順番を意識する必要はあるかもしれない?けど、状態を取ってくる場面において無駄なクエリは流れなくなるんじゃないかなぁと。こいつはすごい。
Eloquent(ActiveRecord)の場合
同じようなことをするサンプルをEloquent(ActiveRecord)で書いてみます。
<?php declare(strict_types = 1); use App\Models\Member; // 取得 $member = Member::find(1); echo $member->profile; // 'hoge' と出力 // 更新 $member->profile = 'updated'; $member->save(); // saveしないとhogeのまま // 再取得 $updatedMember = Member::find(1); echo $updatedMember->profile; // 'updated' と出力
Eloquentの場合は、以下3本のクエリが流れます。
-- 取得のところ select * from `members` where `members`.`id` = 1 limit 1; -- 更新のところ update `members` set `profile` = updated where `id` = 1; -- 再取得のところ select * from `members` where `members`.`id` = 1 limit 1;
やはり、クエリの発行を減らすには工夫が必要そうです。
アノテーションにAttributesを使う
(Doctrine情報)PHP8ではannotationでなくattributeを使ってくださいね〜 #phpcon_okinawa #track_b
— ななうぇぶ (@77web) September 16, 2023
@77webさんに補足していただいたのですが、Entityに記述するアノテーションの形式はPHP8を使っているのであればannotations
ではなく、attributes
で書いておけばよかったですね。
Annotations
Annotationsはこういう形式で、自分がスライドでチラッと見せたやつです。
use App\Infrastructure\Repository\MemberRepository; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(repositoryClass=MemberRepository::class) * @ORM\Table(name="members") */ class Member { /** * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ private $id; <pre><code>// 略 </code></pre>
Attributes
で、Attributesがこういう形式です。こっちのほうがええな!
use App\Infrastructure\Repository\MemberRepository; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: MemberRepository::class)] #[ORM\Table(name: "members")] class Member { #[ORM\Id, ORM\Column(type: "integer"), ORM\GeneratedValue()] private $id; <pre><code>// 略 </code></pre>
※ [orm] Meta Data – Laravel Doctrine も参照してください
注意点
で、これもしかしたら自分の環境のせいなのかもしれませんが、laravel-doctrineを入れた場合、config/doctrine.php
のDOCTRINE_METADATA
の値をattributes
に変えないとClass "App\Entities\Member" is not a valid entity or mapped super class.
と怒られてしまいました。
※ config/doctrine.phpについては、ここらへん を参照してください
なので、
'managers' => [ 'default' => [ 'dev' => env('APP_DEBUG', true), 'meta' => env('DOCTRINE_METADATA', 'attributes'), // デフォルトだと、<code>annotations</code>なので<code>attributes</code>に変える 'connection' => env('DB_CONNECTION', 'mysql'), 'paths' => [ base_path('app/Entities') ],
こんな感じで、env('DOCTRINE_METADATA', 'attributes')
のようにする必要があるかもしれません。
テストコード
Unit of work風EloquentとDoctrineの違いは、Model|Entityのロジックに対してユニットテストを書くときに、前者では依然としてModelがDB接続に依存する(DBを使わないテストが書けない)のに対し、 DoctrineではDBを使わない純粋なユニットテストが書ける点です。 #phpcon_okinawa #track_b
— ななうぇぶ (@77web) September 16, 2023
これまた、@77webさんに補足していただいたのですが、EntityはPOPOであるがゆえのテスタビリティの良さ(DB接続不要)についても触れられたらよかったですね。
これも時間がなさすぎた。。
このへんの、スライドに書ければよかったことは別途どこかで記事を書きたいなと思います。
おわりに
なんだかんだ長くなってしまいました。
どの発表も素晴らしく、ペチコン沖縄最高でした!!
思い出干し終わりました pic.twitter.com/B7Oty3D25P
— しまぶ (@shimabox) September 18, 2023
魂を拾いに沖縄へ行ったはずですが、魂はやっぱり置いたままにしておきます!!!
仕事に魂入れてくださいw
— Ryo Tomidokoro (@hanhan1978) September 17, 2023
P.S. 懇親会終わりに琉球大学でさまよって最終バスを乗り過ごし、結局タクシーとゆいれーるで、しめじさんと一緒に帰ったのはいい思い出です