找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1499

积分

0

好友

190

主题
发表于 5 天前 | 查看: 17| 回复: 0

第六章:泛型与面向对象的深度融合——构建类型安全、高复用的通用组件库

6.1 引言:为什么泛型是 OOP 的终极搭档?

面向对象编程(OOP)擅长解决行为抽象问题,例如通过接口和继承来定义契约与层次关系。然而,它在数据类型抽象方面的支持却相对有限。例如,一个只能操作object类型的列表会带来诸多问题:

  • 失去编译时类型安全;
  • 需要频繁装箱与拆箱,性能堪忧;
  • 无法在代码层面清晰表达“这个列表只存储订单”的业务语义。

泛型(Generics) 正是为了弥补这一短板而生,其核心思想是:在保持类型安全的前提下,实现算法与数据类型的解耦。

当泛型与OOP三大特性协同工作时,将释放巨大威力:

  • 封装 + 泛型 → 构建安全的容器与服务,对外隐藏复杂的类型处理逻辑。
  • 继承 + 泛型 → 创建灵活的基类模板,让通用逻辑得以复用。
  • 多态 + 泛型 → 实现类型感知的行为策略,在编译时确保正确性。

本章将深入探讨如何利用泛型构建企业级的通用组件库。

6.2 泛型基础回顾与高级特性

6.2.1 泛型类、方法、接口

泛型可以应用于类、方法和接口,提供高度的代码复用能力。

// 泛型类
public class Repository<T> where T : class, IEntity
{
    public T GetById(string id) { /* ... */ }
}

// 泛型方法
public static TResponse Map<TRequest, TResponse>(TRequest request)
{
    // 使用 AutoMapper 或手写映射逻辑
}

// 泛型接口
public interface IValidator<T>
{
    ValidationResult Validate(T instance);
}
6.2.2 泛型约束(Constraints)

约束通过where关键字指定,用于限制可使用的类型参数,使API更安全、意图更明确。

约束 作用 典型应用场景
where T : class T必须是引用类型 Repository<T>
where T : struct T必须是值类型 Span<T>
where T : new() T必须有无参构造函数 Activator.CreateInstance<T>()
where T : IEntity T必须实现指定接口或继承指定类 强制领域契约
where T : unmanaged T必须是非托管类型 高性能数值计算场景

🔑 最佳实践

  • 约束应尽可能具体,以增强API的安全性和可读性。
  • 避免过度约束,例如同时要求 classnew() 可能会限制在单元测试中使用 Mock 对象。

6.3 泛型 + 封装:构建安全的通用容器

6.3.1 案例:类型安全的缓存包装器

传统的缓存(如MemoryCache)使用object作为值类型,容易导致运行时错误:

// ❌ 不安全:编译通过,但运行时类型转换会失败
_cache.Set("order_123", order); // 存入的是 Order 对象
var customer = _cache.Get<Customer>("order_123"); // 错误地尝试作为 Customer 取出!

✅ 通过泛型进行封装,可以确保类型安全:

public interface ITypedCache<TKey, TValue>
{
    TValue GetOrAdd(TKey key, Func<TKey, TValue> factory);
    void Set(TKey key, TValue value);
    bool TryGet(TKey key, out TValue value);
}

public class MemoryTypedCache<TKey, TValue> : ITypedCache<TKey, TValue>
{
    private readonly IMemoryCache _cache;
    private readonly string _scope;

    public MemoryTypedCache(IMemoryCache cache, string scope)
    {
        _cache = cache;
        _scope = scope; // 通过作用域隔离不同类型缓存的键空间,避免冲突
    }

    public TValue GetOrAdd(TKey key, Func<TKey, TValue> factory)
    {
        var fullKey = $"{_scope}:{key}";
        return _cache.GetOrCreate(fullKey, entry => factory(key));
    }

    public void Set(TKey key, TValue value)
    {
        _cache.Set($"{_scope}:{key}", value);
    }

    public bool TryGet(TKey key, out TValue value)
    {
        return _cache.TryGetValue($"{_scope}:{key}", out value);
    }
}

优势

  • 编译时类型安全:确保TKeyTValue的类型正确性。
  • 自动键空间隔离:通过_scope前缀避免不同类型缓存之间的键冲突。
  • 依赖注入友好:可被注册为ITypedCache<string, Order>等具体类型。
6.3.2 注册与使用
// Program.cs 中注册开放泛型服务
services.AddSingleton(typeof(ITypedCache<,>), typeof(MemoryTypedCache<,>));

// 在应用服务中注入并使用
public class OrderService
{
    private readonly ITypedCache<string, Order> _orderCache;

    public OrderService(ITypedCache<string, Order> orderCache)
    {
        _orderCache = orderCache;
    }

    public Order GetOrder(string id)
    {
        return _orderCache.GetOrAdd(id, LoadFromDb);
    }
}

🧩 开放泛型注册是 .NET 依赖注入容器的强大特性,它允许你注册一个未完全指定类型参数的泛型定义,容器会在解析时自动填充具体类型。

6.4 泛型 + 继承:创建可扩展的基类模板

6.4.1 案例:通用 CRUD 仓储基类

通过泛型抽象类,可以消除各实体仓储中重复的CRUD样板代码。

public abstract class RepositoryBase<T> : IRepository<T>
    where T : class, IEntity
{
    protected readonly DbContext _context;

    public RepositoryBase(DbContext context)
    {
        _context = context;
    }

    public virtual T GetById(string id)
    {
        return _context.Set<T>().Find(id);
    }

    public virtual void Add(T entity)
    {
        _context.Set<T>().Add(entity);
    }

    public virtual void Update(T entity)
    {
        _context.Set<T>().Update(entity);
    }

    public virtual void Delete(T entity)
    {
        _context.Set<T>().Remove(entity);
    }
}

// 具体实体仓储只需继承并专注于特有查询
public class OrderRepository : RepositoryBase<Order>, IOrderRepository
{
    public OrderRepository(AppDbContext context) : base(context) { }

    // 扩展:特定于订单的查询方法
    public IEnumerable<Order> GetByCustomer(string customerId)
    {
        return _context.Orders.Where(o => o.CustomerId == customerId);
    }
}

继承 + 泛型的价值

  • 消除重复:所有实体共有的CRUD操作只需在基类中实现一次。
  • 灵活扩展:子类可以覆盖(virtual)基类方法或新增特有方法。
  • 类型安全DbContext.Set<T>()确保我们操作的是正确的实体集。
6.4.2 泛型抽象类 vs 泛型接口
方式 优点 缺点
泛型抽象类 可以提供默认实现,显著减少子类中的重复代码。 受C#单继承限制,一个类只能继承一个抽象类。
泛型接口 支持多实现,更加灵活,是定义契约的首选。 在C# 8.0之前无法提供默认实现(现在可以部分弥补)。

💡 建议

  • 如果需要为多个相关类共享通用的实现代码,请使用泛型抽象类
  • 如果只需要定义一套行为契约,或者一个类需要实现多种契约,请使用泛型接口

