DB Designer
用于创建、可视化和管理实体关系图(ERD)的工具。
在本文档中,我将详细介绍在 DB Designer 中实现的架构分析、设计模式和技术决策。DB Designer 是一款面向客户端的工具,旨在创建、可视化和管理实体关系图 (ERD)。
业务实用性与通用工作流
我构建 DB Designer 的主要目的是解决快速、直观地对数据库模式进行建模的需求,且无需依赖云端基础设施。我经常观察到,数据库设计工具需要复杂的配置、用户账户或与真实数据库的连接。我决定通过将系统设计为完全离线运行并通过 IndexedDB 在本地持久化信息来消除这一障碍。
我构建的核心工作流非常直观:
- 初始化:用户访问主仪表板 (Dashboard),可以在其中查看以前的项目或初始化一个新项目(从头开始或使用预定义模板)。
- 视觉建模:在我编写的编辑器中,用户添加代表表单的节点。我在每个表单中实现了动态字段添加,允许指定其名称、数据类型以及是否充当主键 (PK) 或外键 (FK)。
- 交互式关系:在连接节点时,我的系统通过图形连接推断语义,自动生成关系的视觉表示(例如 1:N)。我开发了一个智能路由系统 (Smart Edges),以在视觉上优化连接点的位置。
- 可视化与导出:用户可以预览基于视觉模型生成的 SQL 代码,并将整个图表导出为图像格式。我添加了此功能,以确保设计可以轻松集成到外部文档中。
深入架构分析与模型设计
我将该项目构思为面向客户端的架构(Client-Side Architecture),专注于浏览器的自主性,利用 Next.js 的 App Router 功能,但将计算和存储负担完全转移到客户端。
整体架构
我精心设计的应用设计反映了保持代码解耦、可维护和高效的关键模式:
- Local-First Architecture:我通过
idb库使用IndexedDB将数据持久化完全委托给用户的浏览器。我集成这一点是为了确保按设计保护隐私、低延迟和离线可用性(通过 PWA 增强)。我特意避免耦合后端来管理中央状态。 - 可预测的状态管理:我将
Zustand用于全局状态(如节点、边、撤销/重做历史记录),以防止 prop drilling 并促进交互式画布 (React Flow) 所需的响应式更新。 - 职责分离:我将视觉组件 (UI) 隔离在可重用的组件(按钮、选择器、弹出框)中。我将图表的数学逻辑(通过
dagre进行自动布局定位和智能边计算)提取到位于/app/lib/中的纯模块中。
graph TD
UI[React/Tailwind 用户界面] --> RF[React Flow 画布]
RF --> |变更事件| Zustand[Zustand 状态管理器]
Zustand --> |订阅/保存| IDB[(本地 IndexedDB)]
UI --> |导出| H2I[html-to-image]
UI --> |SQL 预览| Monaco[Monaco Editor]
subgraph "业务逻辑 (lib)"
AutoLayout[Dagre 自动布局]
SmartEdges[连接计算]
end
Zustand <--> AutoLayout
RF <--> SmartEdges
数据建模与逻辑
我将领域模型的定义集中在严格的 TypeScript 类型 (/app/types.ts) 中,这使我能够保证整个数据流的一致性。我围绕 ER 图的结构组织了主要实体:
- Project:包含图表元数据(ID、名称、创建和更新日期)以及节点和边集合的根实体。
- TableNodeData:每个 React Flow 节点的核心载荷。包含表标签、其语义颜色和列数组。
- Column:定义表的属性,包括其 SQL 数据类型、标识符以及主键或外键约束的布尔标志。
classDiagram
class Project {
+String id
+String name
+Number createdAt
+Number updatedAt
+AppNode[] nodes
+AppEdge[] edges
+EdgeSettings edgeSettings
}
class AppNode {
+String id
+String type
+Object position
+TableNodeData data
}
class TableNodeData {
+String label
+String color
+Column[] columns
}
class Column {
+String id
+String name
+ColumnType type
+Boolean isPk
+Boolean isFk
}
Project "1" *-- "*" AppNode : 包含
AppNode "1" *-- "1" TableNodeData : 载荷
TableNodeData "1" *-- "*" Column : 定义
我以不可变的方式管理节点状态。界面上的交互式事件(如按“Enter”添加字段)会派发粒度化的动作,我对其进行了编程,使其仅更新 Zustand store 中的相关片段。
技术栈
我做出的技术决策反映了我对高级交互性和开发者体验的关注:
| 工具 | 在架构中的技术角色 |
|---|---|
| Next.js 16.1 (App Router) | 我选择它作为页面路由、项目结构和 PWA 配置的基础框架。 |
| React 19 | 主要 UI 库;我用它来管理响应性、Portal 组件和 DOM 事件。 |
| Zustand | 我将其用于异步和同步全局状态管理。它支持我实现的编辑器撤销/重做系统。 |
| React Flow (@xyflow/react) | 我将其集成作为视觉图表编辑器的核心渲染引擎。它允许我管理交互式画布、节点和连接。 |
| IndexedDB (idb) | 它为我提供了浏览器中的异步本地存储。支持我针对 Project 模型做出的 Local-First 持久化决策。 |
| Tailwind CSS v4 | 这是我基于实用程序的样式系统;我用它来管理应用于亮/暗模式的语义 CSS 变量。 |
| Monaco Editor | 我集成的代码编辑器组件,用于提供 SQL 语法的强壮预览。 |
| Dagre | 我将其用作有向图的自动布局引擎,在视觉上组织复杂的图表模式。 |
设计决策的影响
我设计的架构在界面发生大量快速变更(拖动节点、编辑交互文本)的环境中最大化了响应速度。通过应用本地异步持久化和轻量级状态管理器,我成功完全避免了网络瓶颈。我对 TypeScript 严格类型的详尽使用以及层分离(将呈现与视觉几何计算隔离)确保了项目保持高度稳定性。这种结构使我能够在未来扩展系统,添加数据库模式中的新分析或生成功能,而无需进行破坏性的重构。
拖动可移动图示。按 + 和 − 缩放,方向键移动,0 重置视图。
一起合作
软件工程师、高级全栈开发工程师,常驻玻利维亚Santa Cruz,远程办公。欢迎新的工作机会。