アプリケーション構造
概要
EC-SPOKEシステムの具体的なディレクトリ構成と、各種コンポーネントの配置方針を定義します。実装時の具体的なガイドラインとして参照してください。
基本方針
EC-SPOKEシステムのアプリケーション構造は、以下の原則に基づいて設計されています:
1. ドメイン駆動の構造化
- モデルとドメインの分離: Eloquentモデル(
app/Models/{Domain})とドメイン固有の振る舞い(app/Domains/{Domain})を明確に分離し、責務を明確化する - ドメイン別の集約: 各ドメイン(Catalog、Order、Page等)は、モデル・サービス・ポリシー・アクション・オブザーバを独立したディレクトリに集約する
- グローバルコンポーネントの明確化: 複数ドメインで共通利用されるコンポーネント(Enums、Rules、Services、Workflows)は、グローバルに配置し、ドメイン固有のものと区別する
2. レイヤー別の責務分離
- プレゼンテーション層: Admin(Filament管理画面)、Frontend(Livewireコンポーネント)、Http(API/Webコントローラ)を明確に分離
- ビジネスロジック層: ドメインサービス、ワークフロー、アクションを配置し、UIから独立したビジネスロジックを実現
- インフラ層: Providers、Support、Traits、Facades、Consoleコマンドを配置し、アプリケーション基盤を提供
3. 近接配置の原則
- Filamentリソースとビュー: 近接配置ルールに従い、リソースとビューを同じディレクトリ配下に配置し、探索性を向上
- ワークフロー関連ファイル: ワークフロー名別のディレクトリに、Workflow、Pipeline、Context、State、Actions、Jobs、Pipesを集約
4. イベント駆動アーキテクチャ
- Observers: モデルのライフサイクルに密接に関連する処理(検索インデックス更新、監査ログ等)
- Events/Listeners: ビジネスイベントを表現し、ドメイン間の疎結合な連携を実現
- 使い分け: モデルの状態変化はObservers、ビジネス的に意味のある出来事はEvents/Listenersを使用
5. 技術スタックの活用
- PHP 8.3以上: readonly class、enum、match式、型付きプロパティなど最新の機能を積極的に活用
- Laravel標準構造: プロジェクトルート配下のディレクトリ(database、routes、resources、bootstrap、tests、storage、public)はLaravel標準に従う
- Filament v4: 管理画面のリソース、コンポーネント、ウィジェットを
app/Admin/配下に配置
ディレクトリ構成例
app/
├── Models/
│ ├── Catalog/
│ │ ├── Product.php
│ │ ├── ProductVariant.php
│ │ ├── Inventory.php
│ │ └── Price.php
│ ├── Order/
│ │ ├── Order.php
│ │ └── OrderItem.php
│ └── ...(他ドメインも同様に配置)
├── Domains/
│ ├── Catalog/
│ │ ├── Services/
│ │ │ ├── ProductCatalogService.php
│ │ │ └── InventoryService.php
│ │ ├── Policies/
│ │ │ └── ProductPolicy.php
│ │ ├── Actions/
│ │ │ └── ArchiveProductAction.php
│ │ ├── Observers/
│ │ │ └── ProductObserver.php
│ │ ├── ValueObjects/
│ │ │ └── Price.php
│ │ └── Constants/
│ │ └── ProductConstants.php
│ └── Order/
│ ├── Services/
│ │ └── OrderPlacementService.php
│ ├── ValueObjects/
│ │ ├── ReservedInventory.php
│ │ ├── PaymentResult.php
│ │ └── ShippingArrangement.php
│ └── ...
├── Enums/
│ ├── OrderStatus.php
│ ├── PaymentStatus.php
│ └── ...(グローバルなEnum)
├── Rules/
│ ├── SubscriptionPlanUniqueRule.php
│ ├── ValidProductVariantRule.php
│ └── ...(カスタムバリデーションルール)
├── Services/
│ ├── CartService.php
│ └── PageCompiler.php
├── Workflows/
│ └── OrderPlacement/
│ ├── OrderPlacementWorkflow.php
│ ├── OrderPlacementPipeline.php
│ ├── OrderPlacementContext.php
│ ├── OrderPlacementState.php
│ ├── Actions/
│ │ ├── ReserveInventoryAction.php
│ │ ├── ProcessPaymentAction.php
│ │ └── ArrangeShippingAction.php
│ ├── Jobs/
│ │ ├── ReserveInventoryJob.php
│ │ ├── ProcessPaymentJob.php
│ │ └── ArrangeShippingJob.php
│ └── Pipes/
│ ├── ReserveInventoryPipe.php
│ └── ProcessPaymentPipe.php
├── Admin/
│ ├── Catalog/
│ │ ├── Resources/
│ │ │ └── ProductResource.php
│ │ └── Views/
│ │ └── product-form.blade.php
│ └── ...
├── Frontend/
│ ├── Components/
│ ├── PageBuilder/
│ ├── Services/
│ └── Themes/
│ ├── Default/
│ └── Bootstrap/
├── Http/
│ ├── Controllers/
│ ├── Middleware/
│ ├── Requests/
│ └── Resources/
│ ├── ProductApiResource.php
│ ├── OrderApiResource.php
│ └── CategoryApiResource.php
├── Providers/
│ ├── AppServiceProvider.php
│ ├── Filament/
│ │ └── AdminPanelProvider.php
│ └── ThemeServiceProvider.php
├── Support/
│ ├── Helpers/
│ ├── helpers.php
│ └── MultibyteSlug.php
├── Traits/
│ └── HasSortableTree.php
├── Facades/
│ └── Cart.php
├── Console/
│ └── Commands/
│ ├── CleanupOrphanedBlocks.php
│ ├── CompilePageCommand.php
│ ├── ConvertEcSpokeLang.php
│ └── ExportEcSpokeLang.php
├── Observers/
│ └── ...(グローバルなオブザーバ)
├── Events/
│ └── .gitkeep
├── Listeners/
│ └── .gitkeep
├── Jobs/
│ └── .gitkeep
├── Mail/
│ └── .gitkeep
├── Exceptions/
│ └── .gitkeep
├── Broadcast/
│ └── .gitkeep
└── Notifications/
└── TwoFactorEmailCodeNotification.php
config/
├── catalog.php
├── orders.php
├── subscriptions.php
├── payment.php
└── ...(ドメイン別設定ファイル)
database/
├── factories/
│ └── ...(モデルファクトリー)
├── migrations/
│ └── ...(データベースマイグレーション)
└── seeders/
└── ...(データベースシーダー)
routes/
├── web.php
└── console.php
resources/
├── views/
│ └── ...(Bladeビュー)
├── lang/
│ └── ...(多言語翻訳ファイル)
└── css/
└── ...(CSSファイル)
bootstrap/
├── app.php
└── providers.php
tests/
└── ...(PHPUnitテストファイル)
各ディレクトリの役割分担と配置ルール
各ディレクトリの役割分担と配置ルールをディレクトリ構成に従って整理します。後から見たときに「これこれはここに配置する」というルールが分かるように構成しています。
Models (app/Models/)
- 役割: Eloquentモデルを配置。データベーステーブルと1対1対応するエンティティを定義
- 配置ルール: ドメイン別にサブディレクトリを作成(例:
app/Models/Catalog/,app/Models/Order/) - 例:
Product.php,Category.php,Order.php,User.php,Brand.php,Page.php,PageArea.php,PageBlock.php,OrderItem.php,Address.php,Admin.php
Domains (app/Domains/{Domain}/)
- 役割: ドメイン固有の振る舞いを配置。ユースケース毎のロジックを横断的に再利用
サービス層
- ユースケース単位のサービス:
app/Domains/{Domain}/Servicesに配置 - ユースケースごとにトランザクション境界・バリデーション・イベント発火を担当
- Eloquent への直接的な依存はここに集める
- PHP 8.3の型付きプロパティとコンストラクタプロパティプロモーションを活用
値オブジェクト
- ドメインの概念を表現:
app/Domains/{Domain}/ValueObjectsに配置 - ドメインの概念を表現する不変オブジェクト
readonly classとして実装し、ビジネスルールを含む- ワークフロー固有のデータ構造(Context、State等)は
app/Workflows/{WorkflowName}/配下に配置
ポリシー
- 認可ロジック:
app/Domains/{Domain}/Policiesに配置 - Filament/API 双方の認可に再利用
- Filament の
can*系メソッドは自動的に Policy を参照
アクション
- ビジネスアクション:
app/Domains/{Domain}/Actionsに配置 - Filament Actions やジョブ/コマンドラインから呼び出せる単位にまとめる
- UI ごとの差異を吸収
オブザーバ
- モデル更新の副作用:
app/Domains/{Domain}/Observersに配置 - モデル更新の副作用(検索インデックス更新、監査ログ、イベント発火など)を一元管理
定数
- ドメイン固有の定数:
app/Domains/{Domain}/Constantsに配置 - 変更頻度が低く、ドメイン固有の定数値を管理
- 値オブジェクトとして
readonly classを活用する場合もある
Enums (app/Enums/)
- 役割: グローバルな列挙型を配置。複数のドメインで共通利用される列挙型を定義
- 配置ルール: ドメイン固有のEnumは
app/Domains/{Domain}/Constants/に配置。グローバルなもののみここに配置 - 例:
App\Enums\OrderStatus,App\Enums\PaymentStatus,App\Enums\Page\AreaType - 注意: PHP 8.3のEnum機能を活用。Backed Enum(値を持つEnum)やメソッド付きEnumを積極的に使用
Rules (app/Rules/)
- 役割: カスタムバリデーションルールを配置。FormRequest とサービス層の両方で再利用可能なルールを定義
- 配置ルール: グローバルに配置。ドメイン固有の複雑なバリデーションロジック(例: サブスク契約の重複チェック、商品バリエーションの整合性チェック)を一元管理
- 例:
App\Rules\SubscriptionPlanUniqueRule,App\Rules\ValidProductVariantRule
Services (app/Services/)
- 役割: ドメイン横断的なアプリケーションサービスを配置。複数のドメインにまたがる処理や、アプリケーション全体で共通利用される機能を担当
- 配置ルール: 特定のドメインに属さないサービスを配置。ドメイン固有のサービスは
app/Domains/{Domain}/Services/に配置 - 使用例:
- カート管理: セッション管理、商品追加・削除、合計計算など(
CartService)。商品・注文・ユーザーなど複数ドメインにまたがる処理 - その他: 複数ドメインにまたがる共通処理、アプリケーション全体で使われる機能
- ドメイン固有のサービスとの違い:
app/Services/: カートなど、複数ドメインにまたがる処理app/Domains/{Domain}/Services/: 商品カタログ管理、注文確定、ページコンパイルなど、特定ドメインのビジネスロジック- Serviceの使い分け:
- 使用する場合: ビジネスロジックを持つクラス、状態を持つ可能性がある(セッション、キャッシュ、DB等)
- 特徴: 複数のメソッドを持つ、依存注入で使用、テストしやすい(モック可能)
- 例:
CartService::addToCart()(セッションに状態を保存),PageCompiler::compile()(複雑な処理、ファイル操作) - 判断基準: 状態を持つか?複数のメソッドが必要か?複雑なビジネスロジックか?依存関係があるか?
- 例:
CartService.php(カート管理サービス) - 注意:
PageCompilerは現在app/Services/に配置されているが、Pageドメイン固有の処理のため、将来的にはapp/Domains/Page/Services/への移動を検討
Workflows (app/Workflows/{WorkflowName}/)
- 役割: 決済・在庫連携など複数手順を跨ぐ処理を、Laravel PipelineとWorkflowパターンを用いてパイプライン化し、各ステップをAction/ジョブに分離する
- 使い分け:
- Pipeline: 同期処理(数秒以内)。ユーザーリクエスト内で完結する処理(カート計算、価格計算、バリデーション等)
- Workflow: 同期・非同期両対応(数分〜数時間)。外部API呼び出しが多い処理、進捗を追跡したい処理(注文確定、決済処理、配送手配、在庫連携等)
- 配置ルール: ワークフロー名別にサブディレクトリを作成し、その中に以下のファイル・ディレクトリを配置
- {WorkflowName}Workflow.php: ワークフローオーケストレーター(非同期処理用)。各ステップの実行順序と状態遷移を管理
- {WorkflowName}Pipeline.php: パイプラインオーケストレーター(同期処理用、オプション)
- {WorkflowName}Context.php: Pipeline用のコンテキスト。各Pipe間でデータを伝播
- {WorkflowName}State.php: Workflow用の状態管理モデル。データベースに永続化
- Actions/: ビジネスロジック。ワークフロー固有のActionを配置
- Jobs/: 非同期処理。ワークフロー固有のジョブを配置
- Pipes/: Pipeline用のPipe(同期処理用、オプション)
- 注意: ワークフロー固有のデータ構造(Context、State等)はここに配置。ドメインの概念を表現する値オブジェクトは
app/Domains/{Domain}/ValueObjects/に配置 - 例:
OrderPlacement/OrderPlacementWorkflow.php,OrderPlacement/Actions/ReserveInventoryAction.php,OrderPlacement/Jobs/ProcessPaymentJob.php - 詳細: Workflow-Pipelineパターン を参照
Admin (app/Admin/)
- 役割: Filament管理画面のリソース、コンポーネント、ウィジェット、ページ、プラグインを配置
- 配置ルール:
- Resources/: Filamentリソース(CRUD管理)。各リソースは
{ResourceName}Resource.phpとして配置 - Components/: Filamentカスタムコンポーネント。Livewireコンポーネントやカスタムフィールドを配置
- Widgets/: Filamentウィジェット。ダッシュボード用の小型コンポーネント
- Pages/: Filamentページ。独立したページコンポーネントを配置
- Plugins/: Filamentプラグインの拡張。外部パッケージのプラグインを拡張・カスタマイズする場合に配置。各プラグインは
{PluginName}/ディレクトリに集約し、プラグインクラス、ページ、関連ロジックを同一ディレクトリに配置 - Base/: 基底クラス・共通機能。複数のリソースで共通利用される基底クラス
- Views/: ビューは近接配置ルールに従い、各コンポーネント・リソースと同じディレクトリ直下の
Views/に配置 - プラグイン拡張の配置例:
app/Admin/Plugins/LogViewer/CustomLogViewerPlugin.php- FilamentLogViewerパッケージの拡張プラグイン(Filament\Contracts\Pluginを実装)app/Admin/Plugins/LogViewer/CustomLogTable.php- プラグインが提供するPageapp/Admin/Plugins/LogViewer/CustomLog.php- プラグイン固有のロジック(ログデータ処理等)- 注意: リソース/ウィジェット/フォームビューは
app/Admin/{Domain}以下で近接配置し、必要なサービスやポリシーをuse App\Domains\...で参照する。ドメインフォルダ内のアクションやサービスを呼び出すことで、管理画面・API・ジョブのロジックを統一できる。監査ログ・設定変更などは Administration ドメインのサービスを経由して Filament から操作し、変更履歴を残す。詳細は近接配置ルールを参照
Frontend (app/Frontend/)
- 役割: フロントエンドのLivewireコンポーネント、ページビルダー、テーマ別ビューを配置
- 配置ルール:
- Components/: Livewireコンポーネント(ページ)。認証、カート、商品一覧・詳細、チェックアウト等のページコンポーネント
- PageBuilder/: ページビルダー機能。動的ページレンダリング、エリア・ブロックコンポーネント
- Services/: フロントエンド用サービス。ブロック登録管理、テーマサービス
- Themes/: テーマ別ディレクトリ(Default/Bootstrap等)。テーマ固有のビュー、アセット、ヘルパーを配置
Http (app/Http/)
- 役割: HTTP層のコントローラ、ミドルウェア、リクエストクラス、API Resourceを配置
- 配置ルール: Laravel標準の構造に従う
- Controllers/: コントローラクラス。基底コントローラは
Controller.php - Middleware/: ミドルウェア(Laravel 12では
bootstrap/app.phpで登録) - Requests/: FormRequestクラス。バリデーションルールとカスタムエラーメッセージを定義
- Resources/: API Resourceクラス。JSONレスポンスの形式定義(
JsonResourceを継承) - API Resourceの命名規則:
- 形式:
{Model}ApiResource(サフィックス方式) - 例:
ProductApiResource,OrderApiResource,CategoryApiResource - 配置場所:
app/Http/Resources/{Model}ApiResource.php - Filament Resourceとの区別: Filament Resource(
app/Admin/Resources/{Model}Resource.php)とは完全に独立。名前空間が異なるため、同じディレクトリ名でも問題ない - 詳細: API層の命名規則 を参照
Providers (app/Providers/)
- 役割: サービスプロバイダを配置。アプリケーション起動時の設定、サービス登録を担当
- 配置ルール:
AppServiceProvider.php: アプリケーションプロバイダFilament/AdminPanelProvider.php: Filament管理パネル設定ThemeServiceProvider.php: テーマ切り替えプロバイダ- 注意: Laravel 12では
bootstrap/providers.phpに登録
Support (app/Support/)
- 役割: サポートクラス・ヘルパー関数を配置。アプリケーション全体で利用されるユーティリティ
- 配置ルール:
- Helpers/: ヘルパー関数群。
image.php(画像ヘルパー),theme.php(テーマヘルパー) helpers.php: グローバルヘルパーMultibyteSlug.php: マルチバイトSlug生成などのユーティリティクラス- ヘルパー関数の使い分け:
- 使用する場合: 純粋関数(状態を持たない、単純な変換・フォーマット処理)
- 特徴: 入力→出力の変換処理、グローバルスコープから呼び出し可能、単一の責務
- 例:
get_image_url()(画像URL生成),date_time_format()(日付フォーマット),theme_asset()(アセットURL生成) - 判断基準: 状態を持たないか?単純な変換・フォーマット処理か?複数のメソッドが必要か?(不要ならヘルパー)
Traits (app/Traits/)
- 役割: 再利用可能なトレイトを配置。複数のクラスで共通利用される機能を定義
- 配置ルール: グローバルに配置
- 例:
HasSortableTree.php(ソート可能ツリートレイト。カテゴリで使用)
Facades (app/Facades/)
- 役割: ファサード定義を配置。サービスコンテナに登録されたサービスの静的インターフェースを提供
- 配置ルール: グローバルに配置
- Facadeの使い分け:
- 使用する場合: BladeテンプレートやLivewireコンポーネントから直接呼ぶ必要がある場合のみ(最小限の使用)
- 特徴: Serviceをラップするだけ、静的メソッドとして呼び出せる、Bladeテンプレートから直接呼び出し可能
- 例:
Cart::addToCart()(BladeテンプレートやLivewireから呼ぶ場合のみ) - 判断基準: BladeテンプレートやLivewireコンポーネントから直接呼ぶ必要があるか?Laravel標準のFacade(Cache、DB、Auth等)と同じような使い方か?
- 推奨: それ以外はServiceを依存注入で使用する方が良い(コントローラやサービスでは依存注入を優先)
- 例:
Cart.php(カートファサード)
Console (app/Console/Commands/)
- 役割: Artisanコマンドを配置。バックグラウンド処理、メンテナンス、データ移行用のコマンド
- 配置ルール:
app/Console/Commands/に配置。Laravel 12では自動登録されるため手動登録不要 - 例:
CleanupOrphanedBlocks.php(存在しないブロッククラスを持つページブロックをクリーンアップ)CompilePageCommand.php(ページビルダーで作成したページをBladeファイルにコンパイル)ConvertEcSpokeLang.php(外部翻訳ファイルをLaravel形式にインポート)ExportEcSpokeLang.php(Laravel翻訳ファイルを外部形式にエクスポート)
Observers (app/Observers/)
- 役割: グローバルなオブザーバを配置。ドメイン固有のオブザーバは
app/Domains/{Domain}/Observers/に配置 - 配置ルール: グローバルに配置。複数のドメインにまたがるオブザーバを配置
- 注意: ドメイン固有のオブザーバは
app/Domains/{Domain}/Observers/に配置することを推奨
Notifications (app/Notifications/)
- 役割: 通知クラスを配置。メール、SMS等の通知を定義
- 配置ルール: グローバルに配置
- 例:
TwoFactorEmailCodeNotification.php(2FAメール認証コード通知) - 注意: Mailableクラス(
app/Mail/)とは異なる。通知はより高レベルな抽象化で、複数のチャネル(メール、SMS、データベース等)に対応可能
config/ (config/)
- 役割: 設定ファイルを配置。アプリケーションの設定値を定義
- 配置ルール: Laravel標準に従い、ドメイン別に分割(例:
config/catalog.php,config/orders.php,config/subscriptions.php) - 注意: アプリケーション内では
config('catalog.xxx')形式で参照。env()関数は設定ファイル内のみで使用し、アプリケーション内ではconfig()を使用 - 例:
app.php,auth.php,cache.php,database.php,filament.php,filesystems.php,livewire.php,logging.php,mail.php,queue.php,services.php,session.php,theme.php
プロジェクトルート配下のディレクトリ
database/ (database/)
- 役割: データベース関連ファイルを配置。マイグレーション、シーダー、ファクトリーを定義
- 配置ルール: Laravel標準の構造に従う
- factories/: モデルファクトリー。テストデータ生成用
- migrations/: データベースマイグレーション。スキーマ変更を定義
- seeders/: データベースシーダー。初期データ投入用
- 注意: マイグレーションは時系列で管理され、
YYYY_MM_DD_HHMMSS_create_table_name.php形式で命名
routes/ (routes/)
- 役割: ルート定義ファイルを配置。HTTPリクエストのルーティングを定義
- 配置ルール: Laravel標準の構造に従う
- web.php: Webルート(セッション、CSRF保護、Cookie暗号化が有効)
- console.php: Artisanコマンドのスケジュール定義
- 注意: APIルートは
routes/api.phpに配置(現在は未使用)。Laravel 12ではbootstrap/app.phpでルートファイルを登録
resources/ (resources/)
- 役割: 未コンパイルのアセット(ビュー、言語ファイル、CSS等)を配置
- 配置ルール: Laravel標準の構造に従う
- views/: Bladeビューテンプレート。
app/Admin/やapp/Frontend/配下のビューは近接配置ルールに従う - lang/: 多言語翻訳ファイル。
{locale}/{group}.php形式で配置 - css/: CSSファイル。Tailwind CSSの設定ファイル等
- 注意:
app/Admin/配下のビューは近接配置ルールに従い、app/Admin/.../Views/に配置。resources/views/は標準的なLaravelビュー用
bootstrap/ (bootstrap/)
- 役割: アプリケーション起動時の設定ファイルを配置
- 配置ルール: Laravel標準の構造に従う
- app.php: アプリケーション起動設定。ミドルウェア、例外処理、ルートファイルの登録
- providers.php: サービスプロバイダの登録
- cache/: 設定キャッシュ、ルートキャッシュ等(自動生成)
- 注意: Laravel 12では
app.phpでミドルウェアやルートを登録。providers.phpでサービスプロバイダを登録
tests/ (tests/)
- 役割: PHPUnitテストファイルを配置。アプリケーションのテストを定義
- 配置ルール: Laravel標準の構造に従う
- Feature/: フィーチャーテスト。HTTPリクエスト、データベース操作を含む統合テスト
- Unit/: ユニットテスト。単一のクラスやメソッドのテスト
- 注意: テストは
php artisan testで実行。RefreshDatabaseトレイトを使用する場合は、テスト専用データベースを使用すること
storage/ (storage/)
- 役割: アプリケーションが生成するファイルを配置。ログ、キャッシュ、アップロードファイル等
- 配置ルール: Laravel標準の構造に従う
- app/: アプリケーションが生成するファイル(公開されない)
- framework/: フレームワークが生成するファイル(キャッシュ、セッション等)
- logs/: ログファイル
- 注意:
.gitignoreに含まれているため、バージョン管理されない
public/ (public/)
- 役割: 公開ファイルを配置。Webサーバーのドキュメントルート
- 配置ルール: Laravel標準の構造に従う
- index.php: アプリケーションのエントリーポイント
- assets/: コンパイル済みアセット(CSS、JavaScript、画像等)
- 注意: Viteでコンパイルされたアセットはここに配置される
Observers (app/Observers/)
- 役割: グローバルなオブザーバを配置。ドメイン固有のオブザーバは
app/Domains/{Domain}/Observers/に配置 - 配置ルール: グローバルに配置。複数のドメインにまたがるオブザーバを配置
- 注意: ドメイン固有のオブザーバは
app/Domains/{Domain}/Observers/に配置することを推奨
Observersとは:
- モデルのライフサイクルイベント(created, updated, deleted等)に自動的に反応する仕組み
- モデル保存時に自動的に実行される副作用処理を定義
- 例: 商品更新時に検索インデックスを更新、注文作成時に監査ログを記録
使用場面: - モデル保存時の自動的な副作用処理が必要な場合 - 複数の場所で同じ副作用処理を実行する必要がある場合 - モデルのライフサイクルに密接に関連する処理
実装例:
// app/Domains/Catalog/Observers/ProductObserver.php
class ProductObserver
{
public function updated(Product $product): void
{
// 商品更新時に検索インデックスを更新
UpdateProductIndexJob::dispatch($product);
}
}
Events (app/Events/)
- 役割: イベントクラスを配置。ドメインイベントやアプリケーションイベントを定義
- 配置ルール: グローバルに配置。ドメイン固有のイベントは
app/Domains/{Domain}/Events/に配置することも検討 - 使用例:
OrderPlaced,ProductUpdated,UserRegisteredなど - 使用場面: ドメイン間の疎結合な連携、複数の非同期処理をトリガーする場面
- 詳細: イベント駆動アーキテクチャの実装時に使用。
event()関数またはEvent::dispatch()で発火
Eventsとは:
- ビジネスイベントを表現するクラス(例: OrderPlaced, ProductPublished)
- モデルのライフサイクルではなく、ビジネス的に意味のある出来事を表現
- 複数のドメインにまたがる処理を疎結合に連携するために使用
使用場面: - 注文確定時に、在庫引当・決済処理・メール送信・監査ログなど複数の処理を実行したい場合 - ドメイン間の連携を疎結合にしたい場合 - 非同期処理をトリガーしたい場合
実装例:
// app/Events/OrderPlaced.php
class OrderPlaced
{
public function __construct(
public Order $order
) {}
}
// app/Domains/Order/Services/OrderPlacementService.php
public function placeOrder(Order $order): void
{
DB::transaction(function () use ($order) {
$order->status = OrderStatus::Placed;
$order->save();
// イベント発火
event(new OrderPlaced($order));
});
}
Listeners (app/Listeners/)
- 役割: イベントリスナーを配置。イベント発生時に実行される処理を定義
- 配置ルール: グローバルに配置。ドメイン固有のリスナーは
app/Domains/{Domain}/Listeners/に配置することも検討 - 使用例:
SendOrderConfirmationEmail,UpdateSearchIndex,LogAuditTrailなど - 使用場面: イベント発生時の副作用処理(メール送信、検索インデックス更新、監査ログ等)
- 非同期処理:
ShouldQueueインターフェースを実装することで、リスナーを非同期で実行可能
Listenersとは: - イベントが発生したときに実行される処理を定義するクラス - 1つのイベントに対して複数のリスナーを登録可能 - イベント発火元のコードを変更せずに、新しい処理を追加できる
使用場面: - 注文確定時にメール送信、検索インデックス更新、監査ログ記録など複数の処理を実行したい場合 - イベント発火元のコードを変更せずに新しい処理を追加したい場合 - 非同期処理を実行したい場合
実装例:
// app/Listeners/SendOrderConfirmationEmail.php
class SendOrderConfirmationEmail implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
// 非同期でメール送信
Mail::to($event->order->customer->email)
->send(new OrderConfirmationMail($event->order));
}
}
// app/Listeners/UpdateSearchIndex.php
class UpdateSearchIndex implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
// 非同期で検索インデックス更新
UpdateSearchIndexJob::dispatch($event->order);
}
}
Observers、Events、Listenersの使い分け
補記:使い分けの比較表
| 観点 | Observers | Events/Listeners | 直接呼び出し |
|---|---|---|---|
| トリガー | モデルのライフサイクル(created, updated, deleted等) |
ビジネスイベント(OrderPlaced, ProductPublished等) |
メソッド呼び出し |
| 焦点 | モデルの状態変化 | ビジネス的に意味のある出来事 | 処理の実行 |
| 結合度 | モデルに密結合 | 疎結合(イベント名で連携) | 密結合(直接依存) |
| 実行タイミング | モデル保存時に自動実行 | 明示的にイベント発火 | メソッド呼び出し時 |
| 複数処理の実行 | 1つのObserverで複数処理 | 1つのイベントに複数のListener | 複数メソッドを順次呼び出し |
| 非同期処理 | 可能(ジョブをディスパッチ) | 可能(ShouldQueueを実装) |
可能(ジョブをディスパッチ) |
| 拡張性 | 低い(Observerを変更する必要がある) | 高い(新しいListenerを追加するだけ) | 低い(呼び出し元を変更する必要がある) |
| 使用例 | 商品更新時に検索インデックス更新 | 注文確定時にメール送信・監査ログ・在庫更新 | カートに商品を追加 |
| 推奨場面 | モデルのライフサイクルに密接に関連する処理 | ドメイン間の連携、複数の処理を実行したい場合 | 単純な処理、密結合で問題ない場合 |
使い分けの判断フローチャート
1. モデルのライフサイクル(created/updated/deleted)に密接に関連する処理?
→ Yes → Observers
↓ No
2. ビジネス的に意味のある出来事(注文確定、商品公開等)を表現したい?
→ Yes → Events/Listeners
↓ No
3. 単純な処理で、密結合で問題ない?
→ Yes → 直接呼び出し
↓ No
→ Events/Listeners(疎結合を優先)
実践的なルール
Observersを使う場合
- ✅ モデル保存時に必ず実行したい処理(検索インデックス更新、監査ログ等)
- ✅ モデルのライフサイクルに密接に関連する処理
- ⚠️ ビジネスロジックが複雑な場合は、Events/Listenersを検討
Events/Listenersを使う場合
- ✅ ドメイン間の連携を疎結合にしたい場合
- ✅ 1つのイベントに対して複数の処理を実行したい場合
- ✅ イベント発火元のコードを変更せずに新しい処理を追加したい場合
- ✅ 非同期処理を実行したい場合
直接呼び出しを使う場合
- ✅ 単純な処理で、密結合で問題ない場合
- ✅ カートに商品を追加、価格計算など、即座に結果が必要な処理
- ⚠️ 複数の処理を実行する場合は、Events/Listenersを検討
Jobs (app/Jobs/)
- 役割: グローバルなキュージョブを配置。ワークフロー固有のジョブは
app/Workflows/{WorkflowName}/Jobs/に配置 - 配置ルール: グローバルに配置。複数のワークフローで共通利用されるジョブを配置
- 注意: ワークフロー固有のジョブは
app/Workflows/{WorkflowName}/Jobs/に配置することを推奨 - 使用例: メール送信、検索インデックス更新、外部API連携など
Mail (app/Mail/)
- 役割: Mailableクラスを配置。メール送信用のクラスを定義
- 配置ルール: グローバルに配置。ドメイン固有のMailableは
app/Domains/{Domain}/Mail/に配置することも検討 - 注意: Notifications(
app/Notifications/)とは異なる。Mailableはメール送信専用、Notificationsは複数チャネル対応 - 使用例:
OrderConfirmationMail,PasswordResetMail,WelcomeMailなど
Exceptions (app/Exceptions/)
- 役割: カスタム例外クラスを配置。アプリケーション固有の例外を定義
- 配置ルール: グローバルに配置。ドメイン固有の例外は
app/Domains/{Domain}/Exceptions/に配置することも検討 - 使用例:
InsufficientInventoryException,InvalidPaymentException,OrderNotFoundExceptionなど - 使用場面: ビジネスロジックエラーを明確に表現する場合
Broadcast (app/Broadcast/)
- 役割: ブロードキャストチャンネルを配置。リアルタイム通信(WebSocket等)用のチャンネル定義
- 配置ルール: グローバルに配置
- 使用例: 注文状況のリアルタイム更新、通知のリアルタイム配信など
- 注意: Laravel Echo、Pusher、Socket.IO等と連携して使用
Notifications (app/Notifications/)
- 役割: 通知クラスを配置。メール、SMS等の通知を定義
- 配置ルール: グローバルに配置
- 例:
TwoFactorEmailCodeNotification.php(2FAメール認証コード通知) - 注意: Mailableクラス(
app/Mail/)とは異なる。通知はより高レベルな抽象化で、複数のチャネル(メール、SMS、データベース等)に対応可能
config/ (config/)
- 役割: 設定ファイルを配置。アプリケーションの設定値を定義
- 配置ルール: Laravel標準に従い、ドメイン別に分割(例:
config/catalog.php,config/orders.php,config/subscriptions.php) - 注意: アプリケーション内では
config('catalog.xxx')形式で参照。env()関数は設定ファイル内のみで使用し、アプリケーション内ではconfig()を使用 - 例:
app.php,auth.php,cache.php,database.php,filament.php,filesystems.php,livewire.php,logging.php,mail.php,queue.php,services.php,session.php,theme.php
プロジェクトルート配下のディレクトリ
database/ (database/)
- 役割: データベース関連ファイルを配置。マイグレーション、シーダー、ファクトリーを定義
- 配置ルール: Laravel標準の構造に従う
- factories/: モデルファクトリー。テストデータ生成用
- migrations/: データベースマイグレーション。スキーマ変更を定義
- seeders/: データベースシーダー。初期データ投入用
- 注意: マイグレーションは時系列で管理され、
YYYY_MM_DD_HHMMSS_create_table_name.php形式で命名
routes/ (routes/)
- 役割: ルート定義ファイルを配置。HTTPリクエストのルーティングを定義
- 配置ルール: Laravel標準の構造に従う
- web.php: Webルート(セッション、CSRF保護、Cookie暗号化が有効)
- console.php: Artisanコマンドのスケジュール定義
- 注意: APIルートは
routes/api.phpに配置(現在は未使用)。Laravel 12ではbootstrap/app.phpでルートファイルを登録
resources/ (resources/)
- 役割: 未コンパイルのアセット(ビュー、言語ファイル、CSS等)を配置
- 配置ルール: Laravel標準の構造に従う
- views/: Bladeビューテンプレート。
app/Admin/やapp/Frontend/配下のビューは近接配置ルールに従う - lang/: 多言語翻訳ファイル。
{locale}/{group}.php形式で配置 - css/: CSSファイル。Tailwind CSSの設定ファイル等
- 注意:
app/Admin/配下のビューは近接配置ルールに従い、app/Admin/.../Views/に配置。resources/views/は標準的なLaravelビュー用
bootstrap/ (bootstrap/)
- 役割: アプリケーション起動時の設定ファイルを配置
- 配置ルール: Laravel標準の構造に従う
- app.php: アプリケーション起動設定。ミドルウェア、例外処理、ルートファイルの登録
- providers.php: サービスプロバイダの登録
- cache/: 設定キャッシュ、ルートキャッシュ等(自動生成)
- 注意: Laravel 12では
app.phpでミドルウェアやルートを登録。providers.phpでサービスプロバイダを登録
tests/ (tests/)
- 役割: PHPUnitテストファイルを配置。アプリケーションのテストを定義
- 配置ルール: Laravel標準の構造に従う
- Feature/: フィーチャーテスト。HTTPリクエスト、データベース操作を含む統合テスト
- Unit/: ユニットテスト。単一のクラスやメソッドのテスト
- 注意: テストは
php artisan testで実行。RefreshDatabaseトレイトを使用する場合は、テスト専用データベースを使用すること
storage/ (storage/)
- 役割: アプリケーションが生成するファイルを配置。ログ、キャッシュ、アップロードファイル等
- 配置ルール: Laravel標準の構造に従う
- app/: アプリケーションが生成するファイル(公開されない)
- framework/: フレームワークが生成するファイル(キャッシュ、セッション等)
- logs/: ログファイル
- 注意:
.gitignoreに含まれているため、バージョン管理されない
public/ (public/)
- 役割: 公開ファイルを配置。Webサーバーのドキュメントルート
- 配置ルール: Laravel標準の構造に従う
- index.php: アプリケーションのエントリーポイント
- assets/: コンパイル済みアセット(CSS、JavaScript、画像等)
- 注意: Viteでコンパイルされたアセットはここに配置される
ヘルパー関数・Service・Facadeの使い分け
ヘルパー関数、Service、Facadeの使い分けを明確にするため、以下の基準に従います。
使い分けの判断フローチャート
1. 状態を持つ? → Yes → Service
↓ No
2. 複数のメソッドが必要? → Yes → Service
↓ No
3. 複雑なビジネスロジック? → Yes → Service
↓ No
4. 単純な変換・フォーマット? → Yes → ヘルパー関数
↓ No
5. Blade/Livewireから直接呼ぶ必要がある? → Yes → Facade(Serviceをラップ)
↓ No
→ Service(依存注入)
比較表
| 観点 | ヘルパー関数 | Service | Facade |
|---|---|---|---|
| 役割 | 純粋関数(状態なし、単純な変換) | ビジネスロジック(状態あり、複数メソッド) | Serviceへの静的アクセスラッパー |
| 状態 | 持たない | 持つ可能性がある(セッション、DB、キャッシュ等) | Serviceの状態を参照 |
| メソッド数 | 単一関数 | 複数メソッド | Serviceのメソッドをラップ |
| 呼び出し方法 | グローバル関数 | 依存注入 | 静的メソッド |
| 使用場所 | どこからでも | コントローラ、サービス、テスト | Blade、Livewire(最小限) |
| テスト容易性 | 高い(純粋関数) | 高い(モック注入可能) | やや低い(モック化が必要) |
| 依存関係 | なし | 明確(コンストラクタで注入) | 隠れる(静的呼び出し) |
| 使用例 | get_image_url(), date_time_format(), theme_asset() |
CartService::addToCart(), PageCompiler::compile() |
Cart::addToCart()(Blade/Livewireから) |
| 推奨度 | 適切な場合に使用 | 基本はこちらを使用 | 最小限の使用(Blade/Livewireから直接呼ぶ場合のみ) |
実践的なルール
ヘルパー関数を使う場合
- ✅ 画像URL生成、日付フォーマット、文字列操作など
- ✅ 純粋関数(同じ入力→同じ出力)
- ✅ 状態を持たない
Serviceを使う場合
- ✅ カート管理、注文処理、ページコンパイルなど
- ✅ 状態を持つ(セッション、DB、キャッシュ)
- ✅ 複数のメソッドを持つ
- ✅ ビジネスロジックを含む
- ✅ 基本はこちらを使用(コントローラ、サービス、テストでは依存注入)
Facadeを使う場合
- ✅ BladeテンプレートやLivewireコンポーネントから直接呼ぶ必要がある場合のみ
- ✅ Laravel標準のFacade(Cache、DB、Auth等)と同じような使い方
- ⚠️ 最小限の使用(それ以外はServiceを依存注入で使用)
関連ドキュメント
- ドメイン境界と責務 - ドメイン設計の基本方針と責務定義
- 全体アーキテクチャ - システム全体のアーキテクチャ設計
- AdminViewsResource近接配置 - Filamentビューの近接配置ルール
- API層の命名規則 - API Resourceの命名規則と実装方針