欢迎光临
我们一直在努力

DDD-025:API 设计与 Controller 层

DDD-025:API 设计与 Controller 层

本章导读

在 DDD 架构中,Controller 层作为系统的入口,承担着接收外部请求、协调应用服务、返回响应结果的重要职责。良好的 API 设计不仅能提升系统的可用性和可维护性,还能更好地体现领域模型的设计意图。本章将深入探讨如何在 DDD 架构中设计高质量的 RESTful API,以及 Controller 层的正确职责划分。

学习目标

  • 理解 RESTful API 设计原则与最佳实践
  • 掌握 DDD 中 Controller 层的职责边界
  • 学会参数校验、异常处理、API 文档的标准化实现
  • 前置知识

    • DDD 分层架构基础
    • HTTP 协议基础
    • Spring MVC 基本使用

    阅读时长

    约 50-60 分钟


    【原理】RESTful API 与 Controller 层设计原理

    一、RESTful API 的本质与设计哲学

    1.1 什么是 RESTful API

    REST(Representational State Transfer,表述性状态转移)是一种软件架构风格,由 Roy Fielding 在 2000 年的博士论文中提出。RESTful API 是符合 REST 原则的 Web API 设计方式。

    【原理】

    REST 的核心原则:

  • 资源(Resource)为中心:一切皆为资源,每个资源有唯一标识(URI)
  • 统一接口(Uniform Interface):使用标准 HTTP 方法操作资源
  • 无状态(Stateless):每个请求包含所有必要信息,服务器不保存客户端状态
  • 表述(Representation):资源以特定格式(JSON、XML)返回
  • 超媒体作为应用状态引擎(HATEOAS):响应包含相关操作链接
  • 传统 RPC 风格:
    POST /api/createOrder
    POST /api/deleteOrder
    POST /api/getOrderById

    RESTful 风格:
    POST /api/orders # 创建订单
    DELETE /api/orders/{id} # 删除订单
    GET /api/orders/{id} # 查询订单

    【历史架构问题】

    问题 1:RPC 风格 API 的混乱

    // ❌ 传统 RPC 风格:动词在 URL 中,语义不清
    @RestController
    @RequestMapping(\”/api\”)
    public class OrderApiController {

    @PostMapping(\”/createOrder\”)
    public Result createOrder(@RequestBody CreateOrderRequest request) {

    ... }

    @PostMapping(\”/deleteOrderById\”)
    public Result deleteOrder(@RequestParam String orderId) {

    ... }

    @PostMapping(\”/updateOrderStatus\”)
    public Result updateStatus(@RequestBody UpdateStatusRequest request) {

    ... }

    @PostMapping(\”/getOrderDetail\”)
    public Result getOrderDetail(@RequestParam String orderId) {

    ... }

    @PostMapping(\”/queryOrderList\”)
    public Result queryOrderList(@RequestBody OrderQuery query) {

    ... }
    }

    问题分析:

    • URL 命名不一致(createOrder、deleteOrderById、getOrderDetail)
    • HTTP 方法使用混乱(全是 POST)
    • 违反 REST 的统一接口原则
    • 不利于缓存和中间件处理

    问题 2:HTTP 方法滥用

    // ❌ 错误:用 GET 执行删除操作
    @GetMapping(\”/deleteOrder\”)
    public Result deleteOrder(@RequestParam String id) {


    orderService.delete(id);
    return Result.success();
    }

    // ❌ 错误:用 POST 执行查询
    @PostMapping(\”/queryOrders\”)
    public Result queryOrders(@RequestBody OrderQuery query) {


    return Result.success(orderService.query(query));
    }

    问题分析:

    • GET 请求执行删除,可能被浏览器预加载误触发
    • POST 查询无法被缓存,影响性能
    • 违反 HTTP 方法语义,难以理解

    问题 3:URL 设计不规范

    // ❌ 错误:URL 设计混乱
    @GetMapping(\”/order/detail\”) // 混合风格
    @GetMapping(\”/orders/detail/{id}\”) // 不必要的层级
    @GetMapping(\”/api/v1/order/get/{id}\”) // 动词污染
    @GetMapping(\”/order-info/{orderId}\”) // 不一致的命名

    【DDD 如何解决】

    解决方案:遵循 RESTful 设计原则

    // ✅ 正确:RESTful 风格 API
    @RestController
    @RequestMapping(\”/api/v1/orders\”)
    public class OrderController {

    @PostMapping
    public ApiResponse<OrderId> createOrder(@Valid @RequestBody CreateOrderRequest request) {


    // POST /api/v1/orders – 创建资源
    }

    @GetMapping(\”/{orderId}\”)
    public ApiResponse<OrderResponse> getOrder(@PathVariable String orderId) {


    // GET /api/v1/orders/{orderId} – 查询资源
    }

    @GetMapping
    public ApiResponse<PageResponse<OrderResponse>> listOrders(OrderQueryRequest request) {


    // GET /api/v1/orders – 列表查询
    }

    @PutMapping(\”/{orderId}/status\”)
    public ApiResponse<Void> updateStatus(
    @PathVariable String orderId,
    @Valid @RequestBody UpdateStatusRequest request) {


    // PUT /api/v1/orders/{orderId}/status – 更新资源
    }

    @DeleteMapping(\”/{orderId}\”)
    public ApiResponse<Void> deleteOrder(@PathVariable String orderId) {


    // DELETE /api/v1/orders/{orderId} – 删除资源
    }
    }

    设计要点:

    原则
    说明
    示例
    资源命名 使用名词复数 /orders, /products
    HTTP 方法 遵循语义 GET 查询、POST 创建、PUT 更新、DELETE 删除
    层级清晰 表达资源关系 /orders/{id}/items
    版本控制 URL 或 Header /api/v1/orders
    统一响应 标准响应格式 ApiResponse
    【设计优势】
    维度
    传统 RPC 风格
    RESTful 风格
    语义清晰度 低,需要看文档 高,URL 即文档
    HTTP 语义 混乱 标准,符合规范
    缓存友好 差(全 POST) 好(GET 可缓存)
    工具支持 需要定制 标准工具支持
    学习成本 每个接口都要学 遵循统一规范

    1.2 HTTP 方法的正确使用

    【原理】

    HTTP 方法定义了对资源的操作语义:

    方法
    语义
    幂等性
    安全性
    典型用途
    GET 获取资源 查询
    POST 创建资源 新增、复杂操作
    PUT 全量更新 整体替换
    PATCH 部分更新 部分修改
    DELETE 删除资源 删除

    幂等性(Idempotent):多次执行相同请求,结果与执行一次相同。 安全性(Safe):请求不会改变服务器状态。

    【代码示例】

    // ✅ 正确:遵循 HTTP 方法语义

    // GET – 获取订单(安全、幂等)
    @GetMapping(\”/{orderId}\”)
    public ApiResponse<OrderResponse> getOrder(@PathVariable String orderId) {


    GetOrderQuery query = new GetOrderQuery(OrderId.of(orderId));
    OrderResponse response = orderQueryService.getOrder(query);
    return ApiResponse.success(response);
    }

    // POST – 创建订单(非幂等)
    @PostMapping
    public ApiResponse<OrderId> createOrder(
    @Valid @RequestBody CreateOrderRequest request) {


    CreateOrderCommand command = OrderAssembler.toCommand(request);
    OrderId orderId = orderApplicationService.createOrder(command);
    return ApiResponse.success(orderId);
    }

    // PUT – 全量更新订单地址(幂等)
    @PutMapping(\”/{orderId}/shipping-address\”)
    public ApiResponse<Void> updateShippingAddress(
    @PathVariable String orderId,
    @Valid @RequestBody UpdateAddressRequest request) {


    UpdateAddressCommand command = new UpdateAddressCommand(
    OrderId.of(orderId),
    request.getAddress()
    );
    orderApplicationService.updateShippingAddress(command);
    return ApiResponse.success();
    }

    // PATCH – 部分更新订单备注(非幂等)
    @PatchMapping(\”/{orderId}\”)
    public ApiResponse<Void> patchOrder(
    @PathVariable String orderId,
    @RequestBody Map<String, Object> updates) {


    // 只更新指定字段
    orderApplicationService.patchOrder(OrderId.of(orderId), updates);
    return ApiResponse.success();
    }

    // DELETE – 取消订单(幂等)
    @DeleteMapping(\”/{orderId}\”)
    public ApiResponse<Void> cancelOrder(
    @PathVariable String orderId,
    @RequestParam(required = false) String reason) {


    CancelOrderCommand command = new CancelOrderCommand(
    OrderId.of(orderId),
    reason
    );
    orderApplicationService.cancelOrder(command);
    return ApiResponse.success();
    }

    // POST – 执行复杂业务操作(非 CRUD 操作)
    @PostMapping(\”/{orderId}/payment\”)
    public ApiResponse<PaymentResult> payOrder(
    @PathVariable String orderId,
    @Valid @RequestBody PayOrderRequest request) {


    PayOrderCommand command = new PayOrderCommand(
    OrderId.of(orderId),
    request.getPaymentMethod()
    );
    PaymentResult result = orderApplicationService.payOrder(command);
    return ApiResponse.success(result);
    }

    1.3 URL 设计规范

    【原理】

    URL 设计原则:

  • 使用名词而非动词:资源为中心
  • 使用复数形式:表示资源集合
  • 层级表达关系:通过路径表达资源关系
  • 避免过深层级:建议不超过 3 层
  • 使用连字符分隔:提高可读性
  • 推荐格式:
    /api/{version}/{resource}/{id}/{sub-resource}/{sub-id}

    示例:
    /api/v1/orders # 订单集合
    /api/v1/orders/{orderId} # 单个订单
    /api/v1/orders/{orderId}/items # 订单项集合
    /api/v1/orders/{orderId}/items/{itemId} # 单个订单项
    /api/v1/customers/{customerId}/orders # 客户的订单

    【代码示例】

    // ✅ 正确:规范的 URL 设计

    @RestController
    @RequestMapping(\”/api/v1/orders\”)
    public class OrderController {

    // 订单集合操作
    @GetMapping
    public ApiResponse<PageResponse<OrderSummary>> listOrders(
    @RequestParam(required = false) String customerId,
    @RequestParam(required = false) String status,
    @RequestParam(defaultValue = \”0\”) int page,
    @RequestParam(defaultValue = \”20\”) int size) {


    // GET /api/v1/orders?customerId=xxx&status=CREATED&page=0&size=20
    }

    // 单个订单操作
    @GetMapping(\”/{orderId}\”)
    public ApiResponse<OrderDetail> getOrder(@PathVariable String orderId) {


    // GET /api/v1/orders/ORD-001
    }

    // 订单项子资源
    @GetMapping(\”/{orderId}/items\”)
    public ApiResponse<List<OrderItemResponse>> getOrderItems(
    @PathVariable String orderId) {


    // GET /api/v1/orders/ORD-001/items
    }

    // 订单项详情
    @GetMapping(\”/{orderId}/items/{itemId}\”)
    public ApiResponse<OrderItemResponse> getOrderItem(
    @PathVariable String orderId,
    @PathVariable String itemId) {


    // GET /api/v1/orders/ORD-001/items/ITEM-001
    }
    }

    // 另一个资源的控制器
    @RestController
    @RequestMapping(\”/api/v1/customers\”)
    public class CustomerController {

    // 客户的订单(通过关系访问)
    @GetMapping(\”/{customerId}/orders\”)
    public ApiResponse<List<OrderSummary>> getCustomerOrders(
    @PathVariable String customerId,
    @RequestParam(required = false) String status) {


    // GET /api/v1/customers/CUST-001/orders?status=PAID
    }
    }


    二、Controller 层的职责边界

    2.1 Controller 在 DDD 分层中的位置

    【原理】

    在 DDD 分层架构中,Controller 属于接口层(Interface Layer / User Interface Layer),也称为表示层。

    ┌─────────────────────────────────────────────────────────┐
    │ 接口层(Interface Layer) │
    │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
    │ │ Controller │ │ Assembler │ │ DTO │ │
    │ │ (请求处理) │ │ (对象转换) │ │ (数据传输) │ │
    │ └─────────────┘ └─────────────┘ └─────────────┘ │
    └─────────────────────────────────────────────────────────┘
    ↓ 调用
    ┌─────────────────────────────────────────────────────────┐
    │ 应用层(Application Layer) │
    │ ┌──────────────────────────────────────────────────┐ │
    │ │ Application Service (用例编排) │ │
    │ └──────────────────────────────────────────────────┘ │
    └─────────────────────────────────────────────────────────┘
    ↓ 调用
    ┌─────────────────────────────────────────────────────────┐
    │ 领域层(Domain Layer) │
    │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
    │ │ Entity │ │ Value │ │ Service │ │Repository│ │
    │ │ │ │ Object │ │ │ │ Interface│ │
    │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
    └─────────────────────────────────────────────────────────┘

    Controller 的职责:

  • 接收请求:解析 HTTP 请求参数
  • 参数校验:验证请求参数的有效性
  • 对象转换:将 DTO 转换为应用层使用的 Command/Query
  • 调用应用服务:委托应用服务执行业务逻辑
  • 响应封装:将结果封装为统一响应格式
  • 异常处理:捕获并转换异常为适当的 HTTP 响应
  • Controller 不应该做的事:

  • ❌ 包含业务逻辑
  • ❌ 直接操作领域对象
  • ❌ 直接访问数据库
  • ❌ 进行复杂的数据转换
  • 【历史架构问题】

    问题 1:Controller 承担过多职责

    // ❌ 错误:Controller 包含业务逻辑
    @RestController
    @RequestMapping(\”/api/orders\”)
    public class BadOrderController {

    @Autowired
    private OrderDao orderDao;

    @Autowired
    private ProductDao productDao;

    @Autowired
    private CustomerDao customerDao;

    @PostMapping
    public Result createOrder(@RequestBody CreateOrderDTO dto) {


    // ❌ Controller 直接操作数据库
    Customer customer = customerDao.findById(dto.getCustomerId());
    if (customer == null) {


    return Result.error(\”客户不存在\”);
    }

    // ❌ Controller 包含业务逻辑
    List<OrderItem> items = new ArrayList<>();
    BigDecimal totalAmount = BigDecimal.ZERO;

    for (OrderItemDTO itemDTO : dto.getItems()) {


    Product product = productDao.findById(itemDTO.getProductId());
    if (product == null) {


    return Result.error(\”商品不存在:\” + itemDTO.getProductId());
    }

    // ❌ 库存检查逻辑在 Controller
    if (product.getStock() < itemDTO.getQuantity()) {


    return Result.error(\”库存不足:\” + product.getName());
    }

    // ❌ 价格计算逻辑在 Controller
    BigDecimal subtotal = product.getPrice().multiply(
    new BigDecimal(itemDTO.getQuantity()));
    totalAmount = totalAmount.add(subtotal);

    OrderItem item = new OrderItem();
    item.setProductId(product.getId());
    item.setProductName(product.getName());
    item.setQuantity(itemDTO.getQuantity());
    item.setPrice(product.getPrice());
    items.add(item);
    }

    // ❌ 订单创建逻辑在 Controller
    Order order = new Order();
    order.setId(UUID.randomUUID().toString());
    order.setCustomerId(dto.getCustomerId());
    order.setCustomerName(customer.getName());
    order.setItems(items);
    order.setTotalAmount(totalAmount);
    order.setStatus(\”CREATED\”);
    order.setCreatedAt(LocalDateTime.now());

    // ❌ 直接保存到数据库
    orderDao.insert(order);

    return Result.success(order.getId());
    }
    }

    问题分析:

  • Controller 直接操作多个 DAO,职责过重
  • 业务逻辑(库存检查、价格计算、订单创建)散落在 Controller
  • 无法进行单元测试(依赖真实数据库)
  • 违反单一职责原则
  • 问题 2:Controller 直接操作领域对象

    // ❌ 错误:Controller 直接操作聚合
    @PostMapping(\”/{orderId}/payment\”)
    public Result payOrder(@PathVariable String orderId, @RequestBody PayOrderDTO dto) {


    // ❌ 直接获取聚合
    OrderEntity entity = orderDao.findById(orderId);

    // ❌ 直接修改聚合状态
    if (!\”CREATED\”.equals(entity.getStatus())) {


    return Result.error(\”订单状态不正确\”);
    }

    // ❌ 在 Controller 中调用外部支付服务
    PaymentResult result = paymentService.pay(entity.getTotalAmount(), dto.getPaymentMethod());

    if (result.isSuccess()) {


    entity.setStatus(\”PAID\”);
    entity.setPaymentId(result.getPaymentId());
    entity.setPaidAt(LocalDateTime.now());
    orderDao.update(entity);

    // ❌ 发送消息在 Controller
    messageQueue.send(new OrderPaidMessage(orderId));
    }

    return Result.success(result);
    }

    问题分析:

  • 绕过应用服务,直接操作领域对象
  • 业务规则(状态检查)散落在 Controller
  • 外部服务调用在 Controller,难以测试
  • 事务边界不清晰
  • 【DDD 如何解决】

    解决方案:Controller 只做协调工作

    // ✅ 正确:Controller 职责单一
    @RestController
    @RequestMapping(\”/api/v1/orders\”)
    @Validated
    public class OrderController {

    private final OrderApplicationService orderApplicationService;
    private final OrderQueryService orderQueryService;
    private final OrderAssembler orderAssembler;

    public OrderController(
    OrderApplicationService orderApplicationService,
    OrderQueryService orderQueryService,
    OrderAssembler orderAssembler) {


    this.orderApplicationService = orderApplicationService;
    this.orderQueryService = orderQueryService;
    this.orderAssembler = orderAssembler;
    }

    /**
    * 创建订单
    * Controller 只负责:接收请求 -> 参数校验 -> 调用应用服务 -> 返回响应
    */

    @PostMapping
    public ApiResponse

    赞(0)
    未经允许不得转载:171主机测评 » DDD-025:API 设计与 Controller 层
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址