6.5 泛型 + 多态:实现类型感知的策略模式

6.5.1 案例:通用验证器系统

需求:为不同的领域实体(如OrderCustomer)定义不同的验证规则,但提供一个统一的验证入口。

// 泛型验证接口
public interface IValidator<in T>
{
    ValidationResult Validate(T instance);
}

// 订单验证器
public class OrderValidator : IValidator<Order>
{
    public ValidationResult Validate(Order order)
    {
        if (order.Items.Count == 0)
            return ValidationResult.Fail("Order must have at least one item.");
        return ValidationResult.Success();
    }
}

// 客户验证器
public class CustomerValidator : IValidator<Customer>
{
    public ValidationResult Validate(Customer customer)
    {
        if (string.IsNullOrWhiteSpace(customer.Name))
            return ValidationResult.Fail("Customer name is required.");
        return ValidationResult.Success();
    }
}

🔁 多态困境
虽然OrderValidatorCustomerValidator都实现了IValidator<T>,但由于T不同,它们是两个完全不同的类型,无法直接放入同一个List<IValidator<object>>中进行多态调用。

6.5.2 解决方案:非泛型适配器桥接

为了解决上述困境,我们需要一个非泛型的适配器来桥接。

// 非泛型接口(用于多态集合)
public interface IValidator
{
    ValidationResult Validate(object instance);
}

// 适配器:将泛型验证器适配到非泛型接口
public class ValidatorAdapter<T> : IValidator
{
    private readonly IValidator<T> _innerValidator;

    public ValidatorAdapter(IValidator<T> innerValidator)
    {
        _innerValidator = innerValidator;
    }

    public ValidationResult Validate(object instance)
    {
        if (instance is T typedInstance)
            return _innerValidator.Validate(typedInstance);
        throw new ArgumentException($"Expected instance of type {typeof(T)}, but got {instance?.GetType()}");
    }
}
6.5.3 依赖注入配置
// 注册具体的泛型验证器
services.AddSingleton<IValidator<Order>, OrderValidator>();
services.AddSingleton<IValidator<Customer>, CustomerValidator>();

// 注册对应的适配器(这里展示手动注册,也可用Scrutor等库自动扫描注册)
services.AddSingleton<IValidator>(sp =>
    new ValidatorAdapter<Order>(sp.GetRequiredService<IValidator<Order>>()));
services.AddSingleton<IValidator>(sp =>
    new ValidatorAdapter<Customer>(sp.GetRequiredService<IValidator<Customer>>()));
6.5.4 统一的验证服务
public class ValidationService
{
    private readonly IEnumerable<IValidator> _validators;

    public ValidationService(IEnumerable<IValidator> validators)
    {
        _validators = validators;
    }

    public ValidationResult Validate(object entity)
    {
        // 通过反射找到能处理该实体类型的适配器
        var validator = _validators.FirstOrDefault(v =>
            v.GetType().IsGenericType &&
            v.GetType().GetGenericArguments()[0] == entity.GetType());

        return validator?.Validate(entity) ?? ValidationResult.Success();
    }
}

最终效果:实现了编译时类型安全(每个实体都有专属验证器)与运行时多态分发(通过统一的服务入口调用)。

var result1 = validationService.Validate(order);    // 内部调用 OrderValidator
var result2 = validationService.Validate(customer); // 内部调用 CustomerValidator

6.6 协变(Covariance)与逆变(Contravariance)

泛型变体是C#的高级特性,它允许在安全的前提下,进行更灵活的泛型类型赋值,极大增强了接口和委托的灵活性。理解网络与系统编程中的类型系统对此有帮助。

6.6.1 协变(out):支持从派生类到基类的赋值

协变使用out关键字,表示类型参数仅用于输出位置(如返回值)。

// 声明为协变接口
public interface IProducer<out T>
{
    T Produce();
}

// 使用:可以将派生类型赋值给基类型
IProducer<Order> orderProducer = new OrderProducer();
IProducer<IEntity> entityProducer = orderProducer; // 合法!因为 Order 是 IEntity 的派生类

适用场景只读的集合或工厂,例如 .NET 中的 IEnumerable<out T>

6.6.2 逆变(in):支持从基类到派生类的赋值

逆变使用in关键字,表示类型参数仅用于输入位置(如方法参数)。

// 声明为逆变接口
public interface IConsumer<in T>
{
    void Consume(T item);
}

// 使用:可以将基类型处理器赋值给派生类型变量
IConsumer<IEntity> entityConsumer = new EntityLogger();
IConsumer<Order> orderConsumer = entityConsumer; // 合法!因为能处理IEntity的处理器必然也能处理Order

适用场景只写的操作或比较器,例如 Action<in T>IComparer<in T>

6.6.3 实战:利用逆变简化事件处理器

逆变特性允许我们编写一个能处理整个事件层次结构的通用处理器。

public interface IEventHandler<in TEvent> where TEvent : IDomainEvent
{
    Task HandleAsync(TEvent @event);
}

// 一个通用的审计日志处理器,可以处理任何领域事件
public class AuditLogHandler : IEventHandler<IDomainEvent>
{
    public Task HandleAsync(IDomainEvent @event)
    {
        Console.WriteLine($"[AUDIT] Event {@event.GetType().Name} occurred at {@event.Timestamp}");
        return Task.CompletedTask;
    }
}

// 注册:只需注册这一个处理器
services.AddSingleton<IEventHandler<IDomainEvent>, AuditLogHandler>();

// 此处理器现在可以自动处理 OrderCreatedEvent, PaymentProcessedEvent 等所有派生事件

🔥 逆变的威力:一个注册的 IEventHandler<IDomainEvent> 实例,可以处理所有派生自 IDomainEvent 的具体事件类型,极大地简化了事件处理系统的配置。

6.7 实战项目:通用领域事件发布/订阅系统

我们将构建一个支持类型安全、自动注册、事务一致性的领域事件总线。

6.7.1 需求
  • 领域对象状态变更时发布事件(如OrderCreatedEvent)。
  • 允许多个处理器响应同一事件(如发送邮件、记录日志、更新统计)。
  • 事件与处理器之间通过泛型强关联,确保类型安全。
  • 支持异步处理,并确保在数据库事务回滚时取消事件发布。
6.7.2 核心接口设计
// 领域事件基类
public abstract class DomainEvent : IDomainEvent
{
    public Guid EventId { get; } = Guid.NewGuid();
    public DateTime Timestamp { get; } = DateTime.UtcNow;
}

// 泛型事件处理器接口
public interface IDomainEventHandler<in TEvent> : INotificationHandler<TEvent>
    where TEvent : DomainEvent
{
    // 此接口继承自 MediatR 的 INotificationHandler,便于集成
}

// 事件总线(非泛型,用于应用层统一调用)
public interface IDomainEventBus
{
    Task PublishAsync(DomainEvent @event);
}
6.7.3 事件处理器示例
// 订单创建事件
public record OrderCreatedEvent(string OrderId, string CustomerId) : DomainEvent;

