Keeping PostgreSQL and MongoDB consistent when a single user action (publishing a teaching path) writes to both.
constraint: There is no cross-store transaction. A MongoDB write that fails after a PostgreSQL commit leaves the system reporting a published path that the frontend cannot fully read. Doing the writes in the opposite order has the symmetric failure mode.
All cross-store writes go through a service layer (e.g. StoreTeachingPathValidationService) that does PostgreSQL first (transactional), then MongoDB. If MongoDB fails, the service rolls back the PostgreSQL row through the same use case and emits an event for the retry queue. The cache (Redis) is invalidated last so a partially-written state is never readable.
// Pattern (simplified):
DB::transaction(function () use ($payload) {
$row = TeachingPath::create($payload->structured()); // PostgreSQL
try {
$this->mongo->upsertDraft($row->id, $payload->flexible());
} catch (\Throwable $e) {
// throw rolls back the PG transaction
throw new MongoSyncFailed($row->id, $e);
}
Cache::tags('teaching_paths')->forget($row->id);
});