When would you choose Spring Modulith Moments in production versus a real scheduler, and what distributed-deployment concerns arise?
answer
- Moments = coarse, module-local, testable via TimeMachine
- per-JVM → N instances fire N times
- fix: idempotency + ShedLock / distributed lock
- Quartz/cron for precision, misfire, clustered single-run
- event publication registry for restart-safe listeners (not dedup)
basics
~10 sUse Moments for coarse, in-process, module-local reactions to elapsed time (daily/monthly rollups) where testability via TimeMachine matters. Use Quartz/cron/ShedLock when you need cron precision, missed-fire recovery, or single-firing across a clustered deployment.
solid answer
~50 sMoments is ideal when time-passage logic belongs inside a module, is coarse-grained (hour/day/month boundaries), and benefits from decoupling and deterministic testing via TimeMachine — e.g. 'expire drafts daily', 'emit monthly rollup'. Its limits matter in production: it offers hour-level (not cron) precision, and in a multi-instance deployment each JVM tracks time independently, so every instance would fire the same DayHasPassed — you'd get duplicate work unless listeners are idempotent or you add a distributed lock (e.g. ShedLock). It also has weaker guarantees around missed fires if the app was down across a boundary. So: for cron precision, guaranteed single execution across a cluster, retries/missed-fire handling, or externally observable jobs, prefer Quartz/Spring @Scheduled + ShedLock or an external scheduler. Moments and a real scheduler can coexist — Moments for module choreography, the scheduler for infra-level timing.
go deeper
Know Moments is for simple time-based reactions and a scheduler is for precise timed jobs.
Contrast granularity/precision and know listeners should be idempotent.
Identify the per-JVM duplicate-firing issue and propose idempotency + a distributed lock; know when to prefer Quartz.
Architect a coherent split (Moments for choreography + ShedLock for singletons + scheduler for cron/ops), address restart-safety via the publication registry, and set zone/granularity policy.
## Framing the decision Moments turns elapsed time into in-process Spring events; a scheduler (Spring `@Scheduled`, Quartz, or an external cron/queue) triggers jobs on a defined timetable. They overlap but optimise for different things. ## Where Moments shines - **Domain-local time reactions**: the logic ('expire drafts after a day', 'close the billing month') conceptually belongs to a module and should live with it, reacting to `DayHasPassed`/`MonthHasPassed` rather than a cron bean wired elsewhere. - **Decoupling**: producers of time and consumers of time are separated; multiple modules can react to the same boundary independently. - **Testability**: `TimeMachine.shiftBy(...)` makes 'once a month' logic instant and deterministic — a major advantage over cron jobs that are painful to test. - **Simplicity**: no extra scheduler infrastructure for coarse needs. ## Where a real scheduler wins - **Precision**: Moments works at boundary granularity (`HOURS`/`DAYS`), not arbitrary cron like '0 15 3 * * MON'. For specific times/expressions use `@Scheduled(cron=...)` or Quartz. - **Missed-fire / recovery semantics**: if the app is down across a boundary, Moments' guarantees about catching up are limited; Quartz has explicit misfire policies and persistent job stores. - **Clustering / single execution**: this is the big one — see below. - **Operational visibility**: external schedulers/job stores give dashboards, history, and retry that in-process events don't. ## The distributed-deployment concern (critical) Moments is **per-JVM**: each running instance advances its own notion of time and publishes its own `DayHasPassed`. In a horizontally scaled deployment with N instances, a naive daily listener runs **N times** — duplicating side effects (N monthly invoices, N digest emails). Mitigations: - Make listeners **idempotent** (safe to run repeatedly) — always good practice. - Add a **distributed lock** so only one instance acts, e.g. **ShedLock** around the handler, or a DB advisory lock / 'claim this period' row. - Or move genuinely cluster-singleton work to Quartz with a clustered `JobStore`, keeping Moments for local, idempotent reactions. ## Reliability nuances - Moments events are in-memory Spring events; if you need **guaranteed, at-least-once, restart-surviving** delivery of the *downstream* work, combine with Spring Modulith's **event publication registry** (`@ApplicationModuleListener` + persisted event publication log) so incomplete listener executions are retried after restart. That addresses listener reliability but not the multi-instance duplicate-firing problem — you still need locking/idempotency. - Zone/DST: boundaries are computed in the configured `zone-id`; policy choices affect when 'a day passes'. ## A pragmatic architecture - Moments for **module choreography** on coarse boundaries, with **idempotent** listeners. - ShedLock (or equivalent) where a boundary reaction must run **once per cluster**. - Quartz/external scheduler for **cron-precise**, **operationally visible**, or **misfire-sensitive** jobs. - Standardise time reads via the `Clock`/`Moments` abstraction so tests can drive everything with `TimeMachine`. ## Interview-level judgement The strong answer names the **per-JVM duplicate-firing** trap and the **precision/misfire** gaps, proposes **idempotency + distributed lock** as the fix, and frames Moments and schedulers as **complementary**, not either/or.
- Three app instances each fire DayHasPassed and you send 3 duplicate daily emails. How do you fix it without abandoning Moments?Make the listener idempotent and guard the side effect with a distributed lock (e.g. ShedLock) or a 'claim this day' row/advisory lock so exactly one instance performs the work; the others no-op.
- Does Spring Modulith's event publication registry solve the multi-instance duplicate firing?No. The registry gives restart-safe, at-least-once listener execution (retrying incomplete publications). Each instance still generates its own Moments events, so you still need idempotency plus locking to avoid duplicate work across the cluster.
- When is Quartz clearly the better choice than Moments?When you need cron-expression precision, guaranteed single execution across a cluster, explicit misfire/recovery policies, persistent job history, or operational dashboards — things in-process time events don't provide.
saying these in an interview costs you the question
- Assuming Moments fires exactly once across a multi-instance deployment
- Treating Moments as a drop-in for cron-precise scheduling
- Believing the event publication registry deduplicates across instances
- Ignoring zone/DST effects on boundary timing