// 邮件通知处理器
public class SendOrderConfirmationEmailHandler : IDomainEventHandler<OrderCreatedEvent>
{
    public async Task Handle(OrderCreatedEvent notification, CancellationToken ct)
    {
        await EmailService.SendAsync(
            to: "customer@example.com",
            subject: "Your order is confirmed",
            body: $"Order {notification.OrderId} has been created successfully."
        );
    }
}

// 日志记录处理器
public class LogOrderCreationHandler : IDomainEventHandler<OrderCreatedEvent>
{
    public Task Handle(OrderCreatedEvent notification, CancellationToken ct)
    {
        Logger.LogInformation("Order {OrderId} created for customer {CustomerId}",
            notification.OrderId, notification.CustomerId);
        return Task.CompletedTask;
    }
}
6.7.4 事件总线实现(基于 MediatR)
public class DomainEventBus : IDomainEventBus
{
    private readonly IMediator _mediator;
    private readonly ILogger<DomainEventBus> _logger;

    public DomainEventBus(IMediator mediator, ILogger<DomainEventBus> logger)
    {
        _mediator = mediator;
        _logger = logger;
    }

    public async Task PublishAsync(DomainEvent @event)
    {
        _logger.LogDebug("Publishing domain event: {EventType}", @event.GetType().Name);
        await _mediator.Publish(@event);
    }
}

MediatR 的优势:它会自动解析所有实现了 INotificationHandler<TEvent> 的处理器并调用,天然支持事件的多播(一个事件,多个处理器)。

6.7.5 在聚合根中发布事件
public class Order : AggregateRoot
{
    private readonly List<DomainEvent> _domainEvents = new();

    public void Create(string orderId, string customerId)
    {
        // ... 订单创建的领域逻辑
        _domainEvents.Add(new OrderCreatedEvent(orderId, customerId));
    }

    public IReadOnlyCollection<DomainEvent> DequeueEvents()
    {
        var events = _domainEvents.ToList();
        _domainEvents.Clear();
        return events;
    }
}

// 在应用服务中协调
public class OrderService
{
    public async Task CreateOrderAsync(CreateOrderCommand command)
    {
        var order = new Order();
        order.Create(command.Id, command.CustomerId);

        _orderRepository.Add(order);
        await _unitOfWork.SaveChangesAsync(); // 1. 先提交数据库事务

        // 2. 事务成功后,发布所有累积的事件
        foreach (var @event in order.DequeueEvents())
        {
            await _eventBus.PublishAsync(@event);
        }
    }
}

🔒 事务安全性:事件仅在数据库事务成功提交后才发布,避免了“业务数据已保存,但后续事件处理失败导致状态不一致”的问题。

6.7.6 依赖注入配置
// 注册 MediatR 并扫描程序集
services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<Order>());

// 注册自定义的事件总线
services.AddScoped<IDomainEventBus, DomainEventBus>();

🧩 零配置:只要你的处理器实现了 IDomainEventHandler<TEvent>,MediatR 就会自动发现并注册它,无需额外配置。

6.8 泛型与性能:避免常见陷阱

6.8.1 消除装箱/拆箱

泛型在编译时生成特定类型的代码,从而避免了值类型的装箱操作。

// ❌ 使用非泛型集合会有装箱
var list = new ArrayList();
list.Add(42); // int 被装箱为 object

// ✅ 使用泛型集合,无装箱开销
var list = new List<int>();
list.Add(42); // 直接存储 int
6.8.2 泛型爆炸(Generic Explosion)

每个封闭的泛型类型(如 List<int>, List<string>)在JIT编译时都会生成一份独立的本地代码。

// 以下三种会生成三份不同的机器代码
var repo1 = new Repository<Order>();
var repo2 = new Repository<Customer>();
var repo3 = new Repository<Product>();

⚠️ 注意:在极端情况下,大量不同的泛型实例化可能会轻微增加内存占用和工作集大小,但对于绝大多数应用,现代 .NET 的优化已使此开销可忽略不计。

6.8.3 避免在泛型方法中滥用反射

反射调用性能开销大,应尽量避免在泛型方法中频繁使用 typeof(T)

// ❌ 性能较差:每次调用都使用反射
public T CreateInstance<T>()
{
    return (T)Activator.CreateInstance(typeof(T));
}

// ✅ 性能更佳:使用 new() 约束,直接调用构造函数
public T CreateInstance<T>() where T : new()
{
    return new T(); // 编译时为调用特定类型的构造函数
}

6.9 本章小结

本章深入探讨了泛型如何与面向对象编程的三大特性深度融合:

  • 泛型 + 封装:用于构建类型安全的通用组件,如缓存包装器、仓储基类。
  • 泛型 + 继承:用于创建可复用的代码模板,大幅减少系统中的样板代码。
  • 泛型 + 多态:通过适配器模式或MediatR等库,实现编译时安全、运行时灵活的策略分发。
  • 协变与逆变:提升了泛型接口的灵活性,使得基于类型层次结构的编程更加直观。
  • 实战项目:通过一个通用领域事件总线,展示了泛型在解耦业务逻辑、实现最终一致性方面的核心价值。
  • 性能意识:理解了泛型如何消除装箱,并避免在热路径中滥用反射。

💡 终极心法
“用泛型来抽象数据类型,用OOP来抽象行为逻辑。二者相辅相成,才能构建出既灵活又健壮的软件系统。”


第七章:异步编程与面向对象的融合——构建高性能、响应式的领域模型

7.1 引言:为什么异步不能破坏 OOP?

在传统的同步OOP模型中,代码清晰、内聚且易于推理:

public class OrderService
{
    public void Process(Order order)
    {
        _paymentService.Charge(order);      // 同步调用
        _inventoryService.Reserve(order);   // 同步调用
        _emailService.SendConfirmation(order);
    }
}

然而,当系统面临高并发或依赖缓慢的外部资源(如数据库、HTTP API)时,同步阻塞会迅速耗尽线程池,导致系统吞吐量急剧下降。

异步编程(async/await) 是 .NET 应对高并发I/O操作的核心机制。
但若使用不当,异步代码会轻易破坏OOP的封装性、可测试性和逻辑一致性。

本章的目标是:在不牺牲OOP核心原则的前提下,安全、高效地将异步行为引入到领域模型和应用架构中。

7.2 异步方法设计的基本原则

7.2.1 “Async All the Way” 原则

一旦调用链中的某个方法变为异步,那么它的所有调用者都应该异步化,避免混合同步与异步调用。

// ❌ 反模式:在同步方法中阻塞等待异步任务
public void ProcessOrder(string id)
{
    var order = _orderRepository.GetById(id).Result; // 死锁风险极高!
    _paymentService.ChargeAsync(order).Wait();       // 性能差,且可能死锁
}

// ✅ 正确做法:保持调用链全程异步
public async Task ProcessOrderAsync(string id)
{
    var order = await _orderRepository.GetByIdAsync(id);
    await _paymentService.ChargeAsync(order);
}

