第六章 应用层与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> CreateOrder(CreateOrderCommand command)
- {
- var result = await _mediator.Send(command);
-
- return result.Match>(
- order => CreatedAtAction(
- nameof(GetOrder),
- new { id = order.Id },
- order),
- error => BadRequest(new { Error = error }));
- }
-
- [HttpGet("{id}")]
- public async Task> GetOrder(Guid id)
- {
- var result = await _mediator.Send(new GetOrderByIdQuery(id));
-
- return result.Match>(
- 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>> 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> GetProduct(Guid id)
- {
- var result = await _mediator.Send(new GetProductByIdQuery(id));
-
- return result.Match>(
- 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> CreateProduct(
- [FromBody] CreateProductCommand command)
- {
- var result = await _mediator.Send(command);
-
- return result.Match>(
- 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 UpdateProduct(
- Guid id,
- [FromBody] UpdateProductCommand command)
- {
- if (id != command.ProductId)
- return BadRequest("ID mismatch");
-
- var result = await _mediator.Send(command);
-
- return result.Match(
- _ => 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 DeleteProduct(Guid id)
- {
- var result = await _mediator.Send(new DeleteProductCommand(id));
-
- return result.Match(
- _ => 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>> 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> GetProduct(Guid id)
- {
- // v1版本的实现
- }
- }
- [ApiVersion("2.0")]
- [Route("api/v{version:apiVersion}/[controller]")]
- public class ProductsV2Controller : ControllerBase
- {
- [HttpGet("{id}")]
- public async Task> 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> GetProductV1(Guid id)
- {
- // v1版本
- }
-
- [HttpGet("{id}")]
- [Produces("application/vnd.myapi.product.v2+json")]
- public async Task> 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文档,便于集成
- 易于测试:支持单元测试和集成测试
记住,应用层是系统的门面,但不是系统的全部。保持它的简洁和专注,让领域层来处理复杂的业务逻辑。这样你的系统才能保持长期的可维护性和扩展性。
在下一章中,我们将探讨服务拆分与边界定义,这是微服务架构中的核心挑战。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |