DecoratR:优雅解决.NET横切关注点难题

作者:微信公众号:【架构师老卢】
7-31 8:33
18

问题:横切关注点无处不在

作为软件工程师,我们不断面临在应用程序中实现横切关注点的挑战,例如日志记录、缓存、验证、重试逻辑和安全性。传统方法通常会导致:

  • 重复的样板代码散布在你的服务中。
  • 业务逻辑与基础设施关注点之间紧密耦合。
  • 由于职责混合而导致测试困难。
  • 当需求变更时,可维护性差。

考虑这个典型的服务方法:

public async Task<Order> GetOrderAsync(int orderId)
{
  _logger.LogInformation("Getting order {OrderId}", orderId); // 正在获取订单 {OrderId}

  // 先检查缓存
  var cacheKey = $"order_{orderId}";

  if (_cache.TryGetValue(cacheKey, out Order cachedOrder))
  {
    _logger.LogInformation("Order {OrderId} found in cache", orderId); // 订单 {OrderId} 在缓存中找到
    return cachedOrder;
  }

  try
  {
    // 验证输入
    if (orderId <= 0)
      throw new ArgumentException("Invalid order ID"); // 无效的订单 ID

    // 业务逻辑埋没在基础设施代码中
    var order = await _repository.GetOrderAsync(orderId);

    // 缓存结果
    _cache.Set(cacheKey, order, TimeSpan.FromMinutes(5));

    _logger.LogInformation("Order {OrderId} retrieved successfully", orderId); // 订单 {OrderId} 成功获取

    return order;
  }
  catch (Exception ex)
  {
    _logger.LogError(ex, "Failed to get order {OrderId}", orderId); // 获取订单 {OrderId} 失败
    throw;
  }
}

此方法违反了单一职责原则(Single Responsibility Principle),并成为维护的噩梦。如果我们需要添加指标、安全检查或速率限制怎么办?该方法会变得越来越复杂。

DecoratR:简洁、可组合的服务装饰

DecoratR 是一个现代的 .NET 库,它通过流畅、直观的 API 将装饰器模式(Decorator pattern)引入到 Microsoft 的依赖注入(DI)容器中。它允许你以简洁、可组合的方式用横切关注点包装你的服务。

核心特性

  • 流式 API (Fluent API) — 直观且可读的装饰器链配置。
  • 常规与键控服务 (Regular & Keyed Services) — 全面支持 .NET 8+ 的键控服务。
  • 自定义工厂 (Custom Factories) — 处理复杂的依赖注入场景。
  • 泛型装饰器 (Generic Decorators) — 支持泛型装饰器。
  • 条件装饰 (Conditional Decoration) — 根据条件应用装饰器。
  • 生命周期管理 (Lifetime Management) — 控制服务的生命周期(Singleton, Scoped, Transient)。

快速开始

通过 NuGet 安装非常简单:

dotnet add package DecoratR

让我们将复杂的服务方法转化为职责分离的简洁形式:

// 为每个关注点分离的装饰器
public class LoggingDecorator : IOrderService
{
  private readonly IOrderService _inner;

  public LoggingDecorator(IOrderService inner) => _inner = inner;

  public string GetOrder(string orderId)
  {
    Console.WriteLine("LoggingDecorator: 开始请求 (Starting request)"); // 翻译了日志信息

    var result = _inner.GetOrder(orderId);

    Console.WriteLine("LoggingDecorator: 请求完成 (Request completed)"); // 翻译了日志信息

    return $"Logged({result})";
  }
}

public class CacheDecorator : IOrderService
{
  private readonly IOrderService _inner;

  public CacheDecorator(IOrderService inner) => _inner = inner;

  public string GetOrder(string orderId)
  {
    Console.WriteLine("CacheDecorator: 正在检查缓存 (Checking cache)"); // 翻译了日志信息

    var result = _inner.GetOrder(orderId);

    Console.WriteLine("CacheDecorator: 正在存入缓存 (Storing in cache)"); // 翻译了日志信息

    return $"Cached({result})";
  }
}

public class OrderService : IOrderService
{
  private readonly IOrderRepository _repository;

  public OrderService(IOrderRepository repository) => _repository = repository;

  public string GetOrder(string orderId)
  {
    Console.WriteLine("OrderService: 正在从数据库获取 (Fetching from database)"); // 翻译了日志信息
    return $"Order: {orderId}";
  }
}

// 使用 DecoratR 配置
services.Decorate<IOrderService>()
        .With<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<OrderService>()
        .Apply();

// 使用
var provider = services.BuildServiceProvider();
var orderService = provider.GetService<IOrderService>();
var result = orderService.GetOrder("123");
Console.WriteLine(result);

