第六章 应用层与API设计

第六章 应用层与API设计

应用层是DDD架构中的"导演",它不演戏(业务逻辑),但负责协调所有演员(领域对象)来完成一场精彩的表演(业务用例)。这一层设计得好坏,直接影响整个系统的可维护性和扩展性。

6.1 应用服务设计

6.1.1 应用服务的真正角色

很多团队把应用服务写成了"事务脚本",里面塞满了if-else和业务逻辑。这不是DDD的精神。应用服务应该是优雅的协调者,就像交响乐队的指挥。

应用服务的核心职责

让我用一个电商下单的例子来说明:

// 应用服务接口 - 定义业务用例
public interface IOrderApplicationService
{
    Task<PlaceOrderResult> PlaceOrder(PlaceOrderCommand command);
    Task<ConfirmOrderResult> ConfirmOrder(ConfirmOrderCommand command);
    Task<CancelOrderResult> CancelOrder(CancelOrderCommand command);
    Task<ShipOrderResult> ShipOrder(ShipOrderCommand command);
}

// 应用服务实现 - 协调领域对象
public class OrderApplicationService : IOrderApplicationService
{
    private readonly ICustomerRepository _customerRepository;
    private readonly IProductRepository _productRepository;
    private readonly IOrderRepository _orderRepository;
    private readonly IInventoryService _inventoryService;
    private readonly IPaymentService _paymentService;
    private readonly IUnitOfWork _unitOfWork;
    private readonly IDomainEventPublisher _domainEventPublisher;
    private readonly ILogger<OrderApplicationService> _logger;
    
    public OrderApplicationService(
        ICustomerRepository customerRepository,
        IProductRepository productRepository,
        IOrderRepository orderRepository,
        IInventoryService inventoryService,
        IPaymentService paymentService,
        IUnitOfWork unitOfWork,
        IDomainEventPublisher domainEventPublisher,
        ILogger<OrderApplicationService> logger)
    {
        _customerRepository = customerRepository;
        _productRepository = productRepository;
        _orderRepository = orderRepository;
        _inventoryService = inventoryService;
        _paymentService = paymentService;
        _unitOfWork = unitOfWork;
        _domainEventPublisher = domainEventPublisher;
        _logger = logger;
    }
    
    public async Task<PlaceOrderResult> PlaceOrder(PlaceOrderCommand command)
    {
        _logger.LogInformation("Placing order for customer {CustomerId}", command.CustomerId);
        
        try
        {
            // 1. 获取领域对象
            var customer = await _customerRepository.GetByIdAsync(new CustomerId(command.CustomerId));
            if (customer == null)
                return PlaceOrderResult.Fail($"Customer {command.CustomerId} not found");
            
            // 2. 验证商品库存
            var orderItems = new List<OrderItem>();
            foreach (var item in command.Items)
            {
                var product = await _productRepository.GetByIdAsync(new ProductId(item.ProductId));
                if (product == null)
                    return PlaceOrderResult.Fail($"Product {item.ProductId} not found");
                    
                var stockAvailable = await _inventoryService.CheckStockAsync(product.Id, item.Quantity);
                if (!stockAvailable)
                    return PlaceOrderResult.Fail($"Insufficient stock for product {product.Name}");
                    
                orderItems.Add(new OrderItem(product.Id, product.Name, product.Price, item.Quantity));
            }
            
            // 3. 执行业务操作 - 委托给领域对象
            var order = customer.PlaceOrder(orderItems);
            
            // 4. 预留库存
            foreach (var item in order.Items)
            {
                await _inventoryService.ReserveStockAsync(item.ProductId, item.Quantity);
            }
            
            // 5. 持久化
            await _orderRepository.AddAsync(order);
            await _unitOfWork.CommitAsync();
            
            // 6. 发布领域事件
            await _domainEventPublisher.PublishEventsAsync(order);
            
            _logger.LogInformation("Order placed successfully: {OrderId}", order.Id.Value);
            
            return PlaceOrderResult.Success(order.Id, order.TotalPrice);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error placing order for customer {CustomerId}", command.CustomerId);
            return PlaceOrderResult.Fail("An error occurred while placing the order");
        }
    }
    
