<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>エンジニア見習い</title>
	<atom:link href="https://otonan-syusyoku.work/feed" rel="self" type="application/rss+xml" />
	<link>https://otonan-syusyoku.work</link>
	<description>三流プログラマー</description>
	<lastBuildDate>Tue, 17 Mar 2026 15:50:33 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://otonan-syusyoku.work/wp-content/uploads/2023/10/cropped-名称未設定のデザイン-16-32x32.png</url>
	<title>エンジニア見習い</title>
	<link>https://otonan-syusyoku.work</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>結婚式の「写ルンです」が高すぎたので、写真共有Webサービスを自作した話</title>
		<link>https://otonan-syusyoku.work/archives/2216</link>
					<comments>https://otonan-syusyoku.work/archives/2216#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 15:29:10 +0000</pubDate>
				<category><![CDATA[生涯独学]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2216</guid>

					<description><![CDATA[どうもPHPerです。 先日、私の結婚式がありまして、その際に使用することにした写真共有WEBアプリのお話をさせてください。 これリポジトリ： https://github.com/HrOkiG2/wedding-mem [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>どうもPHPerです。</p>



<p>先日、私の結婚式がありまして、その際に使用することにした写真共有WEBアプリのお話をさせてください。</p>



<p>これリポジトリ： <a href="https://github.com/HrOkiG2/wedding-memory-sns">https://github.com/HrOkiG2/wedding-memory-sns</a></p>



<h2 class="wp-block-heading">何を作ったの？</h2>



<p>結婚式によくある卓に配置されたインスタントカメラ（写ルンです）の代わりにリアルタイムで式の様子を共有するWEBアプリを作成しました。</p>



<p>下記のようにQRコードとスクリーンに式の様子が共有されるというWEBアプリです。<br>（このQRコードには悪意のあるスクリプトが含まれているので読み込まないように。。嘘です、もうページ開けないです）</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1000" height="750" src="https://otonan-syusyoku.work/wp-content/uploads/2026/02/dsfadfas.jpg" alt="" class="wp-image-2227" srcset="https://otonan-syusyoku.work/wp-content/uploads/2026/02/dsfadfas.jpg 1000w, https://otonan-syusyoku.work/wp-content/uploads/2026/02/dsfadfas-300x225.jpg 300w, https://otonan-syusyoku.work/wp-content/uploads/2026/02/dsfadfas-768x576.jpg 768w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure>



<h2 class="wp-block-heading">なぜ自作したのか</h2>



<p>シンプルにお金がなかったからです。</p>



<p>自身の結婚式にあたり、初めはゲストが写真を撮って楽しめる演出として、各テーブルに「写ルンです」を配置することを検討していました。</p>



<p>しかし、本体代に加えて「現像・データ化代」を含めると、想定以上のコストになることが判明しました。</p>



<ul class="wp-block-list">
<li>本体代： 2,000円/1台 * 卓数分</li>



<li>現像代： 950円 * 卓数分</li>



<li>データ化： 900円 * 卓数分</li>
</ul>



<p>今回の結婚式ではゲストの卓数が15になるため以下のコストが掛かります。</p>



<p><code>(2000 * 15) + </code>(950 * 15) + (900 * 15) = 57,750</p>



<p>結婚式を実施するだけでもウン百万かかっているので、不必要な出費がかなり痛く、そもそもの段階として <strong>写ルンですを配置するという企画を取りやめうと考えました。</strong></p>



<p>嫁さんからの<strong>「あんたエンジニアだからどうにかならないの」</strong>の一言からそれもそうだ自分で作ろうと思い立ち結婚式用の写真共有WEBアプリを開発しました。</p>



<h2 class="wp-block-heading">システム概要</h2>



<p>仕組みはシンプルです。</p>



<ol start="1" class="wp-block-list">
<li>各テーブルに <strong>QRコード</strong> を設置。</li>



<li>ゲストがスマホでスキャンすると、ブラウザアプリが起動（ログイン不要）。</li>



<li>撮った写真をアップロードすると、会場の <strong>スクリーンにリアルタイムで反映</strong> される。</li>
</ol>



<h2 class="wp-block-heading">技術構成</h2>



<p>開発期間は仕事の合間を縫って約2週間。</p>



<p>「<strong>維持費を極限まで下げる</strong>（サーバーレス）」ことと、「<strong>TypeScriptで統一する</strong>」ことをテーマに構成しました。</p>



<h3 class="wp-block-heading">フロントエンド</h3>



<ul class="wp-block-list">
<li><strong>Framework</strong>: Nuxt 3 (Vue 3)</li>



<li><strong>Language</strong>: TypeScript</li>



<li><strong>Styling</strong>: Tailwind CSS</li>



<li><strong>Hosting</strong>: S3 + CloudFront (Static Site)</li>
</ul>



<h3 class="wp-block-heading">バックエンド・インフラ (AWS CDK)</h3>



<p>インフラは全て <strong>AWS CDK</strong> でコード管理しています。<br>使用したスタックは以下のとおりです。</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>サービス</strong></td><td><strong>用途</strong></td></tr></thead><tbody><tr><td><strong>CloudFront</strong></td><td>CDN・HTTPS終端・APIへのプロキシ</td></tr><tr><td><strong>S3</strong></td><td>フロントエンドのホスティング &amp; 写真ストレージ</td></tr><tr><td><strong>API Gateway</strong></td><td>HTTP API (REST)</td></tr><tr><td><strong>Lambda</strong></td><td>Node.js 20 (ARM64) で実装。認証と署名付きURL発行を担当</td></tr><tr><td><strong>DynamoDB</strong></td><td>ゲスト認証情報、写真メタデータ、レートリミット管理</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">こだわった技術ポイントと「ハマりどころ」</h2>



<p>単純なアップローダーに見えますが、<strong>「不特定多数のゲストが」「多様な端末で」「一斉に使う」</strong> という環境特有の課題がありました。</p>



<h3 class="wp-block-heading">署名付きURL (Presigned URL) の活用</h3>



<p>Lambdaを経由して画像をアップロードすると、ペイロードサイズ制限（6MB）や実行時間課金の問題が発生します。</p>



<p>そこで、クライアントから「アップロードしたい」とリクエストがあった際に、Lambdaが <strong>S3への一時的な書き込み許可証（Presigned URL）</strong> を発行し、クライアントがS3へ直接アップロードする構成にしました。</p>



<p>これにより、サーバー負荷を最小限に抑えています。</p>



<h3 class="wp-block-heading">iPhoneの「コードスキャナー」</h3>



<p>これが開発中最大の落とし穴でした。</p>



<p>iPhoneのコントロールセンターにある「コードスキャナー」でQRを読むと、Safariではなく<strong>一時的なアプリ内ブラウザ</strong>で開かれてしまいます。</p>



<p>このブラウザは<strong>画面を閉じるとCookieやLocal Storageが即座に破棄される</strong>ため、「さっきアップした写真を確認しよう」と再度QRを読み込むとログアウト状態になってしまいます。</p>



<p>技術的に「強制的にSafariを開かせる」方法は存在しないため、<strong>「iPhoneの方は標準カメラアプリで読み取ってください」</strong> という物理的なPOPを各卓に置くという、アナログな解決策に着地しました。</p>



<h2 class="wp-block-heading">やらなかったこと</h2>



<p>あえて、アップロードされた画像のダウンロード機能は実装しませんでした。</p>



<p>これは嫁と相談した結果なのですが、アップロードされた画像は一度、私達夫婦のもとに落としてそれをLINE等で発信したい。その過程の中で結婚式の思い出にもう一度触れたいという考えのもとです。</p>



<p>結果としては、LINEで結婚式について参列者の友人たちと思い出に浸ることもできました。</p>



<p>また、機能減らしたことで実装コストが下がったのも大きかったですね。</p>



<h2 class="wp-block-heading">コスト比較結果</h2>



<p>AWS利用料は、開発期間を含めても数百円。当日のスパイクアクセス（CloudFront, Lambda, S3）も微々たるものでした。</p>



<p><strong>約3.5万円の節約</strong> に成功し、その分を料理や引き出物のグレードアップに回すことができました。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">参列者の反応</h2>



<p>結果は <strong>大成功</strong> でした。</p>



<ul class="wp-block-list">
<li><strong>リアルタイム性:</strong> 投稿した写真がすぐスクリーンに出るため、「今の面白い瞬間」が共有され、会場の一体感が生まれた。</li>



<li><strong>懐かしさの共有:</strong> 「新郎新婦との昔の写真」をスマホから掘り出して投稿してくれるゲストが多数おり、スライドショーがエモい雰囲気に。</li>



<li><strong>交流:</strong> ゲスト同士で「今の写真送って！」ではなく「QRから上げといて！」という会話が生まれ、自然な交流のきっかけになった。</li>
</ul>



<p>副次的な成果として、参列していた友人2名から「自分の式でもこれを使わせてほしい」とオファーをもらいました。(すごく嬉しかった)</p>



<p>また、アップロードされた画像は約300枚になり、予定していた100枚よりも大幅に多い画像がアップロードされました。<br>余興の多い沖縄の結婚式の中で、これだけの数の画像がアップロードされたのは機能を絞ったこととUIをシンプルにしたことが影響したのかなぁという印象です。（シンプルにすごく嬉しい）</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">今後の改善点</h2>



<ol start="1" class="wp-block-list">
<li><strong>画像配信の最適化:</strong>現在はS3のPresigned URLで画像を表示しているため、エッジキャッシュが効いていません。CloudFront経由の署名付きCookie/URLに切り替え、表示速度を向上させたいです。</li>



<li><strong>不適切画像のAI検知:</strong>今回はゲストのモラルに助けられましたが、Amazon Rekognitionを使って「不適切な画像」を自動で弾く機能を入れれば、より安心して運用できます。</li>



<li><strong>アクセス解析:</strong>Microsoft Clarityなどを入れて、ゲストがどこでつまづいたか（UX）を計測すべきでした。</li>



<li>これネイティブアプリにしたら面白そうじゃねという気持ちがあります（作ったことないけど…誰か共同で開発しませんか…！）</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">まとめ</h2>



<p>「節約」から始まったプロジェクトでしたが、結果として <strong>「写ルンです」には出せないリアルタイムな体験価値</strong> を提供できました。</p>



<p>なにより、自分の技術で一生に一度のイベントを盛り上げられた達成感は、何にも代えがたいものでした。</p>



<p>これから結婚式を挙げるエンジニアの皆さん、<strong>「結婚式自作Webサービス」</strong>、おすすめです。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2216/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>TerraformとCDK のロールバックについて</title>
		<link>https://otonan-syusyoku.work/archives/2193</link>
					<comments>https://otonan-syusyoku.work/archives/2193#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Thu, 12 Feb 2026 08:49:22 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[ポエム]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2193</guid>

					<description><![CDATA[どうも、PHPerです。 先日、友人のWebアプリケーションエンジニアとIaC（Infrastructure as Code）について話す機会がありました。 話の発端は「Terraform と CDK、どっちを採用すべき [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>どうも、PHPerです。</p>



<p>先日、友人のWebアプリケーションエンジニアとIaC（Infrastructure as Code）について話す機会がありました。 話の発端は「Terraform と CDK、どっちを採用すべきか？」というよくあるテーマだったのですが、友人からこんな強烈な一言をもらいました。</p>



<p><strong>「CDK一択でしょ。だってCDK（CloudFormation）はデプロイ失敗時に自動ロールバックしてくれるけど、Terraformは作りかけで止まるでしょ？怖くて使えないよ」</strong></p>



<p>なるほど、確かにその視点はあります。DBのトランザクションに慣れ親しんだエンジニアからすれば、「失敗したらAtomicに元に戻る」挙動こそが正義でしょう。</p>



<p>しかし、普段Terraformを触っている身からすると「うーん、それは『ロールバック』の定義や運用思想が違うだけでは？」というモヤモヤが残りました。</p>



<p>今回は、この「IaCにおけるロールバックと復旧戦略」について、公式ドキュメント等の情報を交えながら整理してみたいと思います。</p>



<h2 class="wp-block-heading">そもそも「ロールバック」とは何を指しているのか？</h2>



<p>友人が言っていた「ロールバック」は、主に <strong>「デプロイ（Apply）が途中でコケた時の挙動」</strong> を指しています。</p>



<h3 class="wp-block-heading">CDK の場合</h3>



<p>CDKは、コンパイルされると最終的に <strong>AWS CloudFormation テンプレート</strong> になり、デプロイもCFnを通じて行われます。</p>



<ul class="wp-block-list">
<li><strong>挙動</strong><br>スタックの更新中にエラーが発生すると、CFnは <strong>「自動的に直前の正常な状態に戻そう」</strong> とします。</li>



<li><strong>ステータス</strong><br><code>UPDATE_ROLLBACK_IN_PROGRESS</code> → <code>UPDATE_ROLLBACK_COMPLETE</code></li>
</ul>



<p>これはCloudFormationの基本機能として提供されています。<br> <a href="https://www.google.com/search?q=https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-monitor-rollback.html" target="_blank" rel="noreferrer noopener">スタックの更新のロールバック &#8211; AWS CloudFormation</a></p>



<p><strong>メリット</strong><br>何もしなくても「壊れた状態」で放置されることが少ないです。<br>「オール・オア・ナッシング」に近い挙動をするため、整合性が保たれやすい安心感があります。</p>



<p><strong>デメリット</strong><br>ロールバックには時間がかかります。<br>リソース作成に30分かかって失敗した場合、ロールバックにも同等の時間がかかることがあります。 <br>また、ロールバック自体が失敗する<strong>UPDATE_ROLLBACK_FAILED</strong>という状態に陥ると、手動での介入が必要になり、復旧難易度が跳ね上がります。 <br><a href="https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html" target="_blank" rel="noreferrer noopener">参考: 更新のロールバックに失敗したスタックを更新できるようにする &#8211; AWS CloudFormation</a></p>



<h3 class="wp-block-heading">Terraform の場合</h3>



<p>Terraformは、AWSのAPIを直接叩いてリソースを操作します。</p>



<ul class="wp-block-list">
<li><strong>挙動</strong><br><code>terraform apply</code> 中にエラーが発生すると、<strong>その時点で処理が停止します</strong>。</li>



<li><strong>ステータス</strong><br>成功したリソースは作成済み、失敗したリソースは未作成（または中途半端）な状態で残ります。</li>
</ul>



<p>Terraformには「自動ロールバック」という機能自体が存在しません。これはバグではなく仕様（思想）です。 <br><a href="https://developer.hashicorp.com/terraform/cli/commands/apply" target="_blank" rel="noreferrer noopener">HashiCorp Terraform: Command: apply</a></p>



<p><strong>メリット</strong><br>エラーの原因が明確で、<strong>「ここまでは成功した」という状態がStateファイルに保存されます</strong>。 <br>コードを修正して再実行（<code>terraform apply</code>）すれば、<strong>差分のみ</strong>を適用し直すため、復旧までの時間が圧倒的に短いです。</p>



<p><strong>デメリット</strong><br>「中途半端な状態」が一時的に発生するため、手動でのリカバリ（State操作やTaint）が必要になる場合があります。</p>



<h2 class="wp-block-heading">なぜTerraformには自動ロールバックがないのか？</h2>



<p>Terraform派の意見として、「自動ロールバックがないこと」は <strong>「Fail Fast（早く失敗して早く直す）」</strong> という設計思想に基づいています。</p>



<ol start="1" class="wp-block-list">
<li><strong>Stateの一貫性重視</strong><br>Terraformは「現在のインフラの状態」をtfstateファイルで厳密に管理します。<br>勝手にロールバック（過去の状態に戻すAPIコール）を行うと、ローカルのコードと現実のリソースの乖離が複雑化するリスクがあります。 <a href="https://developer.hashicorp.com/terraform/language/state" target="_blank" rel="noreferrer noopener">State &#8211; Terraform by HashiCorp</a></li>



<li><strong>Roll Forward（前進による修復）の推奨</strong><br>インフラ変更において、失敗した場合は「元に戻す」よりも「修正して適用し直す」方が早いケースが多いです。<br>Terraformは <code>plan</code> コマンドで事前に変更内容を詳細に確認できるため、実行時エラーは「権限不足」や「パラメータ不正」などが主であり、コードを直して再Applyすれば済むことがほとんどです。</li>
</ol>



<h2 class="wp-block-heading">ロールバックというバージョン切り戻し</h2>



<p>友人の言う「デプロイ失敗時の自動復旧」ではなく、<strong>「正常にデプロイしたけどアプリが動かないから昨日の構成に戻したい」</strong> という場合のロールバックはどうでしょうか。</p>



<ul class="wp-block-list">
<li><strong>CDK (CloudFormation)</strong>: Gitで以前のコードに戻し、再デプロイします。 ※ CFnの自動ロールバックはあくまで「デプロイエラー時」の挙動であり、論理的なバグまでは戻してくれません。</li>



<li><strong>Terraform</strong>: Gitで以前のコードに戻し、<code>terraform apply</code> します。</li>
</ul>



<p>つまり、<strong>「正常デプロイ後の切り戻し（Revert）」に関しては、両者の手間に大きな差はありません。</strong> どちらも「Gitを正」として、過去のコミットの状態へインフラを収束させる作業になります。</p>



<h2 class="wp-block-heading">結論：どっちが良いのか？</h2>



<p>友人の「CDKの方が優秀」という意見は、<strong>「デプロイ作業中の安全性」</strong> に重きを置いた意見と言えます。特にアプリケーションエンジニアにとって、デプロイ失敗時に環境が散らかったままになるのは恐怖でしょう。</p>



<p>一方でTerraformは、<strong>「状態の透明性と制御」</strong> に重きを置いています。「勝手に戻るより、どこで止まったか教えてくれ。俺が直すから」というスタンスです。</p>



<h3 class="wp-block-heading">まとめ</h3>



<ul class="wp-block-list">
<li><strong>CDK (CloudFormation)</strong>
<ul class="wp-block-list">
<li><strong>特徴</strong>: 失敗時は自動で元通り（トランザクション的）。</li>



<li><strong>向いているケース</strong><br>インフラの詳細なステータス管理より、デプロイパイプラインの安定性を重視したい場合。ロールバック待ち時間が許容できる場合。</li>
</ul>
</li>



<li><strong>Terraform</strong>
<ul class="wp-block-list">
<li><strong>特徴</strong><br>失敗時はそこでストップ（即時停止）。修正して再実行（ロールフォワード）。</li>



<li><strong>向いているケース</strong><br><code>terraform plan</code> で事前に結果を予測し、失敗時も自分の手ですぐに復旧させたい場合。APIコールの速さを求める場合。</li>
</ul>
</li>
</ul>



<p>「ロールバックがあるからCDK」ではなく、「失敗時のリカバリ戦略として、自動復帰（CDK）と即時停止（Terraform）のどちらが自分たちのチームに合っているか」で選ぶのが正解ではないでしょうか。</p>



<p>私は……やっぱり <code>terraform plan</code> で「何が起きるか完全に把握してから実行する」安心感が好きですね（笑）</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2193/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GithubActions における DockerImage の Buildキャッシュ戦略</title>
		<link>https://otonan-syusyoku.work/archives/2205</link>
					<comments>https://otonan-syusyoku.work/archives/2205#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Thu, 12 Feb 2026 06:41:33 +0000</pubDate>
				<category><![CDATA[生涯独学]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[Githubactions]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2205</guid>

					<description><![CDATA[どうも、PHPerです。 直近、業務でECSへのデプロイワークフローを実装することになりました。 主な流れは以下の通りです。 この中でボトルネックになるのが 「2. Docker Image のビルド」 です。 毎回フル [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>どうも、PHPerです。</p>



<p>直近、業務でECSへのデプロイワークフローを実装することになりました。</p>



<p>主な流れは以下の通りです。</p>



<ol start="1" class="wp-block-list">
<li>フロントエンドのビルド</li>



<li><strong>Docker Image のビルド</strong></li>



<li>ECR へのログイン</li>



<li>ECR へPush</li>



<li>ECS のサービス更新</li>
</ol>



<p>この中でボトルネックになるのが <strong>「2. Docker Image のビルド」</strong> です。</p>



<p>毎回フルビルドしていると時間がかかりすぎるため、キャッシュの利用は必須です。</p>



<p>Docker Buildx のキャッシュバックエンドには、大きく分けて以下の3つの選択肢があります。</p>



<ol start="1" class="wp-block-list">
<li><strong>GitHub Actions Cache</strong> (<code>type=gha</code>)</li>



<li><strong>GitHub Container Registry</strong> (<code>type=registry</code>)</li>



<li><strong>Amazon ECR</strong> (<code>type=registry</code>)</li>
</ol>



<p>これらを比較検討した結果、今回は <strong>「GitHub Actions Cache (<code>type=gha</code>)」</strong> を採用しました。</p>



<p>その理由はズバリ、<strong>「利用料（ランニングコスト）」と「実装（設定）コスト」が最も低かったから</strong>です。</p>



<h2 class="wp-block-heading">比較検討：3つのキャッシュ戦略</h2>



<p>それぞれの特徴を「コスト」と「手間」の観点で整理しました。</p>



<h3 class="wp-block-heading">1. Amazon ECR (<code>type=registry</code>)</h3>



<p>AWS環境に統一できるメリットはありますが、以下のデメリットがあります。</p>



<ul class="wp-block-list">
<li><strong>実装コスト</strong><br>IAMロールの設定、<code>aws-actions/amazon-ecr-login</code> の設定が必要。</li>



<li><strong>利用料</strong>
<ul class="wp-block-list">
<li><strong>データ転送量</strong><br>GitHub Actions (インターネット) ⇔ AWS ECR 間の通信が発生します。<br>NAT Gatewayを経由する場合などは特に通信費がかさむ可能性があります。</li>



<li><strong>ストレージ料金</strong><br>キャッシュイメージの分だけECRのストレージ料金が発生します。</li>



<li><strong>実行時間</strong><br>ネットワーク越しにPush/Pullするため、ビルド時間が伸び＝GitHub Actionsの課金対象時間（Minutes）も消費します。</li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading">2. GitHub Container Registry (<code>type=registry</code>)</h3>



<p>GitHubのエコシステム内で完結しますが、こちらも考慮が必要です。</p>



<ul class="wp-block-list">
<li><strong>実装コスト</strong><br><code>docker/login-action</code> での認証設定や、パッケージの読み書き権限設定が必要。</li>



<li><strong>利用料</strong>
<ul class="wp-block-list">
<li>Organizationのプランによっては、GHCRのストレージ容量やデータ転送量に制限・課金が発生する場合があるらしい</li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading">3. GitHub Actions Cache (<code>type=gha</code>)</h3>



<p>今回採用した方法です。</p>



<ul class="wp-block-list">
<li><strong>実装コスト</strong><br><strong>認証設定が一切不要</strong>。YAMLに数行書くだけ。</li>



<li><strong>利用料</strong>
<ul class="wp-block-list">
<li><strong>通信費ゼロ</strong><br>GitHub Actionsの内部ネットワークで完結するため、外部への転送コストがかかりません。</li>



<li><strong>ストレージ無料</strong><br>リポジトリあたり10GBまで利用可能（超過分は古いものから自動削除されるだけなので、追加課金のリスクがない）。</li>



<li><strong>実行時間短縮</strong><br>ネットワークが高速なため、キャッシュのリストアが速く、結果としてActionsの実行時間を節約できます。</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading">比較まとめ表</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>キャッシュ戦略</strong></td><td><strong>実装(設定)コスト</strong></td><td><strong>利用料(ランニングコスト)</strong></td><td><strong>速度</strong></td></tr></thead><tbody><tr><td><strong>GHA Cache</strong> (<code>type=gha</code>)</td><td><strong>◎ (認証不要)</strong></td><td><strong>◎ (基本無料)</strong></td><td><strong>◎ (最速)</strong></td></tr><tr><td><strong>GHCR</strong> (<code>type=registry</code>)</td><td>△ (Token設定必要)</td><td>◯ (プラン依存)</td><td>◯</td></tr><tr><td><strong>ECR</strong> (<code>type=registry</code>)</td><td>△ (IAM設定必要)</td><td>△ (通信費・保管費)</td><td>△ (通信発生)</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">なぜ <code>type=gha</code> を選んだか</h2>



<p>今回の要件において、私が重視したのは以下の2点です。</p>



<ol start="1" class="wp-block-list">
<li><strong>ワークフローの利用料を抑えたい</strong>
<ul class="wp-block-list">
<li>無駄なデータ転送費やストレージ料金を払いたくない。</li>



<li>ビルド時間を短縮して、GitHub Actionsの利用枠（Minutes）を節約したい。</li>
</ul>
</li>



<li><strong>実装コストを最小限にしたい</strong>
<ul class="wp-block-list">
<li>キャッシュのためだけにIAM権限を調整したり、Secretsを管理したりする手間を省きたい。</li>



<li>シンプルに保ち、メンテナンスしやすくしたい。</li>
</ul>
</li>
</ol>



<p>この2点を満たすのが <strong><code>type=gha</code></strong> でした。</p>



<p>「ローカル開発環境でも同じキャッシュを使いたい」という要件があれば <code>registry</code> 系が候補に挙がりますが、今回は「CIの高速化」が主目的だったため、迷わずこちらを選択しました。</p>



<h2 class="wp-block-heading">実装コード</h2>



<p>設定は驚くほどシンプルです。<code>docker/build-push-action</code> に <code>cache-from</code> / <code>cache-to</code> を追加するだけです。</p>



<pre class="wp-block-code"><code>      # Buildxのセットアップ（必須）
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # ビルド &amp; Push
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.login-ecr.outputs.registry }}/my-app:latest
          # ★ここがポイント
          cache-from: type=gha
          cache-to: type=gha,mode=max
</code></pre>



<h3 class="wp-block-heading">ポイント：<code>mode=max</code></h3>



<p><code>cache-to: type=gha,mode=max</code> を指定することで、最終的なイメージだけでなく、中間レイヤー（<code>npm install</code> などを行ったレイヤー）もキャッシュされます。<br>これにより、コードを少し変更しただけでもフルビルドが走るのを防げます。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p>技術選定において「高機能であること」も重要ですが、<strong>「コストがかからない」「設定が楽」</strong> というのは、運用を続ける上で正義です。</p>



<p>ECSデプロイのビルド時間に悩んでいる方は、まずは一番手軽で財布に優しい <code>type=gha</code> から試してみてはいかがでしょうか。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2205/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CDKで管理しているRDSを暗号化したい</title>
		<link>https://otonan-syusyoku.work/archives/2198</link>
					<comments>https://otonan-syusyoku.work/archives/2198#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Thu, 12 Feb 2026 06:29:36 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[生涯独学]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[CDK]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[RDS]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2198</guid>

					<description><![CDATA[どうも、PHPerです。 先日、業務にて「既存のRDSのストレージを暗号化したい」という、シンプルかつ重たい要件が降ってきました。 これの何が困るかというと、皆様ご存知の通りRDSの仕様です。 RDSは作成後の暗号化ステ [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>どうも、PHPerです。</p>



<p>先日、業務にて<strong>「既存のRDSのストレージを暗号化したい」</strong>という、シンプルかつ重たい要件が降ってきました。</p>



<p>これの何が困るかというと、皆様ご存知の通りRDSの仕様です。 RDSは<strong>作成後の暗号化ステータスの変更ができません</strong>。つまり、<strong>既存のRDSを暗号化するためには、「暗号化有効」設定で作り直す（再作成する）必要</strong>があります。 </p>



<p><a href="https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/patterns/automatically-remediate-unencrypted-amazon-rds-db-instances-and-clusters.html" target="_blank" rel="noreferrer noopener">参考: Amazon RDS DB インスタンスおよびクラスターの暗号化 &#8211; AWS Prescriptive Guidance</a></p>



<p>さらに今回はインフラを <strong>AWS CDK</strong> で管理しています。<br> 単純にCDKのコードを書き換えてデプロイすると、CloudFormationの仕様により「リソースの置換」が発生し、予期せぬダウンタイムやデータ損失のリスクがあります。<br>また、よくある「スナップショットから復元して差し替え」という手法も、CDKのState管理との兼ね合いで今回は採用できませんでした。</p>



<p>様々な制約がある中、「既存データを維持しつつ」「CDK管理のまま」暗号化リソースへ移行できたので、その手法を共有します。</p>



<h2 class="wp-block-heading">今回の要件と制約</h2>



<p>今回のミッションにおける前提条件は以下の通りです。</p>



<ul class="wp-block-list">
<li><strong>既存RDSの暗号化</strong>（最重要）</li>



<li>既存データをそのまま移行できること</li>



<li>アプリ側の変更を避けるため、<strong>エンドポイント（またはDNS）の変更がないこと</strong></li>



<li><strong>ダウンタイムは許容</strong>（ALBでメンテナンス画面を挟む前提）</li>



<li>バッチ処理が稼働していない夜間帯での作業</li>



<li><strong>CDKコードの変更は最小限</strong>に留め、今後もCDKで管理し続けること</li>
</ul>



<h2 class="wp-block-heading">結論：CDKデプロイを3回行う</h2>



<p>結論から言うと、<strong>「削除ポリシーを変更して既存RDSをCDK管理から切り離し、新規で暗号化RDSを作成する」</strong>という力技で解決しました。</p>



<p>具体的には以下の3ステップでデプロイを行います。</p>



<ol start="1" class="wp-block-list">
<li><code>RemovalPolicy.RETAIN</code> を設定（既存保護）</li>



<li><code>instanceIdentifier</code> を変更（既存切り離し）</li>



<li><code>storageEncrypted: true</code> を指定（新規作成）</li>
</ol>



<h3 class="wp-block-heading">なぜこの手順が必要なのか？（ハマりポイント）</h3>



<p>通常、CDKで管理されているリソースに対し、<code>instanceIdentifier</code>（物理ID）を固定したまま <code>storageEncrypted: true</code> に変更してデプロイしようとすると、以下のエラーが発生して失敗します。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>CloudFormation cannot update a stack when a custom-named resource requires replacing</strong></p>
</blockquote>



<p>これは、「カスタム名（明示的な名前）がついているリソースを置換（削除＆作成）することはできない」というCloudFormationの安全装置です。これを回避するために、あえて識別子を変更して別リソースとして認識させる必要があります。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">手順詳細</h2>



<h3 class="wp-block-heading">1. 削除ポリシーの変更</h3>



<p>まず、既存のRDSがCDKのスタック操作によって削除されないようにします。 これをやらないと、次の手順で「既存RDSの削除」が走ってしまい、データが消えます（超重要）。</p>



<p>コード スニペット</p>



<pre class="wp-block-code"><code>new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db', // 現在の識別子
  // ...
  removalPolicy: cdk.RemovalPolicy.RETAIN, // ここを追加！
});
</code></pre>



<p>この状態で一度 <strong><code>cdk deploy</code></strong> します。 これで、このRDSはスタックから削除される際も、AWS上にはリソースが残るようになります。</p>



<h3 class="wp-block-heading">2. instanceIdentifier の変更</h3>



<p>次に、既存のRDSをCDKの管理下から外す（Orphanにする）作業です。 CDK上の識別子を、既存のものとは別の名前に書き換えます。</p>



<p>コード スニペット</p>



<pre class="wp-block-code"><code>new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db-temp', // 3と被らない一時的な名前に変更
  // ...
  removalPolicy: cdk.RemovalPolicy.RETAIN,
});
</code></pre>



<p>この状態で <strong><code>cdk deploy</code></strong> します。</p>



<p><strong>何が起きるか：</strong><br> CloudFormationは「<code>my-prod-db</code> を管理から外し、新しく <code>my-prod-db-temp</code> を作る」という挙動をします。 ステップ1で <code>RETAIN</code> を設定しているため、<strong>旧RDS（データ入り）は削除されずにそのまま残ります。</strong> <br>※この時点でアプリはまだ旧RDSを見ています。</p>



<h3 class="wp-block-heading">3. いよいよ暗号化RDSの構築</h3>



<p>最後に、本命の「暗号化されたRDS」を作成します。</p>



<p>コード スニペット</p>



<pre class="wp-block-code"><code>new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db', // 元の名前に戻す（ここが変わるとエンドポイント変わるので注意）
  storageEncrypted: true,           // 暗号化をON
  removalPolicy: cdk.RemovalPolicy.RETAIN,
  // ...
});
</code></pre>



<p>この状態で <strong><code>cdk deploy</code></strong> します。</p>



<p><strong>注意点：</strong><br>もし <code>instanceIdentifier</code> を手順1と同じ名前（<code>my-prod-db</code>）に戻したい場合、手順2の時点でAWS上に古い <code>my-prod-db</code> が残っているため、名前重複でエラーになる可能性があります。<br>その場合は、<strong>AWSマネジメントコンソールまたはCLIから、古いRDS（手順2で切り離されたもの）の識別子を手動で <code>my-prod-db-old</code> などにリネーム</strong> してからデプロイしてください。</p>



<p>これで、暗号化された空っぽのRDSが立ち上がりました。</p>



<h2 class="wp-block-heading">データ移行</h2>



<p>新旧2つのRDSが存在している状態になりました。</p>



<ul class="wp-block-list">
<li><strong>旧RDS</strong>
<ul class="wp-block-list">
<li>データあり</li>



<li>非暗号化</li>



<li>CDK管理外</li>
</ul>
</li>



<li><strong>新RDS</strong>
<ul class="wp-block-list">
<li>データなし</li>



<li>暗号化済み</li>



<li>CDK管理下</li>
</ul>
</li>
</ul>



<p>データ量がそれほど多くない（数GB〜数十GB程度）ため、今回はシンプルに <code>mysqldump</code> で移行を行いました。TB級の場合は AWS DMS (Database Migration Service) の利用を検討してください。</p>



<pre class="wp-block-code"><code># SSM Session Manager 等で踏み台サーバーに接続

# 1. 旧RDSからデータをエクスポート（パイプで圧縮して転送時間を短縮）
mysqldump -h &#91;旧RDSエンドポイント] -u &#91;ユーザー名] -p \
  --single-transaction --routines --triggers \
  --databases &#91;対象のスキーマ] \
  | gzip &gt; dump.sql.gz

# 2. 新RDSへインポート
zcat dump.sql.gz | mysqldump -h &#91;新RDSエンドポイント] -u &#91;ユーザー名] -p
</code></pre>



<p>データの移行が完了したら、アプリケーションの接続先が新RDSに向いていることを確認し、メンテナンスを解除します。</p>



<h2 class="wp-block-heading">後片付け</h2>



<p>無事稼働確認が取れたら、不要なリソースを削除してお財布を守りましょう。</p>



<ol start="1" class="wp-block-list">
<li><strong>手順2で立ち上がった一時的なRDS</strong>（CDKの管理からは外れているはずですが、念の為確認して削除）</li>



<li><strong>手順1以前から存在していた旧RDS</strong>（リネームした <code>my-prod-db-old</code> など）</li>
</ol>



<p>スナップショットが取れていることを確認した上で、マネジメントコンソールから削除しました。</p>



<h2 class="wp-block-heading">(おまけ) 試したけどうまくいかなかったこと</h2>



<h3 class="wp-block-heading"><code>cdk import</code> での既存リソース取り込み</h3>



<p>既存のリソースをリネームして、新しいスタックに取り込もうと <code>cdk import</code> を試みましたが、以下のようなメッセージが出てスキップされました。</p>



<pre class="wp-block-code"><code>TmpStack: no new resources compared to the currently deployed stack, skipping import.
</code></pre>



<p>また、定義と実リソースのプロパティ（暗号化の有無）が食い違っているため、インポート自体が整合性エラーになる可能性が高いです。</p>



<h3 class="wp-block-heading"><code>DatabaseInstanceFromSnapshot</code> の利用</h3>



<p>スナップショットから復元するCDKのクラスですが、これをメインの定義にしてしまうと、常に「スナップショットから作られた状態」が正となり、パラメータグループやバージョン更新などの運用時にドリフト（設定乖離）の管理が面倒になると判断し、今回は見送りました。</p>



<h2 class="wp-block-heading">まとめ</h2>



<p>CDKで管理しているステートフルなリソース（RDSなど）の「再作成必須な変更」は非常に神経を使います。 <strong>「Retainで保護」→「Identifier変更で管理から切り離し」→「新規作成」</strong> というフローは、RDSに限らず他のリソースでも応用が効くテクニックなので、覚えておいて損はないはずです。</p>



<p>なにはともあれですが、RDSは最初から暗号化しましょう。これだけは覚えて頂けると。。。</p>



<p>同じ悩みを抱えるPHPer（およびAWS使い）の助けになれば幸いです！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2198/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【CDK】LAMP環境を題材に考えるスタック間のリソース共有が難しい問題を解決する</title>
		<link>https://otonan-syusyoku.work/archives/2194</link>
					<comments>https://otonan-syusyoku.work/archives/2194#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Wed, 14 Jan 2026 06:26:23 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[LAMP]]></category>
		<category><![CDATA[Typescript]]></category>
		<category><![CDATA[実務]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2194</guid>

					<description><![CDATA[こんちゃーす。最近 CDK と格闘している PHPer です。 CDK、楽しいですよね。Typescript でインフラがかけるなんて最高です。でも、学習を始めていくとある壁にぶつかりました。 ネット上の「CDKでECS [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>こんちゃーす。最近 CDK と格闘している PHPer です。</p>



<p>CDK、楽しいですよね。Typescript でインフラがかけるなんて最高です。でも、学習を始めていくとある壁にぶつかりました。</p>



<p>ネット上の「CDKでECS構築してみた！」系の記事を参考にすると、大抵の場合 <strong>VPCもECSもRDSも全部一つの</strong> <code>lib/my-stack.ts</code> <strong>に処理が書かれている」</strong>のです。サンプルコードとしてはわかりやすいのですが、いざ実務でLMAP環境を作ろうとすると⋯…</p>



<ul class="wp-block-list">
<li>VPC は更新頻度が低いからスタックを分けたい</li>



<li>RDS はステートフルだから独立させたい</li>



<li>ECS はアプリケーションコードのデプロイで頻繁に更新が入るかも</li>
</ul>



<p>とスタックを分割したくなりませんか？<br>でも、分割した途端に「StackA で作ったVPCのIDをどうやってStackBにわたすの？」という問題が発生します。</p>



<p>当記事では、現時点での私の持論である <strong>スタック間のリソース共有</strong>を紹介します。</p>



<p></p>



<h2 class="wp-block-heading">IDではなくオブジェクトを渡す</h2>



<p>VPC スタックとECSスタックを分けるときに、ついやってしまいがちなのが <strong>VPC IDを Props で渡す</strong> という手法です。<br>Terraform に慣れていると、この発想になりがちですよね。</p>



<p>この手法では CDK の 型安全を活かすことが出来ないので、 <strong>VPCオブジェクトそのもの</strong> を渡すのがベストだと考えています。</p>



<p>私の構成案：<br>司令塔となる エントリーポイント <code>app</code> がバケツリレーのようにオブジェクトを渡していくイメージです。</p>



<ol class="wp-block-list">
<li>VPCStack： VPCを作る。 <code>public readonly vpc</code>でVPCオブジェクトを公開する</li>



<li>APP： VpcStack からVPCオブジェクトをもらい、 ECSStackに渡す</li>



<li>EcsStack： 受け取ったVPC オブジェクトを使ってクラスターを作る</li>
</ol>



<p>実際にコードを見ていきましょう。</p>



<h2 class="wp-block-heading">型安全の恩恵を受ける</h2>



<h3 class="wp-block-heading">VPCStack ⇒ 渡す側</h3>



<p>ここでは <code>this.vpc</code>をクラスのメンバ変数として公開しておきます。</p>



<pre class="wp-block-code"><code>// lib/vpc-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';

export class VpcStack extends cdk.Stack {
  // 外部に公開するプロパティ（これが重要！）
  public readonly vpc: ec2.IVpc;

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    this.vpc = new ec2.Vpc(this, 'LampVpc', {
      maxAzs: 2,
    });
  }
}</code></pre>



<p></p>



<h3 class="wp-block-heading">EcsStack ⇒ 受け取る側</h3>



<p>ここがポイントデス。 <code>vpcId: string</code>ではなく、 <code>vpc: ec2.IVpc</code> というインターフェース型で受け取ります。</p>



<pre class="wp-block-code"><code>// lib/ecs-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import { Construct } from 'constructs';

// Propsの定義：文字列ではなく「VPCの型」を指定する
interface EcsStackProps extends cdk.StackProps {
  vpc: ec2.IVpc;
}

export class EcsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: EcsStackProps) {
    super(scope, id, props);

    // 文字列から検索する必要なし！そのまま使える
    const cluster = new ecs.Cluster(this, 'LampCluster', {
      vpc: props.vpc, 
    });
  }
}</code></pre>



<p></p>



<h3 class="wp-block-heading">bin/app.ts ⇒ 繋ぐ場所</h3>



<p>最後にエントリーポイントで紐づけます。</p>



<pre class="wp-block-code"><code>// bin/app.ts
import * as cdk from 'aws-cdk-lib';
import { VpcStack } from '../lib/vpc-stack';
import { EcsStack } from '../lib/ecs-stack';

const app = new cdk.App();

// 1. VPCを作る
const vpcStack = new VpcStack(app, 'VpcStack');

// 2. VPCオブジェクトをECSスタックに渡す
const ecsStack = new EcsStack(app, 'EcsStack', {
  vpc: vpcStack.vpc, // ここでバケツリレー
});</code></pre>



<p></p>



<h3 class="wp-block-heading">この方法の何が良いの？</h3>



<p>この方法のメリットは2つあると思っています。<br>（一言で言うとプログラミング言語による抽象化です。）</p>



<ol class="wp-block-list">
<li>圧倒的な型安全性<br>もし間違えてS3のオブジェクトを渡そうとすると、エディタがその場でエラーを吐いてくれます。<br>「デプロイしてみたらIDが違ってコケた」という悲劇が未然に防げます</li>



<li>記述がメチャ楽<br>受け取る側で <code>ec2.Vpc.fromLookup()</code> のようなインポート処理を書く必要がありません。渡された瞬間から <code>props.vpc.addInterfaceEndpoint</code> のようにメソッドが使えます</li>
</ol>



<p></p>



<h2 class="wp-block-heading">議論したいポイント</h2>



<p>ここまで「これが正解だ」という顔で書いてきましたが、実はこの構成には明確なデミリットもあります。それは、<strong> スタック同士が密結合</strong> になることです。</p>



<p>Cloudformation の <code>Export/Import</code> 機能でガッチリ紐づいてしまうため、以下のような問題が置きます。</p>



<ul class="wp-block-list">
<li>スタック間の参照があるため、「VPCだけを作り直したい」が出来ない</li>
</ul>



<p>ただし、私は上記のような問題が発生したとしても、<br>LAMP 環境という一つのアプリケーションにおいて、VPCとECSは「運命共同体」にあたるため問題ないと考えています。</p>



<p>「ECSが生きているのにVPCだけ消したい」という状況は稀ではないでしょうか。そのためこの<strong>密結合はあえて受け入れるべき仕様</strong>だと割り切っています。</p>



<p>ただ、もし組織の基盤となる共通のVPCを作る場合は、ライフサイクルが異なるため、疎結合なID渡しにするのが正解なのかもしれません。</p>



<p>皆さんの構成おしてほしいっす。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2194/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>とりあえず出力していたログから意図を持ったログにするために</title>
		<link>https://otonan-syusyoku.work/archives/2189</link>
					<comments>https://otonan-syusyoku.work/archives/2189#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Wed, 07 Jan 2026 02:42:51 +0000</pubDate>
				<category><![CDATA[生涯独学]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[実務]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2189</guid>

					<description><![CDATA[「ログ、とりあえず出してますか？」 正直に告白します。僕はこれまで、なんとなくログを出していました。「エラーが起きたら怖いから、とりあえず try-catch してログに残しておこう」「ないよりはマシだろう」 でも、いざ [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>「ログ、とりあえず出してますか？」</p>



<p>正直に告白します。僕はこれまで、なんとなくログを出していました。<br>「エラーが起きたら怖いから、とりあえず <code>try-catch</code> してログに残しておこう」<br>「ないよりはマシだろう」</p>



<p>でも、いざ本番で障害が起きたとき、そのログは僕を助けてくれませんでした。<br>深夜のアラート対応で、システムエラーが発生しました とだけ書かれたログを前に途方に暮れた経験、皆さんにもありませんか？</p>



<p>今回は、そんな「なんとなくログ」を卒業し、トラブルシューティングや分析に本当に役立つ「使えるログ」を設計するために学んだことをまとめました。</p>



<h2 class="wp-block-heading">ログの目的を再定義する</h2>



<p>そもそも、なぜログを書くのでしょうか？ チームで決まっているからではなく、エンジニアとして以下の3つの目的を意識する必要があります。</p>



<ol start="1" class="wp-block-list">
<li><strong>トラブルシューティング（Why &amp; Where?）</strong>
<ul class="wp-block-list">
<li>「なぜ落ちたのか」「どこで落ちたのか」を特定し、バグを再現・修正するため。</li>
</ul>
</li>



<li><strong>可観測性・分析（Observability）</strong>
<ul class="wp-block-list">
<li>「この機能、どれくらい使われてる？」「処理に何秒かかってる？」といったシステムの健康状態やユーザー行動を知るため。</li>
</ul>
</li>



<li><strong>監査・セキュリティ（Security）</strong>
<ul class="wp-block-list">
<li>「いつ、誰が、重要なデータを変更したか」の証跡を残すため。</li>
</ul>
</li>
</ol>



<p>これらを意識すると、<strong>「とりあえず出力」ではなく「目的に合わせて出力」する必要がある</strong>ことに気づきます。</p>



<h2 class="wp-block-heading">構造化ログってなんや</h2>



<p>ログ設計を学ぶと必ず出てくるのが<strong>「構造化ログ（Structured Logging）」</strong>です。</p>



<p>構造化ログとは<strong>「ログを人間への手紙ではなく、機械へのデータとして扱う」</strong>ということです。</p>



<h3 class="wp-block-heading">非構造化ログ</h3>



<p>従来のテキスト形式のログです。</p>



<pre class="wp-block-code"><code>// 人間には読めるけど、機械（検索）には辛い
Log::error("User 123 failed to login from 192.168.0.1");
</code></pre>



<p>これだと、「ユーザーID 123 のエラーだけ集計したい」と思ったときに、正規表現で頑張って文字列解析をしなければなりません。<br>ログの文言が少し変わっただけで検索できなくなるので、運用が非常に辛くなります。</p>



<h3 class="wp-block-heading">構造化ログ</h3>



<p>ログをJSONなどの形式で出力します。</p>



<pre class="wp-block-code"><code>// メッセージとデータを分ける
Log::error("Login failed", &#91;
    "user_id" =&gt; 123,
    "ip_address" =&gt; "192.168.0.1",
    "event" =&gt; "auth_error"
]);
</code></pre>



<p>これが出力されると、以下のようなJSONになります。</p>



<pre class="wp-block-code"><code>{
  "level": "ERROR",
  "message": "Login failed",
  "context": {
    "user_id": 123,
    "ip_address": "192.168.0.1",
    "event": "auth_error"
  },
  "datetime": "2024-01-07T12:00:00+09:00"
}
</code></pre>



<p>こうしておけば、DatadogやCloudWatch Logsなどのログ管理ツールで <code>context.user_id = 123</code> のようにクエリ一発で検索・集計ができます。</p>



<p>現代のWeb開発では、<strong>ログは「読むもの」ではなく「検索・集計するもの」</strong>です。<br><strong>構造化ログはマスト</strong>と言えます。</p>



<h2 class="wp-block-heading">ログ設計の実践</h2>



<p>では、実際にコードを書くときに何を意識すべきでしょうか。</p>



<h3 class="wp-block-heading">やるべきこと</h3>



<h4 class="wp-block-heading">コンテキスト（文脈）を含める</h4>



<p>「エラーです」だけでは無意味です。<br>「その時何が起きていたか」の情報を連想配列（Context）として渡しましょう。</p>



<p>何が起こったかを把握するためにも 5W1H を意識するといいかもですね。</p>



<ul class="wp-block-list">
<li><strong>誰が？</strong> (User ID)</li>



<li><strong>何を？</strong> (Input Data)</li>



<li><strong>どこで？</strong> (Request URL, IP Address)</li>
</ul>



<h4 class="wp-block-heading">トレースID (Trace ID / Request ID) を通す</h4>



<p>これが今回一番の学びでした。 <br>「1回のリクエスト」に対して、ユニークなID（Trace ID）を割り当て、それを全てのログに含めます。</p>



<pre class="wp-block-code"><code>// 理想的なログ出力
{
    "message": "DB error",
    "request_id": "a0eebc99-9c0b...",  // これ
    "user_id": 101
}
</code></pre>



<p>これがあれば、マイクロサービスや非同期処理でログがバラバラになっても、<strong>IDで検索して「一連の処理の流れ」</strong>を追うことができます。</p>



<p>Laravelなどでは、ミドルウェアで自動的にIDを発行し、<code>Log::shareContext()</code> 等を使って全ログに自動付与する設定を入れておくと、コードを書くときに意識しなくて済むので最高です。</p>



<h4 class="wp-block-heading">ログレベルを適切に使い分ける</h4>



<p>チーム内の基準に従いましょう。ない場合は基準を決めておきましょう。</p>



<p>下記は一例です。</p>



<ul class="wp-block-list">
<li><strong>ERROR:</strong> 即時対応が必要（深夜でも電話がかかってくるレベル）。</li>



<li><strong>WARN:</strong> 異常だがシステムは稼働継続可能（後で要確認）。</li>



<li><strong>INFO:</strong> 正常系イベント（KPI分析や動作確認用）。</li>



<li><strong>DEBUG:</strong> 開発時のデバッグ用（本番では出さない）。</li>
</ul>



<h3 class="wp-block-heading">アンチパターン</h3>



<h4 class="wp-block-heading">機密情報の出力</h4>



<p>パスワード、アクセストークン、クレジットカード番号、個人情報（PII）をログに出してはいけません。</p>



<p>ログファイル自体が漏洩したときのリスクが甚大です。平文なのでね。</p>



<h4 class="wp-block-heading">「とりあえずcatchしてログ」による握りつぶし</h4>



<p>一番やりがちなやつです。</p>



<pre class="wp-block-code"><code>try {
    $user-&gt;save();
} catch (Exception $e) {
    // 最悪なパターン：エラーが起きた事実だけログして、処理を続行してしまう
    Log::error("Error happened"); 
}
</code></pre>



<p>これだと、データ不整合が起きているのにシステムが動き続け、後で原因不明のバグになります。<br>対処できないエラーはログに出すだけでなく、適切に例外を再スローするか、エラーレスポンスを返す必要があります。</p>



<h2 class="wp-block-heading">ログ運用</h2>



<p>「ログをどこに出すか」も重要です。</p>



<p>DockerやKubernetesなどのコンテナ環境では、<strong>「標準出力（stdout/stderr）に吐く」</strong>のが定石です（<a href="https://12factor.net/ja/logs">The 12-Factor App</a>の思想）。</p>



<p>アプリは標準出力にJSONを流すだけ。あとはFluentdやDatadog Agentがそれを拾って、S3やログ管理基盤に転送する。アプリは「ログの保存場所」に関知しない設計にするのがモダンな運用です。</p>



<h2 class="wp-block-heading">5. ログローテート</h2>



<p>最後に、地味だけど大事な「お掃除」の話です。 もしファイルにログを出力する場合、書き続けるといつかディスクが溢れます。数GBのテキストファイルを開こうとしてエディタが固まった経験、ありますよね？<br>（いつぞやに対応したEC2のアラート対応で、15GBのログファイルがサーバーを圧迫していた事件がありました。）</p>



<ul class="wp-block-list">
<li><strong>ローテート（Rotation）：</strong> 日次やサイズ指定でファイルを分割する（例: <code>app.log</code> -> <code>app.log.2024-01-07</code>）。</li>



<li><strong>保持期間（Retention）：</strong> 「14日経過したら削除する」といったルールを決める。</li>
</ul>



<p>PHP（Monolog）なら <code>RotatingFileHandler</code> を使うことで簡単に実装できますし、Linuxの <code>logrotate</code> コマンドで管理することもあります。</p>



<p>「ディスクフルでサーバーダウン」は一番悲しい障害なので、リリース前に必ず確認しましょう。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">おわりに</h2>



<p>ログ設計を見直すことは、「未来の自分やチームメイトへの思いやり」です。</p>



<p>障害発生時、適切なログ（構造化され、Trace IDがあり、必要な情報が詰まっているログ）があれば数分で解決できる問題が、ログがないために数日かかることもあります。</p>



<p>「何かあったときに、このログを見て原因がわかるか？」 「一年後の自分がこのログを見て感謝するか？」</p>



<p>そう自問しながら、明日からの <code>Log::info()</code> を書いていきたいと思います。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2189/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>「単一テーブルならトランザクションいらない説」について、バックエンドエンジニアとして真剣に考えてみた</title>
		<link>https://otonan-syusyoku.work/archives/2182</link>
					<comments>https://otonan-syusyoku.work/archives/2182#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Tue, 06 Jan 2026 06:58:49 +0000</pubDate>
				<category><![CDATA[生涯独学]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2182</guid>

					<description><![CDATA[こんにちは！バックエンドエンジニアとして日々データベースと向き合っている皆さん、お疲れ様です。 先日、同僚との雑談の中でこんな一言が飛び出し、思わずコーヒーカップを持つ手が止まりました。 「単一テーブルの更新なら、わざわ [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>こんにちは！バックエンドエンジニアとして日々データベースと向き合っている皆さん、お疲れ様です。</p>



<p>先日、同僚との雑談の中でこんな一言が飛び出し、思わずコーヒーカップを持つ手が止まりました。</p>



<p><strong>「単一テーブルの更新なら、わざわざトランザクション貼らなくても良くない？」</strong></p>



<p>なるほど、確かに。「ユーザーの名前を変更する」みたいなSQLが1行だけなら、データベースの自動コミット（Auto Commit）が働くし、失敗してもエラーが返るだけ。<br>わざわざ <code>BEGIN~COMMIT</code> で囲う必要なんてない、という意見も一理あるように聞こえます。</p>



<p>でも、<strong>「本当にそれで大丈夫？」</strong> と問いかけると、実は冷や汗をかくような落とし穴が潜んでいるんです。</p>



<p>今日は、私がなぜ「単一テーブルでも（場合によっては）トランザクション推奨派」なのか、技術的な挙動と「防御的プログラミング」の観点から整理してみたいと思います。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"> 単一テーブル更新時（単純なUPDATE/INSERT）：実は同僚が正しかった？</h2>



<p>まず、同僚が言っていた「単一テーブルへの単純な更新」についてです。</p>



<pre class="wp-block-code"><code>UPDATE users SET name = 'Tanaka' WHERE id = 1;
</code></pre>



<p>正直に言います。<br><strong>データの物理的な整合性（ACID特性）という点では、同僚の言う通り「トランザクションは必須ではありません」</strong>。</p>



<p>現代のRDBMS（MySQLやPostgreSQLなど）は非常に優秀です。<br>この1行のSQLが実行されるとき、DB内部では自動的にトランザクションのような処理が走り、以下を保証してくれます。</p>



<ul class="wp-block-list">
<li>データ行の書き換え</li>



<li>インデックス（B-Treeなど）の更新</li>



<li>一意制約などのチェック</li>
</ul>



<p>これらは「成功するか、失敗するか」のどちらかであり、「データは書き換わったけどインデックスが壊れた」なんてことは起きません。</p>



<h3 class="wp-block-heading">それでも私が「トランザクション枠」を作る理由</h3>



<p>では、なぜ私はそれでもトランザクションを貼ることが多いのか。それは<strong>「未来のバグを防ぐため（防御的プログラミング）」</strong>です。</p>



<p>今は「名前の変更だけ」かもしれません。<br>でも半年後、<strong>仕様変更で「名前変更の履歴もログテーブルに残して」</strong>と言われたら？</p>



<p>もしトランザクションの枠組み（<code>DB::transaction</code> など）がないコードだと、改修担当者がうっかり履歴保存処理を単に追加するだけで、トランザクション漏れを起こすリスクがあります。</p>



<p>最初から「更新処理の塊」として定義しておけば、処理が増えても安全です。<br>「転ばぬ先の杖」として、設計の意図をコードに残す意味でも価値があると思っています。</p>



<h2 class="wp-block-heading"> 単一テーブル更新時（SELECTが絡むとき）：ここが最大の落とし穴</h2>



<p>「単一テーブルだから大丈夫」と油断して一番痛い目を見るのが、<strong>Read-Modify-Write（読んで、加工して、書く）</strong>のパターンです。</p>



<p>例えば、銀行口座や在庫の管理など、「今の値を取得して、計算して、更新する」処理です。</p>



<ol start="1" class="wp-block-list">
<li><strong>SELECT:</strong> 現在の残高を取得（例：1000円）</li>



<li><strong>App:</strong> アプリ側で500円引く計算（1000 &#8211; 500 = 500）</li>



<li><strong>UPDATE:</strong> 残高を「500円」で更新</li>
</ol>



<p>これを安全に行うには、読み取る時点で排他ロック <code>SELECT ... FOR UPDATE</code>が必要です。</p>



<h3 class="wp-block-heading">トランザクションがないとロックは機能しない！</h3>



<p>ここで重要なのが、<strong>「ロックの寿命はトランザクションの終わりまで」</strong>というルールです。</p>



<p>もし明示的にトランザクションを貼らずに <code>FOR UPDATE</code> を投げたとしましょう。</p>



<pre class="wp-block-code"><code>-- トランザクションなしの場合

-- ① ロック付きで読む... つもりだが
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- ★ ここでAuto Commitが働き、即座にトランザクション終了＆ロック解除！

-- （この計算処理中に、他の人が割り込めてしまう！）

-- ② 更新
UPDATE accounts SET balance = 500 WHERE id = 1;
</code></pre>



<p>このように、トランザクションの枠がないと、せっかくのロックが一瞬で外れてしまい、<strong>競合（Race Condition）</strong>によるデータ不整合を防げません。</p>



<p>単一テーブルであっても、<strong>「ロジックを含んだ更新」をするならトランザクションは必須</strong>なのです。</p>



<p></p>



<pre class="wp-block-code"><code>SELECT * FROM users WHERE id = 1 FOR UPDATE;</code></pre>



<pre class="wp-block-code"><code>use Illuminate\Support\Facades\DB;<br>use App\Models\User;<br><br>DB::transaction(function () use ($userId) {<br>    // ▼ ここでロック<br>    $user = User::lockForUpdate()-&gt;find($userId);<br><br>    /** <br>    * この時点でレコードはロックされています。<br>    * 他のトランザクションは、この行を lockForUpdate しようとすると<br>    * ここで待機状態になります。<br>    */<br><br>    $user-&gt;balance = $user-&gt;balance - 500;<br>    $user-&gt;save();<br>}, 3); // デッドロック対策。リトライ処理</code></pre>



<h2 class="wp-block-heading">複数テーブル更新時：言わずもがなの大本命</h2>



<p>最後に、これはもう「必須」と言って反対する人はいないでしょう。複数テーブルにまたがる更新です。</p>



<ul class="wp-block-list">
<li><code>users</code> テーブルにユーザーを作成</li>



<li><code>profiles</code> テーブルに初期プロフィールを作成</li>
</ul>



<p>もしトランザクションがなかったら、「ユーザーは作れたけど、プロフィール作成でエラーが出た」という状態で処理が終わり、<strong>「プロフィールを持たない不完全なユーザー」</strong>がDBに残存してしまいます。</p>



<p>これを防ぐのがトランザクションの <strong>「All or Nothing（全部やるか、全部やらないか）」</strong> という鉄の掟です。ここは議論の余地なしですね。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">トランザクションは「お守り」ではなく「シートベルト」</h2>



<p><strong>「単一テーブルならトランザクションはいらない」</strong>という意見は、単純な値のセット（Setter的な動作）においては技術的に正解です。</p>



<p>しかし、バックエンドエンジニアとしてのスタンスは以下の通りが良いのではないでしょうか。</p>



<ol start="1" class="wp-block-list">
<li><strong>単純な更新（UPDATE一発）なら：</strong> 必須ではないが、将来の拡張性を考えて貼っておくと安心。</li>



<li><strong>読んでから書く（SELECT + UPDATE）なら：</strong> ロックを維持するために<strong>絶対必須</strong>。単一テーブルでも関係ない。</li>



<li><strong>複数テーブルなら：</strong> <strong>絶対必須</strong>。</li>
</ol>



<p>DBはアプリケーションの心臓部です。「たかが単一テーブル」と侮らず、愛と敬意（そして適切なロック）を持ってトランザクションを貼っていきましょう！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2182/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>エラー通知で消耗してない？例外の「型」で緊急度を自動振り分けする方法</title>
		<link>https://otonan-syusyoku.work/archives/2179</link>
					<comments>https://otonan-syusyoku.work/archives/2179#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Tue, 06 Jan 2026 04:55:26 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2179</guid>

					<description><![CDATA[前回の記事で、LogicException（バグ）とRuntimeException（環境エラー）の違いについてお話ししました。 「違いはわかったけど、実際の開発でどう役立つの？」 そう思った方もいるかもしれません。 実 [&#8230;]]]></description>
										<content:encoded><![CDATA[<p data-path-to-node="39">前回の記事で、<code data-path-to-node="39" data-index-in-node="7">LogicException</code>（バグ）と<code data-path-to-node="39" data-index-in-node="26">RuntimeException</code>（環境エラー）の違いについてお話ししました。</p>
<p data-path-to-node="40">「違いはわかったけど、実際の開発でどう役立つの？」</p>
<p data-path-to-node="41">そう思った方もいるかもしれません。 実は、この使い分けを徹底すると、<b data-path-to-node="41" data-index-in-node="34">「運用の楽さ」</b> が劇的に変わるんです！</p>
<p data-path-to-node="42">今回は実践編として、<b data-path-to-node="42" data-index-in-node="10">「例外の種類によって、Slack通知やエラー画面を自動で振り分ける方法」</b> をご紹介します。</p>
<p data-path-to-node="42">
<h2 data-path-to-node="43">すべてのエラーを通知していませんか？</h2>
<p data-path-to-node="44">開発現場でよくあるのが、「エラーが出たら全部Slackに通知！」という運用。 これ、最初はいいんですが、だんだんこうなりませんか？</p>
<ul data-path-to-node="45">
<li>
<p data-path-to-node="45,0,0">深夜に「DB接続タイムアウト」の通知が来て叩き起こされる（でも数秒後に自然復旧してる）</p>
</li>
<li>
<p data-path-to-node="45,1,0">通知が多すぎて、本当に重要なバグ報告を見逃す</p>
</li>
<li>
<p data-path-to-node="45,2,0">結果、<b data-path-to-node="45,2,0" data-index-in-node="3">「通知チャンネル誰も見なくなる」</b> 現象が発生…</p>
</li>
</ul>
<p data-path-to-node="46">これは、<b data-path-to-node="46" data-index-in-node="4">「バグ（緊急）」</b> と <b data-path-to-node="46" data-index-in-node="15">「環境トラブル（要確認）」</b> を混ぜてしまっているのが原因です。</p>
<h2 data-path-to-node="47">「型」で振り分ける実装パターン</h2>
<p data-path-to-node="48">ここで、前回の知識が活きてきます。 例外ハンドラー（エラーを一元管理するクラス）で、以下のように振り分けてみましょう。</p>
<pre class="line-numbers">use Psr\Log\LoggerInterface;

class AppExceptionHandler
{
    public function __construct(
        private LoggerInterface $logger,
        private SlackNotifier $slack
    ) {}

    public function handle(\Throwable $e): void
    {
        // パターン1：LogicException / DomainException
        // 意味：「コードがおかしい（バグ）」
        if ($e instanceof \LogicException) {
            // これは緊急事態！開発者にすぐ知らせる
            $this-&gt;logger-&gt;critical($e-&gt;getMessage());
            $this-&gt;slack-&gt;send("&#x1f6a8; 緊急: 実装バグが発生！すぐ直して！: " . $e-&gt;getMessage());
            
            // ユーザーには「システムエラー」とだけ表示
            $this-&gt;renderErrorPage(500);
            return;
        }

        // パターン2：RuntimeException
        // 意味：「外部要因で失敗（環境エラー）」
        if ($e instanceof \RuntimeException) {
            // ログには残すけど、深夜に叩き起こすほどではないかも
            $this-&gt;logger-&gt;error($e-&gt;getMessage());
            
            // ユーザーには「現在混み合っています」など、やんわり伝える
            $this-&gt;renderErrorPage(503);
            return;
        }

        // その他の予期せぬエラー
        $this-&gt;logger-&gt;error('Unknown Error: ' . $e-&gt;getMessage());
        $this-&gt;renderErrorPage(500);
    }
}
</pre>
<p>&nbsp;</p>
<h3 data-path-to-node="50">この設計のメリット</h3>
<p data-path-to-node="51">こうすることで、開発チームの動き方が変わります。</p>
<ol start="1" data-path-to-node="52">
<li>
<p data-path-to-node="52,0,0"><b data-path-to-node="52,0,0" data-index-in-node="0">Slack通知が来たら「即対応」</b></p>
<ul data-path-to-node="52,0,1">
<li>
<p data-path-to-node="52,0,1,0,0"><code data-path-to-node="52,0,1,0,0" data-index-in-node="0">LogicException</code> だけが通知されるので、「通知 = 自分のコードミス」と認識できます。</p>
</li>
</ul>
</li>
<li>
<p data-path-to-node="52,1,0"><b data-path-to-node="52,1,0" data-index-in-node="0">ログが綺麗になる</b></p>
<ul data-path-to-node="52,1,1">
<li>
<p data-path-to-node="52,1,1,0,0">一時的な接続エラーなどはログファイルに溜まるだけなので、精神衛生上とても良いです（もちろん定期的なチェックは必要ですよ！）。</p>
</li>
</ul>
</li>
<li>
<p data-path-to-node="52,2,0"><b data-path-to-node="52,2,0" data-index-in-node="0">ユーザーへの案内が親切になる</b></p>
<ul data-path-to-node="52,2,1">
<li>
<p data-path-to-node="52,2,1,0,0">バグなら <code data-path-to-node="52,2,1,0,0" data-index-in-node="5">500</code>、アクセス過多なら <code data-path-to-node="52,2,1,0,0" data-index-in-node="18">503</code> と、状況に合わせたHTTPステータスコードを返せます。</p>
</li>
</ul>
</li>
</ol>
<h2 data-path-to-node="53">例外処理は「未来の自分」へのメッセージ</h2>
<p data-path-to-node="54">例外クラスを使い分けることは、単なるルールではありません。</p>
<ul data-path-to-node="55">
<li>
<p data-path-to-node="55,0,0"><b data-path-to-node="55,0,0" data-index-in-node="0">LogicException</b> を投げるときは、「これバグだから絶対直してね！」というメッセージ。</p>
</li>
<li>
<p data-path-to-node="55,1,0"><b data-path-to-node="55,1,0" data-index-in-node="0">RuntimeException</b> を投げるときは、「運用でカバーしてね」というメッセージ。</p>
</li>
</ul>
<p data-path-to-node="56">この意図を込めてコードを書けるようになると、一人前のエンジニアへまた一歩近づけます。 「エラーハンドリング」という地味な部分ですが、こここだわるとカッコいいですよ！</p>
<p data-path-to-node="57">ぜひ、自分たちのプロジェクトにも導入できないか検討してみてくださいね。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2179/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>とりあえず Exception にしてない？正しい使い分けで「デキる」コードへ</title>
		<link>https://otonan-syusyoku.work/archives/2175</link>
					<comments>https://otonan-syusyoku.work/archives/2175#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Tue, 06 Jan 2026 04:46:19 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2175</guid>

					<description><![CDATA[「例外処理、なんとなく try-catch で囲って終わり…」 「全部 Exception クラスを使っちゃってるけど、これっていいの？」 PHPを勉強し始めて少し経つと、こんな悩みが出てきませんか？ 実は、僕も最初はそ [&#8230;]]]></description>
										<content:encoded><![CDATA[<p data-path-to-node="5">「例外処理、なんとなく <code data-path-to-node="5" data-index-in-node="12">try-catch</code> で囲って終わり…」</p>
<p data-path-to-node="5">「全部 <code data-path-to-node="5" data-index-in-node="36">Exception</code> クラスを使っちゃってるけど、これっていいの？」</p>
<p data-path-to-node="6">PHPを勉強し始めて少し経つと、こんな悩みが出てきませんか？ 実は、僕も最初はそうでした。</p>
<p data-path-to-node="7">でも、<b data-path-to-node="7" data-index-in-node="3">「例外の型（種類）」</b> を適切に使い分けるだけで、コードの品質がグッと上がり、バグの原因究明が驚くほど早くなるんです。</p>
<p data-path-to-node="8">今回は、PHPの標準例外（SPL）の中でも特に重要な <b data-path-to-node="8" data-index-in-node="27"><code data-path-to-node="8" data-index-in-node="27">LogicException</code></b>、<b data-path-to-node="8" data-index-in-node="42"><code data-path-to-node="8" data-index-in-node="42">DomainException</code></b>、<b data-path-to-node="8" data-index-in-node="58"><code data-path-to-node="8" data-index-in-node="58">RuntimeException</code></b> の3つについて、<b data-path-to-node="8" data-index-in-node="83">「現場ではどう使い分けているのか」</b> を解説していきます！</p>
<h2 data-path-to-node="9">なぜ使い分ける必要があるの？</h2>
<p data-path-to-node="10">結論から言うと、<b data-path-to-node="10" data-index-in-node="8">「誰のせいなのか」</b> をはっきりさせるためです。</p>
<p data-path-to-node="11">エラーが起きたとき、それが「プログラマーのミス（バグ）」なのか、「運悪くサーバーが落ちた（環境要因）」のかによって、対応方法は変わりますよね？</p>
<ul data-path-to-node="12">
<li>
<p data-path-to-node="12,0,0"><b data-path-to-node="12,0,0" data-index-in-node="0">バグなら</b> → コードを修正しないといけない</p>
</li>
<li>
<p data-path-to-node="12,1,0"><b data-path-to-node="12,1,0" data-index-in-node="0">環境要因なら</b> → リトライしたり、ユーザーに「混み合ってます」と伝えたりする</p>
</li>
</ul>
<p data-path-to-node="13">これをクラスの型で表現するために、使い分けが必要なんです。</p>
<h3 data-path-to-node="15">LogicException（ロジック例外）</h3>
<p data-path-to-node="16"><b data-path-to-node="16" data-index-in-node="0">「これは開発者のミス！ コードを直して！」</b></p>
<p data-path-to-node="17">これは、「プログラムの論理がおかしい」ときに投げる例外です。 基本的に、<b data-path-to-node="17" data-index-in-node="36">本番環境でこれが出たらアウト</b>。即修正案件です。</p>
<p data-path-to-node="18">例えば、「APIキーをセットしていないのに、リクエストを送ろうとした」みたいなケースですね/p&gt;</p>
<pre class="line-numbers"><code class="language-php">
class ApiClient
{
    private ?string $apiKey = null;

    public function setApiKey(string $key): void
    {
        $this-&gt;apiKey = $key;
    }

    public function request(): void
    {
        if ($this-&gt;apiKey === null) {
            // 開発者がセットアップ手順を間違えているため、LogicException
            throw new \LogicException('API Keyが設定されていません。request()の前にsetApiKey()を呼んでください。');
        }

        // リクエスト処理...
    }
}
</code></pre>
<p>&nbsp;</p>
<p data-path-to-node="20">この例外が出たら、<code data-path-to-node="20" data-index-in-node="9">catch</code> して握りつぶすのではなく、コード自体を直しましょう。</p>
<h3 data-path-to-node="21">2. DomainException（ドメイン例外）</h3>
<p data-path-to-node="22"><b data-path-to-node="22" data-index-in-node="0">「そのデータ、仕様としてありえないよ！」</b></p>
<p data-path-to-node="23">これは <code data-path-to-node="23" data-index-in-node="4">LogicException</code> の親戚みたいなものです。 「ロジックは動くけど、渡されたデータがビジネスルール（仕様）としてありえない」場合に使います。</p>
<p data-path-to-node="24">例えば、「信号機の色」を扱うクラスがあったとして、「紫」を指定されたら困りますよね？</p>
<div _ngcontent-ng-c1297688001="" class="code-block ng-tns-c1297688001-182 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_3ddd59c9e0f35997&quot;,&quot;c_864d881f3293f3e8&quot;,null,&quot;rc_69e8998f44149c04&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c1297688001="" class="formatted-code-block-internal-container ng-tns-c1297688001-182">
<div _ngcontent-ng-c1297688001="" class="animated-opacity ng-tns-c1297688001-182">
<pre _ngcontent-ng-c1297688001="" class="ng-tns-c1297688001-182"><code _ngcontent-ng-c1297688001="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c1297688001-182"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TrafficLight</span>
</span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> ALLOWED_COLORS = [<span class="hljs-string">'red'</span>, <span class="hljs-string">'yellow'</span>, <span class="hljs-string">'blue'</span>];

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> <span class="hljs-variable">$color</span></span>)
    </span>{
        <span class="hljs-keyword">if</span> (!in_array(<span class="hljs-variable">$color</span>, <span class="hljs-built_in">self</span>::ALLOWED_COLORS, <span class="hljs-literal">true</span>)) {
            <span class="hljs-comment">// 定義外の値がコードから渡された！</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">DomainException</span>(<span class="hljs-string">"色 '<span class="hljs-subst">{$color}</span>' は信号機に存在しません。"</span>);
        }
    }
}
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<p data-path-to-node="26">ユーザーの入力ミスというよりは、「コード上で変な値を渡してしまった」ときに検知するためのものです。</p>
<h3 data-path-to-node="27">3. RuntimeException（実行時例外）</h3>
<p data-path-to-node="28"><b data-path-to-node="28" data-index-in-node="0">「コードは合ってるけど、環境のせいで失敗しました…」</b></p>
<p data-path-to-node="29">一番よく使うのがこれです。 コードは正しいけれど、<b data-path-to-node="29" data-index-in-node="25">「DBに繋がらない」「ファイルがない」「外部APIが落ちてる」</b> といった、実行時の状況（Runtime）によって起きるエラーです。</p>
<div _ngcontent-ng-c1297688001="" class="code-block ng-tns-c1297688001-183 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_3ddd59c9e0f35997&quot;,&quot;c_864d881f3293f3e8&quot;,null,&quot;rc_69e8998f44149c04&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c1297688001="" class="formatted-code-block-internal-container ng-tns-c1297688001-183">
<div _ngcontent-ng-c1297688001="" class="animated-opacity ng-tns-c1297688001-183">
<pre _ngcontent-ng-c1297688001="" class="ng-tns-c1297688001-183"><code _ngcontent-ng-c1297688001="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c1297688001-183"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConfigLoader</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">load</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-variable">$filePath</span></span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">if</span> (!file_exists(<span class="hljs-variable">$filePath</span>)) {
            <span class="hljs-comment">// パスは合ってるはずだけど、ファイルが消えてる（実行時の事情）</span>
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">RuntimeException</span>(<span class="hljs-string">"設定ファイルが見つかりません: <span class="hljs-subst">{$filePath}</span>"</span>);
        }
        
        <span class="hljs-comment">// 読み込み処理...</span>
    }
}
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<p data-path-to-node="31">このエラーが出たときは、<code data-path-to-node="31" data-index-in-node="12">try-catch</code> で捕まえて「ただいま混み合っております」のようなエラー画面を出してあげる必要があります</p>
<h2 data-path-to-node="33">まとめ</h2>
<ul data-path-to-node="34">
<li>
<p data-path-to-node="34,0,0"><b data-path-to-node="34,0,0" data-index-in-node="0">Logic / Domain 系</b></p>
<ul data-path-to-node="34,0,1">
<li>
<p data-path-to-node="34,0,1,0,0">意味：「コードのバグ」</p>
</li>
<li>
<p data-path-to-node="34,0,1,1,0">対応：コードを修正する（Catchして無視しちゃダメ！）</p>
</li>
</ul>
</li>
<li>
<p data-path-to-node="34,1,0"><b data-path-to-node="34,1,0" data-index-in-node="0">Runtime 系</b></p>
<ul data-path-to-node="34,1,1">
<li>
<p data-path-to-node="34,1,1,0,0">意味：「実行時のトラブル」</p>
</li>
<li>
<p data-path-to-node="34,1,1,1,0">対応：エラー画面を出したり、ログに残したりして運用でカバーする</p>
</li>
</ul>
</li>
</ul>
<p data-path-to-node="35">この意識を持つだけで、「エラーが起きたときに何をすればいいか」が一瞬でわかるようになります。 ぜひ、明日からのコーディングで意識してみてくださいね！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2175/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>2025年、AIを使った開発で役立ったTipsたち</title>
		<link>https://otonan-syusyoku.work/archives/2187</link>
					<comments>https://otonan-syusyoku.work/archives/2187#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Sat, 27 Dec 2025 10:10:09 +0000</pubDate>
				<category><![CDATA[仕事の独り言]]></category>
		<category><![CDATA[生涯独学]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Typescript]]></category>
		<category><![CDATA[ポエム]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2187</guid>

					<description><![CDATA[AIによるコーディング支援は、もう「当たり前」の景色になりました。 AIに頼めば、ものの数秒で大量のコードが生成されます。まるで魔法のようですが、同時にこうも思うのです。「これ、本当に動くの？」「仕様、合ってる？」と。  [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>AIによるコーディング支援は、もう「当たり前」の景色になりました。</p>



<p>AIに頼めば、ものの数秒で大量のコードが生成されます。<br>まるで魔法のようですが、同時にこうも思うのです。「これ、本当に動くの？」「仕様、合ってる？」と。</p>



<p>大量に出力されるコードの波に飲まれず、<strong>「人間が手綱を握り続ける」</strong>ために僕が実践してきた開発のTipsを書き残しておきます。</p>



<p>これは、2026年の自分への手紙でもあります。<br>「あの頃はそんな苦労をしてたんだな」と笑って読み返せますように。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">機会にできることは機械に任せる（Lint / Pre-commit / Auto Format）</h2>



<p>AIは優秀ですが、たまにインデントを崩したり、変な書き癖を出してきたりします。<br>生成された大量のコードを目視でレビューして「ここ、スペース空いてないよ」なんて指摘するのは、人間のやる仕事じゃありませんよね。</p>



<p>だからこそ、<strong>Lint</strong> と <strong>Pre-commit</strong>、そして <strong>自動フォーマット</strong> は必須でした。</p>



<ul class="wp-block-list">
<li><strong>保存＝整形（Auto Format）</strong><br>エディタで保存した瞬間に、コードが綺麗になる設定はマスト。AIが吐き出したコードを貼り付けた瞬間、プロジェクトの規約に合わせて「シュッ」と整う快感。これがないと精神衛生が保てません。</li>



<li><strong>コミット前の門番（Pre-commit）</strong><br> Gitにコミットする前に、Linterが自動で走るように設定しました。型エラーや未使用変数は、人間が気づく前に機械に弾いてもらう。</li>
</ul>



<p><strong>「コードの見た目」や「単純なミス」に脳のメモリを使わない。</strong>これがAI時代を生き抜く第一歩でした。（AI開発がどうっていう話ではないがより一層重要になっている</p>



<p></p>



<h2 class="wp-block-heading">実装指示は細かくStepByStep形式</h2>



<p>最終的なゴールを明示したうえで、StepByStepで実装させました。</p>



<p>ゴールは <code>.documents</code> にMarkdownを配置し、その仕様書通りになるように指示をします。</p>



<pre class="wp-block-code"><code>## As Is

## To Be 

## Task

## Remarks</code></pre>



<ol class="wp-block-list">
<li>〇〇を最終ゴールとします。実装方針を出して。</li>



<li>ツッコミを入れる
<ul class="wp-block-list">
<li>Step1の実装のセキュリティホールは？</li>



<li>タイムアウトの条件は〇〇に</li>
</ul>
</li>



<li>テスト書いてもらう</li>



<li>もちろんRed</li>



<li>テストのレビュー
<ul class="wp-block-list">
<li>テスト問題なければ実装</li>



<li>問題あれば2を見直す</li>
</ul>
</li>



<li>テストがGreenになるような実装</li>
</ol>



<p>この辺はガイドラインやコーディング規約などを整備すればよかったのかなぁと考えている反省点です。</p>



<h2 class="wp-block-heading">テスト戦略：AIに任せる部分、僕が譲らない部分</h2>



<p>ここが一番のキモです。<br> AIはコードを書くのは早いですが、<strong>「僕たちが本当に作りたいもの」</strong>を100%理解しているわけではありません。<br>だからテストの役割分担をこう決めました。</p>



<h3 class="wp-block-heading">単体テスト（ViteTest / PHPUnit）</h3>



<p>関数単体や小さなロジックの検証には、ViteTestやPHPUnitを使いました。<br>ここはAIにも手伝ってもらいやすい領域です。「この関数のテスト書いて」と言えば、エッジケースまで網羅したテストを提案してくれます。<br><br>※ 単体テストをAIに書いてもらうために、関数は限りなく小さくするような単一責任の原則でAIに実装してもらっています。ぜーんぶAIです。</p>



<h3 class="wp-block-heading">Featureテスト（結合・機能テスト）は「僕」が書く</h3>



<p>けれど、機能全体が正しく動くかを確認する<strong>Featureテストだけは、僕自身の手で書く</strong>ようにしました。<br>これには明確な理由が2つあります。</p>



<ol start="1" class="wp-block-list">
<li><strong>コードの理解</strong>：<br>AIが書いたブラックボックスなコードも、テストを書く過程で読み解く必要があります。「どう動くべきか」を定義するのは人間です。</li>



<li><strong>要求仕様の達成</strong>：<br>求められている「要件」を満たしているか判定できるのは、今のところまだ人間だけです。</li>
</ol>



<p><strong>「AIにコードを書かせるなら、人間はテスト（仕様）を書け」</strong> これが2025年の僕の合言葉でした。<br>テストが通る＝仕様を満たしているという安心感があって初めて、AIのスピードを享受できるんです。</p>



<h2 class="wp-block-heading">2026年の僕へ</h2>



<p>どうですか？ 2026年の開発現場は。</p>



<p>AIが仕様の矛盾すら指摘して、テストまで完ぺきに書いてくれるようになっているのでしょうか。</p>



<p>もしそうなっていたとしても、2025年の僕は「正しく動くものを届けたい」という一心で、Linterを設定し、Featureテストを書いていたことを覚えていてください。</p>



<p>エンジニアとしての仕事に楽しさは残っていますか？2025年末では、だんだんと辛くなってきています。</p>



<p>それじゃ、また。良い開発ライフを！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2187/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
