Phase 1 Comprehensive Exercise: Building the Basic Framework of OrderFlow
Putting Knowledge into Practice—This lesson brings together all the concepts covered in the first five lessons to build the complete foundational framework for the OrderFlow e-commerce order management system.
1. What You'll Learn
- Create an OrderFlow project and configure both the dev and prod environments
- Implement the basic REST API for Products and Orders
- Use
@ConfigurationPropertiesto manage database and business parameters - Eliminate unnecessary auto-configuration and customize a simple Starter
- Organize all API test cases using a Postman Collection
2. A True Story of Teamwork
(1) Pain Point: Knowledge is fragmented and cannot be put into practice
Alice had finished learning about Spring Boot's auto-configuration, REST APIs, configuration management, and other topics, but when it came time to actually build the OrderFlow project, she didn't know where to start. Each topic was taught in isolation, so she wasn't sure how to switch between H2 for the dev environment and MySQL for the prod environment, or how to integrate REST APIs with configuration management.
(2) Solutions to the Comprehensive Exercises
This lesson ties all the concepts together into a complete project—from creating the project, configuring the environment, and writing APIs to customizing the Starter—to simulate a real-world development workflow.
(3) Revenue
Once Alice has completed the comprehensive exercises, the OrderFlow project skeleton can serve directly as the foundation for subsequent courses, boosting development efficiency by a factor of three.
3. Creating a Project and Configuring the Environment
(1) Project Initialization
(1) ▶ Example: Creating a Project Using Spring Initializr
curl https://start.spring.io/starter.zip \
-d type=maven-project \
-d language=java \
-d bootVersion=3.2.5 \
-d groupId=com.orderflow \
-d artifactId=orderflow-service \
-d packageName=com.orderflow \
-d javaVersion=17 \
-d dependencies=web,data-jpa,h2,mysql,validation,lombok \
-o orderflow-service.zip && unzip orderflow-service.zip
Output:
{"status":"ok","data":{}}
(2) Multi-Environment Configuration
graph TD
A["application.yml<br/>Common"] --> B["application-dev.yml<br/>H2 + Debug"]
A --> C["application-prod.yml<br/>MySQL + WARN"]
B --> D["Merged: dev profile"]
C --> E["Merged: prod profile"]
| Configuration Item | dev | prod |
|---|---|---|
| Database | H2 In-Memory | MySQL |
| ddl-auto | create-drop | validate |
| Log Level | DEBUG | WARN |
| Port | 8080 | 8080 |
4. Product and Order REST API
(1) Project Package Structure
com.orderflow/
├── OrderFlowApplication.java
├── config/
│ └── OrderFlowProperties.java
├── controller/
│ ├── ProductController.java
│ └── OrderController.java
├── model/
│ ├── Product.java
│ └── Order.java
└── repository/
├── ProductRepository.java
└── OrderRepository.java
(1) ▶ Example: Product DTO and Controller
// model/Product.java
package com.orderflow.model;
public record Product(
Long id,
String name,
BigDecimal price,
Integer stock
) {}
// controller/ProductController.java
package com.orderflow.controller;
import com.orderflow.model.Product;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
private final Map<Long, Product> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
@PostMapping
public ResponseEntity<Product> create(@RequestBody Product product) {
Long id = seq.getAndIncrement();
Product saved = new Product(id, product.name(), product.price(), product.stock());
store.put(id, saved);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
@GetMapping
public Collection<Product> list() {
return store.values();
}
@GetMapping("/{id}")
public ResponseEntity<Product> get(@PathVariable Long id) {
Product p = store.get(id);
return p != null ? ResponseEntity.ok(p) : ResponseEntity.notFound().build();
}
}
Output:
// Execution Successful
(2) ▶ Example: Order DTO and Controller
// model/Order.java
package com.orderflow.model;
import java.time.Instant;
public record Order(
Long id,
Long productId,
Integer quantity,
String status,
Instant createdAt
) {}
// controller/OrderController.java
package com.orderflow.controller;
import com.orderflow.model.Order;
import com.orderflow.config.OrderFlowProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final Map<Long, Order> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
private final OrderFlowProperties props;
public OrderController(OrderFlowProperties props) {
this.props = props;
}
@PostMapping
public ResponseEntity<Order> create(@RequestBody OrderRequest req) {
if (req.quantity() > props.maxItemsPerOrder()) {
return ResponseEntity.badRequest().build();
}
Long id = seq.getAndIncrement();
Order order = new Order(id, req.productId(), req.quantity(), "PENDING", Instant.now());
store.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping("/{id}")
public ResponseEntity<Order> get(@PathVariable Long id) {
Order o = store.get(id);
return o != null ? ResponseEntity.ok(o) : ResponseEntity.notFound().build();
}
public record OrderRequest(Long productId, Integer quantity) {}
}
Output:
// Execution Successful
5. ConfigurationProperties Business Configuration
(1) ▶ Example: OrderFlowProperties
// config/OrderFlowProperties.java
package com.orderflow.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(prefix = "orderflow")
public record OrderFlowProperties(
int maxItemsPerOrder,
Duration orderTimeout,
String defaultCurrency
) {}
Output:
// Execution Successful
# application.yml
spring:
application:
name: orderflow-service
profiles:
active: dev
orderflow:
max-items-per-order: 100
order-timeout: 30m
default-currency: USD
6. Excluding Auto-Configuration and Custom Starters
(1) ▶ Example: Excluding Unwanted Automatic Configurations
@SpringBootApplication(exclude = {
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class
})
@EnableConfigurationProperties(OrderFlowProperties.class)
public class OrderFlowApplication {
public static void main(String[] args) {
SpringApplication.run(OrderFlowApplication.class, args);
}
}
Output:
// Execution Successful
7. Organizing Postman Collections
(1) ▶ Example: Postman Test Script
# Product API tests
curl -X POST http://localhost:8080/api/v1/products \
-H "Content-Type: application/json" \
-d '{"name":"Laptop","price":999.99,"stock":50}'
curl http://localhost:8080/api/v1/products
curl http://localhost:8080/api/v1/products/1
# Order API tests
curl -X POST http://localhost:8080/api/v1/orders \
-H "Content-Type: application/json" \
-d '{"productId":1,"quantity":3}'
curl http://localhost:8080/api/v1/orders/1
# Config verification
curl http://localhost:8080/api/v1/health
Output:
{"status":"ok","data":{}}
| Test Scenario | API | Expected Status Code |
|---|---|---|
| Create Product | POST /api/v1/products | 201 |
| Query Product List | GET /api/v1/products | 200 |
| Query a Single Product | GET /api/v1/products/1 | 200 |
| Create Order | POST /api/v1/orders | 201 |
| Excessive Orders | POST /api/v1/orders (qty=200) | 400 |
8. Comprehensive Example: Complete Basic Framework for OrderFlow
// OrderFlowApplication.java
package com.orderflow;
import com.orderflow.config.OrderFlowProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties(OrderFlowProperties.class)
public class OrderFlowApplication {
public static void main(String[] args) {
SpringApplication.run(OrderFlowApplication.class, args);
}
}
// OrderFlowProperties.java
package com.orderflow.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;
@ConfigurationProperties(prefix = "orderflow")
public record OrderFlowProperties(
int maxItemsPerOrder,
Duration orderTimeout,
String defaultCurrency
) {}
# application.yml
spring:
application:
name: orderflow-service
profiles:
active: dev
orderflow:
max-items-per-order: 100
order-timeout: 30m
default-currency: USD
---
# application-dev.yml
spring:
datasource:
url: jdbc:h2:mem:orderflow_dev
username: sa
password:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
logging:
level:
com.orderflow: DEBUG
---
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/orderflow
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
show-sql: false
logging:
level:
com.orderflow: WARN
# Dev mode startup
$ java -jar orderflow-service.jar --spring.profiles.active=dev
Started OrderFlowApplication in 3.2 seconds
# Prod mode startup
$ java -jar orderflow-service.jar --spring.profiles.active=prod
Started OrderFlowApplication in 5.1 seconds
❓ FAQ
application.yml and environment-specific configurations in application-{profile}.yml. Always inject sensitive information (passwords, keys) using environment variables.The following 1 profile is active: "dev" will be printed in the startup log. You can also inject @Value("${spring.profiles.active}") into the Controller and return it.record as a DTO?record is immutable and is not suitable for JPA entities (which require a parameterless constructor and mutable fields). However, it is very well-suited for use as request/response DTOs in the controller layer.📖 Summary
- Use Spring Initializr, Profile, and @ConfigurationProperties together to set up the project skeleton
- The REST APIs for
ProductandOrdercurrently use an in-memory map for storage; this will be replaced with JPA in the future. - Business configurations are centralized in
OrderFlowProperties, with multi-environment support provided throughProfile - Exclude unnecessary automatic configurations to prevent startup errors
- Postman Collections are grouped by resource, making it easier to perform continuous testing
📝 Exercises
-
Basic Exercise (Difficulty: ⭐): Complete all the code for this lesson, ensure that the dev environment starts successfully, and verify that all curl test commands pass.
-
Advanced Exercise (Difficulty ⭐⭐): Add the
PUT /api/v1/products/{id}andDELETE /api/v1/products/{id}endpoints, add theGET /api/v1/orderslist endpoint to the OrderController, and implement filtering using thestatusquery parameter. -
Challenge (Difficulty: ⭐⭐⭐): Create a
orderflow-spring-boot-starterthat wraps the OrderFlow health check endpoint (which returns the application name, version, profile, and JVM information), integrate it into the OrderFlow project, and verify that the auto-configuration takes effect.



