Trellis 具有声明式 DSL,可简化服务引导。
let cluster = try await Bootstrap {
Group {
Store(model: IdentityModel.self)
.mutate(on: IdentityAction.self) { model, action, send in
// ...
}
.mutate(on: StartUpAction.self) { model, action, send in
// ...
}
.with(model: identityModel)
Store(model: ArticlesModel.self)
.mutate(on: ArticlesAction.self) { model, action, send in
// ...
}
.with(model: articlesModel)
}
.emit(using: notificationsStream)
.transformError {
ErrorAction.error($0)
}
.observe(on: IdentityAction.self) {
// ...
}
}
这将设置两个服务来管理用户的身份及其文章。生成的集群仅公开一个函数 send
,该函数可用于与服务交互,而无需显式知道哪个服务处理哪个操作。
try await cluster.send(action: StartUpAction.appDidCompleteLaunching)
大多数时候,我们不会像这样声明服务。相反,我们会编写一个自定义服务来包装每个存储。
// IdentityService.swift
struct IdentityService: Service {
var body: some Service {
Store(model: IdentityModel.self)
.mutate(on: IdentityAction.self) { model, action, send in
// ...
}
.mutate(on: StartUpAction.self) { model, action, send in
// ...
}
}
// SomeOtherFile.swift
let cluster = try await Bootstrap {
IdentityService()
.with(model: identityModel)
}
请注意,实际模型是如何从服务外部注入的,从而实现依赖注入。
使用 Swift Package Manager
.package(name: "Trellis",
url: "https://github.com/valentinradu/Trellis.git",
.upToNextMinor(from: "0.3.0-beta"))
服务是响应动作的实体。它们形成树状结构,允许每个父服务将其动作委托给其子服务。Trellis 中的大多数实体都是服务。
修饰符更改服务的行为。大多数修饰符(如 .serial()
)将遍历服务树并应用于其下的所有子服务,而某些修饰符(如 .mutate(on:)
)仅在应用于其下的直接服务时才有意义。有关修饰符的更多信息,请查看下面的相应部分。
组是惰性服务,它们将动作传递给其子服务,而无需采取任何其他步骤。它们主要用于将修饰符(例如 emit(using:consumeAtBootstrap:)
)应用于多个服务,或绕过服务可以拥有的最大子服务数量 (8)。
每个存储封装一个模型,该模型反过来处理一组协同工作的任务(及其相关数据)。每次将动作发送到集群时,存储都允许您使用和修改包装的模型。
.emit(using:consumeAtBootstrap:)
- 接受来自外部事件源(异步流)的事件,该事件源输出动作并将它们馈送到其下的所有服务。当设置 `
.transformError(transformHandler:)
- 将源自其下所有服务的错误转换为动作,并将它们反馈回集群。如果转换后的错误再次抛出,则操作将失败,并且 send(action:)
函数将抛出错误。
.concurrent()
- 以并发方式执行其下的所有服务。这是默认设置。
.serial()
- 依次执行其下的所有服务。适用于您希望在允许其他服务处理动作之前执行某些操作(例如用户身份)的情况。
.bootstrap(bootstrapHandler:)
- 在服务创建后立即调用,它使服务可以在处理任何动作之前初始化状态或引导模型。
.observe(observeHandler:)
- 每次收到动作时调用。非常适合日志记录和更新外部(例如,表示层)状态。
.mutate(on:mutateHandler:)
- 每次收到动作时调用。在处理程序内部,您可以根据收到的动作来修改模型,并发送其他动作以进行进一步处理。
.with(model:)
- 为其下的所有子服务设置模型。
Trellis 使用 Swift 并发模型,并保证服务始终在主线程上构建和引导。没有其他保证,因此,所有模型都应该是 actors。
使用 Trellis,单元测试主要集中在模型上。但是,如果您还希望测试服务集成,则可以轻松做到这一点。您可以简单地将模型替换为模拟版本,并将集群发送函数替换为记录动作的函数。
// SomeTest.swift
let cluster = try await Bootstrap {
IdentityService()
.with(model: mockedIdentityModel)
.environment(\.send, recordingSend)
}
try await cluster.send(action: StartUpAction.appDidCompleteLaunching)
// Assert the state of the mocked identity model and the recorded actions