Back to Prompt Optimizer

PreferenceService架构优化

docs/architecture/preference-service-optimization.md

2.10.26.4 KB
Original Source

PreferenceService架构优化

📋 优化背景

在存储键架构重构过程中,发现了一个重要的架构不一致性问题:

问题描述

用户提出了一个关键问题:"为什么exportAllData的时候要对preferenceService特别处理呢?preferenceService直接提供一个获取所有数据的接口不就好了?其他几个manager都是这样的"

架构不一致性分析

其他Manager的统一模式

typescript
// 所有其他服务都提供批量获取接口
const models = await this.modelManager.getAllModels();
const userTemplates = await this.templateManager.listTemplates();
const history = await this.historyManager.getAllRecords();

PreferenceService的特殊处理(问题)

typescript
// ❌ 原有的特殊处理方式
for (const key of PREFERENCE_BASED_KEYS) {
  const value = await this.preferenceService.get(key, null);
  if (value !== null) {
    userSettings[key] = String(value);
  }
}

🎯 优化方案

1. 添加批量获取接口

为PreferenceService添加getAll()方法,保持与其他Manager的接口一致性:

typescript
export interface IPreferenceService {
  // 现有方法...
  
  /**
   * 获取所有偏好设置
   * @returns 包含所有偏好设置的键值对对象
   */
  getAll(): Promise<Record<string, string>>;
}

2. 实现批量获取逻辑

typescript
async getAll(): Promise<Record<string, string>> {
  try {
    const allKeys = await this.keys();
    const result: Record<string, string> = {};
    
    for (const key of allKeys) {
      try {
        const value = await this.get(key, null);
        if (value !== null) {
          result[key] = String(value);
        }
      } catch (error) {
        console.warn(`Failed to get preference for key "${key}":`, error);
        // 继续处理其他键,不因单个键失败而中断
      }
    }
    
    return result;
  } catch (error) {
    console.error('Error getting all preferences:', error);
    throw new Error(`Failed to get all preferences: ${error}`);
  }
}

3. 简化DataManager导出逻辑

typescript
// ✅ 优化后的统一处理方式
async exportAllData(): Promise<ExportData> {
  // 获取所有偏好设置(统一接口)
  const userSettings = await this.preferenceService.getAll();
  
  // 获取其他数据(统一接口)
  const models = await this.modelManager.getAllModels();
  const userTemplates = await this.templateManager.listTemplates();
  const history = await this.historyManager.getAllRecords();
  
  return {
    version: 1,
    data: { userSettings, models, userTemplates, history }
  };
}

📊 优化效果

架构一致性

所有服务现在都遵循相同的接口模式:

服务批量获取方法返回类型
ModelManagergetAllModels()ModelConfig[]
TemplateManagerlistTemplates()Template[]
HistoryManagergetAllRecords()PromptRecord[]
PreferenceServicegetAll()Record<string, string>

代码简化

  • 移除了存储键分类常量 - 不再需要PREFERENCE_BASED_KEYSDIRECT_STORAGE_KEYS
  • 简化了DataManager逻辑 - 从复杂的分类处理变为统一的批量调用
  • 减少了维护成本 - 新增偏好设置不需要更新DataManager

性能提升

  • 减少异步调用次数 - 从多次get()调用变为一次getAll()调用
  • 批量处理更高效 - 一次性获取所有数据,减少存储访问次数
  • 错误处理更健壮 - 单个键失败不影响其他键的获取

🔧 实现细节

错误处理策略

typescript
// 健壮的错误处理:单个键失败不影响整体
for (const key of allKeys) {
  try {
    const value = await this.get(key, null);
    if (value !== null) {
      result[key] = String(value);
    }
  } catch (error) {
    console.warn(`Failed to get preference for key "${key}":`, error);
    // 继续处理其他键
  }
}

数据类型统一

typescript
// 所有值都转换为字符串,保持JSON导出的一致性
result[key] = String(value);

前缀处理透明化

  • getAll()返回的键名是原始键名(不带pref:前缀)
  • 内部前缀处理对调用者完全透明
  • 保持了PreferenceService的封装性

🧪 测试覆盖

为新的getAll()方法添加了完整的测试覆盖:

typescript
describe('批量操作', () => {
  it('should get all preferences', async () => {
    await preferenceService.set('app:settings:ui:theme-id', 'dark');
    await preferenceService.set('app:settings:ui:preferred-language', 'zh-CN');
    
    const allPreferences = await preferenceService.getAll();
    
    expect(allPreferences).toEqual({
      'app:settings:ui:theme-id': 'dark',
      'app:settings:ui:preferred-language': 'zh-CN'
    });
  });

  it('should handle errors gracefully in getAll', async () => {
    // 测试错误处理逻辑
  });
});

🚀 最佳实践总结

1. 接口一致性原则

  • 同类型服务应提供一致的接口模式
  • 批量操作比逐个操作更高效和简洁
  • 避免在上层代码中进行特殊处理

2. 错误处理策略

  • 批量操作中单个项目失败不应影响整体
  • 提供详细的错误日志便于调试
  • 保持操作的原子性和一致性

3. 封装性设计

  • 内部实现细节(如前缀)对外部透明
  • 接口设计应符合调用者的期望
  • 保持向后兼容性

📝 相关文件

修改的文件

  • packages/core/src/services/preference/types.ts - 添加getAll接口
  • packages/core/src/services/preference/service.ts - 实现getAll方法
  • packages/core/src/services/data/manager.ts - 简化导出逻辑
  • packages/core/tests/unit/preference/service.test.ts - 新增测试文件

移除的复杂性

  • 删除了PREFERENCE_BASED_KEYSDIRECT_STORAGE_KEYS常量
  • 简化了DataManager的存储键分类逻辑
  • 统一了导入导出的处理方式

🎉 总结

这次优化体现了**"保持架构一致性"**的重要性:

  1. 识别不一致性 - 用户的观察非常准确,指出了架构问题
  2. 统一接口模式 - 所有Manager都提供批量获取接口
  3. 简化上层逻辑 - DataManager不再需要特殊处理
  4. 提升性能和可维护性 - 更少的代码,更好的性能

这是一个很好的例子,说明了用户反馈如何推动架构改进,以及简单一致的设计比复杂特殊处理更优雅