Skip to content

Mediator

把"谁处理这个请求"从调用方剥离出去——调用方只管 send(),框架负责路由到正确的处理器。

适用场景

  • 控制器里塞了过多注入——IOrderServiceIInventoryServiceIEmailService 七八个依赖排成一排
  • 想加日志、校验、事务这些横切逻辑,但不想在每个方法里重复 try/catch/commit
  • 希望命令和查询走不同路径,写操作与读操作职责分离
  • 需要一对多的通知——比如订单创建后同时通知物流系统和积分系统

核心问题

没有中介器时,控制器直接依赖多个服务:

text
Controller → IOrderService + IInventoryService + IEmailService + ILogger + ...

每加一个横切关注点(日志、事务、校验),所有控制器都要改一轮。

中介器把这条链路拆成两步:调用方发请求 → 框架路由到处理器。处理器之间互不知道对方存在,横切逻辑收敛到管道里。

text
Controller → IMediator.send(request)

                ├─ Pipeline(日志 → 事务 → 校验)

                └─ Handler(业务逻辑)

快速开始

定义请求 + 处理器,一行 send() 发起:

cangjie
import soulsoft_mediator.*
import soulsoft_extensions_injection.*

// 1. 定义请求
public class PingRequest <: IRequest<String> {}

// 2. 实现处理器
public class PingHandler <: IRequestHandler<PingRequest, String> {
    public func handle(_: PingRequest): String {
        "pong"
    }
}

// 3. 注册
let services = ServiceCollection()
services.addMediator()
    .registerServices(PackageInfo.get("myapp"))
let provider = services.build()

// 4. 调用
try (scope = provider.getOrThrow<IServiceScopeFactory>().createScope()) {
    let mediator = scope.services.getOrThrow<IMediator>()
    let result = mediator.send<PingRequest, String>(PingRequest())
    // result == "pong"
}

核心概念

两种消息模式

模式接口返回值处理器数量典型用途
请求/响应IRequest<TResponse>有返回值严格一对一命令、查询
通知/事件INotification无返回值一对多领域事件广播

请求send(),框架保证只存在一个处理器,找不到就抛异常:

cangjie
public class CreateOrderCommand <: IRequest<Int64> {
    public var productId: Int64 = 0
    public var count: Int64 = 0
}

public class CreateOrderHandler <: IRequestHandler<CreateOrderCommand, Int64> {
    public func handle(cmd: CreateOrderCommand): Int64 {
        // 业务逻辑
        return newOrderId
    }
}

let orderId = mediator.send<CreateOrderCommand, Int64>(cmd)

通知publish(),所有订阅者都会收到:

cangjie
public class OrderCreatedEvent <: INotification {
    public var orderId: Int64 = 0
}

public class LogisticsHandler <: INotificationHandler<OrderCreatedEvent> {
    public func handle(e: OrderCreatedEvent): Unit { /* 通知物流 */ }
}

public class PointsHandler <: INotificationHandler<OrderCreatedEvent> {
    public func handle(e: OrderCreatedEvent): Unit { /* 发放积分 */ }
}

// 两个处理器都会执行
mediator.publish(OrderCreatedEvent(orderId: 123))

管道:横切关注点的统一入口

IPipelineHandler 是中介器最关键的扩展点——它让你在"请求到达处理器之前"和"处理器返回之后"插入逻辑,不修改处理器本身。

cangjie
public interface IPipelineHandler {
    func handle(request: Any, next: RequestDelegate): Any
}

next() 调用下一个管道或最终的处理器。你可以:

  • next() 之前做前置处理(校验、开启事务)
  • next() 之后做后置处理(提交事务、记录日志)
  • 不调用 next() 直接返回(短路,如权限不足直接拒绝)
  • 捕获 next() 的异常(统一异常处理)
  • 修改 next() 的返回值(脱敏、包装)

注册顺序决定嵌套顺序——先注册的在最外层:

cangjie
services.addMediator()
    .addPipelineHandler<LoggingBehavior>()       // ① 最外层
    .addPipelineHandler<TransactionBehavior>()   // ② 内层
    .registerServices(PackageInfo.get("myapp"))

