Review

Drain cold-chain outbox before broker ACK

Vaccine lot handoffs write shipment.handed_off into a Postgres outbox in the same transaction as the inventory transfer. Add a FastAPI lifespan poller that drains claimed rows so logistics, customs, and the 2–8°C monitoring service stay in sync without dual-writes from the request path.

FastAPITier 7outboxmessagingconsistency

Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.

app/workers/coldchain_outbox.py+22-0
5252async def drain_once(self, limit: int = 100) -> int:
5353 async with self.session_factory() as session:
54+ # Claim work, drop the row, then fan-out — keeps the table lean under load.
55+ result = await session.execute(
56+ text(
57+ "SELECT id, topic, payload FROM coldchain_outbox "
58+ "ORDER BY id ASC LIMIT :lim FOR UPDATE SKIP LOCKED"
59+ ),
60+ {"lim": limit},
61+ )
62+ rows = result.mappings().all()
63+ if not rows:
64+ return 0
65+
66+ ids = [r["id"] for r in rows]
67+ await session.execute(
68+ text("DELETE FROM coldchain_outbox WHERE id = ANY(:ids)"),
69+ {"ids": ids},
70+ )
71+ await session.commit()
72+
73+ for r in rows:
74+ await self.broker.publish(r["topic"], r["payload"])
75+ return len(rows)
5476
5577
5678@asynccontextmanager
5779async def lifespan(app: FastAPI):