在本次空间转录组学数据分析平台项目开发过程的初期阶段,我负责了后端数据库关系模式设计,以及backend/app/models目录下整个 Models 层的数据模型设计与实现工作。

models层解决的就是系统内部如何组织核心业务数据、如何描述实体之间的关系、如何保证数据在持久化与业务流转中的一致性。为了保证项目后续的文件管理、项目编排、服务调用、知识管理、分析流程追踪、智能体对话与代码生成等功能可以稳定扩展,我在设计这一层时,重点围绕以下几个目标展开:

1. 保证核心实体划分清晰,避免不同模块的数据边界混乱。

2. 保证模型字段约束明确,在进入数据库前就完成尽可能充分的校验。

3. 保证实体间关系可追踪,尤其是用户-项目-文件-服务执行这条主链路必须闭环。

4. 兼顾 MongoDB 文档存储的灵活性与业务关系模式的可维护性。

5. 为后续 API、Service、Repository 层提供统一、稳定、可复用的数据结构基础。

一、数据库设计的整体思路

本项目后端采用 MongoDB 作为主要数据存储,因此底层并不是传统意义上的严格关系型数据库表设计,而是以文档数据库为底座、以逻辑关系模式为核心的设计思路。

从业务上看,系统中的核心主线非常明确:

用户→项目文件→服务执行/分析流程→输出文件。

除此之外,还存在Analysis模块,用于组织文件分析树、算法节点和流程状态。

实体名称 主要标识字段 核心作用 关联对象
User user_id 用户身份与账户信息 Project、File、ServiceExecution
Project project_id 项目组织与资源聚合 User、File、ServiceExecution
File file_id 文件资源统一抽象 User、Project、FileType、ServiceExecution
FileType file_type_id 文件类型规范 File、Service
ServiceInfo service_id 算法服务注册信息 FileType、ServiceExecution、Project
ServiceExecution execution_id 服务调用执行记录 ServiceInfo、File、Project、User

二、核心关系模式设计

1. 用户与项目

整个系统中,User是最顶层的业务主体,而Project是承载业务资源的一级容器。一个用户可以创建多个项目,形成了非常明确的一对多关系:一个User对应多个Project,每个Project只属于一个User。

同时,项目本身不是孤立对象,而是一个资源聚合容器,因此我在Project模型中进一步维护了:file_id、knowledge_id、service_id、execution_id,也就是说,项目不仅有名称和描述,还能够聚合文件、关联服务并串起执行历史。

2. 项目与文件

文件是空间转录组平台里最重要的基础资源之一,很多后续分析、可视化、算法服务调用都依赖文件作为输入。我在关系模式上将File设计为独立实体,而不是直接嵌入项目文档中,原因主要有三点:

(1)文件本身生命周期独立,上传、查看、分析、删除都需要单独管理。

(2)文件元数据比较丰富,不适合简单挂在项目中。

(3)文件可能被多个流程复用,例如既参与分析树,又参与服务执行,还可能是其他文件的父文件或子文件。

因此这里采用了双向弱关联的设计:File.project_id用于记录文件当前归属项目,Project.file_id用于从项目侧快速聚合文件列表,这实际上是一种逻辑外键+聚合引用的折中方案,既符合MongoDB的灵活存储特性,又满足业务查询效率需求。

同时,为了支持文件派生链路,我又设计了FileRelation模型,用parent_file_id和child_file_id描述文件之间的父子关系。这一点非常适合空间转录组学分析场景,因为一个原始文件经过质控、筛选、归一化、可视化转换之后,往往会生成多个派生文件。

关系 表达字段 含义
用户拥有文件 File.user_id 文件归属哪个用户
项目包含文件 File.project_id 文件与项目的挂接关系
文件类型归类 File.file_type_id 文件属于哪种类型
文件派生关系 FileRelation.parent_file_id、child_file_id 记录分析链路中的上下游文件关系

3. 文件与文件类型

为了避免文件类型字段在不同模块中被随意写成字符串,导致后续判断混乱,我专门设计了FileType模型,将文件类型抽象为独立实体。FileType中保存了file_type_id、name、isplay_name、cription、category、extensions。

这样做的意义原因如下:

(1)文件类型定义与文件实例解耦,便于统一维护。

(2)服务模块可以通过accepted_files直接声明支持哪些file_type_id。

(3)后续前端展示时,可以基于display_name和category输出更友好的类型信息。

(4)文件上传校验可以统一依据extensions完成。

这使得系统不再是上传任意文件再临时判断,而是先有类型规范,再有文件实例,整体结构更清晰。

4. 服务与执行记录

在本项目中,算法服务并不是写死在代码里的,而是通过ServiceInfo进行统一注册和描述。这是我在Models层设计中比较重要的一部分,因为它直接决定了平台能否支持后续服务扩展。

ServiceInfo负责描述一个服务“是什么”,如:

  • 服务地址baseurl
  • 访问路径service_suffix
  • 下载路径 download_suffix
  • 参数模板parameter_template
  • 参数定义parameter_schema
  • 可接收文件类型 accepted_files
  • 输出结构output_config
  • 可见性visibility
  • 状态status。

