Skip to content

Latest commit

 

History

History
128 lines (102 loc) · 4.49 KB

File metadata and controls

128 lines (102 loc) · 4.49 KB

Spring Quickstart

Threadmill's Spring Boot integration is the lowest-boilerplate path. It still uses at-least-once delivery: handlers must be idempotent because recovery can run the same logical job more than once.

Spring Boot 4.x. This module requires Spring Boot 4.0 or newer. Spring Boot 3.x is not supported and will fail fast at application startup with a clear message.

Dependencies

Use Java 25 and add the Spring module plus one store. These examples target the unreleased 1.0.0 candidate; see the release status.

implementation("com.hemju.threadmill:threadmill-spring-boot:1.0.0")
implementation("com.hemju.threadmill:threadmill-store-postgres:1.0.0")
// or: implementation("com.hemju.threadmill:threadmill-store-redis:1.0.0")

The default Spring enqueue mode is after_commit: returned ids are reserved before persistence, and the job insert can fail after the business transaction commits. Observe AfterCommitEnqueueFailure, or choose join_transaction with the same PostgreSQL DataSource for atomic business/job writes. Cross-datastore atomicity requires an application-owned durable outbox. See transaction modes.

Handler

package com.example.mail;

import com.hemju.threadmill.core.handler.JobExecutionContext;
import com.hemju.threadmill.core.handler.JobHandler;
import com.hemju.threadmill.core.handler.JobPayload;
import com.hemju.threadmill.spring.Job;
import com.hemju.threadmill.spring.JobScheduler;
import org.springframework.stereotype.Component;

public record SendEmail(String to, String subject) implements JobPayload {}

@Component
@Job(queue = "email", timeout = "PT2M", maxAttempts = 5)
final class SendEmailHandler implements JobHandler<SendEmail> {
    @Override
    public void run(SendEmail payload, JobExecutionContext ctx) {
        ctx.log("sending " + payload.subject() + " to " + payload.to());
        // Make the external side effect idempotent.
    }
}

Enqueue

@RestController
final class MailController {
    private final JobScheduler jobs;

    MailController(JobScheduler jobs) {
        this.jobs = jobs;
    }

    @PostMapping("/mail")
    JobId send(@RequestBody SendEmail command) {
        return jobs.enqueue(SendEmailHandler.class, command);
    }
}

JobScheduler verifies the handler/payload pair at enqueue time. If two @Job beans handle the same payload type, startup fails and names both handlers.

Enqueue And Transactions

By default the auto-configured JobScheduler is transaction-aware: a job enqueued inside an active Spring transaction is held until afterCommit. A rollback leaves nothing in the queue, so a job can never pick up state that the surrounding transaction did not commit.

@Transactional
public void scheduleWelcome(UserCreated created) {
    userRepo.save(created.toUser());        // pending write
    jobs.enqueue(SendEmailHandler.class, new SendEmail(created.email(), "Welcome"));
    // The job insert is attempted after commit; observe AfterCommitEnqueueFailure.
}

The returned JobId is reserved synchronously (UUIDv7 is generated client side), but the deferred store insert is attempted only after the transaction commits. Within that transaction, store.findById(id) cannot yet find the job. Outside a transaction the default wrapper inserts immediately. The immediate mode also inserts immediately inside a transaction, but that job can run before the business transaction commits and survives its rollback:

threadmill:
  spring:
    enqueue-mode: immediate

For Spring + Postgres, threadmill.spring.enqueue-mode=join_transaction makes normal enqueue, scheduled enqueue, bulk enqueue, and dedup enqueue part of the caller's SQL transaction.

Configure A Store

Without a configured durable store or an application-provided JobStore, startup fails. For disposable local development only, explicitly set threadmill.store.memory.enabled=true; all jobs are lost when that process stops. Configure PostgreSQL or Redis for durable work.

threadmill:
  store:
    redis:
      mode: standalone
      uri: redis://localhost:6379

For Postgres, add threadmill-store-postgres and define a normal Spring DataSource. Spring auto-configures PostgresJobStore from that DataSource and runs pending Threadmill schema migrations by default. Use threadmill.store.postgres.schema-mode=validate if your deployment pipeline applies the DDL separately. See postgres-schema.md for manual SQL and reset guidance.