如何设计应用层与API以适应需求?

摘要:第六章 应用层与API设计 应用层是DDD架构中的"导演",它不演戏(业务逻辑),但负责协调所有演员(领域对象)来完成一场精彩的表演(业务用例)。这一层设计得好坏,直接影响整个系统的可维护
第六章 应用层与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架构中的门面,它直接面向用户和其他系统。一个好的应用层应该: 职责清晰:只做协调,不做业务决策 接口友好:API设计符合RESTful原则 错误友好:提供清晰的错误信息和状态码 文档完善:自动生成API文档,便于集成 易于测试:支持单元测试和集成测试 记住,应用层是系统的门面,但不是系统的全部。保持它的简洁和专注,让领域层来处理复杂的业务逻辑。这样你的系统才能保持长期的可维护性和扩展性。 在下一章中,我们将探讨服务拆分与边界定义,这是微服务架构中的核心挑战。