404 Not Found

404 Not Found


nginx

Phase 2 Comprehensive Exercise: Refining the Core Business Functions of OrderFlow

Integration is the litmus test of understanding—combining JPA, transactions, validation, exception handling, and security into a single, fully functional system.

1. What You'll Learn


2. A True Story of a Full-Stack Developer

(1) Pain Point: Modules Operating in Isolation

Alice has completed her studies on the five modules—JPA, Service, Validation, Exception Handling, and Security—but they all exist in isolation. When actually placing an order, she needs to link together the entire process: "validate input → check inventory → deduct inventory → create order → ensure transaction integrity → enforce access control → handle exceptions." She's not sure how the various modules work together.

(2) Solutions to the Comprehensive Exercises

This lesson integrates all the modules into a complete order placement process, simulating a real-world business scenario.

(3) Revenue

With the integration by Alice complete, OrderFlow now has a fully closed-loop business capability—from user login to placing orders, querying, and canceling, each step is protected by validation, transactions, permissions, and exception handling.


3. Data Model Integration

(1) Complete ER Model

100%
erDiagram
    PRODUCT ||--o{ ORDER_ITEM : "included in"
    ORDER ||--o{ ORDER_ITEM : "contains"
    USER ||--o{ ORDER : "places"

    PRODUCT {
        bigint id PK
        varchar name
        decimal price
        int stock
        varchar sku
    }
    ORDER {
        bigint id PK
        bigint user_id FK
        varchar status
        decimal total_amount
        timestamp created_at
    }
    ORDER_ITEM {
        bigint id PK
        bigint order_id FK
        bigint product_id FK
        int quantity
        decimal unit_price
    }
    USER {
        bigint id PK
        varchar username
        varchar password
        varchar email
        varchar role
    }

(1) ▶ Example: User Entity

JAVA
@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String role;

    protected User() {}

    public User(String username, String password, String email, String role) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.role = role;
    }
    // getters
}

Output:

TEXT
// Execution Successful

4. Ordering Process Integration

(1) Complete Order Timeline

100%
sequenceDiagram
    participant C as Client
    participant Ctrl as OrderController
    participant Val as Validation
    participant Svc as OrderService
    participant Tx as Transaction
    participant Repo as Repository

    C->>Ctrl: POST /api/v1/orders
    Ctrl->>Val: @Valid CreateOrderRequest
    alt Validation fails
        Val-->>C: 400 VALIDATION_ERROR
    end
    Ctrl->>Svc: createOrder(request)
    Svc->>Tx: Begin Transaction
    Svc->>Repo: findProductById
    alt Product not found
        Svc-->>C: 404 RESOURCE_NOT_FOUND
    end
    Svc->>Repo: deductStock (check quantity)
    alt Insufficient stock
        Tx->>Tx: Rollback
        Svc-->>C: 409 INSUFFICIENT_STOCK
    end
    Svc->>Repo: save Order + OrderItems
    Tx->>Tx: Commit
    Svc-->>Ctrl: Order created
    Ctrl-->>C: 201 Created

(1) ▶ Example: Complete OrderController

JAVA
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @PostMapping
    @PreAuthorize("hasAnyRole('CUSTOMER', 'ADMIN')")
    public ResponseEntity<OrderResponse> createOrder(
            @Valid @RequestBody CreateOrderRequest request,
            @AuthenticationPrincipal UserDetails userDetails) {
        OrderResponse order = orderService.createOrder(
            request, userDetails.getUsername());
        return ResponseEntity.status(HttpStatus.CREATED).body(order);
    }

    @GetMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN') or @orderSecurity.isOwner(#id, authentication)")
    public ResponseEntity<OrderResponse> getOrder(@PathVariable Long id) {
        return ResponseEntity.ok(orderService.getOrder(id));
    }

    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public ResponseEntity<Void> cancelOrder(@PathVariable Long id) {
        orderService.cancelOrder(id);
        return ResponseEntity.noContent().build();
    }
}

Output:

TEXT
// Execution Successful

5. Transaction Integration at the Service Layer

(1) ▶ Example: Complete Implementation of OrderService

JAVA
@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;
    private final UserRepository userRepository;

    public OrderService(OrderRepository orderRepository,
                        ProductRepository productRepository,
                        UserRepository userRepository) {
        this.orderRepository = orderRepository;
        this.productRepository = productRepository;
        this.userRepository = userRepository;
    }

    @Transactional(rollbackFor = Exception.class)
    public OrderResponse createOrder(CreateOrderRequest request, String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new ResourceNotFoundException("User", 0L));

        Product product = productRepository.findById(request.productId())
            .orElseThrow(() -> new ResourceNotFoundException("Product", request.productId()));

        if (product.getStock() < request.quantity()) {
            throw new InsufficientStockException(
                product.getId(), product.getStock(), request.quantity());
        }

        product.deductStock(request.quantity());

        Order order = new Order(user, product, request.quantity());
        Order saved = orderRepository.save(order);

        return OrderResponse.from(saved);
    }

    @Transactional(rollbackFor = Exception.class)
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findByIdWithItems(orderId)
            .orElseThrow(() -> new ResourceNotFoundException("Order", orderId));

        if (!"PENDING".equals(order.getStatus())) {
            throw new OrderStateException(orderId, order.getStatus(), "CANCELLED");
        }

        order.getItems().forEach(item ->
            item.getProduct().addStock(item.getQuantity()));
        order.setStatus("CANCELLED");
    }

    @Transactional(readOnly = true)
    public OrderResponse getOrder(Long orderId) {
        Order order = orderRepository.findByIdWithItems(orderId)
            .orElseThrow(() -> new ResourceNotFoundException("Order", orderId));
        return OrderResponse.from(order);
    }
}