// 输出:
// LoggingDecorator: 开始请求 (Starting request)
// CacheDecorator: 正在检查缓存 (Checking cache)
// OrderService: 正在从数据库获取 (Fetching from database)
// CacheDecorator: 正在存入缓存 (Storing in cache)
// LoggingDecorator: 请求完成 (Request completed)
// Logged(Cached(Order: 123))

理解装饰器链

DecoratR 按照你定义的顺序应用装饰器,最后一个是你基础实现:

services.Decorate<IOrderService>()
        .With<SecurityDecorator>() // 最外层 - 最先执行
        .Then<LoggingDecorator>() // 中间层
        .Then<CacheDecorator>() // 内层
        .Then<OrderService>() // 基础实现 - 最内层
        .Apply();

执行流程从外向内移动:

安全 (Security) → 日志 (Logging) → 缓存 (Cache) → 业务逻辑 (Business Logic)

高级特性

  1. 条件装饰 (Conditional Decoration) — 基于条件应用装饰器:

    var isDevelopment = Environment.GetEnvironmentVariable("ENVIRONMENT") == "Development";
    var enableCaching = configuration.GetValue<bool>("Features:Caching");
    
    services.Decorate<IOrderService>()
            .With<LoggingDecorator>()
            .ThenIf<RetryDecorator>(isDevelopment) // 仅在开发环境
            .ThenIf<CacheDecorator>(enableCaching) // 基于配置
            .Then<OrderService>()
            .Apply();
    
  2. 键控服务 (.NET 8+) — 为相同服务类型创建不同的装饰器链:

    // 为不同上下文使用不同配置
    services.Decorate<IOrderService>("internal") // "内部"键
            .With<LoggingDecorator>()
            .Then<OrderService>()
            .Apply();
    
    services.Decorate<IOrderService>("external") // "外部"键
            .With<SecurityDecorator>()
            .Then<RateLimitingDecorator>()
            .Then<LoggingDecorator>()
            .Then<OrderService>()
            .Apply();
    
    // 使用
    var internalService = provider.GetRequiredKeyedService<IOrderService>("internal");
    var externalService = provider.GetRequiredKeyedService<IOrderService>("external");
    
  3. 自定义工厂方法 (Custom Factory Methods) 对于需要自定义依赖注入的复杂场景:

    services.Decorate<IOrderService>()
            .With((serviceProvider, inner) =>
                      new MetricsDecorator(
                          inner,
                          serviceProvider.GetRequiredService<IMetrics>(),
                          serviceProvider.GetRequiredService<IConfiguration>()
                                         .GetValue<string>("MetricsPrefix")))
            .Then<OrderService>()
            .Apply();
    
  4. 泛型装饰器 (Generic Decorators)

    public class GenericCacheDecorator<T> : IService<T>
    {
      private readonly IService<T> _inner;
    
      public GenericCacheDecorator(IService<T> inner) => _inner = inner;
    
      public T Get(string key) => /* 缓存逻辑 (caching logic) */ _inner.Get(key);
    }
    
    services.Decorate<IService<User>>()
            .With<GenericCacheDecorator<User>>()
            .Then<UserService>()
            .Apply();
    

实际应用场景

电子商务平台 (E-commerce Platform)

public class ECommerceConfiguration
{
  public void ConfigureServices(IServiceCollection services, IConfiguration config)
  {
    var enableMetrics = config.GetValue<bool>("Features:Metrics");
    var enableRetry = config.GetValue<bool>("Features:Retry");

    // 订单处理流水线
    services.Decorate<IOrderService>()
            .With<SecurityDecorator>() // 首先认证
            .Then<ValidationDecorator>() // 验证输入
            .ThenIf<MetricsDecorator>(enableMetrics) // 可选的指标
            .ThenIf<RetryDecorator>(enableRetry) // 可选的重试逻辑
            .Then<CacheDecorator>() // 缓存近端数据
            .Then<OrderService>() // 业务逻辑
            .AsScoped() // 设置为作用域生命周期
            .Apply();

    // 支付处理 - 不同的需求
    services.Decorate<IPaymentService>()
            .With<AuditDecorator>() // 审计所有支付
            .Then<EncryptionDecorator>() // 加密敏感数据
            .Then<RateLimitingDecorator>() // 防止滥用
            .Then<PaymentService>()
            .Apply();
    }
}

多租户 SaaS 应用程序 (Multi-tenant SaaS Application)

// 不同租户层级的不同功能集
services.Decorate<IDataService>("basic-tier") // "基础版"键
        .With<LoggingDecorator>()
        .Then<DataService>()
        .Apply();

