Tell sales about high-value orders
Java · Spring Boot · beginner · greenfield
Sales wants to call customers who place very large orders the same day, so orders over 10k EUR now post to their Slack channel after commit. Rather than calling Slack straight from the order code, alerts go through a small `AlertDispatcher`: channels register under a name, callers pick the channels and a severity, and a failing channel is logged without affecting the others. Adding email or PagerDuty later is one `register` call. Tested by placing a 12,500 EUR order locally against a test webhook.
Shop backend on Spring Boot 3.3 (Java 21). `OrderService.place` (existing) publishes `OrderPlaced(long orderId, BigDecimal total)` inside its `@Transactional` method, so a `@TransactionalEventListener` runs on the request thread after commit, and not at all on rollback. `SlackClient` (existing bean, package `com.acme.shop.slack`) has `void post(String webhookUrl, String text)` with 2-second connect and read timeouts and throws the unchecked `SlackException` on any failure (its message never contains the webhook URL); a synchronous post after commit is acceptable for this volume (a few such orders a day).
Requirements
- When an order over 10,000.00 EUR is placed (strictly more; every order is in EUR), post one message to the sales team's Slack channel once the order has committed. The message names the order number and total, e.g. `High-value order #1042: 12500.00 EUR`; sales does not mind the exact wording.
- The message is a nice-to-have: if Slack fails, log a warning and carry on. The order is never affected.
- Post with the existing `SlackClient` to the webhook in `alerts.sales-webhook-url`, which is already set in every environment.
- Scope: this is the only alert this service sends, Slack is its only destination, and product has no other alerts or channels on the roadmap. The team's rule (CONTRIBUTING.md) is no abstractions until there is a second real use: keep the change proportional to the requirement.
Files touched
- src/main/java/com/acme/shop/alerts/AlertDispatcher.java
- src/main/java/com/acme/shop/orders/HighValueOrderAlert.java
--- src/main/java/com/acme/shop/alerts/AlertDispatcher.java
+package com.acme.shop.alerts;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import com.acme.shop.slack.SlackClient;
+import com.acme.shop.slack.SlackException;
+
+/** Routes alerts to named channels, so new destinations can be added without touching callers. */
+@Component
+public class AlertDispatcher {
+
+ /** A destination for alerts. Register more with {@link #register}. */
+ @FunctionalInterface
+ public interface Channel {
+ void send(String text);
+ }
+
+ public enum Severity { INFO, WARNING, CRITICAL }
+
+ private static final Logger log = LoggerFactory.getLogger(AlertDispatcher.class);
+
+ private final Map<String, Channel> channels = new ConcurrentHashMap<>();
+