directorytree/opensearch-migrations
OpenSearch index migrations for Laravel. Create, configure, and run versioned index migrations with a familiar artisan workflow, including mappings, settings, index and alias prefixes, and publishing config files.
Architecture fit. The package occupies a narrow, well-defined slot: it makes OpenSearch index definitions first-class, versioned artifacts in a Laravel codebase, mirroring php artisan migrate semantics. For any Laravel service whose search behavior depends on a specific mapping (Scout-backed models, custom query builders, aggregations), that is a genuine gap — mappings currently live in console history or wiki pages, and drift is discovered in production. The design is deliberately conservative: one artisan-generated PHP class per change, an up()/down() pair, a fluent Mapping/Settings builder, and a database ledger table (opensearch_migrations) that records filenames and batches. It does not touch queries, indexing, or reindex orchestration, which keeps the integration surface small but also caps the value — this is a schema tool, not a search data platform.
Integration feasibility. Installation is two publishes (opensearch.php from directorytree/opensearch-client, then opensearch-migrations.php) plus a PATH-visible migrations directory at base_path('opensearch/migrations'). Command surface maps 1:1 onto the database commands a Laravel team already knows (migrate, rollback, reset, refresh, fresh, status), which minimizes the learning curve and the documentation burden we'd own. Prefix config (index_name_prefix, alias_name_prefix) defaults to SCOUT_PREFIX, so an existing Scout deployment inherits environment namespacing for free. The real feasibility gate is the transitive chain: opensearch-migrations → opensearch-client → opensearch/php-client, all three of which must satisfy our PHP and Laravel version constraints. That must be resolved before we commit, not after.
Technical risk. Ranked:
Index::create() succeeds and the ledger insert fails, the next run retries an already-applied migration and OpenSearch rejects the duplicate. migrate:status will report pending while the cluster says exists. Nothing in the package detects or repairs this; the runbook is manual.migrate:rollback executes down(), which for index migrations is typically Index::dropIfExists() — i.e. deleting a serving index. migrate:fresh drops all indexes. These are correct schema-migration semantics and dangerous operational verbs.opensearch:migrate invocations is not documented. Given two stateful systems instead of one, a racing deploy is materially more likely to produce the divergence in (1) than the database equivalent.v1.0.0 in the recent past, v1.1.0 already changed fake-index assertion callback semantics (a test-API break between minor versions), zero dependents on the registry. Low adoption means low defect discovery and a real abandonment risk.Key questions.
opensearch:migrate take a lock, and is it safe under N replicas running the deploy job concurrently? (Read the source; do not assume.)opensearch-client and opensearch/php-client versions does our Laravel/PHP matrix resolve to, and who owns patching that chain if it drifts?down() allowed to drop a live index, or do we require index-plus-alias swap instead?Stack fit. Adopt only if we are on Laravel with Scout or a hand-rolled OpenSearch integration and we currently lack any versioned index-definition story. If search is a secondary feature with two indices, the ceremony may exceed the payoff; if mappings are load-bearing for relevance or aggregations, the case is strong. The package composes cleanly with the existing directorytree client and adapter rather than replacing them, so it is an additive layer, not a migration of transport.
Migration path. The critical path is baselining, not installation. Sequence: (1) resolve the dependency chain and pin versions in composer.json; (2) run both publishes; (3) for every index currently serving traffic, author a migration whose up() reproduces the live mapping verbatim and insert its filename plus batch number into opensearch_migrations by hand; (4) only then begin adding incremental migrations. Skipping step 3 means the first opensearch:migrate either errors on an existing index or gets "fixed" by deletion — the worst outcome. Greenfield environments skip the baseline and start clean.
Compatibility. Verify PHP and Laravel constraint satisfaction across all three packages before committing, and add a CI job that installs the resolved set from a clean lockfile. Treat opensearch-client minor bumps as potentially breaking until proven otherwise given the v1.1.0 precedent. Keep the Index facade behind a thin internal service or interface so application code never references the package directly — roughly an hour of work that buys the option to swap or vendor the implementation.
Sequencing.
php artisan opensearch:migrate --no-interaction to the deploy pipeline immediately after php artisan migrate --force, in the same serialized job.opensearch/migrations exists and is non-empty in the artifact (it lives at project root, outside app/, and partial-copy or broad .gitignore will silently produce a no-op).Maintenance. Small surface: config, a migrations directory, and a ledger table. Ongoing cost is dominated by dependency governance rather than code — we are maintaining a three-link chain where the top link has one maintainer and near-zero public dependents. Budget for periodic version-audit work and a contingency plan (vendor the package, or fork) that is documented rather than improvised.
Support. No vendor, no SLA, README-only documentation. Internal support means a named owner, a runbook, and a team convention: never run migrate:fresh outside local or CI, never run migrate:rollback in production without first confirming what that specific down() does. Expect onboarding cost to be low for the command surface and moderate for the gotchas — the failure modes are not obvious from the README and must be taught.
Scaling. The migration path scales fine (it is a deploy-time batch operation). What does not scale automatically is change management around mapping evolution. Field types cannot be altered in place — text to keyword requires a new index and a reindex — so changes that look like one-line edits are actually multi-migration sequences with backfill windows. The package's alias config hints at alias-based access but ships no swap/reindex orchestration; zero-downtime reindexing is our build, not theirs. Plan mapping changes as a distinct workstream with its own review, capacity estimate, and cutover step, not as a routine migration.
Failure modes to instrument.
migrate:status output against actual index existence and mappings; alert on mismatch.opensearch/migrations disappears between commits.Ramp-up. A Laravel developer is productive in under an hour given the command parity with database migrations. Realistic timeline: day one for a spike and a greenfield index; one to two weeks to baseline existing indices, verify locking behavior, wire the deploy step, and write the runbook; ongoing cost is low but non-zero. The decision hinges less on the code — which is compact and idiomatic — than on whether we are willing to own a single-maintainer dependency in the deploy path of a production search tier. Given the narrow blast radius if kept behind a seam and confined to index definitions, the risk is acceptable for teams that already treat mappings as critical; it is disproportionate for teams that do not.
How can I help you explore Laravel packages today?