Кастомный алгоритм сопоставления¶
Система Юниверс предоставляет возможность создавать свои алгоритмы для поиска и объединения дубликатов записей.
В системе представлено сопоставление на основе базы Postgres. Также возможно добавить свой алгоритм или написать свою реализацию хранилища.
Реализация пользовательского алгоритма для хранилища на базе Postgres:
Примеры алгоритмов можно посмотреть в пакетах org.unidata.mdm.matching.storage.postgres.service.impl.algorithm
и com.universe.mdm.sdk.spring.service.impl.algorithm
.
Зависимости¶
Модуль с алгоритмом нуждается в зависимостях:
'org.unidata.mdm:org.unidata.mdm.matching.core:6.9.0-RELEASE'
'org.unidata.mdm:org.unidata.mdm.matching.storage.postgres:6.9.0-RELEASE'
Также необходимо указать зависимости в имплементации модуля dependencies:
@Override
public Collection<Dependency> getDependencies() {
return Arrays.asList(
// system, core, etc
new Dependency("org.unidata.mdm.matching.core", "6.0"),
new Dependency("org.unidata.mdm.matching.storage.postgres", "6.0")
);
}
Интерфейсы¶
Пользовательский алгоритм на базе PostgreSQL должен быть наследником
org.unidata.mdm.matching.core.service.impl.algorithm.AbstractSystemMatchingAlgorithm
.
public static final String ALGORITHM_NAME = "sdkMatchingAlgorithm";
private static final String ALGORITHM_DISPLAY_NAME = MODULE_ID + ".algorithm.display.name";
private static final String ALGORITHM_DESCRIPTION = MODULE_ID + ".algorithm.description";
public ExampleMatchingAlgorithm() {
super(ALGORITHM_NAME,
() -> TextUtils.getText(ALGORITHM_DISPLAY_NAME),
() -> TextUtils.getText(ALGORITHM_DESCRIPTION),
PostgresMatchingStorageConfigurationConstants.MATCHING_STORAGE_NAME);
}
Примечание
Обязательно необходимо задать локализацию для имени и описания алгоритма в resources/sdk_spring_messages_{en|ru}.properties
com.universe.mdm.sdk.spring.algorithm.display.name
- Пример алгоритма сопоставления
com.universe.mdm.sdk.spring.description
- Алгоритм ищет дубликаты записей по совпадению двум первым символам строкового параметра
Реализуйте интерфейс
org.unidata.mdm.matching.core.type.algorithm.MatchingAlgorithm::configure()
, даже если не требуются дополнительные настройки:
@Override
public MatchingAlgorithmConfiguration configure() {
return MatchingAlgorithmConfiguration.configuration()
.parameter(Collections.emptyList())
.build();
}
Если необходимы параметры для работы алгоритма, определите их и добавьте в
MatchingAlgorithmConfiguration
AlgorithmParamConfiguration<Boolean> caseInsensitive = AlgorithmParamConfiguration.bool()
.name(CASE_INSENSITIVE_PARAM)
.displayName(() -> TextUtils.getText(CASE_INSENSITIVE_PARAM_DISPLAY_NAME))
.description(() -> TextUtils.getText(CASE_INSENSITIVE_PARAM_DESCRIPTION))
.required(false)
.defaultValue(false)
.build();
return MatchingAlgorithmConfiguration.configuration()
.parameter(Collections.singletonList(caseInsensitive))
.build();
Реализуйте интерфейс
org.unidata.mdm.matching.storage.postgres.type.algorithm.PostgresMatchingAlgorithm
с двумя методами:
/**
* Обработка столбца MT. Создает коллекцию физических колонок БД и вычисляет правила предварительной обработки.
* Вызывается при выполнении операций DDL и хранения данных (вставка, удаление и т.д.).
*
* @param column matching column definition
* @param settings matching algorithm settings
* @return columns
*/
Collection<Column> processColumn(MatchingColumnElement column, AlgorithmSettingsElement settings);
/**
* Обработка столбца MT. Создает коллекцию физических колонок БД и вычисляет правила предварительной обработки.
* Вызывается операция MATCH.
*
* @param column matching column definition
* @param settings matching algorithm settings
* @return columns
*/
Collection<Column> matchColumn(MatchingColumnElement column, AlgorithmSettingsElement settings);
Возвращаемый объект
Column
описывает столбец в БД с возможностью задать дополнительные обработчики или правила сопоставления. Если алгоритм работает только со строками:
@Override
public Collection<Column> processColumn(MatchingColumnElement column, AlgorithmSettingsElement settings) {
return List.of(
new Column(column.getName(), ColumnType.STRING).withAlias(column.getName())
);
}
Параметр
Column.preprocessingRule
обрабатывает входное значение, результат будет сохранен в таблицу сопоставления.
Например, для реализации "поиск дубликатов по первым двум символам" можно обработать входной параметр, сократив его:
@Override
public Collection<Column> processColumn(MatchingColumnElement column, AlgorithmSettingsElement settings) {
Column def = new Column(column.getName(), ColumnType.STRING)
.withAlias(column.getName())
.withPreprocessingRule(f -> f.getValue() == null || f.getValue().toString().length() < 3
? null
: f.getValue().toString().substring(0, 2));
return List.of(def);
}
@Override
public Collection<Column> matchColumn(MatchingColumnElement column, AlgorithmSettingsElement settings) {
return processColumn(column, settings);
}
Параметр
Column.postCreateRule
обеспечивает возможность задачи дополнительных настроек столбца в базе данных, например, создание дополнительного индекса. Код изorg.unidata.mdm.matching.storage.postgres.service.impl.algorithm.InexactAlgorithm
:
Column original = new Column(originalColumnName, ColumnType.STRING)
.withAlias(column.getName())
.withInexact(true)
.withPostCreationRule(t -> new StringBuilder()
.append("create index ix_")
.append(t.getTableName().toLowerCase())
.append("_")
.append(originalColumnName)
.append(" on ")
.append(t.getTableName().toLowerCase())
.append(" using gin (")
.append(originalColumnName)
.append(" gin_trgm_ops)")
.toString());
Параметр
Column.conditionAppendingRule
задается вPostgresMatchingAlgorithm::matchColumn
и обеспечивает возможность переопределения поиска дубликатов (по умолчанию equals). Пример изorg.unidata.mdm.matching.storage.postgres.service.impl.algorithm.SetAlgorithm
:
@Override
public Collection<Column> matchColumn(MatchingColumnElement column, AlgorithmSettingsElement settings) {
final String columnName = column.getName();
final String setMatchingType = settings.getParameter(SET_MATCHING_TYPE_PARAM).getValue();
final boolean intersection = StringUtils.equals(setMatchingType, INTERSECTION_ENUM_VALUE);
Column value = new Column(columnName, ColumnType.HSTORE)
.withAlias(column.getName())
.withConditionAppendingRule(b -> b
.append(" and ((t1.")
.append(columnName)
.append(" is null and t2.")
.append(columnName)
.append(" is null) or t1.")
.append(columnName)
.append(intersection ? " ??| akeys(t2." : " ??& akeys(t2.")
.append(columnName)
.append("))"));
return List.of(value);
}
Регистрация алгоритма в системе¶
Реализуйте в компоненте интерфейс AfterPlatformStartup
и обновите модель. Пример com.universe.mdm.sdk.spring.service.impl.algorithm.RegisterMatchingAlgorithmsComponent
:
@Override
public void afterPlatformStartup() {
MatchingStorage storage =
matchingStorageService.getMatchingStorage(PostgresMatchingStorageDescriptors.MATCHING_STORAGE.getStorageId());
boolean notExists = storage != null
&& !storage.descriptor().getMatchingAlgorithms().contains(ExampleMatchingAlgorithm.class.getName());
// Если алгоритм уже зарегистрирован в системе, повторно регистрировать его не нужно
if (notExists) {
MatchingAlgorithmSource source = new MatchingAlgorithmSource()
.withCreateDate(OffsetDateTime.now())
.withDisplayName(TextUtils.getText(ExampleMatchingAlgorithm.ALGORITHM_DISPLAY_NAME))
.withDescription(TextUtils.getText(ExampleMatchingAlgorithm.ALGORITHM_DESCRIPTION))
.withLibraryName(PostgresMatchingStorageConfigurationConstants.MATCHING_STORAGE_LIBRARY_NAME)
.withLibraryVersion(MatchingAlgorithmJavaLibraryType.DEFAULT_LIBRARY_VERSION)
.withMatchingStorageId(PostgresMatchingStorageConfigurationConstants.MATCHING_STORAGE_NAME)
.withJavaClass(ExampleMatchingAlgorithm.class.getName())
.withName(ExampleMatchingAlgorithm.ALGORITHM_NAME);
MatchingModelUpsertContext uCtx = MatchingModelUpsertContext.builder()
.algorithmsUpdate(List.of(source))
.force(true)
.build();
metaModelService.upsert(uCtx);
}
}