Skip to content

Latest commit

 

History

History

Folders and files

README.md

gplanchat/durable-bridge-illuminate

Durable execution on a Laravel database connection.

Read-only mirror. This repository is a subtree-split of gplanchat/durable-dev, published so Composer can require this package on its own. Issues and pull requests are disabled here — open them on the monorepo.

The tests are in the monorepo, not here. This split carries source only. What covers it is tests/unit/Bridge/Illuminate/ in the monorepo, run by its unit suite.

Documentation: durable.rocks.

Four stores behind the four storage ports, written against Illuminate\Database\Connection — the query builder, not Eloquent. No Doctrine anywhere in the tree.

Port This package
EventStoreInterface IlluminateEventStore
WorkflowMetadataStore IlluminateWorkflowMetadataStore
ChildWorkflowParentLinkStoreInterface IlluminateChildWorkflowParentLinkStore
WorkflowRunCatalogInterface (+ WorkflowRunProjectionInterface) IlluminateWorkflowRunCatalog

Give the stores a connection of their own

DUR030 sells durable execution on one database with no cluster. It does not need, and Durable does not offer, a transaction shared with business code. Pass these stores a connection of their own, not the application's default one (DUR054): on a shared connection, the transactions the event store opens to fence a pass (DUR053) nest inside the application's, so a business rollback erases journal events and a claim stays invisible to the other workers until the business code commits.

An activity that writes and then dies runs again on replay. The answer is to make it idempotent.

Why the query builder and not Eloquent

Journal rows are append-only facts: execution_id, event_type, a JSON payload, recorded_at. DB::table() writes them directly. An ActiveRecord model would add identity, events, casts and timestamps over a row whose entire contract is that it is never mutated.

What proves it

Every store extends its conformance suite from DUR041 — the same cases the in-memory reference and the DBAL bridge run. The journal suite joins the replay tier: a real workflow with an activity, a timer and two side effects runs against this store and against InMemoryEventStore, and what replay reads back is compared. A payload deformed by a round trip breaks replay silently, not at write time, and that comparison is what catches it.

tests/unit/Bridge/Illuminate/  — 44 cases, four ports

Not in this package

  • A queue, and jobs to put on it. The exclusion between two workers, Queue\ResumeLock, is in gplanchat/durable-laravel, and so are the jobs. Nothing here decides which queue.

  • The Durable service provider. Registering stores, binding ports, adding worker commands — that belongs to the Laravel integration package. A set of stores does not decide how an application wires them.

    The provider this package does ship, DurableIlluminateServiceProvider, registers nothing. It does the one thing no other package can do for it: tell Laravel where its migrations are.

One resume at a time

Queue\ResumeLock, the per-execution lock that keeps two workers from resuming the same execution, lives in gplanchat/durable-laravel (Gplanchat\Durable\Laravel\Queue\ResumeLock), which uses it on every backend. If you wire these stores yourself, require that package for the lock.

Install

composer require gplanchat/durable gplanchat/durable-bridge-illuminate
php artisan migrate

The four tables ship as a migration, loaded straight from the package — migrate is enough. Publishing is for when you want to edit them, and from that point they are yours:

php artisan vendor:publish --tag=durable-migrations

Keep the published file's name. Laravel keys migrations by basename and lets database/migrations win the tie — that shadowing is exactly what makes your copy the one that runs. Rename it and the two become two migrations, both run, and the second fails on table "durable_events" already exists with the database half migrated.

Schema\DurableSchema still creates the tables on demand. That is what a test and a worker booting on an empty database use; an application uses the migration. Two ways to create the same four tables is two chances to drift, so MigrationMatchesSchemaTest renders the DDL of both — against MySQL's grammar, which carries lengths and indexes where SQLite discards them — and compares them statement for statement.

MIT. See LICENSE.