cultuurnet/udb3-search-service

Silex 应用程序,用于索引 UDB3 JSON-LD 文档并提供搜索 API。

2024.08.27.143100 2024-08-27 14:30 UTC

README

SAPI3 是一个搜索 API,它建立在 UDB3 的 json-ld 文档之上,并使用 ElasticSearch。

技术文档

一般信息

SAPI3 应用程序由 5 个主要部分组成

  1. 用于索引和搜索事件、地点和组织者的值对象、服务接口和抽象类(src
  2. 这些接口和抽象类的 ElasticSearch 实现(src/ElasticSearch
  3. HTTP 层(src/Http
  4. 一个用于引导和运行一切的网络应用程序(appweb/index.php
  5. 一个控制台应用程序,用于在 CLI 上运行特定操作(bin/app.phpapp/Console

索引版本控制

我们有一个命令可以将 ElasticSearch 索引迁移到新版本 如果需要的话

./bin/app.php elasticsearch:migrate

只有在脚本检测到文档映射中有新版本号时,才会进行实际的迁移。此时,它将创建一个新的索引,并使用最新的映射来配置它,然后通过遍历它们并将每个从 UDB3 中获取的最新 JSON-LD 索引到新索引中,来重新索引旧索引中的所有文档。

这意味着此命令是幂等的。您可以多次运行它,而无需事先进行任何检查,例如在每次部署或 git pull 之后。

工作原理

为了在生产(和其他环境)中重新索引时保持搜索索引处于活动状态,我们使用两个别名

  • udb3_core_read
  • udb3_core_write

实际的索引有一个带版本的名称,例如 udb3_core_v20191008132400

大多数时候,这两个别名将指向同一个索引,即最新的一个。

当迁移脚本检测到需要执行迁移,因为有一个新的版本号时,它将首先创建新的索引,并将 udb3_core_write 别名移动到新的索引。这样,新文档将已经在新索引中索引。

同时,udb3_core_read 别名仍然指向旧索引,因此用户不会在新的索引中发生重新索引时突然看到搜索结果的大幅下降。

在新索引重新索引后,迁移脚本也将移动 udb3_core_read 别名。

通过这种方法,迁移的唯一副作用是在新的索引化发生时,用户可能会获得一些过时的搜索结果。

JSON 文档结构

事件、地点和组织者在 ElasticSearch 中的 JSON 文档结构与 UDB3 中的 JSON-LD 结构不同。

这是故意的,因为我们可能需要索引具有多个分析器或/和在索引之前对数据结构进行更改的字段。

因此,SAPI3 在 HTTP 响应中返回的 JSON-LD 不是 ElasticSearch 内部索引的 JSON 文档。相反,我们还索引原始 JSON-LD 作为未分析的字段,并使用它来在 HTTP 响应中返回原始 JSON-LD。

索引新字段

所有文档的字段映射可以在 src/ElasticSearch/Operations/json 中找到,作为 mapping_*.json 文件。

在相应文件中按照Elasticsearch官方文档添加您的字段映射。如上所述,不必完全遵循JSON-LD结构或命名,因为这在某些情况下会使查询变得非常困难。例如,由于在单独的availableFromavailableTo字段上执行范围查询比较困难,因此我们将其索引为一个单独的availableRange字段。

将字段添加到映射后,更新UDB3_CORE版本号在src/ElasticSearch/Operations/SchemaVersions.php

有效的版本号示例为20191008132400。这仅仅是将当前日期时间以YYYYMMDDHHIISS格式(年、月、日、小时、分钟、秒,之间没有任何内容)表示。

此更改将使迁移脚本能看到新的映射并为其创建一个新的索引。然而,我们仍然缺少一种将JSON-LD文档中的属性转换为Elasticsearch文档中属性的方法。

这种转换发生在位于以下路径的JsonTransformer实现中:

  • src/ElasticSearch/JsonDocument/EventTransformer.php
  • src/ElasticSearch/JsonDocument/PlaceTransformer.php
  • src/ElasticSearch/JsonDocument/OrganizerTransformer.php

在从JSON-LD复制嵌套属性到Elasticsearch JSON时,不要复制包含该属性的整个对象。只需复制我们具有显式映射的(子)属性。否则,其他子属性(如名称)也将通过自动化映射进行索引,这将在用于高级查询的q参数中暴露。

例如,JSON-LD中的事件有一个production属性,如下所示

{
  "@type": "Event",
  "@id": "https://io.uitdatabank.dev/events/bcd9242d-ef85-4a32-8ad0-01af6f675634",
  ...
  "production": {
    "id": "08314739-ab47-4e89-a80c-ce46ef07ba1d",
    "name": "Test production",
    "otherEvents": [ ... ]
  }
}

当我们想索引生产ID,但不一定索引生产名称时,我们必须使生成的Elasticsearch JSON看起来像这样

{
  ...
  "production": {
    "id": "08314739-ab47-4e89-a80c-ce46ef07ba1d"
  }
}

在添加从一种格式复制属性到另一种格式的逻辑后,您可以运行迁移脚本来重新索引索引中的所有文档,并使用新的字段。

添加过滤器

URL参数

为了将SAPI3的Elasticsearch实现与代码的其他部分分离,我们使用查询构建器的概念。

有一个针对报价的查询构建器,还有一个针对组织者的查询构建器。每个都有一个接口,并有一个Elasticsearch实现。

HTTP控制器和其他代码只依赖于查询构建器接口。在引导和运行应用程序时,我们注入实际的Elasticsearch实现类。这样,理论上我们可以用另一个搜索引擎替换Elasticsearch。

因此,要添加一个新URL参数以进行过滤,您需要做出以下更改:

  1. 在相关的查询构建器接口(报价和/或组织者)上添加新方法
  2. 在Elasticsearch实现类上实现新方法
  3. 更改HTTP控制器以查找新查询参数并使用该参数调用新查询构建器方法

查询构建器接口

查询构建器接口位于以下位置:

  • src/Offer/OfferQueryBuilderInterface.php
  • src/Organizer/OrganizerQueryBuilderInterface.php

请注意,实现应该是不可变的,因此我们使用链式with方法,该方法返回具有新属性的调用对象副本。

Elasticsearch实现

查询构建器接口的Elasticsearch实现位于以下位置:

  • src/ElasticSearch/Offer/ElasticSearchOfferQueryBuilder
  • src/ElasticSearch/Organizer/ElasticSearchOrganizerQueryBuilder

这些类使用ongr/elasticsearch-dsl包构建查询。然而,它们都扩展了提供许多便利方法的AbstractElasticSearchQueryBuilder类,如match、term等。

HTTP控制器

HTTP控制器位于以下位置:

  • src/Http/OfferSearchController.php
  • src/Http/OrganizerSearchController.php

过去,控制器本身进行了大量的查询参数解析。然而,后来我们引入了“请求解析器”的概念,它接受API请求对象和查询构建器对象,然后在查询构建器对象上查找特定的查询参数并添加必要的过滤器。这样我们可以更好地划分每个类的职责。

如果您需要为全新的URL参数添加逻辑,首先在以下位置创建请求解析器:

  • src/Http/Offer/RequestParser
  • src/Http/Organizer/RequestParser

然后,将其添加到应用程序控制器提供者的请求解析器集合中,以便将其注入到相关控制器中

  • app/Offer/OfferSearchControllerFactory.php(我们需要为/offers//events//places/端点创建多个此控制器实例,因此使用工厂模式。)
  • app/Organizer/OrganizerServiceProvider.php

注意,您还需要更改单元测试,以包含控制器中的新请求解析器。

如果您需要更改现有的URL参数且尚未创建请求解析器,最好首先将逻辑从控制器移动到请求解析器!

最后,将您的新URL参数添加到支持的查询参数列表中

  • src/Http/Parameters/OfferSupportedParameters.php
  • src/Http/Parameters/OrganizerSupportedParameters.php

参数 q

q URL参数(使用文档基本上是一个ElasticSearch的"query string"查询

此查询还支持Lucene查询语法以查询特定字段。

因此,我们不自己处理此查询,只是将其传递给ElasticSearch。要“添加”在q参数中对特定字段进行过滤的方式,您只需使用适当的分析器索引该字段即可。 因此,最好在索引字段和JSON-LD文档中的命名保持相似(如果可能的话相同)。