クレンジングルール
ヒント
さまざまな業務場面におけるデータガバナンス要件を満たすため、システムは「クレンジングルール」機能を提供します。この記事では、この機能を利用して必要なデータクレンジングルールを自主的に設定および拡張し、多様なデータ処理場面に対応する方法を説明します。
1. クレンジングルールテーブル定義
クレンジングルールのメタ情報は ATT_CLEAN_RULE テーブルに統一して保存されます。このテーブルは、クレンジングルールの基本情報、利用場面、戦略識別子などを管理するために使用します。
テーブル作る文
-- テーブルを作る
CREATE TABLE QDATA_TEST.ATT_CLEAN_RULE (
ID BIGINT IDENTITY(1,1) NOT NULL,
NAME VARCHAR2(128) NOT NULL,
"LEVEL" CHAR(1) DEFAULT '0' NOT NULL,
DESCRIPTION VARCHAR2(512) NULL,
VALID_FLAG VARCHAR2(1) DEFAULT '1' NOT NULL,
DEL_FLAG VARCHAR2(1) DEFAULT '0' NOT NULL,
CREATE_BY VARCHAR2(32) NULL,
CREATOR_ID BIGINT NULL,
CREATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
UPDATE_BY VARCHAR2(32) NULL,
UPDATER_ID BIGINT NULL,
UPDATE_TIME DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
REMARK VARCHAR2(512) NULL,
EXAMPLE VARCHAR2(512) NULL,
"TYPE" VARCHAR2(10) NULL,
USE_CASE VARCHAR2(512) NULL,
STRATEGY_KEY VARCHAR2(32) NULL,
CODE VARCHAR2(32) NULL,
CAT_CODE VARCHAR2(128) NULL,
CONSTRAINT PK_ATT_CLEAN_RULE PRIMARY KEY (ID)
);
-- テーブルコメント:
COMMENT ON TABLE QDATA_TEST.ATT_CLEAN_RULE IS 'クレンジングルールテーブル';フィールド説明
| フィールド名 | 型 | 制約/既定値 | 説明 |
|---|---|---|---|
| ID | BIGINT | 主キー、自動採番 | ルールID |
| NAME | VARCHAR2(128) | NOT NULL | ルール名 |
| LEVEL | CHAR(1) | 既定 '0'、NOT NULL | ルールレベル。1: フィールドレベル、2: テーブルレベル |
| DESCRIPTION | VARCHAR2(512) | ルール説明 | |
| VALID_FLAG | VARCHAR2(1) | 既定 '1'、NOT NULL | 有効かどうか。0: 無効、1: 有効 |
| DEL_FLAG | VARCHAR2(1) | 既定 '0'、NOT NULL | 取り除きフラグ。1: 取り除き済み、0: 未取り除き |
| CREATE_BY | VARCHAR2(32) | 作る者 | |
| CREATOR_ID | BIGINT | 作る者ID | |
| CREATE_TIME | DATETIME | 既定 CURRENT_TIMESTAMP | 作る日時 |
| UPDATE_BY | VARCHAR2(32) | アップデート者 | |
| UPDATER_ID | BIGINT | アップデート者ID | |
| UPDATE_TIME | DATETIME | 既定 CURRENT_TIMESTAMP | アップデート日時 |
| REMARK | VARCHAR2(512) | 備考 | |
| EXAMPLE | VARCHAR2(512) | 例 | |
| TYPE | VARCHAR2(10) | クレンジングルールカテゴリID | |
| USE_CASE | VARCHAR2(512) | 利用場面 | |
| STRATEGY_KEY | VARCHAR2(32) | 戦略識別子。クレンジングコンポーネント実行時のルール識別に使用 | |
| CODE | VARCHAR2(32) | ルールコード | |
| CAT_CODE | VARCHAR2(128) | カテゴリコード |
重要フィールド:
STRATEGY_KEY
このフィールドはクレンジングコンポーネント実行時にルール識別と振り分けに使用され、新しいルールを拡張するときの必須項目です。
2. フロントエンドのパラメーター化接続
クレンジングルールテーブルで基本情報と識別子(STRATEGY_KEY を含む)を設定すると、クレンジングルールは ETL のクレンジングコンポーネント一覧に閲覧されます。
ただし、この時点でクリックしても反応しないため、フロントエンド拡張が必要です。
デベロップメント説明
フロントエンドページデベロップメント
- 新規クレンジングルールに対応する設定ページを作るします。
- ページはルールに必要なパラメーターの入力と検証に使用します。
パラメーター封装
- フロントエンドで設定したパラメーターを ETL コンポーネントの
taskParamsに封装します。 - パラメーター構造は、バックエンド
parse2メソッドの解析ロジックと一致させる必要があります。
- フロントエンドで設定したパラメーターを ETL コンポーネントの
バックエンドへ渡す
- フロントエンド保存時、パラメーターはノード情報とともに ETL のタスク定義へ書き込まれます。
- 後続のタスク実行時には、
taskParamsを通じて実行コンポーネントへ渡されます。
注意:
- 新規クレンジングルールに加えるパラメーターがない場合は、既定構造をそのまま継承できます。
- 拡張が必要な場合は、
taskParamsに新しいパラメーターフィールドを定義し、解析ロジックも同期してアップデートしてください。
3. ETL パラメーター封装とクレンジングコンポーネントへの反映
フロントエンドで ETL コンポーネント情報を保存すると、システムはパラメーター封装段階へ入ります。
関連ロジックは qdata-module-dpp/qdata-module-dpp-biz モジュール配下にあります。
tech.qiantong.qdata.module.dpp.utils.TaskConverter#buildEtlTaskParams
メソッド説明
/**
* ETL パラメーターデータを構築
*/
public static Map<String, Object> buildEtlTaskParams(
String taskDefinitionList,
Map<String, DppEtlNodeSaveReqVO> nodeMap,
Map<String, Object> taskInfo,
List<DsResource> resourceList) {
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> transitionList = new ArrayList<>();
List<DppEtlNodeSaveReqVO> nodeList = JSON.parseArray(taskDefinitionList, DppEtlNodeSaveReqVO.class);
for (DppEtlNodeSaveReqVO dppEtlNodeSaveReqVO : nodeList) {
Integer version = 1;
if (nodeMap.containsKey(dppEtlNodeSaveReqVO.getCode())) {
version = nodeMap.get(dppEtlNodeSaveReqVO.getCode()).getVersion();
}
// コンポーネント種別
String componentType = dppEtlNodeSaveReqVO.getComponentType();
TaskComponentTypeEnum taskComponentTypeEnum = TaskComponentTypeEnum.findEnumByType(componentType);
Map<String, Object> data = ComponentFactory.getComponentItem(componentType)
.parse2(dppEtlNodeSaveReqVO.getCode(), version, taskComponentTypeEnum,
dppEtlNodeSaveReqVO.getTaskParams(), resourceUrl, resourceList);
data.put("nodeName", dppEtlNodeSaveReqVO.getName());
data.put("projectCode", taskInfo.get("projectCode"));
switch (taskComponentTypeEnum) {
case DB_READER:
case EXCEL_READER:
case CSV_READER:
result.put("reader", data);
break;
case SPARK_CLEAN:
case SORT_RECORD:
case FIELD_DERIVATION:
transitionList.add(data);
break;
case DB_WRITER:
result.put("writer", data);
break;
}
}
// config を設定
Map<String, Object> config = new HashMap<>();
config.put("taskInfo", taskInfo);
config.put("rabbitmq", rabbitmqConfig);
config.put("resourceUrl", resourceUrl);
result.put("transition", transitionList);
result.put("config", config);
return result;
}重要ポイント
コンポーネント分類処理
reader: DB / Excel / CSV リーダーtransition: クレンジング、ソート、フィールド派生などの中間処理チェーンwriter: DB ライター
クレンジングコンポーネント種別
SPARK_CLEANがクレンジングコンポーネントのtypeであり、この段階でtransitionListに加えるされます。
パラメーター受け渡し
- クレンジングルールページで封装したパラメーターは
taskParamsに入ります。 parse2メソッド内で、これらのパラメーターは解析されてtransitionに書き込まれます。nodeName、projectCode、versionなどの共通メタデータはbuildEtlTaskParamsメソッドが統一して補完します。
- クレンジングルールページで封装したパラメーターは
注意
- 新しいクレンジングルールをデベロップメントするとき、
buildEtlTaskParamsメソッドを変更する必要はありません。- フロントエンドから渡される
taskParamsパラメーター構造 とparse2メソッドの解析ロジック が一致していることだけを保証してください。
4. クレンジングコンポーネントのパラメーター封装と拡張
クレンジングコンポーネントのパラメーターは、buildEtlTaskParams 内で parse2 メソッドを呼び出して封装されます。
Map<String, Object> data = ComponentFactory.getComponentItem(componentType)
.parse2(dppEtlNodeSaveReqVO.getCode(),
version,
taskComponentTypeEnum,
dppEtlNodeSaveReqVO.getTaskParams(),
resourceUrl,
resourceList);メソッド例:
@Override
public Map<String, Object> parse2(String nodeCode,
Integer nodeVersion,
TaskComponentTypeEnum componentType,
Map<String, Object> taskParams,
String resourceUrl,
List<DsResource> resourceList) {
// リーダー設定
Map<String, Object> reader = new HashMap<>();
reader.put("nodeCode", nodeCode);
reader.put("nodeVersion", nodeVersion);
reader.put("componentType", componentType.getCode());
// パラメーター
Map<String, Object> parameter = new HashMap<>();
Map<String, Object> mainArgs = (Map<String, Object>) taskParams.get("mainArgs");
parameter.put("cleanRuleList", mainArgs.get("cleanRuleList"));
parameter.put("tableFields", taskParams.get("tableFields"));
parameter.put("where", taskParams.get("where"));
reader.put("parameter", parameter);
return reader;
}重要ポイント
パラメーター封装
- クレンジングコンポーネントのすべてのパラメーターは
taskParamsに統一して保存されます。 parse2メソッド内で、これらのパラメーターは解析されparameterフィールドへ移されます。
- クレンジングコンポーネントのすべてのパラメーターは
既定構造
cleanRuleList: クレンジングルール集合tableFields: 現在のテーブルフィールド情報where: 条件フィルター式
拡張説明
- 通常は
parse2の既定ロジックを変更する必要はありません。 - 拡張が必要な場合は、
taskParamsに新しいパラメーターフィールドを加えるし、parse2内で保存できます。
- 通常は
注意
- フロントエンドページのパラメーターモデルは、
parse2メソッドの解析ロジックと一致している必要があります。- 新しいパラメーターフィールドを加えるするときは後方互換性を保ち、既存ルールの実行へ影響しないようにしてください。
5. 実行側実装(Spark クレンジング実行)
クレンジングルールの実行は qdata-etl モジュールにあります。
クラス: tech.qiantong.qdata.spark.etl.transition.CleanTransition
主要フロー(抜粋)
JSONObject parameter = transition.getJSONObject("parameter");
// 1) パラメーター取得
List<Map<String, Object>> tableFieldList = (List<Map<String, Object>>) parameter.get("tableFields");
if (tableFieldList == null || tableFieldList.isEmpty()) {
return transitionOld(dataset, transition, logPath);
}
// 2) グローバル where
String where = parameter.getString("where");
if (StringUtils.isNotEmpty(where)) {
dataset = safeFilter(dataset, where, logPath);
}
// 3) ルールを1件ずつ処理
for (Map<String, Object> rule : tableFieldList) {
String ruleCode = MapUtils.getString(rule, "ruleCode");
String ruleType = MapUtils.getString(rule, "ruleType");
JSONObject ruleConfig = JSONObject.parseObject((String) rule.get("ruleConfig"));
String whereClause = MapUtils.getString(rule, "whereClause");
if (StringUtils.isNotEmpty(whereClause)) {
dataset = safeFilter(dataset, whereClause, logPath);
}
// フィールド存在チェック
if (!checkColumnsExist(dataset, ruleConfig)) {
LogUtils.writeLog(logPath, String.format("ルール %s をスキップ(フィールドが存在しません)", ruleCode));
continue;
}
// 4) 実行を振り分け
switch (ruleType) {
case "WITHIN_BOUNDARY": // 数値境界調整
dataset = applyNumericBoundary(dataset, ruleConfig);
break;
case "REMOVE_EMPTY_COMBINATION": // 組み合わせフィールドが空の場合に取り除き
dataset = applyDeleteIfAllNull(dataset, ruleConfig);
break;
case "ADD_PREFIX_SUFFIX": // フィールド前後接辞の統一
dataset = applyPrefixSuffix(dataset, ruleConfig);
break;
case "MENU_CUSTOM": // 列挙値マッピングの標準化
dataset = normalizeEnumMapping(dataset, ruleConfig);
break;
case "KEEP_LATEST_OR_FIRST": // 組み合わせフィールドで重複排除(最新または先頭を保持)
dataset = deduplicateByFieldsKeepFirst(dataset, ruleConfig);
break;
default:
LogUtils.writeLog(logPath, "未知のルール: " + ruleCode);
}
}パラメーターの出所
parameter.tableFields- クレンジングルール一覧を含みます
- 各ルールには
ruleCode/ruleType/ruleConfig/whereClauseが含まれます
parameter.where- グローバルフィルター条件
- ルールレベルの
whereClauseより先に実行されます
拡張ガイド
ルール登録
- クレンジングルールテーブルに新しいレコードを加えるします。
- 一意の
STRATEGY_KEYを設定します。これはフロントエンドのruleTypeと対応している必要があります。
フロントエンドモデリング
- クレンジングコンポーネント設定ページに、そのルールのパラメーターモデルを加えるします。
- パラメーターを
taskParams.mainArgs.cleanRuleList[*].ruleConfigに書き込みます。
実行振り分け
switch (ruleType)に新しい分岐を加えるします。- 分岐名は
STRATEGY_KEYと一致させることを推奨します。 - 対応する実装メソッドを呼び出します。
ルール実装
CleanTransitionクラスに具体的な実装メソッドを加えるします(例:applyYourRuleName(dataset, ruleConfig))。- メソッド内では次を完了する必要があります。
- フィールド存在チェック
- データ変換ロジック
- ログ記録
注意事項
ルール実行順序
tableFields配列の順序に従って順次実行されます。- ルール間に依存関係がある場合は、フロントエンドまたは保存時に順序を明確にしてください。
フィールド検証
- 各ルールの実行前に
checkColumnsExist検証を通過する必要があります。 - 動的列については、欠落列の処理戦略(スキップ / 既定値 / エラー)を定義してください。
- 各ルールの実行前に
条件フィルタリング
- グローバル
whereとルールレベルwhereClauseは、どちらもsafeFilterで実行されます。 - 推奨実行順序: 先にグローバルフィルター、その後にローカルフィルター。
- 注入リスクを避けるため、ログ出力は統一してください。
- グローバル
拡張規約
- 新規ルールでは、
STRATEGY_KEYとruleTypeが対応し、かつ一意であることを保証してください。 - 解析例外を避けるため、パラメーター構造はフロントエンド
taskParams定義と一致させてください。
- 新規ルールでは、
6. 注意事項一覧
クレンジングルールを拡張および実装するときは、次の点に特に注意してください。
パラメーター拡張
- 新しいパラメーターは
ruleConfigに統一して入れ、共通構造の変更を避けます。
- 新しいパラメーターは
DB互換性
- 複数DBで実行する場合は、データ接続モジュールの方言適合を参照し、型と構文の互換性を確保してください。
一意識別子
STRATEGY_KEYはグローバルに一意である必要があり、クレンジングコンポーネント実行時のルール識別に使用されます。- 変更時には後方互換性と過去ルールの円滑な移行を考慮してください。
フロントエンドとバックエンドの整合性
- フロントエンドのパラメーターモデルは、バックエンド
parse2の解析ロジックと一致している必要があります。 - 「汚れたパラメーター」が実行側に入らないよう、パラメーター検証はフロントエンドとバックエンドの両方で行ってください。
- フロントエンドのパラメーターモデルは、バックエンド
実行順序
transition内のルールは配列順に実行されます。- ルールに依存関係がある場合は、保存時に順序を調整してください。
条件フィルタリング
- グローバル
whereとルールレベルwhereClauseは重ねて使用できます。実行順序はグローバル優先、ローカル次点です。
- グローバル
フィールド存在性
- 実行前にフィールド検証を行うことを推奨します。
- 動的列には欠落時の処理戦略(スキップ / 既定値 / エラー)を定義してください。
空値と型の処理
null値には明確な戦略(無視 / 既定値)を持たせてください。- 数値および日付系フィールドは統一して
castし、暗黙変換を避けてください。
性能最適化
- 先にフィルター条件を実行し、後続データ量を減らします。
shuffleを引き起こす操作は慎重に使用し、必要に応じてキャッシュを加えるします。
ログと可観測性
- ルール実行では、開始、終了、所要時間、影響行数を記録してください。
- 例外ログには
ruleCodeとノード名を含めることを推奨し、調査しやすくします。
拡張規約
- 新規ルールには単体または結合テスト例を提供してください。
- ルールドキュメントも同期してアップデートし、デベロップメントと運用が参照できるようにしてください。
