DataWorks は、メタデータ管理のための包括的な API 操作セットを提供します。このガイドでは、DataWorks API を使用してテーブルのクエリ、テーブル詳細の取得、メタデータの管理を行う方法を説明します。
前提条件
開始する前に、次のリソースを確認してください:
1. テーブルのリスト
このプラクティスでは、API を使用して MaxCompute プロジェクト/スキーマ配下のリストをクエリする方法について説明し、ページ分割クエリをサポートします。主な手順は次のとおりです:
バックエンド: MetaServiceProxy を使用してテーブル詳細をクエリする
MetaServiceProxy に ListTables メソッドを記述して、フロントエンドのパラメーターを処理し、ListTables API にリクエストを送信して、メタデータテーブルのリスト情報を取得します。
サポートされるクエリパラメーター: 親エンティティ ID、名前 (あいまい一致)、コメント (あいまい一致)、タイプ、ソートパラメーター、およびページネーションパラメーター。
説明詳細については、「メタデータエンティティ関連の概念説明」をご参照ください。親エンティティ ID は 2 つのフォーマットで存在する可能性があります。
親エンティティ ID は MaxCompute プロジェクト ID、フォーマット:
maxcompute-project:${Alibaba Cloud アカウント ID}::{projectName}親エンティティ ID は MaxCompute スキーマ ID、フォーマット:
maxcompute-schema:${Alibaba Cloud アカウント ID}::{projectName}:{schemaName}
クエリ結果に含まれるもの: データテーブルの総数、ページネーション情報、および各データテーブルについて: ID、親エンティティ ID、テーブル名、コメント、データベース名、スキーマ名、タイプ、パーティションフィールドリスト、作成時間、および変更時間。
Alibaba Cloud アカウント ID の取得: RAM ユーザーまたは Alibaba Cloud アカウントのいずれを使用していても、ページの右上隅で Alibaba Cloud アカウント ID を表示できます。
Alibaba Cloud アカウントまたは RAM ユーザーを使用して DataWorks コンソールにログインします。
右上隅のプロファイル画像にマウスを合わせると、Alibaba Cloud アカウント ID が表示されます。
Alibaba Cloud アカウント: [アカウント ID] は、取得する必要がある Alibaba Cloud アカウント ID です。
RAM ユーザー: [Alibaba Cloud アカウント ID] を直接表示できます。
サンプルコード:
/** * @author dataworks デモ */ @Service public class MetaServiceProxy { @Autowired private DataWorksOpenApiClient dataWorksOpenApiClient; /** * DataWorks OpenAPI : ListTables * * @param listTablesDto */ public ListTablesResponseBodyPagingInfo listTables(ListTablesDto listTablesDto) { try { Client client = dataWorksOpenApiClient.createClient(); ListTablesRequest listTablesRequest = new ListTablesRequest(); // 親エンティティ ID (メタデータエンティティの概念を参照) listTablesRequest.setParentMetaEntityId(listTablesDto.getParentMetaEntityId()); // テーブル名、あいまい一致をサポート listTablesRequest.setName(listTablesDto.getName()); // テーブルコメント、あいまい一致をサポート listTablesRequest.setComment(listTablesDto.getComment()); // テーブルタイプ、デフォルトではすべてのタイプを返す listTablesRequest.setTableTypes(listTablesDto.getTableTypes()); // ソートメソッド、CreateTime (デフォルト) / ModifyTime / Name / TableType listTablesRequest.setSortBy(listTablesDto.getSortBy()); // ソート方向、Asc (デフォルト) / Desc をサポート listTablesRequest.setOrder(listTablesDto.getOrder()); // ページ番号、デフォルトは 1 listTablesRequest.setPageNumber(listTablesDto.getPageNumber()); // ページサイズ、デフォルトは 10、最大 100 listTablesRequest.setPageSize(listTablesDto.getPageSize()); ListTablesResponse response = client.listTables(listTablesRequest); // 要件を満たすテーブルの総数を取得 System.out.println(response.getBody().getPagingInfo().getTotalCount()); for (Table table : response.getBody().getPagingInfo().getTables()) { // テーブル ID System.out.println(table.getId()); // 親エンティティ ID System.out.println(table.getParentMetaEntityId()); // テーブル名 System.out.println(table.getName()); // テーブルコメント System.out.println(table.getComment()); // データベース/MaxCompute プロジェクト名 System.out.println(table.getId().split(":")[3]); // スキーマ名 System.out.println(table.getId().split(":")[4]); // テーブルタイプ System.out.println(table.getTableType()); // テーブル作成時間 System.out.println(table.getCreateTime()); // テーブル変更時間 System.out.println(table.getModifyTime()); // テーブルのパーティションフィールドリスト System.out.println(table.getPartitionKeys()); } return response.getBody().getPagingInfo(); } catch (TeaException error) { // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } }
MetaRestController に
listTablesメソッドをフロントエンドアクセスのエントリポイントとして追加します。/** * DataWorks API を使用したカスタムメタデータプラットフォーム * * @author dataworks デモ */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * ページごとにテーブルデータをクエリ * * @param listTablesDto * @return {@link ListTablesResponseBodyPagingInfo} */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listTables") public ListTablesResponseBodyPagingInfo listTables(ListTablesDto listTablesDto) { System.out.println("listTablesDto = " + listTablesDto); return metaService.listTables(listTablesDto); } }
フロントエンド: テーブルメタデータ情報の表示
import React from "react";
import cn from "classnames";
import {
Table,
Form,
Field,
Input,
Button,
Pagination,
Dialog,
Card,
Grid,
Select,
} from "@alifd/next";
import type { TableListInput, TableEntity } from "../services/meta";
import * as services from "../services";
import DetailContent from "./detailContent";
import LineageContent from "./lineageContent";
import classes from "../styles/app.module.css";
import moment from "moment";
export interface Props {}
const { Column } = Table;
const { Item } = Form;
const { Header, Content } = Card;
const { Row, Col } = Grid;
const { Option } = Select;
const App: React.FunctionComponent<Props> = () => {
const field = Field.useField();
const [datasource, setDatasource] = React.useState<TableEntity[]>([]);
const [loading, setLoading] = React.useState<boolean>(false);
const [total, setTotal] = React.useState<number>(0);
const [current, setCurrent] = React.useState<number>(1);
const onSubmit = React.useCallback(
(pageNumber: number = 1) => {
field.validate(async (errors, values) => {
if (errors) {
return;
}
setLoading(true);
try {
const response = await services.meta.getTableList({
pageNumber,
...values,
} as TableListInput);
setDatasource(response.tables);
setCurrent(pageNumber);
setTotal(response.totalCount);
} catch (e) {
throw e;
} finally {
setLoading(false);
}
});
},
[field]
);
const onViewDetail = React.useCallback((record: TableEntity) => {
Dialog.show({
title: "データテーブル詳細",
content: <DetailContent item={record} />,
shouldUpdatePosition: true,
footerActions: ["ok"],
});
}, []);
const onViewLineage = React.useCallback((record: TableEntity) => {
Dialog.show({
title: "テーブルリネージ関係",
content: <LineageContent item={record} />,
shouldUpdatePosition: true,
footerActions: ["ok"],
});
}, []);
React.useEffect(() => {
field.setValue("dataSourceType", "odps");
}, []);
return (
<div className={cn(classes.appWrapper)}>
<Card free hasBorder={false}>
<Header title="メタデータテーブル管理シナリオデモ (MaxCompute)" />
<Content style={{ marginTop: 24 }}>
<Form field={field} colon fullWidth>
<Row gutter="1">
<Col>
<Item
label="MaxCompute プロジェクト/スキーマ ID"
name="parentMetaEntityId"
required
help="ID"
>
<Input />
</Item>
</Col>
</Row>
<Row gutter="4">
<Col>
<Item label="名前" name="name">
<Input placeholder="テーブル名を入力、あいまい一致" />
</Item>
</Col>
<Col>
<Item label="コメント" name="comment">
<Input placeholder="テーブルコメントを入力、あいまい一致" />
</Item>
</Col>
<Col>
<Item label="タイプ" name="tableTypes">
<Select mode="multiple" style={{ width: "100%" }}>
<Option value="TABLE">テーブル</Option>
<Option value="VIEW">ビュー</Option>
<Option value="MATERIALIZED_VIEW">マテリアライズドビュー</Option>
</Select>
</Item>
</Col>
<Col>
<Item label="ソートメソッド" name="sortBy">
<Select defaultValue="CreateTime" style={{ width: "100%" }}>
<Option value="CreateTime">作成時間</Option>
<Option value="ModifyTime">変更時間</Option>
<Option value="Name">名前</Option>
<Option value="TableType">タイプ</Option>
</Select>
</Item>
</Col>
<Col>
<Item label="ソート方向" name="order">
<Select defaultValue="Asc" style={{ width: "100%" }}>
<Option value="Asc">昇順</Option>
<Option value="Desc">降順</Option>
</Select>
</Item>
</Col>
</Row>
<div
className={cn(
classes.searchPanelButtonWrapper,
classes.buttonGroup
)}
>
<Button type="primary" onClick={() => onSubmit()}>
クエリ
</Button>
</div>
</Form>
<div>
<Table
dataSource={datasource}
loading={loading}
className={cn(classes.tableWrapper)}
emptyContent={
<div className={cn(classes.noDataWrapper)}>データなし</div>
}
>
<Column
title="プロジェクト名"
dataIndex="id"
cell={(value) => {
const parts = value.split(":");
return parts.length > 1 ? parts[parts.length - 3] : "";
}}
/>
<Column
title="スキーマ"
dataIndex="id"
cell={(value) => {
const parts = value.split(":");
return parts.length > 1 ? parts[parts.length - 2] : "";
}}
/>
<Column title="テーブル名" dataIndex="name" />
<Column title="テーブルコメント" dataIndex="comment" />
<Column title="テーブルタイプ" dataIndex="tableType" />
<Column
title="パーティションステータス"
dataIndex="partitionKeys"
cell={(value) => {
return value != null && value.length > 0 ? "はい" : "いいえ";
}}
/>
<Column
title="作成時間"
dataIndex="createTime"
cell={(value) => {
return moment(value).format("YYYY-MM-DD HH:mm:ss");
}}
/>
<Column
title="変更時間"
dataIndex="modifyTime"
cell={(value) => {
return moment(value).format("YYYY-MM-DD HH:mm:ss");
}}
/>
<Column
title="操作"
width={150}
cell={(value, index, record) => (
<div className={cn(classes.buttonGroup)}>
<Button
type="primary"
onClick={() => onViewDetail(record)}
text
>
詳細を表示
</Button>
<Button
type="primary"
onClick={() => onViewLineage(record)}
text
>
テーブルリネージを表示
</Button>
</div>
)}
/>
</Table>
<Pagination
current={current}
total={total}
onChange={onSubmit}
showJump={false}
className={cn(classes.tablePaginationWrapper)}
/>
</div>
</Content>
</Card>
</div>
);
};
export default App;上記のコード開発が完了したら、プロジェクトコードをデプロイして実行できます。MaxCompute プロジェクト ID やその他のパラメーターを入力して、プロジェクトスペース配下のすべてのテーブルのリストをクエリして取得でき、ページ分割クエリをサポートします。
2. テーブル詳細のクエリ
以下のプラクティスでは、メタデータ内の 3 つの API 操作、GetTable、ListColumns、および ListPartitions を組み合わせてテーブル詳細をクエリし、UpdateColumnBusinessMetadata API を使用してフィールドのビジネス記述を更新します。プラクティスのワークフローは次のとおりです。
バックエンド: MetaServiceProxy を使用してテーブル詳細をクエリする
MetaServiceProxy に
getTable、listColumns、listPartitionsメソッドを作成して、テーブルの基本情報、テーブルのフィールド情報、およびテーブルのパーティション情報を取得する操作を呼び出し、updateColumnBusinessMetadataメソッドを作成してフィールドのビジネス記述を更新します。/** * @author dataworks デモ */ @Service public class MetaServiceProxy { @Autowired private DataWorksOpenApiClient dataWorksOpenApiClient; /** * DataWorks OpenAPI : getTableDto * * @param getTableDto */ public Table getTable(GetTableDto getTableDto) { try { Client client = dataWorksOpenApiClient.createClient(); GetTableRequest getTableRequest = new GetTableRequest(); // テーブル ID getTableRequest.setId(getTableDto.getId()); // ビジネスメタデータを返すかどうか getTableRequest.setIncludeBusinessMetadata(getTableDto.getIncludeBusinessMetadata()); GetTableResponse response = client.getTable(getTableRequest); // 対応するデータテーブル Table table = response.getBody().getTable(); // テーブル ID System.out.println(table.getId()); // 親エンティティ ID System.out.println(table.getParentMetaEntityId()); // テーブル名 System.out.println(table.getName()); // テーブルコメント System.out.println(table.getComment()); // データベース/MaxCompute プロジェクト名 System.out.println(table.getId().split(":")[3]); // スキーマ名 System.out.println(table.getId().split(":")[4]); // テーブルタイプ System.out.println(table.getTableType()); // テーブル作成時間 System.out.println(table.getCreateTime()); // テーブル変更時間 System.out.println(table.getModifyTime()); // テーブルがパーティション化されているか確認 System.out.println(table.getPartitionKeys() != null && !table.getPartitionKeys().isEmpty()); // テーブルの技術メタデータ TableTechnicalMetadata technicalMetadata = table.getTechnicalMetadata(); // テーブルオーナー (名前) System.out.println(technicalMetadata.getOwner()); // 圧縮テーブルかどうか System.out.println(technicalMetadata.getCompressed()); // 入力フォーマット (HMS、DLF およびその他のタイプをサポート) System.out.println(technicalMetadata.getInputFormat()); // 出力フォーマット (HMS、DLF およびその他のタイプをサポート) System.out.println(technicalMetadata.getOutputFormat()); // テーブルのシリアル化に使用されるクラス (HMS、DLF およびその他のタイプをサポート) System.out.println(technicalMetadata.getSerializationLibrary()); // テーブルのストレージ場所 (HMS、DLF およびその他のタイプをサポート) System.out.println(technicalMetadata.getLocation()); // その他のパラメーター Map<String, String> parameters = technicalMetadata.getParameters(); // 最終 DDL 更新時間 (ミリ秒レベルのタイムスタンプ)、MaxCompute タイプのみサポート System.out.println(parameters.get("lastDDLTime")); // ライフサイクル (単位: 日)、MaxCompute タイプのみサポート System.out.println(parameters.get("lifecycle")); // ストレージサイズ (単位: バイト)、リアルタイムではなく、MaxCompute タイプのみサポート System.out.println(parameters.get("dataSize")); // テーブルのビジネスメタデータ TableBusinessMetadata businessMetadata = table.getBusinessMetadata(); if (businessMetadata != null) { // 使用説明書 System.out.println(businessMetadata.getReadme()); // カスタムタグ情報 List<TableBusinessMetadataTags> tags = businessMetadata.getTags(); if (tags != null && !tags.isEmpty()) { for (TableBusinessMetadataTags tag : tags) { System.out.println(tag.getKey() + ":" + tag.getValue()); } } // 上流の生産タスク List<TableBusinessMetadataUpstreamTasks> upstreamTasks = businessMetadata.getUpstreamTasks(); if (upstreamTasks != null && !upstreamTasks.isEmpty()) { for (TableBusinessMetadataUpstreamTasks upstreamTask : upstreamTasks) { // タスク ID とタスク名、タスク詳細は GetTask API を通じて取得可能 System.out.println(upstreamTask.getId() + ":" + upstreamTask.getName()); } } // カテゴリ List<List<TableBusinessMetadataCategories>> categories = businessMetadata.getCategories(); if (categories != null && !categories.isEmpty()) { // 複数レベルのカテゴリを走査 for (List<TableBusinessMetadataCategories> category : categories) { // 単一の複数レベルのカテゴリを出力 System.out.println( category.stream() .map(TableBusinessMetadataCategories::getName) .collect(Collectors.joining("->")) ); } } // 拡張情報、MaxCompute タイプのみサポート TableBusinessMetadataExtension extension = businessMetadata.getExtension(); if (extension != null) { // プロジェクト ID System.out.println(extension.getProjectId()); // 環境 (Prod: 本番 / Dev: 開発) System.out.println(extension.getEnvType()); // お気に入り数 System.out.println(extension.getFavorCount()); // 読み取り数 System.out.println(extension.getReadCount()); // 表示回数 System.out.println(extension.getViewCount()); } } return table; } catch (TeaException error) { // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } /** * DataWorks OpenAPI : ListColumns * * @param listColumnsDto */ public ListColumnsResponseBodyPagingInfo listColumns(ListColumnsDto listColumnsDto) { try { Client client = dataWorksOpenApiClient.createClient(); ListColumnsRequest listColumnsRequest = new ListColumnsRequest(); // テーブル ID (ListTables API から) listColumnsRequest.setTableId(listColumnsDto.getTableId()); // フィールド名、あいまい一致をサポート listColumnsRequest.setName(listColumnsDto.getName()); // フィールドコメント、単語分割マッチングをサポート listColumnsRequest.setComment(listColumnsDto.getComment()); // ソートメソッド、Name / Position (デフォルト) をサポート listColumnsRequest.setSortBy(listColumnsDto.getSortBy()); // ソート方向、Asc (デフォルト) / Desc をサポート listColumnsRequest.setOrder(listColumnsDto.getOrder()); // ページ番号、デフォルトは 1 listColumnsRequest.setPageNumber(listColumnsDto.getPageNumber()); // ページサイズ、デフォルトは 10、最大 100 listColumnsRequest.setPageSize(listColumnsDto.getPageSize()); ListColumnsResponse response = client.listColumns(listColumnsRequest); // 要件を満たすフィールドの総数を取得 System.out.println(response.getBody().getRequestId()); System.out.println(response.getBody().getPagingInfo().getTotalCount()); for (Column column : response.getBody().getPagingInfo().getColumns()) { // フィールド ID System.out.println(column.getId()); // フィールド名 System.out.println(column.getName()); // フィールドコメント System.out.println(column.getComment()); // フィールドタイプ System.out.println(column.getType()); // フィールド位置 System.out.println(column.getPosition()); // パーティションフィールドかどうか System.out.println(column.getPartitionKey()); // プライマリキーかどうか、MaxCompute タイプのみサポート System.out.println(column.getPrimaryKey()); // 外部キーかどうか、MaxCompute タイプのみサポート System.out.println(column.getForeignKey()); // フィールドのビジネス記述 (MaxCompute、HMS、および DLF でサポート) if (column.getBusinessMetadata() != null) { System.out.println(column.getBusinessMetadata().getDescription()); } } return response.getBody().getPagingInfo(); } catch (TeaException error) { // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } /** * DataWorks OpenAPI : UpdateColumnBusinessMetadata * * @param updateColumnBusinessMetadataDto */ public boolean updateColumnBusinessMetadata(UpdateColumnBusinessMetadataDto updateColumnBusinessMetadataDto) { try { // 現在、MaxCompute、DLF、HMS (EMR クラスター) タイプをサポート Client client = dataWorksOpenApiClient.createClient(); UpdateColumnBusinessMetadataRequest updateColumnBusinessMetadataRequest = new UpdateColumnBusinessMetadataRequest(); // フィールド ID updateColumnBusinessMetadataRequest.setId(updateColumnBusinessMetadataDto.getId()); // フィールドのビジネス記述 updateColumnBusinessMetadataRequest.setDescription(updateColumnBusinessMetadataDto.getDescription()); UpdateColumnBusinessMetadataResponse response = client.updateColumnBusinessMetadata(updateColumnBusinessMetadataRequest); System.out.println(response.getBody().getRequestId()); // 更新が成功したかどうか return response.getBody().getSuccess(); } catch (TeaException error) { // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return false; } /** * DataWorks OpenAPI : ListPartitions * * @param listPartitionsDto */ public ListPartitionsResponseBodyPagingInfo listPartitions(ListPartitionsDto listPartitionsDto) { try { // 現在、MaxCompute タイプと HMS (EMR クラスター) タイプをサポート Client client = dataWorksOpenApiClient.createClient(); ListPartitionsRequest listPartitionsRequest = new ListPartitionsRequest(); // テーブル ID (ListTables API から) listPartitionsRequest.setTableId(listPartitionsDto.getTableId()); // パーティション名、あいまい一致をサポート listPartitionsRequest.setName(listPartitionsDto.getName()); // ソートメソッド、Name (HMS メソッドのデフォルト、MaxCompute タイプでサポート) / CreateTime (MaxCompute タイプのデフォルト) / ModifyTime (MaxCompute タイプでサポート) / RecordCount (MaxCompute タイプでサポート) / DataSize (MaxCompute タイプでサポート) をサポート listPartitionsRequest.setSortBy(listPartitionsDto.getSortBy()); // ソート方向、Asc (デフォルト) / Desc をサポート listPartitionsRequest.setOrder(listPartitionsDto.getOrder()); // ページ番号、デフォルトは 1 listPartitionsRequest.setPageNumber(listPartitionsDto.getPageNumber()); // ページサイズ、デフォルトは 10、最大 100 listPartitionsRequest.setPageSize(listPartitionsDto.getPageSize()); ListPartitionsResponse response = client.listPartitions(listPartitionsRequest); // 要件を満たすパーティションの総数を取得 System.out.println(response.getBody().getPagingInfo().getTotalCount()); for (Partition partition : response.getBody().getPagingInfo().getPartitionList()) { // テーブル ID System.out.println(partition.getTableId()); // パーティション名 System.out.println(partition.getName()); // 作成時間 (ミリ秒レベルのタイムスタンプ) System.out.println(partition.getCreateTime()); // 変更時間 (ミリ秒レベルのタイムスタンプ) System.out.println(partition.getModifyTime()); // パーティションレコード数、MaxCompute タイプのみサポート System.out.println(partition.getRecordCount()); // パーティションストレージサイズ (単位: バイト)、MaxCompute タイプのみサポート System.out.println(partition.getDataSize()); } return response.getBody().getPagingInfo(); } catch (TeaException error) { // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } }MetaRestController に、フロントエンド呼び出し用に
getTable、listColumns、listPartitions、およびupdateColumnBusinessMetadataの 4 つのメソッドを提供します。/** * DataWorks API を使用したカスタムメタデータプラットフォーム * * @author dataworks デモ */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * テーブル詳細の取得 * * @param getTableDto * @return {@link Table} */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/getTable") public Table getTable(GetTableDto getTableDto) { System.out.println("getTableDto = " + getTableDto); return metaService.getTable(getTableDto); } /** * ページネーション付きで列をリスト * * @param listColumnsDto * @return {@link ListColumnsResponseBodyPagingInfo} */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listColumns") public ListColumnsResponseBodyPagingInfo listColumns(ListColumnsDto listColumnsDto) { System.out.println("listColumnsDto = " + listColumnsDto); return metaService.listColumns(listColumnsDto); } /** * 列のビジネスメタデータを更新 * * @param updateColumnBusinessMetadataDto * @return {@link Boolean} */ @CrossOrigin(origins = "http://localhost:8080") @PostMapping("/updateColumnBusinessMetadata") public Boolean updateColumnBusinessMetadata(@RequestBody UpdateColumnBusinessMetadataDto updateColumnBusinessMetadataDto) { System.out.println("updateColumnBusinessMetadataDto = " + updateColumnBusinessMetadataDto); return metaService.updateColumnBusinessMetadata(updateColumnBusinessMetadataDto); } /** * ページごとにパーティションデータをクエリ * * @param listPartitionsDto * @return {@link ListPartitionsResponseBodyPagingInfo} */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listPartitions") public ListPartitionsResponseBodyPagingInfo listPartitions(ListPartitionsDto listPartitionsDto) { System.out.println("listPartitionsDto = " + listPartitionsDto); return metaService.listPartitions(listPartitionsDto); } }
フロントエンド: テーブルの基本情報、フィールド情報、およびパーティション情報の表示
import React from "react";
import moment from "moment";
import {
Form,
Grid,
Table,
Pagination,
Tag,
Field,
Input,
Button,
Select,
Dialog,
} from "@alifd/next";
import cn from "classnames";
import * as services from "../services";
import {
type ListColumnsInput,
type TableColumn,
type TableEntity,
type TablePartition,
} from "../services/meta";
import classes from "../styles/detailContent.module.css";
export interface Props {
item: TableEntity;
}
const formItemLayout = {
labelCol: {
fixedSpan: 6,
},
wrapperCol: {
span: 18,
},
labelTextAlign: "left" as const,
colon: true,
className: cn(classes.formItemWrapper),
};
const { Row, Col } = Grid;
const { Item } = Form;
const { Column } = Table;
const { Option } = Select;
const DetailContent: React.FunctionComponent<Props> = (props) => {
let columnsPageSize = 10;
let partitionsPageSize = 5;
const listColumnsField = Field.useField();
const listPartitionsField = Field.useField();
const labelAlign = "top";
const [detail, setDetail] = React.useState<Partial<TableEntity>>({});
const [columns, setColumns] = React.useState<Partial<TableColumn[]>>([]);
const [partitions, setPartitions] = React.useState<Partial<TablePartition[]>>(
[]
);
const [columnsTotal, setColumnsTotal] = React.useState<number>(0);
const [partitionTotal, setPartitionTotal] = React.useState<number>(0);
const [editingKey, setEditingKey] = React.useState<string>("");
const [editingDescription, setEditingDescription] =
React.useState<string>("");
const [updateColumnDialogVisible, setUpdateColumnDialogVisible] =
React.useState<boolean>(false);
const onUpdateColumnDialogOpen = () => {
setUpdateColumnDialogVisible(true);
};
const onUpdateColumnDialogOk = () => {
handleColumnDescriptionSave();
onUpdateColumnDialogClose();
};
const onUpdateColumnDialogClose = () => {
setUpdateColumnDialogVisible(false);
};
const handleEditColumnDescription = async (record: TableColumn) => {
setEditingKey(record.id);
setEditingDescription(record.businessMetadata?.description);
onUpdateColumnDialogOpen();
};
const handleColumnDesrciptionChange = async (e: string) => {
setEditingDescription(e);
};
const handleColumnDescriptionSave = async () => {
try {
const res = await services.meta.updateColumnDescription({
id: editingKey,
description: editingDescription,
});
if (!res) {
console.log("Request update failed");
}
} catch (e) {
console.error("Update failed", e);
} finally {
listColumns();
setEditingKey("");
}
};
const getTableDetail = React.useCallback(async () => {
const response = await services.meta.getTableDetail({
id: props.item.id,
includeBusinessMetadata: true,
});
setDetail(response);
}, [props.item.id]);
const listColumns = React.useCallback(
async (pageNumber: number = 1) => {
listColumnsField.setValue("tableId", props.item.id);
listColumnsField.setValue("pageSize", columnsPageSize);
listColumnsField.validate(async (errors, values) => {
if (errors) {
return;
}
try {
const response = await services.meta.getMetaTableColumns({
pageNumber,
...values,
} as ListColumnsInput);
setColumns(response.columns);
setColumnsTotal(response.totalCount);
} catch (e) {
throw e;
} finally {
}
});
},
[listColumnsField]
);
const listPartitions = React.useCallback(
async (pageNumber: number = 1) => {
listPartitionsField.setValue("tableId", props.item.id);
listPartitionsField.setValue("pageSize", partitionsPageSize);
listPartitionsField.validate(async (errors, values) => {
if (errors) {
return;
}
try {
const response = await services.meta.getTablePartition({
pageNumber,
...values,
} as ListColumnsInput);
setPartitions(response.partitionList);
setPartitionTotal(response.totalCount);
} catch (e) {
throw e;
} finally {
}
});
},
[listPartitionsField]
);
React.useEffect(() => {
if (props.item.id) {
getTableDetail();
listColumns();
listPartitions();
}
}, [props.item.id]);
return (
<div className={cn(classes.detailContentWrapper)}>
<Dialog
v2
title="フィールドビジネスメタデータの更新"
visible={updateColumnDialogVisible}
onOk={onUpdateColumnDialogOk}
onClose={onUpdateColumnDialogClose}
>
<Row>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="フィールドビジネス記述"
>
<Input
value={editingDescription}
onChange={(e) => handleColumnDesrciptionChange(e.toString())}
></Input>
</Item>
</Col>
</Row>
</Dialog>
<Form labelTextAlign="left">
<Row>
<Col>
<Item {...formItemLayout} label="テーブル ID">
{detail.id}
{detail?.id?.split(":").slice(-3, -2)[0]}
{detail?.id?.split(":").slice(-2, -1)[0]}
{detail.name}
{detail.comment}
{detail.tableType}
{detail.technicalMetadata?.compressed ? "はい" : "いいえ"}
{detail.businessMetadata?.extension?.projectId}
{detail.businessMetadata?.extension?.envType === "Dev"
? "開発"
: "本番"}
{detail.technicalMetadata?.parameters["lifecycle"]
? `${detail.technicalMetadata?.parameters["lifecycle"]} 日`
: "永続"}
{`${detail.technicalMetadata?.parameters["dataSize"]} バイト`}
{moment(
Number(detail.technicalMetadata?.parameters["lastDDLTime"])
).format("YYYY-MM-DD HH:mm:ss")}
{detail.partitionKeys != null && detail.partitionKeys.length > 0
? "はい"
: "いいえ"}
<Tag type="primary" size="small">
{tag.key + (tag.value ? ":" + tag.value : "")}
</Tag>
{detail.businessMetadata?.categories?.map((category, index) => (
<span key={index} className={cn(classes.tag)}>
<Tag type="normal" color="gray" size="small">
{category.map((obj) => obj.name).join("->")}
</Tag>
{detail.businessMetadata?.upstreamTasks?.map((task, index) => (
<span key={index} className={cn(classes.tag)}>
<Tag type="primary" size="small">
{task.name + " (" + task.id + ")"}
</Tag>
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.viewCount}
</Tag>
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.readCount}
</Tag>
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.favorCount}
</Tag>
{moment(detail.createTime).format("YYYY-MM-DD HH:mm:ss")}
{moment(detail.modifyTime).format("YYYY-MM-DD HH:mm:ss")}
{value?.description}非パーティションテーブル上記のコード開発が完了したら、プロジェクトコードをローカルでデプロイして実行できます。デプロイと実行の操作については、「共通操作: ローカルでのデプロイと実行」をご参照ください。
3. テーブルリネージのクエリ
テーブル定義が変更された場合、DataWorks API を使用して上流および下流タスクのリネージ分析を実行し、テーブルに対応するノードを見つけることができます。
ListLineages を使用して、データテーブルの上流および下流のリネージをクエリします。
バックエンド: MetaServiceProxy を使用してテーブルリネージをクエリする
MetaServiceProxy に listTableLineages メソッドを作成し、API を呼び出してテーブルの上流および下流のリネージエンティティと関係情報を取得します。
/** * @author dataworks デモ */ @Service public class MetaServiceProxy { @Autowired private DataWorksOpenApiClient dataWorksOpenApiClient; /** * DataWorks OpenAPI : ListLineages * * @param listTableLineagesDto */ public ListLineagesResponseBodyPagingInfo listTableLineages(ListTableLineagesDto listTableLineagesDto) { try { Client client = dataWorksOpenApiClient.createClient(); ListLineagesRequest listLineagesRequest = new ListLineagesRequest(); // リネージクエリの方向、上流かどうか boolean needUpstream = "up".equalsIgnoreCase(listTableLineagesDto.getDirection()); // テーブル ID (ListTables API から) // パーティション名、あいまい一致をサポート if (needUpstream) { listLineagesRequest.setDstEntityId(listTableLineagesDto.getTableId()); listLineagesRequest.setSrcEntityName(listTableLineagesDto.getName()); } else { listLineagesRequest.setSrcEntityId(listTableLineagesDto.getTableId()); listLineagesRequest.setDstEntityName(listTableLineagesDto.getName()); } // ソートメソッド、Name をサポート listLineagesRequest.setSortBy(listTableLineagesDto.getSortBy()); // ソート方向、Asc (デフォルト) / Desc をサポート listLineagesRequest.setOrder(listTableLineagesDto.getOrder()); // ページ番号、デフォルトは 1 listLineagesRequest.setPageNumber(listTableLineagesDto.getPageNumber()); // ページサイズ、デフォルトは 10、最大 100 listLineagesRequest.setPageSize(listTableLineagesDto.getPageSize()); // 特定のリネージ関係情報を返すかどうか listLineagesRequest.setNeedAttachRelationship(true); ListLineagesResponse response = client.listLineages(listLineagesRequest); // 要件を満たすリネージエンティティの総数を取得 System.out.println(response.getBody().getPagingInfo().getTotalCount()); if (response.getBody().getPagingInfo().getLineages() == null) { response.getBody().getPagingInfo().setLineages(Collections.emptyList()); } if (response.getBody().getPagingInfo().getLineages() != null) { for (ListLineagesResponseBodyPagingInfoLineages lineage : response.getBody().getPagingInfo().getLineages()) { LineageEntity entity; // 対応するリネージエンティティを取得 if (needUpstream) { entity = lineage.getSrcEntity(); } else { entity = lineage.getDstEntity(); } // エンティティ ID System.out.println(entity.getId()); // エンティティ名 System.out.println(entity.getName()); // エンティティ属性情報 System.out.println(entity.getAttributes()); // エンティティ間のリネージ関係リスト List<LineageRelationship> relationships = lineage.getRelationships(); if (relationships != null) { relationships.forEach(relationship -> { // リネージ関係 ID System.out.println(relationship.getId()); // 作成時間 System.out.println(relationship.getCreateTime()); // リネージ関係に対応するタスク LineageTask task = relationship.getTask(); if (task != null) { // タスク ID、DataWorks タスクの場合、タスク詳細は GetTask を通じて取得可能 System.out.println(task.getId()); // タスクタイプ System.out.println(task.getType()); // タスク属性情報 Map<String, String> attributes = task.getAttributes(); if (attributes != null) { // DataWorks タスクの場合、タスクとインスタンスの属性情報をここから取得可能 // プロジェクト ID System.out.println(attributes.get("projectId")); // タスク ID String taskId = attributes.containsKey("taskId") ? attributes.get("taskId") : attributes.get("nodeId"); System.out.println(taskId); // インスタンス ID String taskInstanceId = attributes.containsKey("taskInstanceId") ? attributes.get("taskInstanceId") : attributes.get("jobId"); System.out.println(taskInstanceId); } } }); } } return response.getBody().getPagingInfo(); } } catch (TeaException error) { // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // デモ用です。本番コードでは例外を適切に処理してください。 // エラーメッセージ System.out.println(error.getMessage()); // 診断アドレス System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } return null; } }フロントエンド呼び出し用に MetaRestController に listTableLineages メソッドを提供します。
/** * DataWorks API を使用したカスタムメタデータプラットフォーム * * @author dataworks デモ */ @RestController @RequestMapping("/meta") public class MetaRestController { @Autowired private MetaServiceProxy metaService; /** * テーブルのリネージ関係をクエリ * * @param listTableLineagesDto * @return */ @CrossOrigin(origins = "http://localhost:8080") @GetMapping("/listTableLineages") public ListLineagesResponseBodyPagingInfo listTableLineages(ListTableLineagesDto listTableLineagesDto) { System.out.println("listTableLineagesDto = " + listTableLineagesDto); return metaService.listTableLineages(listTableLineagesDto); } }
フロントエンド: テーブルリネージの表示
import React from 'react';
import Graphin, { Behaviors } from '@antv/graphin';
import type { IUserNode, IUserEdge, GraphinData, GraphEvent } from '@antv/graphin';
import cn from 'classnames';
import * as services from '../services';
import type { TableEntity, ListTableLineageOutput, LineageEntity } from '../services/meta';
import classes from '../styles/lineageContent.module.css';
import '@antv/graphin/dist/index.css';
export interface Props {
item: TableEntity;
}
function transNode(entity: LineageEntity | TableEntity, direction?: string): IUserNode {
return {
id: entity.id,
label: entity.name,
data: {
...entity,
direction,
},
style: {
label: {
value: entity.name,
}
},
}
}
function transEdge(source: LineageEntity | TableEntity, target: LineageEntity | TableEntity): IUserEdge {
return {
source: source.id,
target: target.id,
style: {
keyshape: {
lineDash: [8, 4],
lineWidth: 2,
},
animate: {
type: 'line-dash',
repeat: true,
},
}
};
}
function parse(
source: LineageEntity | TableEntity,
data: ListTableLineageOutput,
direction: string,
): [IUserNode[], IUserEdge[]] {
const nodes: IUserNode[] = [];
const edges: IUserEdge[] = [];
data.lineages.forEach((lineageRelationship) => {
if (direction === 'down') {
nodes.push(transNode(lineageRelationship.dstEntity, direction));
edges.push(transEdge(source, lineageRelationship.dstEntity));
} else {
nodes.push(transNode(lineageRelationship.srcEntity, direction));
edges.push(transEdge(lineageRelationship.srcEntity, source));
}
});
return [nodes, edges];
}
function mergeNodes(prev: IUserNode[], next: IUserNode[]) {
const result: IUserNode[] = prev.slice();
next.forEach((item) => {
const hasValue = prev.findIndex(i => i.id === item.id) >= 0;
!hasValue && result.push(item);
});
return result;
}
function mergeEdges(source: IUserEdge[], target: IUserEdge[]) {
const result: IUserEdge[] = source.slice();
target.forEach((item) => {
const hasValue = source.findIndex(i => i.target === item.target && i.source === item.source) >= 0;
!hasValue && result.push(item);
});
return result;
}
const { ActivateRelations, DragNode, ZoomCanvas } = Behaviors;
const LineageContent: React.FunctionComponent = (props) => {
const ref = React.useRef();
const [data, setData] = React.useState({ nodes: [], edges: [] });
const getTableLineage = async (
collection: GraphinData,
target: TableEntity | LineageEntity,
direction: string,
) => {
if (!direction) {
return collection;
}
const response = await services.meta.getTableLineage({
direction,
tableId: target.id,
});
const [nodes, edges] = parse(target, response, direction);
collection.nodes = mergeNodes(collection.nodes!, nodes);
collection.edges = mergeEdges(collection.edges!, edges);
return collection;
};
const init = async () => {
let nextData = Object.assign({}, data, { nodes: [transNode(props.item)] });
nextData = await getTableLineage(nextData, props.item, 'up');
nextData = await getTableLineage(nextData, props.item, 'down');
setData(nextData);
ref.current!.graph.fitCenter();
};
React.useEffect(() => {
ref.current?.graph && init();
}, [
ref.current?.graph,
]);
React.useEffect(() => {
const graph = ref.current?.graph;
const event = async (event: GraphEvent) => {
const source = event.item?.get('model').data;
let nextData = Object.assign({}, data);
nextData = await getTableLineage(nextData, source, source.direction);
setData(nextData);
};
graph?.on('node:click', event);
return () => {
graph?.off('node:click', event);
};
}, [
data,
ref.current?.graph,
]);
return (
}
layout={{
type: 'dagre',
rankdir: 'LR',
align: 'DL',
nodesep: 10,
ranksep: 40,
}}
>
);
};
export default LineageContent;
上記のコード開発が完了したら、プロジェクトコードをローカルでデプロイして実行できます。デプロイと実行の操作については、「共通操作: ローカルでのデプロイと実行」をご参照ください。
4. テーブルに対応するタスクの検索
テーブル定義が変更された場合、DataWorks API、OpenData、およびメッセージサブスクリプションを通じて下流タスクのリネージ分析を実行し、テーブルに対応するノードを見つけることができます。具体的な操作は次のとおりです。
メッセージは現在、テーブルの変更、タスクの変更などをサポートしています。Enterprise Edition ユーザーはテーブル変更メッセージに接続できます。テーブル変更メッセージを受信すると、テーブルのリネージ関係を表示できます。
リネージタスク
ListLineages を使用してテーブルのリネージを表示し、リネージ関係のタスクから DataWorks タスク ID (およびタスクインスタンス ID) を取得します。
取得したタスク ID に基づいて、GetTask を使用してタスク詳細を取得します。
生産タスク
共通操作: ローカルでのデプロイと実行
依存関係を準備します。
次の依存環境を準備する必要があります: Java 8 以上、Maven ビルドツール、Node 環境、pnpm ツール。次のコマンドを実行して、上記の環境があるかどうかを判断できます:
npm -v // インストールが成功した場合、コマンド出力にバージョンが表示されます。インストールが失敗した場合、コマンドが利用できないことを示すエラーが報告されます。 java -version // インストールが成功した場合、コマンド出力にバージョンが表示されます。インストールが失敗した場合、コマンドが利用できないことを示すエラーが報告されます。 pnpm -v // インストールが成功した場合、コマンド出力にバージョンが表示されます。インストールが失敗した場合、コマンドが利用できないことを示すエラーが報告されます。プロジェクトコードをダウンロードし、次のコマンドを実行します。
プロジェクトコードのダウンロードリンク: meta-api-demo.zip。
pnpm iサンプルプロジェクトの backend/src/main/resources パスにある application.properties ファイルを見つけ、ファイル内のコアパラメーターを変更します。
api.access-key-id と api.access-key-secret は、ご使用の Alibaba Cloud アカウントの AccessKey ID と AccessKey Secret に変更する必要があります。
説明ご使用の Alibaba Cloud アカウントの AccessKey およびその他の関連情報を取得するには、「AccessKey ペアの管理」をご参照ください。
api.region-id と api.endpoint は、呼び出す API のリージョン情報に変更する必要があります。
その他のパラメーターは、実際の状況に応じて変更できます。変更後の入力例は次のとおりです。

プロジェクトのルートディレクトリで次のコマンドを実行して、サンプルコードを実行します。
npm run dev