电商系统设计实战:用PlantUML构建购物车与订单的Java代码骨架

在电商系统的开发中,设计阶段往往决定了整个项目的可维护性和扩展性。我曾参与过一个中型电商平台的重构项目,团队最初直接跳入编码,结果在三个月后陷入了难以维护的泥潭——类之间关系混乱、职责不清,每次添加新功能都像在走钢丝。后来我们引入PlantUML进行设计先行,效率提升了至少40%。本文将分享如何用这个轻量级工具,从购物车到订单系统构建清晰的类关系,并直接生成可运行的Java代码骨架。

1. 环境准备与工具链配置

工欲善其事,必先利其器。PlantUML虽然可以通过简单的文本描述生成UML图,但高效的开发环境能大幅提升工作效率。

1.1 安装PlantUML插件

主流IDE都支持PlantUML插件,这里以IntelliJ IDEA为例:

  1. 打开IDEA的插件市场(Preferences > Plugins)
  2. 搜索"PlantUML"并安装
  3. 重启IDE后,新建 .puml 文件即可开始绘图

对于团队协作,建议统一配置PlantUML渲染服务器。可以在本地搭建或使用公司内部服务器:

# 使用Docker快速启动PlantUML服务器
docker run -d -p 8080:8080 plantuml/plantuml-server:jetty

1.2 Lombok的引入

为了保持代码简洁,我们使用Lombok减少样板代码。在Maven项目中添加依赖:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>

提示:确保团队成员IDE都安装了Lombok插件,否则代码会显示编译错误

2. 电商核心领域建模

电商系统虽然复杂,但其核心领域模型相对稳定。我们先从最基础的购物车、商品、用户开始建模。

2.1 商品与库存模型

商品是电商系统的核心实体,其类图定义如下:

@startuml
class Product {
  + Long id
  + String name
  + BigDecimal price
  + String description
  + ProductStatus status
}

class Inventory {
  + Long id
  + Product product
  + Integer stock
  + Integer locked
}

enum ProductStatus {
  AVAILABLE
  OUT_OF_STOCK
  DISCONTINUED
}

Product "1" -- "1" Inventory
@enduml

对应的Java骨架代码:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    private Long id;
    private String name;
    private BigDecimal price;
    private String description;
    private ProductStatus status;
}

public enum ProductStatus {
    AVAILABLE, OUT_OF_STOCK, DISCONTINUED
}

@Data
public class Inventory {
    private Long id;
    private Product product;
    private Integer stock;
    private Integer locked;
}

2.2 用户与购物车关系

用户与购物车的关系是典型的组合关系——购物车不能脱离用户独立存在:

@startuml
class User {
  + Long userId
  + String username
  + String email
  + List<Cart> carts
}

class Cart {
  + Long cartId
  + List<CartItem> items
  + BigDecimal totalPrice()
}

class CartItem {
  + Product product
  + Integer quantity
}

User "1" *-- "1..*" Cart
Cart "1" *-- "1..*" CartItem
CartItem --> Product
@enduml

Java实现时,我们使用Lombok的 @Builder 简化对象创建:

@Data
@Builder
public class CartItem {
    private Product product;
    private Integer quantity;
}

@Data
public class Cart {
    private Long cartId;
    private List<CartItem> items = new ArrayList<>();
    
