Clear expired reservations out of carts overnight

Java · Spring Boot · intermediate · greenfield

Carts hold stock for 30 minutes per line (`reservedUntil`), and nothing ever clears the stale lines, so abandoned carts keep showing items that are long gone from the reservation system. This adds a nightly job that walks all carts, drops the lines whose reservation has lapsed, and saves the carts it changed. The pass logs how many lines it removed.

Shop backend on Spring Boot 3.3 (Java 21) with Spring Data JPA. Existing, not shown: `Cart` (`getId()`, and `List<CartItem> getItems()`, a `@OneToMany(orphanRemoval = true)` list, so lines removed from it are deleted when the cart is saved; Hibernate's `PersistentBag` wraps an `ArrayList` and iterates with that list's fail-fast iterator), `CartItem` (an `@Entity` with `Long id`, `sku`, `quantity` and `Instant reservedUntil`, read through the accessors `id()`, `sku()`, `quantity()` and `reservedUntil()`, equal by id; ids are unique), `CartRepository` (`List<Cart> findAllWithItems()` loads every cart with its lines in one query; `Cart save(Cart)`), a `Clock` bean, and `@EnableScheduling` on the application class. Only the classes in the diff are new.

Requirements

Files touched

--- src/main/java/com/acme/shop/cart/CartCleanupService.java
+package com.acme.shop.cart;
+
+import java.time.Clock;
+import java.time.Instant;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class CartCleanupService {
+
+    private final CartRepository carts;
+    private final Clock clock;
+
+    public CartCleanupService(CartRepository carts, Clock clock) {
+        this.carts = carts;
+        this.clock = clock;
+    }
+
+    /**
+     * Removes every cart line whose reservation has run out and returns how
+     * many lines were removed. A line is expired once {@code reservedUntil}
+     * is not after the current instant.
+     */
+    @Transactional
+    public int removeExpiredItems() {
+        Instant now = clock.instant();
+        int removed = 0;
+        for (Cart cart : carts.findAllWithItems()) {

Review this PR

Java practice