From Chaos to Production: Building a Real Food-Delivery Backend with Spring Boot
The article chronicles the step‑by‑step evolution of a small team’s chaotic food‑delivery backend into a production‑ready system, detailing how they introduced layering, transactions, security, caching, async processing, messaging, observability, scalability, resilience, containerization, and testing using Spring Boot, Kafka, Redis, JWT, and Docker.
Stage 1 – Get Something Running
The initial implementation lives entirely in a controller with no layering, validation, or error handling, e.g.:
@RestController
@RequestMapping("/api")
public class OrderController {
@PostMapping("/order")
public String createOrder() {
return "order created";
}
@GetMapping("/menu")
public List<String> getMenu() {
return List.of("Burger", "Pizza", "Drink");
}
}The system can place orders, but the code quickly becomes unmaintainable.
Stage 2 – Introduce Layering
As requirements grow (parameter validation, exception handling, new fields, new endpoints), business logic accumulates in the controller, causing global side‑effects and exponential debug cost. The team restructures the project into packages:
/com/icoderoad/order
├── controller
├── service
├── repository
├── modelController now delegates to a service:
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public Order create(@RequestBody OrderRequest request) {
return orderService.createOrder(request);
}
}
@Service
public class OrderService {
public Order createOrder(OrderRequest request) {
// business logic
return new Order();
}
}Dependency injection becomes a practical tool to avoid collapse.
Stage 3 – Ensure Data Consistency with Transactions
A production issue appears: an order is marked as completed even though payment failed. The root cause is non‑atomic database operations. The solution is to annotate the service method with @Transactional:
@Service
public class OrderService {
@Transactional
public void createOrder(OrderRequest request) {
saveOrder();
processPayment();
}
}This guarantees “all‑or‑nothing” behavior.
Stage 4 – Secure the System with Authentication
Another bug lets user A see user B’s orders because the client supplies the user ID. The team replaces client‑provided IDs with server‑side authentication, introducing a user model, role hierarchy (Customer/Restaurant), password hashing, and JWT tokens:
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}All endpoints now rely on the token rather than a request parameter.
Stage 5 – Add Caching for Performance
Frequent menu queries cause repeated database hits. The team adds Spring Cache with Redis backing:
@Cacheable(value = "menu")
public List<MenuItem> getMenu() {
return menuRepository.findAll();
} spring:
cache:
type: redisPerformance improves because the bottleneck is a data‑access strategy, not raw CPU speed.
Stage 6 – Move to Asynchronous Processing
Synchronous payment simulation and email notification block the order thread, inflating response time. The code is refactored to use @Async for email sending:
@Async
public void sendEmailAsync() {
// send email
}Latency drops, but failures in the async task become invisible, highlighting a new reliability concern.
Stage 7 – Achieve Reliable Delivery with Messaging
Intermittent email failures and lack of retries lead to lost events. The team introduces Kafka:
kafkaTemplate.send("order-topic", orderEvent); @KafkaListener(topics = "order-topic")
public void handle(OrderEvent event) {
sendEmail(event);
}Retry mechanisms, dead‑letter queues, and idempotent handling are added, giving the system reliable message delivery.
Stage 8 – Continuous Security Hardening
Token expiration, privilege escalation, and role abuse appear. The team adopts RBAC, token lifecycle management, and fine‑grained endpoint authorization, treating security as an evolving boundary.
Stage 9 – Build Observability
When orders fail without clear reasons, the team adds structured logging, Spring Boot Actuator, metrics, ELK stack, and Grafana:
management:
endpoints:
web:
exposure:
include: "*"This enables queries such as orders per minute, slowest endpoint, and failing services.
Stage 10 – Design for Scalability
Faced with a potential ten‑fold traffic surge, the architecture evolves to include an API gateway and load balancer, shifting scalability from a post‑hoc patch to a design‑time concern.
Stage 11 – Add Resilience with Circuit Breakers
Payment service slowdown can cascade into an avalanche. A circuit‑breaker annotation protects the system:
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallback")
public String callPayment() {
return paymentClient.pay();
}When failures occur, the call is short‑circuited, preventing system‑wide collapse.
Stage 12 – Containerize the Environment
The application is packaged with Docker Compose, defining services for the app, MySQL, Redis, and Kafka:
version: '3'
services:
app:
build: .
mysql:
image: mysql:8
redis:
image: redis
kafka:
image: bitnami/kafkaNow the system is a reproducible runtime environment rather than just source code.
Stage 13 – Establish a Test Suite
To verify correctness, the team adds unit tests, integration tests, and Cucumber BDD scenarios, e.g.:
Scenario: 用户下单
Given 用户已登录
When 提交订单
Then 订单应创建成功Technical and business concerns speak the same language.
Conclusion
The journey shows that Spring Boot, Kafka, Redis, and JWT are not learning goals themselves; they are pragmatic choices forced by emerging problems. The real skill lies in progressively imposing order on chaos, moving from writing code to building a robust, observable, and maintainable system.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
LuTiao Programming
LuTiao Programming is a friendly community offering free programming lessons. We inspire learners to explore new ideas and technologies and quickly acquire job-ready skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