    public async Task<ConfirmOrderResult> ConfirmOrder(ConfirmOrderCommand command)
    {
        var order = await _orderRepository.GetByIdAsync(new OrderId(command.OrderId));
        if (order == null)
            return ConfirmOrderResult.Fail($"Order {command.OrderId} not found");
            
        // 委托给领域对象
        order.Confirm();
        
        // 处理支付
        var paymentResult = await _paymentService.ProcessPayment(order.CustomerId, order.TotalPrice);
        if (!paymentResult.Success)
        {
            return ConfirmOrderResult.Fail($"Payment failed: {paymentResult.ErrorMessage}");
        }
        
        await _unitOfWork.CommitAsync();
        await _domainEventPublisher.PublishEventsAsync(order);
        
        return ConfirmOrderResult.Success(order.Id);
    }
}

6.1.2 CQRS模式的应用

CQRS(命令查询职责分离)是应用层设计的利器。它把读写操作分开,让系统更加清晰和高效。

// 命令 - 改变系统状态
public record CreateProductCommand(
    string Name,
    string Description,
    decimal Price,
    string Currency,
    Guid CategoryId,
    List<string> ImageUrls
) : IRequest<Result<ProductDto>>;

public record UpdateProductPriceCommand(
    Guid ProductId,
    decimal NewPrice,
    string Currency
) : IRequest<Result>;

// 查询 - 获取系统状态
public record GetProductByIdQuery(Guid ProductId) : IRequest<Result<ProductDto>>;
public record GetProductsByCategoryQuery(Guid CategoryId, int Page, int PageSize) 
    : IRequest<Result<PagedResult<ProductDto>>>;

// 命令处理器
public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, Result<ProductDto>>
{
    private readonly IProductRepository _repository;
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;
    
    public async Task<Result<ProductDto>> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        var product = Product.Create(
            request.Name,
            request.Description,
            Money.Create(request.Price, request.Currency),
            new CategoryId(request.CategoryId)
        );
        
        foreach (var imageUrl in request.ImageUrls)
        {
            product.AddImage(imageUrl);
        }
        
        await _repository.AddAsync(product);
        await _unitOfWork.CommitAsync();
        
        return Result.Success(_mapper.Map<ProductDto>(product));
    }
}

// 查询处理器
public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, Result<ProductDto>>
{
    private readonly IProductRepository _repository;
    private readonly IMapper _mapper;
    
    public async Task<Result<ProductDto>> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
    {
        var product = await _repository.GetByIdAsync(new ProductId(request.ProductId));
        if (product == null)
            return Result.Failure<ProductDto>("Product not found");
            
        return Result.Success(_mapper.Map<ProductDto>(product));
    }
}

6.1.3 结果模式:优雅的错误处理

传统的异常处理方式在分布式系统中会带来很多问题。结果模式是一个更好的选择。

// 结果模式基类
public class Result
{
    public bool IsSuccess { get; }
    public bool IsFailure => !IsSuccess;
    public string Error { get; }
    
    protected Result(bool isSuccess, string error)
    {
        IsSuccess = isSuccess;
        Error = error;
    }
    
    public static Result Success() => new Result(true, string.Empty);
    public static Result Failure(string error) => new Result(false, error);
    
    public static Result<T> Success<T>(T value) => new Result<T>(value, true, string.Empty);
    public static Result<T> Failure<T>(string error) => new Result<T>(default, false, error);
}

public class Result<T> : Result
{
    public T Value { get; }
    
    protected internal Result(T value, bool isSuccess, string error)
        : base(isSuccess, error)
    {
        Value = value;
    }
    
    public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<string, TResult> onFailure)
    {
        return IsSuccess ? onSuccess(Value) : onFailure(Error);
    }
}

