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

浙公网安备 33010602011771号