📰 来源: 博客园
在 GIS(地理信息系统)领域,数据分析和数据质检是两个看似独立却又高度关联的场景。数据分析回答"结果是什么",数据质检则关注"过程哪里有问题"。如果为这两个场景分别建设一套系统,就意味着两套执行引擎、两套调度逻辑、两套数据适配——重复建设,维护成本翻倍。
有没有一种方式,能用一套执行底座同时支撑分析和质检两个场景?
这是本文将要讨论的问题。我们将从一个实际的 GIS 数据引擎的构建过程出发,重点讨论其中的架构设计思路、关键技术决策和踩过的坑。
一、问题拆解:分析即算子的编排
回归本质,无论是数据分析还是数据质检,其最小执行单元都可以抽象为一个原子动作:
输入数据 → 执行操作 → 产出结果
这个原子动作我们称之为算子(Operator)。一个"缓冲区分析"是算子,一个"属性完整性检查"也是算子。它们的差异不在于执行机制,而在于输出形态——质检场景输出问题清单,分析场景输出中间数据集。
因此,框架的核心能力不是"做分析"或"做质检",而是:
在这种思路下,框架被设计为方案驱动(Plan-Driven):用户通过 JSON 配置定义一个"分析方案",描述要执行哪些算子、数据从哪来、结果写到哪、依赖关系如何、失败怎么处理。框架负责解析方案、校验合法性、构建执行计划并调度运行。
二、架构设计:七层分治
整体架构采用七层分层设计,每层职责单一,通过接口解耦:
┌─────────────────────────────────────────────────────┐
│ CLI 层 (OpenGisDAF.Cli) │
│ 命令路由 · 异常处理 · DI 容器 │
├─────────────────────────────────────────────────────┤
│ 方案管理层 (OpenGisDAF.PlanManagement) │
│ CRUD · JSON 序列化 · 版本管理 · 校验 │
├─────────────────────────────────────────────────────┤
│ 调度引擎层 (OpenGisDAF.Scheduling) │
│ DAG 构建 · Kahn 拓扑排序 · 失败策略 · 并发控制 │
├─────────────────────────────────────────────────────┤
│ 执行引擎层 (OpenGisDAF.Execution) │
│ 算子执行 · 结果缓存 · 超时重试 · 质量报告 │
├─────────────────────────────────────────────────────┤
│ 算子池 (OpenGisDAF.Operators) │
│ 9 个内置算子 · 插件发现 · 动态加载 · 辅助工具 │
├─────────────────────────────────────────────────────┤
│ 适配器层 (OpenGisDAF.Adapters) │
│ IFeatureSource 数据源 (4) · IFeatureSink 输出 (4) │
│ 映射工具: FieldTypeMapper · GeometryTypeMapper │
├─────────────────────────────────────────────────────┤
│ 基础设施层 (OpenGisDAF.Infrastructure) │
│ 配置 · 日志 (Serilog) · 密码加密 (DPAPI) │
└─────────────────────────────────────────────────────┘
2.1 为什么选择七层而不是简单的三层?
传统三层架构(表示层 / 业务逻辑层 / 数据访问层)在面对"多种数据源 + 可插拔算子 + 复杂调度"这种场景时,会出现职责混杂的问题。
举例来说,如果执行引擎和调度引擎合并为一层,那么"重试机制"的代码就会散落在算子执行和任务调度两个不同的关注点中。同理,如果算子注册和数据适配在同一层,每当新增一种数据源,算子池的代码也需要改动。
七层的划分原则是:每个模块只处理一个维度的变化。数据源变了只改 Adapters,调度策略变了只改 Scheduling,算子增加了只改 Operators。各层之间通过 Core 层定义的接口通信(IOperator、IFeatureSource、ISchedulingEngine 等),Core 层自身不包含任何实现细节。
3.1 算子体系:一切皆为 IOperator
所有算子实现统一的接口:
public interface IOperator
{
OperatorMetadata Metadata { get; }
ValidationResult Validate(AnalysisItem config);
Task<ExecutionResult> ExecuteAsync(
IReadOnlyDictionary<string, IFeatureSource> inputs,
IReadOnlyDictionary<string, object?> parameters,
ExecutionContext context,
CancellationToken cancellationToken);
}
每个算子自带元数据(Metadata):ID、名称、分类、标签、参数定义、输入输出 schema——这些信息不仅用于运行时查找,也是方案校验时的重要依据。例如,校验器会检查 parameters 中传入了不支持的参数名,或参数值不在 AllowedValues 范围内。
内置的 9 个算子覆盖了 GIS 领域最核心的需求:
一个值得展开的设计是质检算子的双路输出模式。同为 geometry_validity_checker,在普通模式下输出"通过检查的要素集",在 QC 模式(_qc_mode = true)下输出 IssueRecord 列表。这意味着同一个算子无需为分析和质检分别编写两套;框架在算子执行前根据方案配置注入 _qc_mode 参数,算子内部据此决定返回对象。这种设计避免了算子逻辑的重复,也保持了对下游的透明性。
3.2 字段计算器:为什么自研表达式引擎?
field_calculator 需要支持三种表达式语义:
"area: {area} ㎡" → 将 {area} 替换为要素的属性值🔗 原文链接: 点击阅读原文
文章评论