而ServiceExecution负责描述一次调用“做了什么、结果如何”,如:

  • 输入文件input_file_id
  • 输出文件 output_file_id
  • 调用用户user_id
  • 所属项目project_id
  • 执行状态status
  • 执行参数parameters
  • 响应数据response_data
  • 错误信息error_message
  • 开始、完成时间与耗时

于是就形成了非常清晰的一对多关系:一个ServiceInfo对应多次ServiceExecution,一次ServiceExecution可以关联多个输入文件和多个输出文件,一次ServiceExecution也可以挂接到某个Project。这一设计非常适合分析平台的运行方式,因为算法服务是模板化定义+多次实例化执行的结构。

class ServiceInfo(BaseModel):
    model_config = ConfigDict(extra="allow")

    service_id: str = Field(default_factory=lambda: str(uuid4()))
    name: str = Field(..., min_length=1, max_length=200)
    description: Optional[str] = Field(None, max_length=1000)
    version: str = Field(default="1.0.0", max_length=50)
    baseurl: str = Field(..., min_length=1)
    service_suffix: str = Field(..., min_length=1)
    download_suffix: Optional[str] = None
    request_config: TaskRequestConfig = Field(default_factory=TaskRequestConfig)
    parameter_template: TaskParameterTemplate = Field(default_factory=TaskParameterTemplate)
    parameter_schema: Optional[Dict[str, ParameterDefinition]] = None
    accepted_files: Optional[Dict[str, AcceptedFileConfig]] = None
    output_config: Optional[ServiceOutputConfig] = None
    visibility: ServiceVisibility = Field(default=ServiceVisibility.PRIVATE)
    status: ServiceStatus = Field(default=ServiceStatus.ACTIVE)
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    created_by: str = Field(..., min_length=1)
class ServiceExecution(BaseModel):
    model_config = ConfigDict(extra="allow")

    execution_id: str = Field(default_factory=lambda: str(uuid4()))
    service_id: str = Field(..., min_length=1)
    service_name: Optional[str] = None
    user_id: str = Field(..., min_length=1)
    input_file_ids: List[str] = Field(..., min_length=1)
    output_file_ids: List[str] = Field(default_factory=list)
    project_id: Optional[str] = None
    status: ServiceExecutionStatus = Field(default=ServiceExecutionStatus.PENDING)
    parameters: Dict[str, Any] = Field(default_factory=dict)
    response_data: Optional[Dict[str, Any]] = None
    error_message: Optional[str] = None
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    started_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None
    duration_seconds: Optional[float] = None

5. 项目、服务与文件执行链路闭环

从整个系统主链路来看,最关键的并不是某一个单独模型,而是下面这条闭环:

User→Project→File→ServiceExecution→Output File→Project

在这条链路中:

(1)用户在项目中上传文件。

(2)项目中的文件作为输入交给服务模块执行。

(3)服务执行产生输出文件。

(4)输出文件重新回流到项目资源池中。

(5)后续分析树、可视化和再次执行都可以继续复用这些结果。

这保证了平台不是零散功能的堆叠,而是有完整数据流闭环的系统。

三、分析流程相关模型设计

1.面向分析流程的树形结构

空间转录组学分析往往不是一条简单线性链路,而是输入文件→若干中间结果→不同算法节点→ 输出文件的树形过程。

因此我设计了AnalysisTreeModel、FileNodeModel和AlgorithmEdgeModel三层结构:FileNodeModel表示文件节点,AlgorithmEdgeModel表示算法边,AnalysisTreeModel表示整棵分析树。

这种设计比用平铺表记录流程更适合前端展示分析树,也更便于追踪每个节点的状态、摘要、错误信息和元数据。

四、Models 层设计中的几个关键原则

1. 统一采用Pydantic V2进行模型定义

整个models层以Pydantic为基础,这具有以下优点:

(1)可以通过Field明确字段类型、长度、默认值和说明。

(2)可以通过field_validator、model_validator在模型层提前进行校验。

也就是说,很多本该在业务层手动if-else判断的数据问题,已经前移到了模型层解决,减少了后续服务层的混乱。

2. 枚举类统一管理状态字段

像文件状态、服务状态、执行状态、可见性、参数类型、代码语言等字段,我都尽量使用Enum而不是裸字符串。这样一来,状态值更规范、代码可读性更强、前后端联调时更不容易出现拼写错误。

例如服务执行状态统一由ServiceExecutionStatus管理,文件分析状态统一由AnalysisStatus管理。

class ServiceExecutionStatus(str, Enum):
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"
class AnalysisStatus(str, Enum):
    PENDING = "pending"
    UPLOADED = "uploaded"
    PROCESSING = "processing"
    COMPLETED = "completed"
    ERROR = "error"

3. 提供to_dict与from_dict,打通模型与MongoDB文档之间的转换

由于MongoDB返回的是字典文档,而业务层更希望使用强类型模型,所以我在多个模型中设计了to_dict()、xxx_from_dict()这两个方向的转换接口。这样做的目的是:

