设计

4 阅读9分钟

架构原则的代码实现详解

本文档详细说明三个核心架构原则在代码中是如何具体实现和保证的。


1. 分层架构:每层只能与下层通信

原则说明

AppController (应用协调层)
     ↓ 初始化并管理
React Views → Page Controllers → DataModel ← ActionManager
     ↓           ↓            ↓            ↓
   通过 Context  只能访问    只能访问     只能访问
   访问 AppController  DataModel   Storage     ServerAPI

关键点

  • AppController 在 React 渲染之前初始化(模块级别)
  • AppController 创建和管理所有下层组件
  • React Views 通过 Context 访问 AppController
  • Page Controllers 由 AppController 创建,可选使用

实现机制

1.1 AppController 的初始化时机和职责

AppController 在模块级别创建(在 React 渲染之前):

const appController = new AppController();

AppController 的初始化展示了层次依赖关系:

constructor() {
  // 1. 创建数据模型层(向下依赖)
  this.appDataModel = new AppDataModel(
    this.requestAssetFunction.bind(this),
    this.fetchFlowsThroughput.bind(this),
    this.getEndpointThroughputData.bind(this)
  );

  // 2. 创建共享控制器(向下依赖)
  this.sharedController = new SharedController(
    this.appDataModel.sharedModel,
    this.appDataModel
  );

关键点

  • AppController 在 React 组件渲染之前创建(模块级别单例)
  • AppController 创建 AppDataModel(向下依赖)
  • AppController 创建 SharedController,并传入 AppDataModel(向下依赖)
  • AppDataModel 不持有 AppController 的引用(避免向上依赖)
  • AppController 通过 Context 提供给 React 组件使用
1.2 DataModel 的独立性保证

DataModel 设计原则(来自 .cursor/rules/data-model.mdc):

// ❌ BAD: DataModel 引用上层组件
class InventoryModel {
  private appController: AppController; // 违反分层原则
}

// ✅ GOOD: DataModel 只依赖 SharedModel
class InventoryModel {
  constructor(private sharedModel: SharedModel) {
    // 只依赖下层
  }
}

实际实现示例

class WorkloadDataModel
  extends AbstractDataModel<{}, {}, {}, DataModelEventsMap, SharedModel>
  implements IFilterableModel<Endpoint>
{
  public sharedModel: SharedModel; // 只依赖 SharedModel(下层)

  constructor(sharedModel: SharedModel, treeStructure?: TreeStructure) {
    super(sharedModel);
    this.sharedModel = sharedModel;
    // 不持有任何 Controller 或 View 的引用
  }
}
1.3 AppController 通过 Context 提供给 React 组件

AppController 通过 Context Provider 提供给所有子组件

return (
  <ThemeProvider onThemeChange={onThemeChange}>
    <AppControllerContext.Provider value={appController}>
      <ActionManagerContext.Provider value={actionManager}>
        <RouterContext.Provider value={router}>
          {/* ... 其他 Context Providers ... */}
          <Pages />
        </RouterContext.Provider>
      </ActionManagerContext.Provider>
    </AppControllerContext.Provider>
  </ThemeProvider>
);

React 组件通过 Context 访问 AppController

// ✅ GOOD: 通过 Context 访问 AppController(向下访问)
const MyComponent = () => {
  const { appController } = useContext(AppControllerContext);
  const [assets] = useDataStorageObjects(
    appController.appDataModel.inventoryModel.storages.assets
  );
  // 组件 → AppController → DataModel → Storage
};

// ❌ BAD: 直接导入 DataModel(绕过层次)
import { inventoryModel } from './DataModel'; // 违反分层原则

关键点

  • AppController 在模块级别创建,不依赖 React
  • AppController 通过 Context 提供给 React 组件
  • React 组件只能通过 Context 访问 AppController,不能直接访问下层
1.4 Action 只能操作 DataModel

Action 的实现展示了只能操作下层:

export abstract class UpdateEndpointAction extends Action {
  protected dataModel: WorkloadDataModel; // 只依赖 DataModel(下层)
  protected endpointId: string;

  constructor(
    dataModel: WorkloadDataModel, // 通过构造函数注入(向下依赖)
    orgId: string,
    endpointId: string,
    attributes: Partial<EndpointAttributes>
  ) {
    super();
    this.dataModel = dataModel;
    // Action 不持有 Controller 或 View 的引用
  }

  public doAction() {
    this.dataModel.updateEndpointIfExist(this.newEndpoint); // 只操作 DataModel
  }
}

ActionManager 的实现

export class ActionManager {
  protected appDataModel: AppDataModel; // 只依赖 DataModel(下层)

  constructor(appDataModel: AppDataModel) {
    this.appDataModel = appDataModel;
    // ActionManager 不持有 Controller 的引用
  }

  public doAction(action: Action) {
    this.actionsStack.push(action);
    action.doAction(); // 执行 Action,Action 操作 DataModel
    action.save() // Action 调用 ServerAPI(下层)
      .then(() => {
        this.events.emit(ACTION_MANAGER_EVENTS.SAVE_SUCCESS, action);
      })
      .catch((err) => {
        action.undoAction(); // 回滚,只操作 DataModel
      });
  }
}

保证机制总结

  1. AppController 提前初始化:在 React 渲染之前创建,不依赖 React 生命周期
  2. 构造函数依赖注入:每层通过构造函数接收下层依赖
  3. 禁止向上引用:DataModel 不持有 Controller/View 引用
  4. Context 访问模式:React 组件通过 Context 访问 AppController,不能直接访问下层
  5. 类型系统约束:TypeScript 类型检查防止错误的依赖关系
  6. 代码审查规则.cursor/rules/ 中的规则文档明确禁止违反分层

2. 单向数据流:数据向下,操作向上

原则说明

数据流(向下):
DataModel → Hooks → React Components

操作流(向上):
User Click → Component → ActionManager → Action → DataModel

实现机制

2.1 数据向下:通过 Hooks 订阅 Storage 事件

useDataStorageObjects Hook 的实现

export const useDataStorageObjects = <T extends StorageObject>(
  dataStorage?: DataStorage<T> | DataStorageWithFallback<T>,
  destroyStorageOnUnmount: boolean = false
) => {
  // 1. 初始状态:从 Storage 读取数据(向下)
  const [objects, setObjects] = useState(dataStorage?.getAllObjects() || []);

  // 2. 订阅 Storage 事件(监听数据变化)
  useEffect(() => {
    const onObjectsChanged = useMicroDebounceHandler(() => {
      const newObjects = dataStorage?.getAllObjects() || [];
      setObjects(newObjects); // 数据向下流动到组件状态
    }, [dataStorage]);

    // 订阅 Storage 的变更事件
    dataStorage?.on("OBJECT_ADDED", onObjectsChanged);
    dataStorage?.on("OBJECT_REMOVED", onObjectsChanged);
    dataStorage?.on("OBJECT_UPDATED", onObjectsChanged);

    return () => {
      // 清理订阅
      dataStorage?.off("OBJECT_ADDED", onObjectsChanged);
      dataStorage?.off("OBJECT_REMOVED", onObjectsChanged);
      dataStorage?.off("OBJECT_UPDATED", onObjectsChanged);
    };
  }, [dataStorage]);

  return [objects]; // 返回数据给组件(向下)
};

数据流路径

Storage.putObject() 
  → Storage.events.emit("OBJECT_ADDED") 
  → Hook 的 onObjectsChanged 回调
  → setObjects(newObjects)
  → React 重新渲染
  → 组件显示新数据
2.2 操作向上:通过 ActionManager 执行变更

组件中的操作流程

// 组件中(React View 层)
const MyComponent = () => {
  const { actionManager } = useContext(AppControllerContext);
  const [assets] = useDataStorageObjects(storage);

  const handleUpdate = (id: string, newName: string) => {
    // 1. 用户操作触发(向上)
    const action = new UpdateAssetAction(storage, id, { name: newName });
    
    // 2. 通过 ActionManager 执行(向上)
    actionManager.doAction(action);
    
    // 3. Action 内部操作 DataModel(向上)
    // 4. DataModel 更新 Storage
    // 5. Storage 发出事件(向下)
    // 6. Hook 订阅事件,更新组件状态(向下)
  };

  return <button onClick={() => handleUpdate("a1", "New Name")}>Update</button>;
};

ActionManager 的执行流程

public doAction(action: Action) {
  this.actionsStack.push(action);
  
  // 1. 立即执行(乐观更新)- 操作向上到 DataModel
  action.doAction(); // → dataModel.updateEndpointIfExist()
  
  // 2. 异步保存到服务器
  action.save()
    .then(() => {
      // 成功:数据已同步
      this.events.emit(ACTION_MANAGER_EVENTS.SAVE_SUCCESS, action);
    })
    .catch((err) => {
      // 失败:回滚(操作向上到 DataModel)
      action.undoAction(); // → dataModel.updateEndpointIfExist(oldObject)
      this.events.emit(ACTION_MANAGER_EVENTS.SAVE_FAILED, action);
    });
}
2.3 事件驱动的数据流

Storage 发出事件(数据变化的源头):

putObjects(objects: TObject[]) {
  const updated: ChangedObjectRecord<TObject>[] = [];
  const added: TObject[] = [];
  
  objects.forEach((object) => {
    const existingObject = this.getObjectByID(objectId);
    if (existingObject) {
      this.updateObjectIndexes(existingObject, object);
      updated.push({ oldObject, object, ... });
    } else {
      this.addToIndexes(object);
      added.push(object);
    }
  });
  
  // 发出事件(数据向下流动的起点)
  if (updated.length > 0) {
    this.versionNumber++;
    this.events.emit(STORAGE_EVENTS.OBJECT_UPDATED, updated); // 事件向下传播
  }
  if (added.length > 0) {
    this.versionNumber++;
    this.events.emit(STORAGE_EVENTS.OBJECT_ADDED, added); // 事件向下传播
  }
}

完整的数据流循环

1. 用户点击按钮
   ↓
2. Component 调用 actionManager.doAction(action)
   ↓
3. ActionManager 执行 action.doAction()
   ↓
4. Action 调用 dataModel.updateEndpointIfExist(newEndpoint)
   ↓
5. DataModel 调用 storage.putObject(newEndpoint)
   ↓
6. Storage 更新索引并发出事件
   ↓
7. Hook 订阅的事件触发 onObjectsChanged
   ↓
8. Hook 调用 setObjects(newObjects)
   ↓
9. React 重新渲染组件
   ↓
10. 用户看到更新后的数据

保证机制总结

  1. 事件订阅模式:Hooks 订阅 Storage 事件,数据变化自动向下传播
  2. Action 模式:所有变更通过 Action,操作统一向上传递
  3. 不可变对象:StorageObject 不可变,确保数据流清晰
  4. 单向事件流:事件只从 Storage 向下传播到 Hooks,不会反向
  5. 类型约束:TypeScript 确保操作路径正确

3. 高性能:支持百万级数据对象的 O(1) 查找

原则说明

  • O(1) 查找:通过 ID 查找对象的时间复杂度为常数
  • 索引系统:使用 Map 数据结构实现快速查找
  • 多级索引:支持按不同属性建立多个索引

实现机制

3.1 核心索引:Map 数据结构

DataStorage 的基础索引

export class DataStorage<TObject extends StorageObject = StorageObject> {
  // 核心索引:Map<ID, Object> - O(1) 查找
  protected index: Map<IdOfStorageObject<TObject>, TObject>;

  protected createIndexes() {
    this.index = new Map<IdOfStorageObject<TObject>, TObject>();
  }

  // O(1) 查找:Map.get() 是常数时间复杂度
  getObjectByID(id: IdOfStorageObject<TObject>) {
    return this.index.get(id) || null;
  }

  // O(n) 批量查找:n 是 ids 数组长度,每个查找是 O(1)
  getObjectsByIds(ids: IdOfStorageObject<TObject>[]) {
    const objs: TObject[] = [];
    for (let i = 0; i < ids.length; i++) {
      const obj = this.getObjectByID(ids[i]); // O(1)
      if (obj) {
        objs.push(obj);
      }
    }
    return objs; // 总体 O(n),但每个查找是 O(1)
  }
}

性能分析

  • Map.get() 的时间复杂度:O(1)
  • 即使有 100 万个对象,查找时间也是常数
  • JavaScript 的 Map 使用哈希表实现,平均查找时间为 O(1)
3.2 多级索引:按不同属性建立索引

EndpointStorage 的扩展索引(示例):

export class EndpointStorage extends DataStorage<Endpoint> {
  // 主索引(继承自 DataStorage)
  protected index: Map<string, Endpoint>; // ID → Endpoint

  // 扩展索引:按不同属性建立索引
  protected clusterIndexes: Map<string, Set<Endpoint>>; // ClusterID → Set<Endpoint>
  protected namespaceIndexes: Map<string, Set<Endpoint>>; // Namespace → Set<Endpoint>
  protected tagsIndexes: Map<string, Set<Endpoint>>; // Tag → Set<Endpoint>
  protected cloudAccountIndexes: Map<string, Set<Endpoint>>; // AccountID → Set<Endpoint>

  protected createIndexes() {
    super.createIndexes(); // 创建主索引
    // 创建扩展索引
    this.clusterIndexes = new Map();
    this.namespaceIndexes = new Map();
    this.tagsIndexes = new Map();
    this.cloudAccountIndexes = new Map();
  }

  // O(1) 查找:通过 ClusterID 查找
  getEndpointsByClusterId(clusterId: string): Set<Endpoint> {
    return this.clusterIndexes.get(clusterId) || new Set();
  }

  // O(1) 查找:通过 AccountID 查找
  getEndpointsByAccountId(accountId: string): Set<Endpoint> {
    return this.cloudAccountIndexes.get(accountId) || new Set();
  }
}

索引维护

addToIndexes(object: TObject) {
  this.index.set(object.getId(), object); // O(1) 插入主索引
}

removeFromIndexes(object: TObject, isUpdate: boolean) {
  if (!isUpdate) {
    this.index.delete(object.getId()); // O(1) 删除
  }
}
3.3 实际应用:KeyAssetStorage 的索引

KeyAssetStorage 的 resourceId 索引

export class KeyAssetStorage extends DataStorage<InventoryKeysAssets> {
  private keyAssetsIndex: Map<string, InventoryKeysAssets>; // ResourceID → Asset

  protected createIndexes() {
    super.createIndexes();
    this.keyAssetsIndex = new Map();
  }

  addToIndexes(object: InventoryKeysAssets): void {
    super.addToIndexes(object); // 主索引:ID → Asset
    const resourceId = object.getAttribute("resource_id");
    this.keyAssetsIndex.set(resourceId, object); // 扩展索引:ResourceID → Asset
  }

  // O(1) 查找:通过 ResourceID 查找
  getKeyAssetByResourceId(resourceId: string) {
    return this.keyAssetsIndex.get(resourceId);
  }
}
3.4 性能优化:批量操作

批量查找优化

getObjectsByIds(ids: IdOfStorageObject<TObject>[]) {
  const objs: TObject[] = [];
  for (let i = 0; i < ids.length; i++) {
    const obj = this.getObjectByID(ids[i]); // 每个查找是 O(1)
    if (obj) {
      objs.push(obj);
    }
  }
  return objs; // 总体 O(n),n 是 ids 长度
}

批量更新优化

putObjects(objects: TObject[]) {
  const updated: ChangedObjectRecord<TObject>[] = [];
  const added: TObject[] = [];
  
  objects.forEach((object) => {
    const objectId = object.getId();
    const existingObject = this.getObjectByID(objectId); // O(1) 查找
    if (existingObject) {
      this.updateObjectIndexes(existingObject, object); // O(1) 更新索引
      updated.push({ ... });
    } else {
      this.addToIndexes(object); // O(1) 添加到索引
      added.push(object);
    }
  });
  
  // 批量发出事件(避免多次触发)
  if (updated.length > 0) {
    this.events.emit(STORAGE_EVENTS.OBJECT_UPDATED, updated);
  }
  if (added.length > 0) {
    this.events.emit(STORAGE_EVENTS.OBJECT_ADDED, added);
  }
}

性能保证机制

3.5 内存效率

Map 的内存特性

  • Map 使用哈希表,内存占用 O(n)
  • 每个对象只存储一次,索引只存储引用
  • 支持百万级对象而不会导致内存问题

实际测试数据(来自文档):

// 从 WorkloadDataModel.ts 注释:
// "it takes 17ms to fill 4k endpoints in tree storage"
// 说明:即使有 4000 个对象,填充树结构也只需要 17ms
3.6 查找性能对比

传统数组查找(O(n))

// ❌ BAD: O(n) 线性查找
const findAsset = (assets: Asset[], id: string) => {
  return assets.find(asset => asset.getId() === id); // O(n)
};
// 100 万个对象:最坏情况需要 100 万次比较

DataStorage 查找(O(1))

// ✅ GOOD: O(1) 哈希查找
const asset = storage.getObjectByID(id); // O(1)
// 100 万个对象:只需要 1 次哈希查找

保证机制总结

  1. Map 数据结构:使用 JavaScript Map 实现 O(1) 查找
  2. 多级索引:按不同属性建立索引,支持多种查询模式
  3. 索引维护:添加/删除对象时同步更新所有索引
  4. 批量操作优化:批量更新时合并事件,减少重新渲染
  5. 内存效率:索引只存储引用,不复制对象

总结

三个原则的实现保证

原则实现机制代码位置
分层架构构造函数依赖注入、禁止向上引用、Context 访问AppController.ts, DataModel/*.ts, Actions/*.ts
单向数据流事件订阅模式、Action 模式、不可变对象UseDataStorageObjects.ts, ActionManager.ts, DataStorage.ts
O(1) 查找Map 索引、多级索引、批量操作优化DataStorage.ts, EndpointStorage.ts, KeyAssetStorage.ts

关键代码文件

  1. 分层架构

    • packages/console/src/AppController.ts - 层次依赖关系
    • packages/console/src/DataModel/*/Model.ts - DataModel 独立性
    • packages/console/src/Actions/ActionManager.ts - Action 层实现
  2. 单向数据流

    • packages/shared-library/src/hooks/UseDataStorageObjects.ts - 数据向下
    • packages/console/src/Actions/ActionManager.ts - 操作向上
    • packages/shared-library/src/DataModelCore/Storage/DataStorage.ts - 事件发出
  3. O(1) 查找

    • packages/shared-library/src/DataModelCore/Storage/DataStorage.ts - 核心索引
    • packages/console/src/DataModel/Storage/EndpointStorage.ts - 多级索引
    • packages/console/src/DataModel/InventoryModel/KeyAssetStorage.ts - 扩展索引

这些实现机制确保了架构原则在代码层面得到严格执行和保证。