    public BigDecimal totalPrice() {
        return items.stream()
            .map(item -> item.getProduct().getPrice()
                .multiply(BigDecimal.valueOf(item.getQuantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

@Data
public class User {
    private Long userId;
    private String username;
    private String email;
    private List<Cart> carts = new ArrayList<>();
}

3. 订单系统的复杂关系建模

订单系统涉及更多复杂关系,包括聚合、组合和依赖关系。

3.1 订单核心模型

@startuml
class Order {
  + Long orderId
  + OrderStatus status
  + List<OrderItem> items
  + BigDecimal totalAmount
  + User user
  + Payment payment
  + Shipping shipping
}

class OrderItem {
  + Product product
  + Integer quantity
  + BigDecimal price
}

enum OrderStatus {
  CREATED
  PAID
  SHIPPED
  COMPLETED
  CANCELLED
}

Order "1" *-- "1..*" OrderItem
Order --> User
Order --> Payment
Order --> Shipping
OrderItem --> Product
@enduml

对应的Java实现需要注意深拷贝问题:

@Data
public class OrderItem {
    private Product product;
    private Integer quantity;
    private BigDecimal price;
    
    public OrderItem(CartItem cartItem) {
        this.product = new Product(cartItem.getProduct());
        this.quantity = cartItem.getQuantity();
        this.price = cartItem.getProduct().getPrice();
    }
}

@Data
public class Order {
    private Long orderId;
    private OrderStatus status;
    private List<OrderItem> items = new ArrayList<>();
    private BigDecimal totalAmount;
    private User user;
    private Payment payment;
    private Shipping shipping;
    
    public static Order fromCart(Cart cart, User user) {
        Order order = new Order();
        order.setUser(user);
        order.setStatus(OrderStatus.CREATED);
        order.setItems(cart.getItems().stream()
            .map(OrderItem::new)
            .collect(Collectors.toList()));
        order.setTotalAmount(cart.totalPrice());
        return order;
    }
}

3.2 支付与物流的关联

支付和物流通常作为独立服务存在,与订单是聚合关系:

@startuml
class Payment {
  + Long paymentId
  + BigDecimal amount
  + PaymentMethod method
  + PaymentStatus status
}

class Shipping {
  + Long shippingId
  + String address
  + ShippingMethod method
  + ShippingStatus status
}

enum PaymentMethod {
  CREDIT_CARD
  PAYPAL
  BANK_TRANSFER
}

enum ShippingMethod {
  STANDARD
  EXPRESS
  SAME_DAY
}

Order o-- Payment
Order o-- Shipping
@enduml

实现时需要注意事务边界:

public interface PaymentService {
    Payment processPayment(Order order, PaymentMethod method);
}

@Service
@RequiredArgsConstructor
public class DefaultPaymentService implements PaymentService {
    private final PaymentRepository paymentRepository;
    
    @Transactional
    public Payment processPayment(Order order, PaymentMethod method) {
        Payment payment = Payment.builder()
            .amount(order.getTotalAmount())
            .method(method)
            .status(PaymentStatus.PENDING)
            .build();
        
        return paymentRepository.save(payment);
    }
}

4. 高级建模技巧与团队协作

4.1 处理多对多关系

商品分类是典型的多对多关系,我们需要引入关联类:

@startuml
class Product {
  + Long id
  + String name
}

class Category {
  + Long id
  + String name
}

class ProductCategory {
  + Product product
  + Category category
  + Integer displayOrder
}

Product "1" -- "*" ProductCategory
Category "1" -- "*" ProductCategory
@enduml

JPA实现示例:

@Entity
@Data
public class ProductCategory {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    private Product product;
    
    @ManyToOne
    private Category category;
    
    private Integer displayOrder;
}

4.2 版本控制与文档化

PlantUML文件应该与代码一起纳入版本控制。建议的目录结构:

src/
  main/
    java/
    resources/
      diagrams/
        product.puml
        order.puml
        payment.puml

在团队协作中,可以配置CI流程自动生成UML图并发布到内部文档站点。以下是一个GitLab CI示例:

generate_diagrams:
  image: plantuml/plantuml
  script:
    - plantuml -tsvg src/main/resources/diagrams/*.puml
  artifacts:
    paths:
      - src/main/resources/diagrams/*.svg

注意:确保团队成员遵循统一的命名规范,如使用 camelCase 命名关系角色

5. 从设计到实现的完整流程

让我们通过一个完整场景串联所有概念:用户将商品加入购物车并创建订单。

5.1 购物车操作序列

@startuml
actor User
participant "CartController" as CC
participant "CartService" as CS
participant "ProductService" as PS

User -> CC : 添加商品(id=123, quantity=2)
CC -> CS : addItem(userId, productId, quantity)
CS -> PS : getProduct(productId)
PS --> CS : Product
CS --> CC : Cart
CC --> User : 更新后的购物车
@enduml

对应的服务层实现:

@Service
@RequiredArgsConstructor
public class CartServiceImpl implements CartService {
    private final CartRepository cartRepository;
    private final ProductService productService;
    
    @Transactional
    public Cart addItem(Long userId, Long productId, int quantity) {
        Product product = productService.getProduct(productId);
        Cart cart = cartRepository.findByUserId(userId)
            .orElseGet(() -> createNewCart(userId));
        
        cart.getItems().stream()
            .filter(item -> item.getProduct().getId().equals(productId))
            .findFirst()
            .ifPresentOrElse(
                item -> item.setQuantity(item.getQuantity() + quantity),
                () -> cart.getItems().add(
                    CartItem.builder()
                        .product(product)
                        .quantity(quantity)
                        .build()
                )
            );
        
        return cartRepository.save(cart);
    }
}

5.2 下单流程的关键设计

下单过程涉及多个聚合根的修改,需要考虑分布式事务:

@startuml
actor User
participant "OrderController" as OC
participant "OrderService" as OS
participant "InventoryService" as IS
participant "PaymentService" as PYS

User -> OC : 创建订单(cartId)
OC -> OS : createOrder(userId, cartId)
OS -> IS : 锁定库存(cartItems)
IS --> OS : 成功/失败
OS -> PYS : 创建支付订单
PYS --> OS : Payment
OS --> OC : Order
OC --> User : 订单详情
@enduml

使用Saga模式处理分布式事务:

public class OrderCreationSaga {
    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final OrderRepository orderRepository;
    
    @Transactional
    public Order createOrder(Long userId, Long cartId) {
        // 1. 获取购物车
        Cart cart = cartService.getCart(cartId);
        
        // 2. 锁定库存
        try {
            inventoryService.lockInventory(cart.getItems());
        } catch (InventoryException e) {
            throw new OrderException("库存不足");
        }
        
        // 3. 创建订单
        Order order = Order.fromCart(cart, user);
        order = orderRepository.save(order);
        
        // 4. 创建支付
        try {
            Payment payment = paymentService.createPayment(order);
            order.setPayment(payment);
        } catch (PaymentException e) {
            inventoryService.unlockInventory(cart.getItems());
            throw new OrderException("支付创建失败");
        }
        
        return orderRepository.save(order);
    }
}

在实际项目中,我们发现将PlantUML图作为代码审查的一部分,能显著提高设计质量。团队约定每个Pull Request必须包含相关的UML图更新,这帮助我们早期发现了很多设计缺陷。

更多推荐