(1)Repository 层和Service层的职责边界更清晰。

(2)时间字段、枚举字段、嵌套字段可以统一转换。

(3)对MongoDB 的_id字段做了适当隔离,不让它污染业务模型。

4. 兼顾严格约束与文档型数据库扩展性

在不少模型里,我使用了ConfigDict(extra="allow"),而在另一些更严格的模型里使用 extra="forbid"。这并不是随意设置,而是根据业务场景做区分。

核心输入参数、类型定义、服务参数这类结构稳定的对象,尽量forbid。面向 MongoDB 扩展、可能带额外字段的业务对象,适度allow。

这样既保证了核心边界不会失控,也保留了文档数据库在业务快速演进中的灵活性。

五、数据库表一览

1、用户相关表

User表

字段 类型 描述

user_id

String

用户唯一标识

username

String

用户名

password

String

密码(加密存储)

email

String

邮箱地址

created_at

DateTime

创建时间

updated_at

DateTime

更新时间

2、文件相关表

FileType表

字段 类型 描述

file_type_id

String

文件类型ID

name

String

唯一名称

display_name

String

对用户展示的名称

description

String

类型描述

category

String

类型分类

extensions

List<String>

允许的扩展名

created_at

DateTime 创建时间

updated_at

DateTime

更新时间

FileInfo表

字段 类型 描述

user_id

String

用户ID

file_id

String

文件ID

filename

String

文件名

file_type_id

String

文件类型ID

file_type_name

String

文件类型名称

file_type_display_name

String

文件类型展示名称

file_path

String

文件路径

upload_time

DateTime

上传时间

last_viewed_time

DateTime

最后查看时间

analysis_status

Enum

分析状态

description

String

文件描述

metadata

Dict

文件元数据

generated_by

Dict

生成信息

FileRelation表

字段 类型 名称

parent_file_id

String

父文件ID

child_file_id

String

子文件ID

project_id

String

项目ID

description

String

关系描述

created_at

DateTime

创建时间

3、项目相关表

Project表

字段 类型 名称

project_id

String

项目ID

user_id

String

所属用户ID

name

String

项目名

description

String

项目描述

file_ids

List<String>

项目包含的文件ID列表

service_ids

List<String>

项目关联的服务ID列表

execution_ids

List<String>

项目关联的执行ID列表

created_at

DateTime

创建时间

updated_at

DateTime

更新时间

ProjectServiceConfig表

字段 类型 名称

mode

Enum

配置模式

whitelist

List<String>

服务ID白名单

blacklist

List<String>

服务ID黑名单

4、服务相关表

ServiceInfo表

字段 类型 名称

service_id

String

Service ID

name

String

Service名称

description

String

Service描述

version

String

Service版本

baseurl

String

基础URL

service_suffix

String

Service后缀

download_suffix

String

Download后缀

request_config

Object

请求配置

parameter_template

Object

参数模板

parameter_schema

Object

参数定义schema

accepted_files

Object

接受的文件类型配置

output_config

Object

输出结果配置

visibility

Enum

Service可见性

created_at

DateTime

创建时间

updated_at

DateTime

更新时间

created_by

String

创建者用户ID

ServiceExecution表

字段 类型 名称

execution_id

String

执行ID

service_id

String

Service ID

service_name

String

Service 名称

user_id

String

用户ID

input_file_ids

List<String>

输入文件ID列表

output_file_ids

List<String>

输出文件ID列表

project_id

String

项目ID

status

Enum

执行状态

parameters

Dict

执行参数

response_data

Dict

远程服务器响应数据

error_message

String

错误信息

created_at

DateTime

创建时间

started_at

DateTime

开始执行时间

completed_at

DateTime

完成时间

duration_seconds

Float

执行耗时(秒)

六、具体收获与思考

这次负责数据库关系模式和 Models 层设计,让我对数据结构先行这件事有了更深的体会。很多时候,后端开发最难的并不是写接口,而是先想清楚系统里到底有哪些实体、谁依赖谁、哪些字段应该固化、哪些关系应该显式保留。

如果关系模式没有设计清楚,后续会出现如同一业务概念在不同模块中字段命名不一致,数据归属关系混乱,后续权限和删除逻辑很难维护,文件、项目、服务执行之间无法形成完整追踪链,新功能接入时只能不断打补丁,而不是稳定扩展等各类棘手的问题。

总的来说,我负责的数据库关系模式设计和 Models 层设计工作,本质上是在为整个后端系统搭建稳定的数据骨架。这部分工作虽然不像界面展示那样直观,但它决定了系统后续能否持续扩展、不同功能之间能否顺畅协同、数据链路能否完整闭环。

通过这次实践,我不仅进一步熟悉了Pydantic模型设计、MongoDB文档建模和后端分层协作方式,也更深刻地理解了模型设计不是简单列字段,而是对整个业务世界进行抽象。后续在项目继续推进过程中,我也会继续结合实际功能迭代,对这套关系模式和模型体系进行完善与优化。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