⚠️ 在 ASP.NET 等拥有同步上下文的应用程序中,使用 .Result.Wait() 极易引发死锁。

7.2.2 异步方法命名规范
  • 所有返回 TaskTask<T> 的公共异步方法,必须使用 Async 后缀。
    public Task<Order> GetOrderAsync(string id);
    public Task SaveChangesAsync();
  • 私有或内部方法可酌情省略,但保持团队内部一致性至关重要。

📏 一致性是代码可读性的基石。

7.2.3 避免 async void

async void 方法无法被等待,其抛出的异常会直接触发 SynchronizationContext 的异常事件,可能导致应用程序崩溃。它仅适用于事件处理器

// ❌ 危险:异常无法被捕获,会导致进程崩溃
public async void Button_Click(object sender, EventArgs e)
{
    await DoSomethingAsync(); // 如果抛出异常,AppDomain可能崩溃
}

// ✅ 安全:使用 async Task,异常可以被正常捕获和处理
public async Task Button_ClickAsync(object sender, EventArgs e)
{
    try { await DoSomethingAsync(); }
    catch (Exception ex) { LogAndHandle(ex); }
}

🔥 在服务层、仓储层、领域层代码中,绝对不要使用 async void

7.3 封装与异步:保护领域对象的纯洁性

7.3.1 领域对象应保持同步

领域模型的核心职责是封装业务规则与状态变更,这些通常是纯计算逻辑,不涉及I/O操作。

// ✅ Order 作为富血模型,保持纯同步
public class Order
{
    private OrderStatus _status = OrderStatus.Pending;

    public void MarkAsPaid() // 同步方法
    {
        if (_status != OrderStatus.Pending)
            throw new InvalidOperationException("Order is already processed.");
        _status = OrderStatus.Paid;
    }

    public Money CalculateTotal() => /* 纯计算逻辑 */;
}

💡 理由

  • 领域逻辑应当是快速、确定、无副作用的。
  • 引入异步会污染聚合根,使其难以进行单元测试和序列化。
  • I/O操作属于基础设施层,应由应用服务层来协调。
7.3.2 应用服务负责异步编排

协调异步I/O和同步领域逻辑是应用服务的职责。

public class OrderApplicationService
{
    public async Task ConfirmOrderAsync(string orderId)
    {
        // 1. 异步加载聚合根(I/O)
        var order = await _orderRepository.GetByIdAsync(orderId);
        // 2. 调用同步的领域行为
        order.MarkAsPaid();
        // 3. 异步调用外部服务(I/O)
        await _paymentGateway.CaptureAsync(order.Total);
        // 4. 异步持久化变更(I/O)
        await _orderRepository.UpdateAsync(order);
        await _unitOfWork.SaveChangesAsync();
    }
}

清晰的职责分离

  • 领域对象:定义 What(业务规则和状态如何变化)。
  • 应用服务:定义 How & When(如何协调领域逻辑和外部I/O,以及执行顺序)。

7.4 继承与异步:基类中的异步模板方法

7.4.1 案例:通用异步工作流基类

当多个业务流程共享相似的步骤时(如“加载→验证→执行→通知”),可以使用异步模板方法模式在基类中定义流程骨架。

public abstract class AsyncWorkflow<TInput, TOutput>
{
    public async Task<TOutput> ExecuteAsync(TInput input)
    {
        var context = await LoadContextAsync(input);
        await ValidateAsync(context);
        var result = await ProcessAsync(context);
        await NotifyAsync(result);
        return result;
    }

    protected abstract Task<WorkflowContext> LoadContextAsync(TInput input);
    protected abstract Task ValidateAsync(WorkflowContext context);
    protected abstract Task<TOutput> ProcessAsync(WorkflowContext context);
    protected virtual Task NotifyAsync(TOutput result) => Task.CompletedTask;
}

// 具体实现
public class OrderProcessingWorkflow : AsyncWorkflow<string, Order>
{
    protected override async Task<WorkflowContext> LoadContextAsync(string orderId)
    {
        var order = await _orderRepo.GetByIdAsync(orderId);
        return new WorkflowContext { Order = order };
    }

    protected override Task ValidateAsync(WorkflowContext context)
    {
        if (context.Order.Items.Count == 0)
            throw new ValidationException("Order cannot be empty.");
        return Task.CompletedTask;
    }

    protected override async Task<Order> ProcessAsync(WorkflowContext context)
    {
        context.Order.MarkAsPaid();
        await _paymentService.CaptureAsync(context.Order.Total);
        return context.Order;
    }
}

继承 + 异步模板方法的价值

  • 复用流程控制逻辑:所有子类共享相同的执行顺序和错误处理框架。
  • 子类专注业务细节:只需实现与自己相关的异步步骤。
  • 天然支持异步:整个工作流从设计上就是异步友好的。

7.5 多态与异步:异步策略与处理器

7.5.1 异步策略接口

多态在异步世界中依然有效。我们可以定义异步策略接口,让不同实现决定具体的异步行为。

public interface IPricingStrategy
{
    Task<Money> CalculateAsync(Order order, CancellationToken ct = default);
}

public class VipPricingStrategy : IPricingStrategy
{
    public async Task<Money> CalculateAsync(Order order, CancellationToken ct)
    {
        // 可能涉及调用外部折扣服务
        var discount = await _discountApi.GetVipDiscountAsync(order.CustomerId, ct);
        return order.BaseTotal * (1 - discount);
    }
}

🔁 调用者无需关心具体策略内部是同步计算还是调用了异步API,多态的特性得以保留。

7.5.2 异步事件处理器(回顾第六章)

我们在第六章实现的事件处理器 (IDomainEventHandler<TEvent>) 本身就是异步多态的完美例子。多个处理器实现同一接口,由事件总线进行运行时多态分发。

public interface IDomainEventHandler<in TEvent>
{
    Task HandleAsync(TEvent @event, CancellationToken ct);
}

结论:异步改变了代码的执行模型,但并未改变多态的设计模型本质。

7.6 异步与事务:Unit of Work 模式的升级

7.6.1 问题:分布式事务的复杂性

在异步跨服务调用中,传统的ACID事务难以实现。

// ❌ 危险模式:如果持久化失败,支付无法回滚
await _paymentService.ChargeAsync(order); // 调用外部支付网关
await _orderRepository.UpdateAsync(order); // 更新本地数据库
await _unitOfWork.SaveChangesAsync(); // 如果失败,支付已发生!

🔄 解决方案:在微服务架构中,我们通常采用最终一致性,并结合领域事件和补偿机制。

7.6.2 解决方案:领域事件 + 补偿机制

核心思想:先将需要异步处理的任务作为领域事件,与主业务数据在同一个数据库事务中持久化。

