Trellis

Swift Xcode MIT

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

许可证

MIT 许可证