すべてのプロダクト
Search
ドキュメントセンター

:ベストプラクティス:API を使用したテーブル管理

最終更新日:Jul 25, 2026

DataWorks は、メタデータ管理のための包括的な API オペレーションセットを提供します。このガイドでは、DataWorks API を使用してテーブルのクエリ、テーブル詳細の取得、メタデータの管理を行う方法を説明します。

前提条件

開始する前に、次のリソースをご確認ください:

1. テーブルのリスト

このプラクティスでは、API を使用して MaxCompute プロジェクト/スキーマ配下のリストをクエリする方法について説明します。ページ分割クエリもサポートされています。主な手順は次のとおりです:

バックエンド:MetaServiceProxy を使用したテーブル詳細のクエリ

  1. 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 を確認できます。

      1. Alibaba Cloud アカウントまたは RAM ユーザーを使用して DataWorks コンソールにログインします。

      2. 右上のプロフィール画像にマウスカーソルを合わせると、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;
           }
      }
  2. 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 を使用したテーブル詳細のクエリ

  1. 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;
           }
       }
  2. 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 を使用したテーブルリネージのクエリ

  1. 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;
           }
       }
  2. 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 のユーザーは、テーブル変更メッセージに接続できます。テーブル変更メッセージを受信すると、テーブルのリネージ関係を表示できます。

リネージタスク

  1. ListLineages を使用してテーブルのリネージを表示し、リネージ関係のタスクから DataWorks タスク ID (およびタスクインスタンス ID) を取得します。

  2. 取得したタスク ID に基づいて、GetTask を使用してタスク詳細を取得します。

生産タスク

  1. エンティティ ID に基づいて、GetTable API を使用してテーブルのビジネスメタデータを取得します。これには、上流の生産タスクに含まれる DataWorks タスク ID が含まれます。

  2. 指定されたタスク ID に基づいて、GetTask を使用してタスク詳細を取得します。

共通操作:ローカルでのデプロイと実行

  1. 依存関係の準備

    次の依存関係環境を準備する必要があります:Java 8 以上、Maven ビルドツール、Node 環境、pnpm ツール。次のコマンドを実行して、上記の環境があるかどうかを判断できます:

    npm -v // インストールが成功した場合、コマンド出力にバージョンが表示されます。インストールが失敗した場合、コマンドがないことを示すエラーが報告されます。
    java -version // インストールが成功した場合、コマンド出力にバージョンが表示されます。インストールが失敗した場合、コマンドがないことを示すエラーが報告されます。
    pnpm -v // インストールが成功した場合、コマンド出力にバージョンが表示されます。インストールが失敗した場合、コマンドがないことを示すエラーが報告されます。
  2. プロジェクトコードをダウンロードし、次のコマンドを実行します。

    プロジェクトコードのダウンロードリンク:meta-api-demo.zip。

    pnpm i
  3. サンプルプロジェクトの 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
  4. プロジェクトのルートディレクトリで次のコマンドを実行し、サンプルコードを実行します。

    npm run dev