public async Task ConfirmOrderAsync(string orderId)
{
    var order = await _repo.GetByIdAsync(orderId);
    order.MarkAsPaid();
    // 1. 在聚合根内添加领域事件(内存操作)
    order.AddDomainEvent(new OrderPaidEvent(order.Id));

    // 2. 保存订单状态和事件到数据库(原子操作)
    await _unitOfWork.SaveChangesAsync(); // <-- 事务边界

    // 3. 事务成功后,异步发布事件(可能失败,但事件已持久化,可重试)
    foreach (var @event in order.DequeueEvents())
    {
        await _eventBus.PublishAsync(@event);
    }
}

🔒 关键保障:只要 SaveChangesAsync() 成功,就意味着业务状态和待处理的事件都已被可靠地记录。后续事件处理失败可以通过重试机制来保证最终一致性。

7.7 异步流(IAsyncEnumerable)与实时处理

7.7.1 场景:大数据量流式导出

传统一次性加载所有数据的方式容易导致内存溢出。

// ❌ 内存压力大
var orders = await _orderRepo.GetAllAsync(); // 假设有100万条
return orders.Select(MapToDto);

✅ 使用 IAsyncEnumerable<T> 进行流式处理:

public async IAsyncEnumerable<OrderDto> StreamOrdersAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    await foreach (var order in _orderRepo.StreamAllAsync(ct))
    {
        yield return MapToDto(order);
        await Task.Delay(1, ct); // 可选:用于控制流式输出的速度,避免客户端过载
    }
}

// 在 Controller 中直接返回异步流
[HttpGet("stream")]
public async IAsyncEnumerable<OrderDto> StreamOrders()
{
    await foreach (var dto in _service.StreamOrdersAsync(HttpContext.RequestAborted))
        yield return dto;
}

🌊 优势

  • 恒定内存占用:每次只在内存中保持一个实体。
  • 即时响应:客户端可以边接收数据边处理或渲染。
  • 支持取消:通过 CancellationToken 可以随时中断流。
7.7.2 异步流 + OOP:封装数据源

将异步流封装在专门的提供者类中,符合OOP的封装思想。

public class OrderStreamProvider
{
    private readonly IOrderRepository _repo;
    public OrderStreamProvider(IOrderRepository repo) => _repo = repo;

    public async IAsyncEnumerable<OrderSummary> GetSummariesAsync(
        DateTime from, DateTime to,
        [EnumeratorCancellation] CancellationToken ct = default)
    {
        await foreach (var order in _repo.StreamByDateRangeAsync(from, to, ct))
        {
            // 封装复杂的转换和计算逻辑
            yield return new OrderSummary
            {
                Id = order.Id,
                Total = order.CalculateTotal(), // 调用同步领域方法
                CustomerName = order.Customer.Name
            };
        }
    }
}

OOP封装 + 异步流 = 安全、高效、可复用的数据管道

7.8 实战项目:高并发订单处理流水线

7.8.1 业务场景
  • 每秒处理 1000+ 订单创建请求。
  • 需调用支付网关、库存服务、风控系统等多个外部依赖。
  • 任一环节失败需记录日志并告警,但不阻塞其他订单的处理。
  • 支持优雅关闭(处理完已在队列中的任务)。
7.8.2 架构设计

我们使用生产者-消费者模式,利用 System.Threading.Channels 实现高性能流水线。

[API 控制器] (生产者)
        ↓ 入队
[Channel<OrderRequest>] (有界队列)
        ↓
[后台工作者池] (消费者) → [支付服务] → [库存服务] → [持久化]
        ↓
[结果Channel] → [响应缓存/Webhook]
7.8.3 核心组件实现
1. 订单处理器(OOP + 异步)
public interface IOrderProcessor
{
    Task<OrderResult> ProcessAsync(OrderRequest request, CancellationToken ct);
}

public class DefaultOrderProcessor : IOrderProcessor
{
    public async Task<OrderResult> ProcessAsync(OrderRequest request, CancellationToken ct)
    {
        try
        {
            // 1. 同步领域验证与创建
            var order = Order.Create(request);
            // 2. 并行调用外部服务
            var paymentTask = _paymentService.AuthorizeAsync(order.Total, ct);
            var riskCheckTask = _riskService.EvaluateAsync(order, ct);
            await Task.WhenAll(paymentTask, riskCheckTask);
            // 3. 检查结果并决策
            if (!riskCheckTask.Result.IsApproved)
                return OrderResult.Rejected("Risk check failed.");
            // 4. 持久化
            await _orderRepository.AddAsync(order, ct);
            await _unitOfWork.SaveChangesAsync(ct);
            return OrderResult.Success(order.Id);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Order processing failed for request {RequestId}", request.Id);
            return OrderResult.Failed(ex.Message);
        }
    }
}
2. 后台处理服务(生产者-消费者核心)
public class OrderProcessingBackgroundService : BackgroundService
{
    private readonly Channel<OrderEnvelope> _inputChannel;
    private readonly IServiceProvider _serviceProvider;

    public OrderProcessingBackgroundService(Channel<OrderEnvelope> inputChannel, IServiceProvider serviceProvider)
    {
        _inputChannel = inputChannel;
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var envelope in _inputChannel.Reader.ReadAllAsync(stoppingToken))
        {
            // 使用 Task.Run 提交到线程池,避免阻塞 Channel 的读取循环
            _ = Task.Run(async () =>
            {
                // 为每个任务创建独立的作用域,确保DI服务生命周期正确
                using var scope = _serviceProvider.CreateScope();
                var processor = scope.ServiceProvider.GetRequiredService<IOrderProcessor>();
                var result = await processor.ProcessAsync(envelope.Request, stoppingToken);
                // 将处理结果设置回请求的 TaskCompletionSource
                await envelope.CompletionSource.SetResultAsync(result);
            }, stoppingToken);
        }
    }
}
3. API控制器(生产者入口)
[ApiController]
public class OrdersController : ControllerBase
{
    private readonly Channel<OrderEnvelope> _channel;
    public OrdersController(Channel<OrderEnvelope> channel) => _channel = channel;

    [HttpPost]
    public async Task<ActionResult<OrderResult>> CreateOrder(OrderRequest request)
    {
        var tcs = new TaskCompletionSource<OrderResult>();
        var envelope = new OrderEnvelope(request, tcs);

        // 非阻塞入队
        await _channel.Writer.WriteAsync(envelope);

        // 等待后台处理结果,并设置超时
        var result = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(30));
        return Ok(result);
    }
}

性能关键点

  • Channel:提供高效的无锁队列。
  • Task.Run:将CPU密集型或可能阻塞的I/O操作卸载到线程池,保持I/O线程畅通。
  • 独立作用域:每个处理任务拥有独立的DI作用域,避免状态污染。
  • 超时与取消:全面支持,保证系统弹性。

7.9 异步异常处理与日志

7.9.1 异常传播

async方法中的异常会被捕获并存储在返回的Task对象中,只有在 await 这个Task时,异常才会被重新抛出

public async Task BadExample()
{
    var task = ThrowExceptionAsync(); // 异常被存储在task中,并未抛出!
    DoOtherWork();                     // 这行代码仍然会执行
    await task;                        // 到这里,异常才会被抛出
}

