3 min readCalm pace · scan the outline anytime

Laravel migrations

Per-module migrations, table ownership, and avoiding cross-module foreign keys for business joins.

Migrations

Each module owns the tables that persist its Domain. Migrations typically live with the module’s persistence adapter:

Ordering/Infrastructure/Persistence/Eloquent/Migrations/

Load them from the module ServiceProvider (path may vary by host convention).

Guidelines

PracticeRationale
Colocate migrations with the owning moduleBounded context owns its data
Avoid cross-module foreign keys for business joinsPrevents hidden coupling through the schema
Read peer data via ACL / EventsMatches Core replaceability
Factories beside Eloquent modelsTest data stays in Infrastructure
Name tables with a clear module prefix when helpfulReduces collisions in large hosts

Ownership checklist

Before adding a column or FK:

  1. Which module’s Domain Entity owns this fact?
  2. If another module needs it now → ACL, not a cross-module Eloquent relation in Application.
  3. If another module reacts later → Domain Event with a rich payload.
  4. If you are tempted to “join everything in one query for Filament” → prefer a read model / ACL query port still owned by the consumer’s Infrastructure.

Loading migrations (illustrative)

// OrderingServiceProvider::boot
$this->loadMigrationsFrom(
    __DIR__ . '/../../Infrastructure/Persistence/Eloquent/Migrations'
);

Anti-patterns

Anti-patternFix
Shared “god” migrations folder with every module’s tables unlabeledColocate per module
FK from Ordering into Warehouse tables for convenience joinsACL / Events; duplicate identifiers as values if needed
Business enums encoded only as DB check constraints with no Domain EnumDomain Enum + mapping in Repository

Schema design is a host concern; ownership and coupling are Core concerns.

Core reference: Ports & persistence · Cross-module ACL.

Modular Hexagonal Domain-Driven Design
Core 1.0.0-draft