You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift整洁架构中多态实体(存在类型)的持久化方案问询

Swift整洁架构下Source协议的持久化方案

核心矛盾拆解

你面临的问题本质是在整洁架构分层约束下,既要实现any Source类型的持久化(需保留类型信息以重构实例),又不能让领域层的Source协议依赖Codable这类持久化相关接口,避免污染核心领域逻辑。

最优实现:分层隔离的类型桥接方案

1. 领域层定义纯业务契约

在领域层的Source协议中新增两个抽象属性,仅聚焦业务需求(标识源类型、提供源配置),完全不涉及持久化细节:

protocol Source {
    func getData(id: String) -> Entity
    
    // 唯一标识当前Source类型(如"local_file"、"remote_api")
    var sourceTypeID: String { get }
    // 提供需要持久化的配置数据(如本地路径、API端点)
    var persistentConfig: [String: Any] { get }
}

这两个属性属于领域逻辑的一部分(用户安装/卸载源本身就是业务需求),并非持久化侵入,完全符合整洁架构对领域层的要求。

2. 数据层实现类型映射与持久化

在数据层实现SourceRepository的具体逻辑,同时维护一个类型注册表,将sourceTypeID映射到对应的Source实例工厂。新增Source实现时,只需扩展注册表,无需修改Repository核心代码:

// 数据层的类型注册表:新增Source实现仅需在这里添加映射
private let sourceFactoryRegistry: [String: ([String: Any]) -> any Source] = [
    "local_file": { config in
        LocalSource(storagePath: config["path"] as! String)
    },
    "remote_api": { config in
        NetworkSource(apiURL: config["apiEndpoint"] as! String)
    }
]

class CoreSourceRepository: SourceRepository {
    func saveInstalledSources(_ sources: [any Source]) throws {
        // 转换为可持久化的结构化数据
        let persistedEntries = sources.map { source in
            [
                "type": source.sourceTypeID,
                "config": source.persistentConfig
            ]
        }
        // 用任意持久化框架保存(示例用UserDefaults,可替换为CoreData/Realm等)
        UserDefaults.standard.set(persistedEntries, forKey: "user_installed_sources")
    }
    
    func loadInstalledSources() throws -> [any Source] {
        guard let persistedEntries = UserDefaults.standard.array(forKey: "user_installed_sources") as? [[String: Any]] else {
            return []
        }
        
        // 通过注册表重构Source实例
        return persistedEntries.compactMap { entry in
            guard let typeID = entry["type"] as? String,
                  let config = entry["config"] as? [String: Any],
                  let factory = sourceFactoryRegistry[typeID] else {
                // 跳过无法识别的源类型
                return nil
            }
            return factory(config)
        }
    }
}

3. 进阶:类型安全的配置处理

如果担心[String: Any]的类型不安全,可以在领域层给Source关联一个配置类型,让每个Source实现类定义自己强类型的配置模型,同时仅在数据层实现配置的序列化/反序列化:

// 领域层:关联配置类型的抽象协议
protocol Source {
    associatedtype Config
    func getData(id: String) -> Entity
    var sourceTypeID: String { get }
    var config: Config { get }
    init(config: Config)
}

// 数据层:给LocalSource添加Codable配置(仅数据层依赖Codable)
extension LocalSource {
    struct StorageConfig: Codable {
        let storagePath: String
    }
    
    var config: StorageConfig { StorageConfig(storagePath: self.path) }
    init(config: StorageConfig) { self.path = config.storagePath }
}

// 数据层注册表调整为处理Codable配置
private let sourceFactoryRegistry: [String: (Data) -> any Source?] = [
    "local_file": { data in
        guard let config = try? JSONDecoder().decode(LocalSource.StorageConfig.self, from: data) else {
            return nil
        }
        return LocalSource(config: config)
    }
]

这种方式既保证了领域层的纯净,又在数据层实现了类型安全的持久化。

关于Codable侵入的顾虑是否合理?

你的顾虑完全正确。整洁架构要求领域层是最稳定、最核心的层,不能依赖任何外部框架(包括Codable)。如果让Source直接继承Codable,会导致领域层与持久化逻辑强绑定,后续更换持久化方案(比如从UserDefaults切换到CoreData)时,领域层也需要修改,违反了依赖倒置原则。

总结

  • 领域层仅定义业务相关的抽象契约,不涉及任何持久化细节
  • 数据层通过类型注册表实现any Source的实例重构,新增Source只需扩展注册表
  • 完全隔离领域层与持久化逻辑,严格遵循整洁架构的分层规则

内容的提问来源于stack exchange,提问作者Cameron Delong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 02:57:46