UNIFIED_CONVENTION_SUMMARY.md 7.03 KB

统一约定重构总结

重构日期: 2025-11-12
核心原则: 统一约定,不做兼容,保持简单


问题

前端筛选日期范围(listing time)没有生效,ES 查询中没有对应的过滤项。

根本原因

数据类型不一致

  • API 层使用 Dict[str, RangeFilter](Pydantic 模型)
  • ES Query Builder 期望普通字典
  • 没有做转换,导致过滤失效

错误方案(违反简洁原则)

# ❌ 支持多种格式(兼容代码)
if hasattr(range_spec, 'model_dump'):
    range_dict = range_spec.model_dump()  # Pydantic 模型
else:
    range_dict = range_spec  # 普通字典

问题

  • 代码复杂
  • 多种数据流
  • 难以维护

正确方案:统一约定

核心思想

整个系统只使用一种数据格式:Pydantic 模型

数据流

API Request (JSON)
    ↓
Pydantic 验证 → Dict[str, RangeFilter]
    ↓
Searcher(透传)
    ↓
ES Query Builder → range_filter.model_dump()
    ↓
ES Query (字典)
    ↓
Elasticsearch

类型定义

# API 层 (models.py)
range_filters: Optional[Dict[str, RangeFilter]] = None

# Searcher 层 (searcher.py)
range_filters: Optional[Dict[str, Any]] = None  # 透传

# ES Query Builder 层 (es_query_builder.py)
range_filters: Optional[Dict[str, 'RangeFilter']] = None  # 明确类型

实现代码

/home/tw/SearchEngine/search/es_query_builder.py

def _build_filters(
    self, 
    filters: Optional[Dict[str, Any]] = None,
    range_filters: Optional[Dict[str, 'RangeFilter']] = None
) -> List[Dict[str, Any]]:
    """
    构建过滤子句。

    Args:
        filters: 精确匹配过滤器字典
        range_filters: 范围过滤器(Dict[str, RangeFilter],RangeFilter 是 Pydantic 模型)
    """
    filter_clauses = []

    # 1. 处理精确匹配过滤
    if filters:
        for field, value in filters.items():
            if isinstance(value, list):
                filter_clauses.append({"terms": {field: value}})
            else:
                filter_clauses.append({"term": {field: value}})

    # 2. 处理范围过滤(RangeFilter Pydantic 模型)
    if range_filters:
        for field, range_filter in range_filters.items():
            # 统一约定:range_filter 就是 RangeFilter 模型
            range_dict = range_filter.model_dump(exclude_none=True)

            if range_dict:
                filter_clauses.append({
                    "range": {field: range_dict}
                })

    return filter_clauses

关键点

  1. 不检查类型:不用 isinstancehasattr 检查
  2. 直接调用:直接调用 range_filter.model_dump()
  3. 类型注解:明确标注 Dict[str, 'RangeFilter']

统一约定的好处

1. 代码简洁

  • 不需要类型检查
  • 不需要兼容逻辑
  • 单一数据流

2. 类型安全

  • 编译时类型明确
  • IDE 类型提示完整
  • 运行时自动验证

3. 易于维护

  • 数据流清晰
  • 修改影响范围小
  • 新人容易理解

4. 高性能

  • 没有运行时类型检查
  • 没有条件分支
  • Pydantic 验证高效

测试结果

✅ 数值范围过滤

{
  "query": "玩具",
  "range_filters": {
    "price": {"gte": 50, "lte": 200}
  }
}

结果: ✓ 50 hits,ES filter 正确生成

✅ 日期时间范围过滤

{
  "query": "玩具",
  "range_filters": {
    "create_time": {"gte": "2024-01-01T00:00:00Z"}
  }
}

结果: ✓ 67 hits,ES filter 正确生成

✅ 混合过滤

{
  "query": "玩具",
  "filters": {"categoryName_keyword": "桌面休闲玩具"},
  "range_filters": {"price": {"gte": 10}}
}

结果: ✓ 50 hits,多个 filter 正确生成


对比:错误 vs 正确

方面 支持多种(错误) 统一约定(正确)
代码行数 更多(if/else) 更少(单一逻辑)
类型检查 运行时多次检查 编译时明确
数据流 多条路径 单一路径
可维护性 复杂 简单
错误处理 隐式容错 明确失败
性能 较慢(检查) 较快(直接)

架构原则

🎯 核心原则

  1. 统一约定 - 全系统使用同一种数据格式
  2. 不做兼容 - 不支持多种格式,明确失败
  3. 保持简单 - 单一数据流,清晰的类型
  4. 早期验证 - 在 API 层验证,内部直接使用

📐 设计决策

何时使用 Pydantic 模型?

  • ✅ API 边界:请求/响应
  • ✅ 内部传递:跨模块传递复杂数据
  • ✅ 配置定义:类型安全的配置

何时使用字典?

  • ✅ ES DSL:最终的查询字典
  • ✅ 简单键值:单层简单数据
  • ❌ 不用于跨模块传递复杂结构

🚫 反模式

避免这些做法:

# ❌ 兼容多种格式
if isinstance(x, dict):
    ...
elif hasattr(x, 'model_dump'):
    ...

# ❌ 运行时类型转换
if is_pydantic_model(x):
    x = x.model_dump()

# ❌ 可选的多种输入
def process(data: Union[Dict, Model]):
    ...

正确做法:

# ✓ 明确单一类型
def process(data: Model):
    dict_data = data.model_dump()
    ...

# ✓ 在边界转换
# API → Pydantic → 内部处理 → Pydantic → Response

系统一致性

统一的数据流模式

1. Facets 配置
   API: List[Union[str, FacetConfig]]
   → Searcher (透传)
   → ES Query Builder (只接受 str 或 FacetConfig)
   ✓ 统一约定

2. Range Filters
   API: Dict[str, RangeFilter]
   → Searcher (透传)
   → ES Query Builder (只接受 RangeFilter)
   ✓ 统一约定

3. 响应 Facets
   ES Response (字典)
   → Searcher: 构建 List[FacetResult]
   → API: 返回 List[FacetResult]
   ✓ 统一约定

所有模块都遵循相同的原则:统一约定,不做兼容


实施指南

添加新功能时

  1. 定义 Pydantic 模型 - 在 api/models.py 定义
  2. API 层验证 - FastAPI 自动验证
  3. 内部直接使用 - 不做类型检查和转换
  4. 明确类型注解 - 让 IDE 和 mypy 检查

重构现有代码时

  1. 识别兼容代码 - 查找 isinstance, hasattr
  2. 统一为一种格式 - 选择 Pydantic 或字典
  3. 移除条件分支 - 直接使用统一格式
  4. 更新类型注解 - 明确标注类型

总结

✅ 已完成

  • ✅ 修复日期范围过滤
  • ✅ 统一 range_filters 为 Pydantic 模型
  • ✅ 移除所有兼容代码
  • ✅ 保持代码简洁

🎯 核心价值

"统一约定,不做兼容,保持简单"

这不仅仅是代码风格,而是架构原则:

  • 降低认知负担
  • 减少 bug 产生
  • 提高代码质量
  • 加速开发效率

📚 参考

  • BEST_PRACTICES_REFACTORING.md - 最佳实践文档
  • FACETS_FIX_SUMMARY.md - Facets 修复总结
  • 本文档 - 统一约定原则

版本: v3.2
状态: ✅ 完成并通过测试
原则: 统一约定 > 兼容多种 > 代码简洁至上