最佳实践:除非你明确想要“触发后不管”(fire-and-forget),否则应该立即await异步调用。

7.9.2 Fire-and-Forget 的安全写法

对于确实不需要等待结果的后台任务,必须妥善处理异常,避免“沉默的失败”。

// ❌ 危险:异常被完全忽略,问题难以排查
_ = DoBackgroundWorkAsync();

// ✅ 安全:将任务提交到线程池,并记录所有异常
_ = Task.Run(async () =>
{
    try { await DoBackgroundWorkAsync(); }
    catch (Exception ex) { _logger.LogError(ex, "Background task failed."); }
});

7.10 测试异步 OOP 代码

7.10.1 单元测试

使用 Moq 等框架可以方便地模拟异步依赖。

[Fact]
public async Task ProcessAsync_Should_ReturnSuccess_WhenAllServicesSucceed()
{
    // Arrange
    _paymentService.Setup(x => x.AuthorizeAsync(It.IsAny<Money>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(new PaymentResult { Success = true });
    _riskService.Setup(x => x.EvaluateAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(new RiskResult { IsApproved = true });
    var processor = new DefaultOrderProcessor(...);

    // Act
    var result = await processor.ProcessAsync(validRequest, CancellationToken.None);

    // Assert
    Assert.True(result.IsSuccess);
}
7.10.2 集成测试(测试真实异步流)
[Fact]
public async Task StreamOrdersAsync_Should_YieldAllOrders()
{
    // Arrange
    await SeedOrders(1000); // 向测试数据库插入1000条订单

    // Act & Assert
    var count = 0;
    await foreach (var _ in _streamProvider.GetSummariesAsync(DateTime.MinValue, DateTime.MaxValue))
    {
        count++;
    }
    Assert.Equal(1000, count);
}

7.11 本章小结

本章系统地探讨了如何在面向对象设计中安全、高效地引入异步编程:

  • 封装:保持领域模型的同步纯洁性,将异步编排职责上移至应用服务层。
  • 继承:利用异步模板方法模式,在基类中定义可复用的异步工作流骨架。
  • 多态:异步策略和事件处理器完美支持运行时分发,设计模式在异步世界中依然有效。
  • 事务与一致性:采用领域事件和最终一致性模式,在分布式环境下可靠地协调状态。
  • 高性能模式:运用 IAsyncEnumerable<T>Channel<T> 构建响应式、高吞吐的数据处理管道。
  • 健壮性:强调了正确的异常处理、取消和超时机制。
  • 可测试性:展示了如何对异步代码进行全面的单元测试和集成测试。

💡 核心思想
“异步是一种执行模型,而面向对象是一种设计模型。二者是正交的,可以且应该完美地共存,共同构建出既清晰又高效的现代应用程序。”


第八章:微服务架构中的面向对象设计——跨服务边界的行为与状态管理

8.1 引言:OOP 在分布式世界的困境与重生

在单体应用程序中,面向对象设计如鱼得水:

  • 聚合根完整地封装了状态与行为。
  • 本地事务保证了数据的强一致性。
  • 方法调用是即时、可靠且低延迟的。

然而,当架构演进到微服务时,挑战随之而来:

  • 一个业务用例(如“创建订单”)需要横跨多个自治的服务(订单、库存、支付)。
  • 无法使用跨数据库的分布式事务,ACID原则让位于BASE理论。
  • 服务间通信存在网络延迟、故障和超时。
  • 原本内聚的领域对象被拆分到不同服务中,导致DTO泛滥,核心业务逻辑可能泄露到服务边界。

核心问题:当 Order 对象不能再直接调用 Inventory.Reserve() 方法时,面向对象设计是否已经过时?

答案并非如此。OOP的核心价值不仅在于“本地封装”,更在于“概念建模”与“行为归属”。 微服务架构并非抛弃OOP,而是要求我们将OOP的原则从单个进程内,扩展到跨进程的边界上下文之间

本章将揭示如何在分布式微服务环境中,以面向对象思想为指导,构建出高内聚、松耦合、易于演化的系统

8.2 聚合根的边界重构:从单体到微服务

8.2.1 单体应用中的聚合根

在单体中,聚合根可能直接依赖并调用外部服务。

public class Order : AggregateRoot
{
    public void Create(List<OrderItem> items)
    {
        // 1. 直接调用库存服务(紧耦合)
        foreach (var item in items)
            _inventoryService.Reserve(item.ProductId, item.Quantity);
        // 2. 直接调用支付服务
        _paymentService.Authorize(Total);
        // 3. 变更自身状态
        Status = OrderStatus.Created;
    }
}

⚠️ 问题

  • Order 聚合根与外部服务紧耦合,违反了聚合的自治原则。
  • 事务跨越了订单和库存表,使得服务拆分变得异常困难。
8.2.2 微服务中的聚合根:专注自身状态

在微服务架构中,每个服务拥有自己独立且完整的领域模型。聚合根只负责管理和保护其自身边界内的状态与不变条件。

// 位于 OrderService 中的 Order 聚合根
public class Order : AggregateRoot
{
    public string Id { get; private set; }
    public OrderStatus Status { get; private set; }
    public List<OrderItem> Items { get; } = new();

    // ✅ 只包含与订单自身状态相关的业务规则
    public void MarkAsCreated()
    {
        if (Items.Count == 0)
            throw new InvalidOperationException("Cannot create an empty order.");
        Status = OrderStatus.Created; // 仅变更自身状态
    }
    public void MarkAsCancelled() => Status = OrderStatus.Cancelled;
}

// 位于 InventoryService 中的 Stock 聚合根
public class Stock : AggregateRoot
{
    public string ProductId { get; private set; }
    public int AvailableQuantity { get; private set; }

    public void Reserve(int quantity)
    {
        if (AvailableQuantity < quantity)
            throw new InsufficientStockException();
        AvailableQuantity -= quantity; // 只管理库存数量
    }
    public void Release(int quantity) => AvailableQuantity += quantity;
}

🔑 关键转变
聚合根不再“命令”其他服务做什么,而是根据外部发生的事实(事件或命令)“响应式”地变更自己的状态。 跨服务的业务流程协调,提升到了应用层或通过事件驱动机制来处理。

8.3 行为协调模式:谁来驱动跨服务流程?

当业务操作涉及多个服务时,我们需要一种机制来协调它们。主要有两种模式:

8.3.1 模式一:编排(Orchestration)——中心化协调者

由一个协调者服务(通常是主导业务流程的服务,如OrderService)充当指挥中心,依次调用或命令其他服务完成任务。
编排模式示意图
优点:流程控制集中,逻辑清晰,易于监控和调试。
缺点:协调者服务会成为“上帝服务”,与所有下游服务耦合,承担过重职责,可能成为瓶颈和单点故障源。

8.3.2 模式二:编舞(Choreography)——事件驱动协作

各个服务之间没有直接的调用关系。它们通过发布和订阅领域事件来进行协作。一个服务完成工作后发布事件,其他对此感兴趣的服务监听并处理该事件。
编舞模式示意图
优点:服务间完全解耦,自治性高,系统整体弹性好。
缺点:业务流程隐含在事件流中,难以全局把握,调试和问题追踪较复杂;需要妥善处理事件丢失、重复消费等问题。

8.3.3 模式选择建议
场景 推荐模式 理由
流程简单、步骤固定(≤3步) 编排 (Orchestration) 实现简单,逻辑直观。
多团队独立开发、服务高度自治 编舞 (Choreography) 减少团队间协调,服务可独立演进。
需要严格的SLA和端到端监控 编排 (Orchestration) 协调中心便于收集监控数据和实施熔断限流。
业务本质是通知、广播(如订单状态更新) 编舞 (Choreography) 事件驱动是天然模型。

💡 混合模式:在实践中,通常会混合使用。例如,核心订单创建流程使用编排确保一致性,而订单创建成功后发送邮件、更新推荐引擎等操作则使用编舞。

8.4 数据一致性:Saga 模式与补偿事务

8.4.1 为什么不用分布式事务?

在微服务中,传统的两阶段提交(2PC)分布式事务因其性能差、同步阻塞、协议复杂以及与HTTP等无状态协议的兼容性问题,通常不是首选。微服务架构更关注可用性和分区容错性(CAP定理中的AP)。

Saga 模式:将一个跨服务的分布式长事务,分解为一系列本地事务。每个本地事务完成后,发布一个事件或消息来触发下一个本地事务。如果某个步骤失败,则执行一系列补偿操作来回滚之前已完成的步骤。

8.4.2 Saga 的两种实现方式
1. 编排式 Saga(Orchestrated Saga)

由Saga协调器(一个特殊的组件)负责集中管理流程和补偿逻辑。

public class CreateOrderSaga
{
    public async Task HandleAsync(CreateOrderCommand command)
    {
        try
        {
            await _orderService.CreatePendingOrderAsync(command);
            await _inventoryService.ReserveAsync(command.Items);
            await _paymentService.AuthorizeAsync(command.Total);
            await _orderService.ConfirmOrderAsync(command.Id); // 最终提交
        }
        catch (Exception ex)
        {
            // 执行补偿逻辑
            await CompensateAsync(command, ex);
        }
    }

    private async Task CompensateAsync(CreateOrderCommand command, Exception error)
    {
        if (error is PaymentFailedException)
        {
            await _inventoryService.ReleaseAsync(command.Items); // 补偿:释放库存
        }
        // ... 其他情况的补偿
        await _orderService.CancelOrderAsync(command.Id); // 最终补偿:取消订单
    }
}

🧩 优势:补偿逻辑集中在一个协调器中,易于理解、维护和测试。

2. 编舞式 Saga(Choreography-based Saga)

每个服务监听相关事件,并自主决定是否执行以及如何补偿。

// OrderService 中的处理器
public class HandlePaymentFailed : IDomainEventHandler<PaymentFailedEvent>
{
    public async Task HandleAsync(PaymentFailedEvent @event)
    {
        await _orderRepo.CancelAsync(@event.OrderId); // 1. 取消订单
        await _eventBus.PublishAsync(new OrderCancelledEvent(@event.OrderId)); // 2. 发布新事件
    }
}

// InventoryService 中的处理器
public class ReleaseOnOrderCancelled : IDomainEventHandler<OrderCancelledEvent>
{
    public async Task HandleAsync(OrderCancelledEvent @event)
    {
        await _stockService.ReleaseAsync(@event.OrderId); // 响应事件,释放库存
    }
}

🌐 优势:完全去中心化,服务间无直接依赖,弹性最佳。缺点:补偿逻辑分散在各个服务中,需要一个全局的关联ID(如OrderId)来追踪整个Saga链路。

8.5 领域对象 vs DTO:跨越服务边界的契约设计

8.5.1 核心原则:不要暴露领域对象!

直接将领域对象作为API的输入或输出是危险的。

// ❌ 危险:暴露了内部状态和实现细节
[HttpGet("{id}")]
public Order GetOrder(string id) // 返回领域对象
{
    return _orderRepository.GetById(id);
}

后果

  • 客户端耦合:客户端代码依赖于服务内部领域模型的细节,服务无法独立演化。
  • 安全风险:可能无意间暴露敏感字段。
  • 技术耦合:领域对象可能包含无法序列化的属性或循环引用。
8.5.2 解决方案:专用 DTO 与映射层

为每个对外接口定义专用的数据传输对象(DTO)。

// OrderService 对外的 DTO
public record OrderDto(
    string Id,
    string Status, // 使用字符串而非枚举,避免客户端强依赖
    List<OrderItemDto> Items
);

// 映射层(可使用 AutoMapper 或手写)
public static class OrderMapper
{
    public static OrderDto ToDto(this Order order)
    {
        return new OrderDto(
            Id: order.Id,
            Status: order.Status.ToString(),
            Items: order.Items.Select(i => new OrderItemDto(i.ProductId, i.Quantity)).ToList()
        );
    }
}

// Controller
[HttpGet("{id}")]
public async Task<OrderDto> GetOrderAsync(string id)
{
    var order = await _orderRepository.GetByIdAsync(id);
    return order.ToDto(); // 返回安全的 DTO
}

原则

  • 服务自治:每个服务定义自己对外和对内的DTO。
  • DTO即契约:DTO应保持稳定,其变更意味着API版本的变更。
  • 严格隔离:领域对象永远不直接跨进程边界。
8.5.3 API 版本演化策略
  • URL版本化/api/v1/orders, /api/v2/orders
  • Header版本化Accept: application/vnd.myapi.v2+json
  • DTO兼容性
    • 新增字段:通常安全,旧客户端可忽略。
    • 废弃字段:标记为 [Obsolete],并在一段时间内继续支持。
    • 重大变更:直接发布新版本API和DTO。

8.6 服务间通信协议选择:REST vs gRPC vs 消息队列

协议 适用场景 OOP 友好度 备注
REST/HTTP 面向公众或浏览器的API,需要高度可见性和可发现性。 需要手动定义和映射DTO,无强类型客户端。
gRPC 内部服务间的高性能、强类型通信。 基于Protocol Buffers,自动生成强类型客户端/服务端代码,调用体验如同本地方法。
消息队列 (Kafka, RabbitMQ) 事件驱动架构,最终一致性,异步解耦,流量削峰。 消息本身就是事件或命令DTO。
8.6.1 gRPC 与 OOP 的天然契合

gRPC 使用 .proto 文件定义服务契约和消息格式,这与定义接口和DTO的OOP思想高度一致。

// order_service.proto
service OrderService {
  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
}
message CreateOrderRequest {
  repeated OrderItem items = 1;
}
message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
}

编译后会生成强类型的C#客户端和服务端代码,后端与架构开发者可以像调用本地方法一样进行远程调用。

// 客户端代码(如在 InventoryService 中调用 OrderService)
var client = new OrderService.OrderServiceClient(channel);
var response = await client.CreateOrderAsync(new CreateOrderRequest
{
    Items = { new OrderItem { ProductId = "P1", Quantity = 2 } }
});

优势强类型、高性能、自动序列化/反序列化、支持流式调用,完美延续了OOP的开发体验。

8.7 实战项目:订单-库存-支付三服务协同

8.7.1 架构概览
  • OrderService:负责订单生命周期管理。
  • InventoryService:负责产品库存的预留与释放。
  • PaymentService:负责与外部支付网关交互。
  • 通信方式:服务间同步调用使用 gRPC,最终一致性事件通知使用 RabbitMQ
  • 一致性方案:采用编排式 Saga 配合补偿事务。
8.7.2 关键流程:创建订单 Saga
步骤 1:OrderService 接收HTTP请求
[HttpPost]
public async Task<ActionResult<OrderDto>> CreateOrderAsync(CreateOrderRequest request)
{
    var command = new CreateOrderCommand(request.Items);
    var result = await _mediator.Send(command); // 使用CQRS模式
    return result.Match(
        success => Ok(success.ToDto()),
        failure => BadRequest(failure.Message)
    );
}
步骤 2:Saga 协调器执行编排逻辑
public class CreateOrderSagaHandler : IRequestHandler<CreateOrderCommand, Result<Order>>
{
    public async Task<Result<Order>> Handle(CreateOrderCommand command, CancellationToken ct)
    {
        // 1. 创建初始状态订单(本地事务)
        var order = Order.CreatePending(command.Items);
        await _orderRepo.AddAsync(order, ct);

        try
        {
            // 2. 同步调用库存服务(gRPC)
            await _inventoryGrpc.ReserveAsync(new ReserveRequest {
                OrderId = order.Id,
                Items = { command.Items.Select(MapToGrpcItem) }
            }, ct);

            // 3. 同步调用支付服务(gRPC)
            await _paymentGrpc.AuthorizeAsync(new AuthorizeRequest {
                OrderId = order.Id,
                Amount = order.Total.Amount
            }, ct);

            // 4. 所有步骤成功,确认订单(本地事务)
            order.Confirm();
            await _unitOfWork.SaveChangesAsync(ct);
            return Result.Success(order);
        }
        catch (Exception ex)
        {
            // 5. 任何步骤失败,执行补偿
            await CompensateAsync(order.Id, ex, ct);
            return Result.Failure<Order>(ex.Message);
        }
    }
}
步骤 3:补偿逻辑
private async Task CompensateAsync(string orderId, Exception error, CancellationToken ct)
{
    // 补偿原则:尽力而为,记录日志
    // 1. 无论何种失败,都尝试释放库存(幂等操作)
    try { await _inventoryGrpc.ReleaseAsync(new ReleaseRequest { OrderId = orderId }, ct); }
    catch (Exception ex) { _logger.LogWarning(ex, "Failed to release inventory for order {OrderId}", orderId); }

    // 2. 如果支付已授权,尝试取消(幂等操作)
    if (!(error is RpcException rpcEx && rpcEx.StatusCode == StatusCode.NotFound))
    {
        try { await _paymentGrpc.CancelAsync(new CancelRequest { OrderId = orderId }, ct); }
        catch { /* 忽略 */ }
    }

    // 3. 将订单状态更新为已取消(本地事务)
    var order = await _orderRepo.GetByIdAsync(orderId, ct);
    if (order != null)
    {
        order.Cancel();
        await _unitOfWork.SaveChangesAsync(ct);
    }
}

🔒 关键保障

  • 超时控制:所有gRPC调用设置合理超时。
  • 补偿幂等ReleaseCancel操作设计为可重复执行。
  • 最终一致:通过补偿机制,系统总能收敛到一个一致的状态。
8.7.3 事件驱动扩展:发送订单确认通知

订单确认后,可以异步发布一个领域事件,由专门的通知服务消费。

// 在 Order.Confirm() 方法内
public void Confirm()
{
    Status = OrderStatus.Confirmed;
    AddDomainEvent(new OrderConfirmedEvent(Id)); // 记录事件
}

// 在应用服务中,事务成功后发布事件
foreach (var @event in order.DequeueEvents())
    await _rabbitMqPublisher.PublishAsync(@event); // 发送到消息队列

📣 NotificationService 监听 OrderConfirmedEvent 队列,并执行发送邮件或短信的逻辑。

8.8 微服务中的测试策略

8.8.1 单元测试:聚焦领域逻辑

与单体应用无区别,测试单个聚合根或领域服务的行为。

[Fact]
public void Confirm_WhenPending_ShouldSetStatusToConfirmed()
{
    var order = Order.CreatePending(validItems);
    order.Confirm();
    Assert.Equal(OrderStatus.Confirmed, order.Status);
}
8.8.2 集成测试:验证服务间协作

可以使用 Testcontainers 等工具在测试中启动真实的依赖(如数据库、其他服务的存根)。

public class CreateOrderIntegrationTests : IClassFixture<TestDatabaseFixture>
{
    [Fact]
    public async Task CreateOrder_Should_ReserveInventoryAndAuthorizePayment()
    {
        // Arrange: 启动 OrderService 及其数据库,并 Mock Inventory/Payment 的 gRPC 服务端
        // Act: 通过 HTTP Client 调用 OrderService 的 CreateOrder API
        // Assert:
        // - 数据库中的订单状态为 Confirmed
        // - Mock 的 InventoryService 收到了 Reserve 请求
        // - Mock 的 PaymentService 收到了 Authorize 请求
    }
}

8.9 本章小结

本章系统性地探讨了面向对象设计在微服务架构下的应用与调整:

  • 聚合根重构:从“命令者”转变为“状态管理者”,专注边界内的一致性。
  • 行为协调:深入对比了编排与编舞两种模式,应根据业务复杂度和团队结构进行选择。
  • 数据一致性:引入Saga模式作为分布式事务的替代方案,详细阐述了其两种实现和补偿机制。
  • 服务边界契约:强调使用DTO隔离领域模型,并讨论了API版本化管理策略。
  • 通信协议:指出gRPC是内部服务间强类型、高性能通信的优选,能提供类OOP的开发体验,这与Go等现代后端语言倡导的RPC理念相通。
  • 实战演练:通过一个三服务协同的订单创建Saga,完整展示了高内聚、松耦合、最终一致的微服务协作模式。

💡 终极认知
“微服务架构的本质不是简单地将数据库拆开,而是根据业务边界(限界上下文)拆分职责;面向对象设计的精髓也不是盲目地创建类,而是构建准确反映业务本质的模型。在微服务世界中,OOP指导我们如何定义服务内部的模型,以及如何设计服务之间清晰、稳定的契约。”




上一篇:OpenAI Agent RFT实战:企业级AI智能体强化微调技术解析
下一篇:C++高性能网络库实战:基于Multi-Reactor架构的10天实现与零拷贝优化
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 20:52 , Processed in 0.529261 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表