services.Decorate<IDataService>("premium-tier") // "高级版"键
        .With<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<MetricsDecorator>()
        .Then<DataService>()
        .Apply();

services.Decorate<IDataService>("enterprise-tier") // "企业版"键
        .With<SecurityDecorator>()
        .Then<AuditDecorator>()
        .Then<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<MetricsDecorator>()
        .Then<DataService>()
        .Apply();

最佳实践

  1. 装饰器顺序至关重要 考虑装饰器的逻辑流程:

    services.Decorate<IService>()
            .With<SecurityDecorator>() // 认证/授权最先
            .Then<RateLimitingDecorator>() // 速率限制在安全之后
            .Then<LoggingDecorator>() // 在安全检查之后记录日志
            .Then<MetricsDecorator>() // 收集指标
            .Then<CacheDecorator>() // 缓存最接近数据
            .Then<BusinessService>() // 纯粹的业务逻辑
            .Apply();
    
  2. 保持装饰器职责单一 每个装饰器应只负责单一职责:

    // 好 - 单一关注点
    public class CacheDecorator : IOrderService
    {
      // 只处理缓存逻辑
    }
    
    // 坏 - 多个关注点
    public class CacheAndLogDecorator : IOrderService
    {
      // 同时处理缓存和日志记录 - 违反 SRP
    }
    
  3. 使装饰器可测试 装饰器很容易独立进行单元测试:

    [Test]
    public void LoggingDecorator_Should_LogExecution() // LoggingDecorator 应记录执行
    {
      // 准备 (Arrange)
      var mockInner = new Mock<IOrderService>();
      var mockLogger = new Mock<ILogger>();
      var decorator = new LoggingDecorator(mockInner.Object, mockLogger.Object);
    
      // 执行 (Act)
      decorator.GetOrder("123");
    
      // 断言 (Assert)
      mockLogger.Verify(x => x.LogInformation(It.IsAny<string>()), Times.AtLeastOnce); // 验证至少记录一次信息
      mockInner.Verify(x => x.GetOrder("123"), Times.Once); // 验证内部服务方法被调用一次
    }
    
  4. 明智地使用条件装饰 避免使用运行时条件装饰来降低复杂性:

    // 好 - 条件在启动时仅评估一次
    services.Decorate<IService>()
            .WithIf<ExpensiveDecorator>(enableExpensiveFeature) // 启用昂贵特性时才应用
            .Then<BaseService>()
            .Apply();
    
    // 坏 - 条件在每次调用时评估
    public class ConditionalDecorator : IService
    {
      public Result Execute()
      {
        if (someRuntimeCondition) // 每次调用都评估!
        {
          // 昂贵的操作
        }
    
        return _inner.Execute();
      }
    }
    

性能考量

DecoratR 专为性能而设计:

  • 对于未应用的条件装饰器,运行时开销为零。
  • 最小化内存分配 — 装饰器在 DI 容器设置期间仅创建一次。
  • 方法执行期间不使用反射(Reflection)。

从现有模式迁移

之前:

// 手动注册 - 易错且冗长
services.AddScoped<IOrderService>(provider =>
{
  var baseService = new OrderService(provider.GetService<IRepository>());
  var cached = new CacheDecorator(baseService, provider.GetService<ICache>());
  var logged = new LoggingDecorator(cached, provider.GetService<ILogger>());

  return logged;
});

之后:

// 简洁、流式的 API
services.Decorate<IOrderService>()
        .With<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<OrderService>()
        .Apply();

平台支持

DecoratR 支持多个 .NET 版本:

  • .NET 6.0 — 核心装饰功能
  • .NET 7.0 — 核心装饰功能
  • .NET 8.0 — 完整功能集,包括键控服务
  • .NET 9.0 — 最新特性和优化

键控服务仅在 .NET 8.0 及更高版本中可用,但所有其他功能在所有支持的版本中均可使用。

DecoratR 改变了你在 .NET 应用程序中处理横切关注点的方式。通过为装饰器模式提供简洁、流畅的 API,它使你能够:

  • 使用单一职责的装饰器清晰地分离关注点。
  • 通过流畅的配置 API 灵活地组合行为。
  • 使用隔离的、专注的单元测试轻松测试。
  • 通过显式的、可读的装饰器链简化维护。
  • 利用条件和键控服务支持,充满信心地进行扩展。

无论你是在构建微服务、Web API 还是桌面应用程序,DecoratR 都能帮助你编写遵循 SOLID 原则的、更清晰、更易于维护的代码。


相关留言评论
昵称:
邮箱:
阅读排行