// 在控制器中使用
[ApiController]
[Route("api/v1/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IMediator _mediator;
    
    [HttpPost]
    public async Task<ActionResult<OrderDto>> CreateOrder(CreateOrderCommand command)
    {
        var result = await _mediator.Send(command);
        
        return result.Match<ActionResult<OrderDto>>(
            order => CreatedAtAction(
                nameof(GetOrder), 
                new { id = order.Id }, 
                order),
            error => BadRequest(new { Error = error }));
    }
    
    [HttpGet("{id}")]
    public async Task<ActionResult<OrderDto>> GetOrder(Guid id)
    {
        var result = await _mediator.Send(new GetOrderByIdQuery(id));
        
        return result.Match<ActionResult<OrderDto>>(
            order => Ok(order),
            error => NotFound(new { Error = error }));
    }
}

6.2 API设计最佳实践

6.2.1 RESTful API设计

// 资源路由设计
[ApiController]
[Route("api/v1/[controller]")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
    // GET api/v1/products?page=1&size=20&category=electronics
    [HttpGet]
    [ProducesResponseType(typeof(PagedResult<ProductDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<PagedResult<ProductDto>>> GetProducts(
        [FromQuery] GetProductsQuery query)
    {
        var result = await _mediator.Send(query);
        return Ok(result);
    }
    
    // GET api/v1/products/{id}
    [HttpGet("{id:guid}")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<ProductDto>> GetProduct(Guid id)
    {
        var result = await _mediator.Send(new GetProductByIdQuery(id));
        
        return result.Match<ActionResult<ProductDto>>(
            product => Ok(product),
            error => NotFound());
    }
    
    // POST api/v1/products
    [HttpPost]
    [Authorize(Roles = "Admin")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<ProductDto>> CreateProduct(
        [FromBody] CreateProductCommand command)
    {
        var result = await _mediator.Send(command);
        
        return result.Match<ActionResult<ProductDto>>(
            product => CreatedAtAction(
                nameof(GetProduct), 
                new { id = product.Id }, 
                product),
            error => BadRequest(new { Error = error }));
    }
    
    // PUT api/v1/products/{id}
    [HttpPut("{id:guid}")]
    [Authorize(Roles = "Admin")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult> UpdateProduct(
        Guid id, 
        [FromBody] UpdateProductCommand command)
    {
        if (id != command.ProductId)
            return BadRequest("ID mismatch");
            
        var result = await _mediator.Send(command);
        
        return result.Match<ActionResult>(
            _ => NoContent(),
            error => BadRequest(new { Error = error }));
    }
    
    // DELETE api/v1/products/{id}
    [HttpDelete("{id:guid}")]
    [Authorize(Roles = "Admin")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult> DeleteProduct(Guid id)
    {
        var result = await _mediator.Send(new DeleteProductCommand(id));
        
        return result.Match<ActionResult>(
            _ => NoContent(),
            error => NotFound(new { Error = error }));
    }
    
    // 子资源 - GET api/v1/products/{id}/reviews
    [HttpGet("{id:guid}/reviews")]
    [ProducesResponseType(typeof(List<ProductReviewDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<List<ProductReviewDto>>> GetProductReviews(Guid id)
    {
        var result = await _mediator.Send(new GetProductReviewsQuery(id));
        return Ok(result);
    }
}

6.2.2 API版本控制策略

// 方式1:URL版本控制
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<ActionResult<ProductDtoV1>> GetProduct(Guid id)
    {
        // v1版本的实现
    }
}

[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsV2Controller : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<ActionResult<ProductDtoV2>> GetProduct(Guid id)
    {
        // v2版本的实现 - 可能包含更多字段
    }
}

// 方式2:媒体类型版本控制
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("{id}")]
    [Produces("application/vnd.myapi.product.v1+json")]
    public async Task<ActionResult<ProductDtoV1>> GetProductV1(Guid id)
    {
        // v1版本
    }
    
    [HttpGet("{id}")]
    [Produces("application/vnd.myapi.product.v2+json")]
    public async Task<ActionResult<ProductDtoV2>> GetProductV2(Guid id)
    {
        // v2版本
    }
}

6.2.3 错误处理标准化

// 统一的错误响应格式
public class ApiErrorResponse
{
    public string Type { get; set; }
    public string Title { get; set; }
    public int Status { get; set; }
    public string Detail { get; set; }
    public string Instance { get; set; }
    public Dictionary<string, string[]> Errors { get; set; }
    
    public static ApiErrorResponse FromException(Exception exception, HttpContext context)
    {
        return exception switch
        {
            ValidationException validationEx => new ApiErrorResponse
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
                Title = "Validation Error",
                Status = StatusCodes.Status400BadRequest,
                Detail = "One or more validation errors occurred.",
                Instance = context.Request.Path,
                Errors = validationEx.Errors
            },
            NotFoundException notFoundEx => new ApiErrorResponse
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4",
                Title = "Resource Not Found",
                Status = StatusCodes.Status404NotFound,
                Detail = notFoundEx.Message,
                Instance = context.Request.Path
            },
            BusinessRuleException businessEx => new ApiErrorResponse
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.8",
                Title = "Business Rule Violation",
                Status = StatusCodes.Status422UnprocessableEntity,
                Detail = businessEx.Message,
                Instance = context.Request.Path
            },
            _ => new ApiErrorResponse
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
                Title = "Internal Server Error",
                Status = StatusCodes.Status500InternalServerError,
                Detail = "An unexpected error occurred.",
                Instance = context.Request.Path
            }
        };
    }
}

// 全局异常处理中间件
public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ErrorHandlingMiddleware> _logger;
    
    public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception occurred");
            await HandleExceptionAsync(context, ex);
        }
    }
    
    private static async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        var response = ApiErrorResponse.FromException(exception, context);
        
        context.Response.ContentType = "application/problem+json";
        context.Response.StatusCode = response.Status;
        
        var json = JsonSerializer.Serialize(response);
        await context.Response.WriteAsync(json);
    }
}

