Ошибки, возникшие из-за перепутанных полей и их решения
Рефакторинг бэкенда
1. Введение — имена полей были сохранены в обратном порядке
Поступил отчет об ошибке, что когда регистрируешь ItemRule через редактор шаблонов опроса, правила на фронтенде не работают так, как ожидалось.
В случае StateRuleбыло зарегистрировано правило “Скрыть этот элемент, если выбран тот элемент”но фактически оно работало наоборот. Сначала мы подозревали, что проблема в логике оценки условий на фронтенде.
Проследив за кодом, мы обнаружили, что проблема была более фундаментальной. Имя поля сущности бэкенда само по себе было привязано в обратном порядке от истинного значения.
|
java // Значение данных, сохраненных в реальной БД (до изменений) targetItemKey = "formId:Q3" // на самом деле это триггер (Source) sourceItemKeys = ["formId:Q1"] // на самом деле это цель эффекта (Target) |
|---|
Триггер (когда изменяется какой-либо элемент) сохранялся в targetItemKey, а цель эффекта (на какой элемент применять правила) сохранялась в sourceItemKeys. Поскольку названия были выданы в обратном порядке, фронтенд, читающий эти данные, не мог не запутаться.
2. Насколько глубоко была проблема
Хотя это выглядело как проблема, которую можно было решить, просто изменив имена полей, на самом деле она была по всей иерархии.
|
# |
Проблема |
Влияние |
|---|---|---|
|
1 |
перевернутое значение targetItemKey ↔ sourceItemKeys |
Смятение новых разработчиков, вызов ошибок |
|
2 |
Нет поля sourceItemKeys в CalculationRule |
Невозможность отслеживания источника, невозможность анализа зависимостей |
|
3 |
Источник распределен по 3 местам: variables, aggregationTargets, conditions |
Увеличение сложности кода |
|
4 |
AggregationTarget смешивает Source и Condition |
Нарушение принципа единственной ответственности |
|
5 |
CDO → Преобразование сущности может привести к потере sourceItemKeys |
Проблема целостности данных |
Это была не просто проблема именования. CalculationRule страдала от того, что необходимые для вычисления входные источники не были организованы в одном поле, что затрудняло отслеживание зависимостей и проверку.
java
// CalculationRule 수정 전 — 소스를 어디서 찾아야 하는가?
private List<FormulaVariable> variables; // 여기도 소스
private List<AggregationTarget> aggregationTargets; // 여기도 소스
private List<EnableCondition> conditions; // 여기도 소스(조건 내 참조)
Источники были распределены по трём местам, и название AggregationTarget было неясным — является ли это объектом агрегации или условием агрегации.
3. Как мы изменили
3-1. Переопределение смысла поля
Первым делом было переопределить, что такое sourceItemKeys.
sourceItemKeys = 트리거 (어떤 항목이 변경될 때 규칙이 발동하는가)
targetItemKeys = 효과 대상 (규칙이 적용될 항목들)
Например, в случае StateRule:
|
“Если элемент I7 изменяется → скрыть элементы I5, I6” sourceItemKeys: ["formId:Q3"] → триггер targetItemKeys: ["formId:Q1", "formId:Q2"] → объекты воздействия |
|---|
3-2. Исправление всех уровней воздействия
На основе этого определения были изменены все уровни Entity, CDO, JPO, Store, Helper, RDO.
|
Domain Entity → переопределение sourceItemKeys (триггер), targetItemKeys (объекты воздействия) CDO → StateRuleCdo, CalculationRuleCdo поля структура коррекции JPO → Исправление отображения столбцов DB Store → Проверка согласованности логики сохранения/запроса RDO → Применение структуры ответа фронтенда dual-output |
|---|
3-3. AggregationTarget → Приведение именования CalcSourceGroup в порядок
Имя AggregationTarget выполняло смешанную роль Source и Condition. Мы заменили его на CalcSourceGroup, четко указав реальную роль каждого поля.
java
// 수정 전
class AggregationTarget {
List<String> sourceKeys;
String conditionItemKey; // Source인가 Condition인가?
List<String> contextRelatedKeys;
}
// 수정 후
class CalcSourceGroup {
List<String> sourceKeys; // 계산할 실제 값의 출처 (순수 Source)
@JsonProperty("conditionItemKey") // JSON 키 하위호환 유지
String filterConditionKey; // 이 그룹의 필터 조건 (Condition)
List<String> contextRelatedKeys; // 의존성 맥락 출처
}
С помощью @JsonProperty("conditionItemKey") мы сохранили ключи JSON Wire, обеспечив совместимость с существующими данными.
4. Проектирование миграции DB
Хотя код изменился, существующие данные в DB остались нетронутыми. Вместе с развертыванием данные также нужно было корректировать.
В случае STATEПорядок двух шагов важен.Сначала необходимо скопировать существующие source_item_keys в новые target_item_keys, а затем перенести target_item_key в новые source_item_keys. Если порядок изменится, мы перезапишем оригинальные данные.
sql
BEGIN;
-- Step 1: 기존 효과 대상(source_item_keys) → 새 효과 대상(target_item_keys)
UPDATE item_rule
SET target_item_keys = COALESCE(source_item_keys, '[]'::jsonb)
WHERE rule_type = 'STATE';
-- Step 2: 기존 트리거(target_item_key) → 새 트리거(source_item_keys)
UPDATE item_rule
SET source_item_keys = CASE
WHEN target_item_key IS NULL OR target_item_key = '' THEN '[]'::jsonb
ELSE jsonb_build_array(target_item_key)
END
WHERE rule_type = 'STATE';
COMMIT;
В случае CALCULATION поля source_item_keys вообще не существовало, поэтому мы извлекли ключи источников, распределенные по variables и aggregation_targets, и заполнили их с помощью денормализации.
sql
-- variables.sourceKey + aggregation_targets[].sourceKeys 에서 중복 제거 후 통합
UPDATE item_rule AS c
SET source_item_keys = sub.keys
FROM (
SELECT r.id,
to_jsonb(ARRAY(
SELECT DISTINCT k FROM (
SELECT v->>'sourceKey' AS k
FROM jsonb_array_elements(COALESCE(r.variables, '[]')) v
UNION ALL
SELECT sk#>>'{}' AS k
FROM jsonb_array_elements(COALESCE(r.aggregation_targets, '[]')) agt,
jsonb_array_elements(COALESCE(agt->'sourceKeys', '[]')) sk
) all_keys WHERE k IS NOT NULL AND k <> ''
)) AS keys
FROM item_rule r
WHERE r.rule_type = 'CALCULATION'
AND (r.source_item_keys IS NULL OR r.source_item_keys = '[]')
) sub
WHERE c.id = sub.id;
5. В заключение
Ключевым моментом этого рефакторинга было не только устранение ошибок в названиях.
Переосмысленные значения полей были переработаны в единые стандарты для домена, API, БД и моделей ответа, при этом обеспечивая совместимость с существующими данными и фронтендом, что было более важной задачей.
В результате StateRule стал более ясным с точки зрения значений sourceItemKeys=триггер и targetItemKeys=эффект, а CalculationRule был организован так, чтобы более четко отслеживать входные источники, основываясь на sourceItemKeys и calcSourceGroups.
Прежде всего, эта работа заключалась не просто в изменении одного имени поля, а в упорядочивании накопившихся интерпретационных путаниц и создании основы для более безопасного расширения функциональности правил в будущем.
Justin