// 执行顺序:Logging.before → Transaction.before → Handler → Transaction.after → Logging.after

常见用法

管道:日志行为

在请求前后各打一条日志,不碰处理器代码:

cangjie
public class LoggingBehavior <: IPipelineHandler {
    public func handle(request: Any, next: RequestDelegate): Any {
        let name = request.getType().name
        println("[开始] ${name}")
        let result = next()
        println("[完成] ${name}")
        return result
    }
}

管道:事务行为

DbContext 管理和事务包裹收敛到一个管道里,服务层不再重复 try(ctx) { try(tx) { ... tx.commit() } } 模板:

cangjie
public class TransactionBehavior <: IPipelineHandler {
    private let _sp: IServiceProvider

    public init(sp: IServiceProvider) {
        _sp = sp
    }

    public func handle(request: Any, next: RequestDelegate): Any {
        try (ctx = _sp.getService<AppDbContext>()) {
            try (tx = ctx.beginTransaction()) {
                let result = next()
                tx.commit()
                return result
            }  // 异常时 close() 自动 rollback
        }
    }
}

这与 事务 中"从重复模板到管道/AOP"一节相呼应——管道一把事务模板从所有服务方法中提走,业务处理器只管核心逻辑。

管道:异常处理

统一捕获处理器异常,避免每个方法写 try/catch

cangjie
public class ExceptionBehavior <: IPipelineHandler {
    public func handle(request: Any, next: RequestDelegate): Any {
        try {
            return next()
        } catch (e: Exception) {
            println("[异常] ${request.getType().name}: ${e.message}")
            throw e
        }
    }
}

短路:权限校验

不满足条件时直接返回,不进入处理器:

cangjie
public class AuthBehavior <: IPipelineHandler {
    public func handle(request: Any, next: RequestDelegate): Any {
        if (request is IAuthRequired) {
            return "无权限"  // 短路,不调用 next()
        }
        return next()
    }
}

返回值转换

next() 返回后对结果做统一包装:

cangjie
public class ResultWrapBehavior <: IPipelineHandler {
    public func handle(request: Any, next: RequestDelegate): Any {
        let result = next()
        return ApiResult(code: 0, data: result)  // 统一包装返回值
    }
}

通知:一对多广播

cangjie
// 处理器中发布通知
public class CreateOrderHandler <: IRequestHandler<CreateOrderCommand, Int64> {
    private let _publisher: IPublisher

    public init(publisher: IPublisher) {
        _publisher = publisher
    }

    public func handle(cmd: CreateOrderCommand): Int64 {
        let orderId = newOrderId
        _publisher.publish(OrderCreatedEvent(orderId: orderId))
        return orderId
    }
}

与其他模块组合

  • 管道中的 DbContext / 事务管理来自 事务
  • 所有处理器和管道行为通过 依赖注入 注册和解析
  • ISender / IPublisher 可从 DI 容器独立解析,IMediator 是二者的合并接口
  • 通知不适合走管道——publish() 直接分发到所有处理器
  • 管道不支持异步(当前实现为同步),需要异步时在处理器内部自行处理

生产建议

  • 管道里放横切关注点,处理器里放业务逻辑——不要反过来
  • 通知处理器各自独立——某个通知处理器抛异常不会影响其他处理器
  • 命令用 IRequest<Unit>——不需要返回值时,用 Unit 表示"只是副作用"
  • registerServices() 只传当前程序集——避免扫描不相关的包,拖慢启动

常见问题

请求处理器能返回 Unit 吗?

能。命令不需要返回值时用 IRequest<Unit>,处理器 handle() 返回 Unit

通知能走管道吗?

不能。publish() 直接遍历处理器列表调用,不经过 IPipelineHandler。如果需要拦截通知,在通知处理器内部处理。

管道执行顺序是怎样的?

先注册的管道在最外层。注册顺序 A → B → C 的执行流程:A.before → B.before → C.before → Handler → C.after → B.after → A.after

一个请求能对应多个处理器吗?

不能。IRequest<TResponse> 严格一对一,多个处理器同类型会抛异常。需要一对多用 INotification