Workflow Pattern — cron + queue + Janus

Core Pattern

iec-cron → POST /internal/cron/{trigger}
  Your service: fetch pending items, fan out jobs
    → POST {IEC_QUEUE_URL}/jobs (one per item)
      iec-queue → POST /internal/jobs/{worker}
        Your worker: process one item, Septor audit

catalog-info.yaml

spec:
  schedules:
    - name: nightly-sync
      cron: "0 2 * * *"
      endpoint: /internal/cron/nightly-sync
      timezone: America/Denver
      timeoutMs: 60000
  queues:
    - name: sync-record
      endpoint: /internal/jobs/sync-record
      concurrency: 10
      retries: 3
      timeoutMs: 30000
  internalDependencies:
    - service: iec-queue   # IEC_QUEUE_URL
    - service: septor      # SEPTOR_URL

Trigger (fan-out) — return 200 FIRST

app.post('/internal/cron/nightly-sync', async (req, res) => {
  res.json({ success: true })  // respond immediately
  try {
    const records = await getPendingRecords()
    await Promise.allSettled(records.map((r) =>
      fetch(`${process.env.IEC_QUEUE_URL}/jobs`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ namespace: 'my-service', queue: 'sync-record', data: { recordId: r.id } }),
      }).catch((err) => logger.warn({ err }, 'Enqueue failed'))
    ))
  } catch (err) { logger.error({ err }, 'Fan-out failed') }
})

Worker — check idempotency, return 500 to retry

app.post('/internal/jobs/sync-record', async (req, res) => {
  const { jobId, data, attempt } = req.body
  if (await isAlreadySynced(data.recordId)) return res.json({ success: true, skipped: true })
  try {
    await syncRecord(data.recordId)
    septor.emit('record.synced', { entityId: data.recordId, data: { jobId }, metadata: { who: 'worker' } })
      .catch((err) => logger.error({ err }, 'Septor failed'))
    res.json({ success: true })
  } catch (err) {
    logger.error({ jobId, attempt, err }, 'Sync failed')
    res.status(500).json({ success: false })  // triggers retry
  }
})

Gas

OperationGas
iec-cron fires (prod)5 tokens
iec-queue job0 (platform)
Internal HTTP0

Anti-Patterns

  • Awaiting all fan-out before responding 200 → cron timeout
  • setTimeout for scheduling → use iec-cron
  • Rolling your own BullMQ → use iec-queue
  • No idempotency check in worker → dupes on retry

Last updated: July 15, 2026