DataWorks は、メタデータ管理のための包括的な API オペレーションセットを提供します。このガイドでは、DataWorks API を使用してテーブルのクエリ、テーブル詳細の取得、メタデータの管理を行う方法を説明します。
前提条件
開始する前に、次のリソースをご確認ください:
1. テーブルのリスト
このプラクティスでは、API を使用して MaxCompute プロジェクト/スキーマ配下のリストをクエリする方法について説明します。ページ分割クエリもサポートされています。主な手順は次のとおりです:
バックエンド:MetaServiceProxy を使用したテーブル詳細のクエリ
-
MetaServiceProxy に ListTables メソッドを記述し、フロントエンドのパラメーターを処理して ListTables API にリクエストを送信し、メタデータテーブルのリスト情報を取得します。
-
サポートされるクエリパラメーター:親エンティティ ID、名前 (あいまい一致)、コメント (あいまい一致)、タイプ、ソートパラメーター、およびページネーションパラメーター。
説明メタデータエンティティ関連の概念説明をご参照ください。親エンティティ ID は 2 つのフォーマットで存在する可能性があります。
-
親エンティティ ID が MaxCompute プロジェクト ID の場合、フォーマットは
maxcompute-project:${Alibaba Cloud account ID}::{projectName}です。 -
親エンティティ ID が MaxCompute スキーマ ID の場合、フォーマットは
maxcompute-schema:${Alibaba Cloud account 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 demo */ @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 demo */ @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 を組み合わせてテーブル詳細をクエリし、データマップテーブルの列のビジネスメタデータを更新 API を使用してフィールドのビジネス記述を更新します。プラクティスのワークフローは次のとおりです。
バックエンド:MetaServiceProxy を使用したテーブル詳細のクエリ
-
MetaServiceProxy に
getTable、listColumns、listPartitionsメソッドを作成し、オペレーションを呼び出してテーブルの基本情報、テーブルのフィールド情報、テーブルのパーティション情報を取得します。また、updateColumnBusinessMetadataメソッドを作成してフィールドのビジネス記述を更新します。/** * @author dataworks demo */ @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 に、フロントエンドから呼び出すための4つのメソッド:
getTable、listColumns、listPartitions、updateColumnBusinessMetadataを提供します。/** * DataWorks API を使用したカスタムメタデータプラットフォーム * * @author dataworks demo */ @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}
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="MaxCompute プロジェクト">
{detail?.id?.split(":").slice(-3, -2)[0]}
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="スキーマ名">
{detail?.id?.split(":").slice(-2, -1)[0]}
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="テーブル名">
{detail.name}
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="コメント">
{detail.comment}
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="テーブルタイプ">
{detail.tableType}
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="圧縮テーブルか">
{detail.technicalMetadata?.compressed ? "はい" : "いいえ"}
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="ワークスペース ID">
{detail.businessMetadata?.extension?.projectId}
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="環境">
{detail.businessMetadata?.extension?.envType === "Dev"
? "開発"
: "本番"}
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="テーブルのライフサイクル">
{detail.technicalMetadata?.parameters["lifecycle"]
? `${detail.technicalMetadata?.parameters["lifecycle"]} 日`
: "永続"}
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="ストレージ容量">
{`${detail.technicalMetadata?.parameters["dataSize"]} バイト`}
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="DDL 最終変更時刻">
{moment(
Number(detail.technicalMetadata?.parameters["lastDDLTime"])
).format("YYYY-MM-DD HH:mm:ss")}
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="パーティションステータス">
{detail.partitionKeys != null && detail.partitionKeys.length > 0
? "はい"
: "いいえ"}
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="タグ">
<div className={cn(classes.formContentWrapper)}>
{detail.businessMetadata?.tags?.map((tag, index) => (
<Tag type="primary" size="small">
{tag.key + (tag.value ? ":" + tag.value : "")}
</Tag>
))}
</div>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="カテゴリ">
{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>
))}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="上流の生産タスク">
{detail.businessMetadata?.upstreamTasks?.map((task, index) => (
<span key={index} className={cn(classes.tag)}>
<Tag type="primary" size="small">
{task.name + " (" + task.id + ")"}
</Tag>
))}
</span>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="表示数">
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.viewCount}
</Tag>
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="読み取り数">
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.readCount}
</Tag>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="お気に入り数">
<Tag type="primary" size="small">
{detail.businessMetadata?.extension?.favorCount}
</Tag>
</Item>
</Col>
</Row>
<Row>
<Col>
<Item {...formItemLayout} label="作成時刻">
{moment(detail.createTime).format("YYYY-MM-DD HH:mm:ss")}
</Item>
</Col>
<Col>
<Item {...formItemLayout} label="更新時刻">
{moment(detail.modifyTime).format("YYYY-MM-DD HH:mm:ss")}
</Item>
</Col>
</Row>
<br />
<Item label="フィールド詳細" colon>
<Form
field={listColumnsField}
colon
fullWidth
style={{ marginTop: 10, marginBottom: 10 }}
>
<Row gutter="4">
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="名前"
name="name"
>
<Input placeholder="フィールド名を入力、あいまい一致" />
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="コメント"
name="comment"
>
<Input placeholder="フィールドコメントを入力、あいまい一致" />
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="ソート方法"
name="sortBy"
>
<Select defaultValue="Position" style={{ width: "100%" }}>
<Option value="Position">位置</Option>
<Option value="Name">名前</Option>
</Select>
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="ソート順"
name="order"
>
<Select defaultValue="Asc" style={{ width: "100%" }}>
<Option value="Asc">昇順</Option>
<Option value="Desc">降順</Option>
</Select>
</Item>
</Col>
<Col>
<div
className={cn(
classes.searchPanelButtonWrapper,
classes.buttonGroup
)}
style={{ marginTop: "20px", textAlign: "right" }}
>
<Button type="primary" onClick={() => listColumns()}>
クエリ
</Button>
</div>
</Col>
</Row>
</Form>
<Table dataSource={columns}>
<Column title="名前" dataIndex="name" />
<Column title="タイプ" dataIndex="type" />
<Column title="コメント" dataIndex="comment" />
<Column
title="ビジネス記述"
dataIndex="businessMetadata"
cell={(value, index, record) => (
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
{value?.description}
<Button
onClick={() => handleEditColumnDescription(record)}
size="small"
type="primary"
text
style={{ marginLeft: 8 }}
>
編集
</Button>
</div>
)}
/>
<Column
title="プライマリキーか"
dataIndex="primaryKey"
cell={(value) => (value ? "はい" : "")}
/>
<Column
title="外部キーか"
dataIndex="foreignKey"
cell={(value) => (value ? "はい" : "")}
/>
<Column
title="パーティションフィールドか"
dataIndex="partitionKey"
cell={(value) => (value ? "はい" : "")}
/>
<Column title="位置" dataIndex="position" />
</Table>
<Pagination
total={columnsTotal}
pageSize={columnsPageSize}
onChange={listColumns}
showJump={false}
className={cn(classes.tablePaginationWrapper)}
/>
</Item>
<br />
<Item label="パーティション詳細" style={{ marginBottom: 32 }} colon>
<Form
field={listPartitionsField}
colon
fullWidth
style={{ marginTop: 10, marginBottom: 10 }}
>
<Row gutter="4">
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="名前"
name="name"
>
<Input placeholder="パーティション名を入力 (部分一致可)" />
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="ソート方法"
name="sortBy"
>
<Select defaultValue="CreateTime" style={{ width: "100%" }}>
<Option value="CreateTime">作成時刻</Option>
<Option value="ModifyTime">更新時刻</Option>
<Option value="Name">パーティション名</Option>
<Option value="RecordCount">パーティションレコード数</Option>
<Option value="DataSize">パーティションサイズ</Option>
</Select>
</Item>
</Col>
<Col>
<Item
{...formItemLayout}
labelAlign={labelAlign}
label="ソート順"
name="order"
>
<Select defaultValue="Asc" style={{ width: "100%" }}>
<Option value="Asc">昇順</Option>
<Option value="Desc">降順</Option>
</Select>
</Item>
</Col>
<Col>
<div
className={cn(
classes.searchPanelButtonWrapper,
classes.buttonGroup
)}
style={{ marginTop: "20px", textAlign: "right" }}
>
<Button type="primary" onClick={() => listPartitions()}>
クエリ
</Button>
</div>
</Col>
</Row>
</Form>
<Table dataSource={partitions} emptyContent={非パーティションテーブル}>
<Column title="名前" dataIndex="name" />
<Column
title="作成時刻"
dataIndex="createTime"
cell={(value) => moment(value).format("YYYY-MM-DD HH:mm:ss")}
/>
<Column
title="更新時刻"
dataIndex="modifyTime"
cell={(value) => moment(value).format("YYYY-MM-DD HH:mm:ss")}
/>
<Column
title="パーティションサイズ"
dataIndex="dataSize"
cell={(value) => `${value} バイト`}
/>
<Column title="パーティションレコード数" dataIndex="recordCount" />
</Table>
<Pagination
total={partitionTotal}
pageSize={partitionsPageSize}
onChange={listPartitions}
showJump={false}
className={cn(classes.tablePaginationWrapper)}
/>
</Item>
</Form>
</div>
);
};
export default DetailContent;
上記のコード開発が完了したら、プロジェクトコードをローカルでデプロイして実行できます。デプロイと実行の操作については、「共通操作:ローカルでのデプロイと実行」をご参照ください。
3. テーブルリネージのクエリ
テーブル定義が変更された場合、DataWorks API を使用して上流および下流タスクのリネージ分析を実行し、テーブルに対応するノードを見つけることができます。
ListLineages を使用して、データテーブルの上流および下流のリネージをクエリします。
バックエンド:MetaServiceProxy を使用したテーブルリネージのクエリ
-
MetaServiceProxy に listTableLineages メソッドを作成し、API を呼び出してテーブルの上流および下流のリネージエンティティと関係情報を取得します。
/** * @author dataworks demo */ @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 demo */ @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 のリージョン情報に変更する必要があります。
その他のパラメーターは、実際の状況に応じて変更できます。変更後の入力例は次のとおりです。
## RAM ユーザーの AccessKey ID api.access-key-id=xxx ## アカウントの AccessKey Secret api.access-key-secret=xxx ## DataWorks サービスの RegionId:cn-shanghai/cn-hangzhou api.region-id=cn-hangzhou ## DataWorks OpenAPI のプロダクト、デフォルトは dataworks-public api.product=dataworks-public ## DataWorks OpenAPI のエンドポイント ## パブリックアクセス:dataworks.${regionId}.aliyuncs.com ## VPC アクセス:dataworks-vpc.${regionId}.aliyuncs.com api.endpoint=dataworks.cn-hangzhou.aliyuncs.com ## ネットワーク環境:パブリックアクセスは false、VPC は true api.vpc-env=false -
-
プロジェクトのルートディレクトリで次のコマンドを実行し、サンプルコードを実行します。
npm run dev