Output:

TEXT
// Execution Successful

6. Integrating DTOs with Validation

(1) ▶ Example: Request/Response DTO

JAVA
// Request DTO with validation
public record CreateOrderRequest(
    @NotNull(message = "Product ID is required")
    Long productId,

    @Min(value = 1, message = "Quantity must be at least 1")
    @Max(value = 100, message = "Quantity cannot exceed 100")
    Integer quantity
) {}

// Response DTO
public record OrderResponse(
    Long id,
    String username,
    String status,
    BigDecimal totalAmount,
    List<OrderItemResponse> items,
    Instant createdAt
) {
    public static OrderResponse from(Order order) {
        List<OrderItemResponse> items = order.getItems().stream()
            .map(item -> new OrderItemResponse(
                item.getId(),
                item.getProduct().getName(),
                item.getQuantity(),
                item.getUnitPrice(),
                item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity()))
            ))
            .toList();
        return new OrderResponse(
            order.getId(),
            order.getUser().getUsername(),
            order.getStatus(),
            order.getTotalAmount(),
            items,
            order.getCreatedAt()
        );
    }
}

public record OrderItemResponse(
    Long id,
    String productName,
    Integer quantity,
    BigDecimal unitPrice,
    BigDecimal subtotal
) {}

Output:

TEXT
// Execution Successful

7. Security Integration

(1) ▶ Example: Custom UserDetailsService

JAVA
@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException(
                "User not found: " + username));
        return User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .roles(user.getRole())
            .build();
    }
}

// OrderSecurity helper for SpEL expressions
@Component("orderSecurity")
public class OrderSecurity {

    private final OrderRepository orderRepository;

    public OrderSecurity(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public boolean isOwner(Long orderId, Authentication auth) {
        String username = auth.getName();
        return orderRepository.findById(orderId)
            .map(order -> order.getUser().getUsername().equals(username))
            .orElse(false);
    }
}

Output:

TEXT
// Execution Successful

8. Comprehensive Example: End-to-End Business Process Testing

BASH
# 1. Seed product data
curl -u alice:admin123 -X POST http://localhost:8080/api/v1/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Laptop Pro","price":1299.99,"stock":50,"sku":"LAP-0001"}'

# 2. Create order as customer
curl -u bob:pass123 -X POST http://localhost:8080/api/v1/orders \
  -H "Content-Type: application/json" \
  -d '{"productId":1,"quantity":3}'

# 3. View own order (customer bob)
curl -u bob:pass123 http://localhost:8080/api/v1/orders/1

# 4. Try invalid quantity -> 400
curl -u bob:pass123 -X POST http://localhost:8080/api/v1/orders \
  -H "Content-Type: application/json" \
  -d '{"productId":1,"quantity":0}'

# 5. Try ordering more than stock -> 409
curl -u bob:pass123 -X POST http://localhost:8080/api/v1/orders \
  -H "Content-Type: application/json" \
  -d '{"productId":1,"quantity":999}'

# 6. Cancel order as admin
curl -u alice:admin123 -X DELETE http://localhost:8080/api/v1/orders/1
Test Scenario Expected Result Verification Points
Create Order 201 Created Transaction Guarantee: Orders and Inventory Are Updated Simultaneously
View My Orders 200 OK Access Control: Users can only view their own orders
Invalid Count 400 VALIDATION_ERROR Bean Validation Enabled
Insufficient Stock 409 INSUFFICIENT_STOCK Business Exception → Global Exception Handling
Cancel Order 204 No Content Transaction: Inventory Restoration
Unauthenticated Access 401 Unauthorized Spring Security Enabled

❓ FAQ

Q What is the correct layered call sequence between modules?
A Controller → Service → Repository. The Controller handles parameter validation and authorization checks, the Service handles business logic and transactions, and the Repository handles data access. Do not make cross-layer calls.
Q How do exception handling and transactions work together?
A Business exceptions are thrown in the Service and handled uniformly by the GlobalExceptionHandler. When a transaction detects an exception, it automatically rolls back. Be careful not to catch an exception in the Service and then not rethrow it; otherwise, the transaction will not roll back.
Q How do I debug SpEL expressions in @PreAuthorize?
A Set logging.level.org.springframework.security=DEBUG to view the access decision logs. You can also add logs to the Bean methods referenced in the SpEL expression.
Q How do I test the security configuration?
A Use the @SpringBootTest and @WithMockUser annotations to simulate an authenticated user. @WithMockUser(roles="ADMIN") Simulate the ADMIN role.
Q What scenarios should end-to-end testing cover?
A At a minimum, it should cover: normal flow, validation failures, business exceptions (insufficient inventory), permission denials (orders placed by someone other than the account holder), and unauthenticated access. Verify the status codes and response formats for each scenario.
Q Can the project be deployed after Phase 2 is complete?
A The core functionality is complete, but it's not yet sufficient. The following are missing: JWT authentication (Phase 3), caching (Phase 3), monitoring (Phase 4), containerization (Phase 4), and CI/CD (Phase 5). We recommend launching the project only after all phases are complete.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty: ⭐): Complete all code integration for this lesson and ensure that the end-to-end processes for placing, querying, and canceling orders all pass the curl tests.

  2. Advanced Exercise (Difficulty ⭐⭐): Add a product management module (ProductController + ProductService) that allows users with the ADMIN role to create, update, and delete products, while regular users can only query them. Include comprehensive validation and exception handling.

  3. Challenge (Difficulty: ⭐⭐⭐): Use @SpringBootTest + @WithMockUser to write integration tests that cover scenarios such as successful order placement, insufficient inventory, and permission denial, achieving a test coverage rate of at least 80% for the core process.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