6.2.4 API文档与测试

// Swagger/OpenAPI配置
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo 
    { 
        Title = "Product API", 
        Version = "v1",
        Description = "API for managing products in the e-commerce system",
        Contact = new OpenApiContact
        {
            Name = "Development Team",
            Email = "dev@company.com"
        }
    });
    
    // 添加XML注释
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    if (File.Exists(xmlPath))
    {
        c.IncludeXmlComments(xmlPath);
    }
    
    // 添加JWT认证
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT Authorization header using the Bearer scheme",
        Type = SecuritySchemeType.Http,
        Scheme = "bearer"
    });
    
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});

// 集成测试示例
public class ProductsApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    
    public ProductsApiTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }
    
    [Fact]
    public async Task CreateProduct_ReturnsCreatedStatus()
    {
        // Arrange
        var client = _factory.CreateClient();
        var command = new CreateProductCommand
        {
            Name = "Test Product",
            Description = "Test Description",
            Price = 99.99m,
            Currency = "USD",
            CategoryId = Guid.NewGuid()
        };
        
        // Act
        var response = await client.PostAsJsonAsync("/api/v1/products", command);
        
        // Assert
        response.EnsureSuccessStatusCode();
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        
        var createdProduct = await response.Content.ReadFromJsonAsync<ProductDto>();
        Assert.NotNull(createdProduct);
        Assert.Equal(command.Name, createdProduct.Name);
    }
    
    [Fact]
    public async Task GetNonExistentProduct_ReturnsNotFound()
    {
        // Arrange
        var client = _factory.CreateClient();
        var nonExistentId = Guid.NewGuid();
        
        // Act
        var response = await client.GetAsync($"/api/v1/products/{nonExistentId}");
        
        // Assert
        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }
}

6.3 小结

应用层和API设计是DDD架构中的门面,它直接面向用户和其他系统。一个好的应用层应该:

  1. 职责清晰:只做协调,不做业务决策
  2. 接口友好:API设计符合RESTful原则
  3. 错误友好:提供清晰的错误信息和状态码
  4. 文档完善:自动生成API文档,便于集成
  5. 易于测试:支持单元测试和集成测试

记住,应用层是系统的门面,但不是系统的全部。保持它的简洁和专注,让领域层来处理复杂的业务逻辑。这样你的系统才能保持长期的可维护性和扩展性。

在下一章中,我们将探讨服务拆分与边界定义,这是微服务架构中的核心挑战。

posted @ 2026-01-22 21:40  高宏顺  阅读(3)  评论(0)    收藏  举报