Batch register custom entities and lineage relationships via OpenAPI

Updated at:

Use DataWorks OpenAPI to batch register metadata objects and lineage relationships from your in-house systems into DataWorks Data Map for unified search, display, lineage analysis, and impact analysis.

Overview

In enterprise data governance, many critical data objects are not produced entirely inside DataWorks. Examples include:

  • BI reports, dashboards, metrics, and data products.

  • Internal service APIs, data service endpoints, and application system objects.

  • Third-party databases, external tables, and external columns.

  • Tasks, workflows, models, or subject-domain objects from in-house platforms.

Without these objects in Data Map, asset discovery, lineage analysis, incident troubleshooting, and change-impact assessment are limited to the tables and columns that DataWorks has collected. The custom entity and lineage registration OpenAPIs let you:

  • Register external business objects in Data Map as metadata entities that are searchable, displayable, and governable.

  • Establish lineage relationships between DataWorks-collected tables, columns, or datasets and your external objects.

  • Register lineage relationships between tables, between columns, between a table and a column, or between a dataset and a table.

  • Attach business attributes such as business domain, source system, owner, and data tier to custom entities, tables, and columns.

  • Consolidate the asset relationships of your external systems into Data Map to support data lineage tracing, impact analysis, and unified asset inventory.

For batch ingestion, treat DataWorks Data Map as the unified consumption entry point for your enterprise metadata and lineage. A typical pipeline is as follows:

  • Enterprise data sources: business systems, third-party databases, BI reports, API services, and so on.

  • Batch ingestion job: extracts entity lists, lineage relationship lists, and attribute definition lists from the source systems as OpenAPI input.

  • DataWorks OpenAPI: calls operations such as CreateMetaEntityDef, CreateCustomAttribute, BatchCreateMetaEntities, and CreateLineageRelationship to write entity definitions, entity objects, custom attributes, and lineage relationships.

  • DataWorks Data Map: displays both collected objects and custom objects in a unified view, with search, detail, lineage graph, and impact-analysis capabilities.

  • Downstream consumption: calls query operations such as ListLineages to support data lineage tracing, impact analysis, broken-link detection, and lineage health checks in operations workflows.

The entity list describes which objects must be onboarded, the lineage relationship list describes how objects flow between each other, and the attribute definition list describes which business fields must be displayed, filterable, and governable in a unified way.

Choose the right object type

Before onboarding, first decide whether each object has already been collected by DataWorks, and then decide whether you need to register it as an entity.

Typical onboarding scenarios

Scenario

Example

Recommended approach

Onboard business objects to Data Map

Reports, APIs, metrics, data products

Register as custom entity objects

Onboard external tables to Data Map

Third-party databases, external tables, external columns

Register as extended table entities

Attach business information to entities

Business domain, owner, source system, security level

Create custom attributes and write them to the entities

Register external lineage

Table to report, table to API, table to external table

Call CreateLineageRelationship

Register lineage between collected entities

Table-to-table, column-to-column, table-to-column, dataset-to-table

Use the DataWorks-collected entity IDs to register lineage

Note

CreateLineageRelationship supports registering lineage between any DataWorks-collected entities. Both endpoints of a lineage edge do not have to be custom objects.

Non-onboarded custom objects (custom-{Type}:{Identifier}) are kept mainly for backward compatibility with legacy usage. For new onboarding scenarios, register objects as DataWorks custom entity objects first, and then create the lineage relationships.

Choose a registration approach based on whether the object is already collected

Object type

Register an entity?

Recommended approach

How to obtain the ID later

Tables, columns, datasets, and dataset versions already collected by DataWorks

No

Use the entity IDs returned by the query operations to register lineage directly

Query operations such as ListTables, GetTable, ListColumns, and GetColumn

Business objects such as reports, APIs, metrics, and data products

Yes

Call CreateMetaEntityDef to create a custom entity definition, then call BatchCreateMetaEntities to write entity objects

The Id returned by BatchCreateMetaEntities, or call GetMetaEntity later

External database/table/column objects that DataWorks does not collect automatically

Yes

Model them as extended table entities: write the database first, then write the table together with its inline columns

The table ID returned by BatchCreateMetaEntities, or call ListTables or ListColumns later

Non-onboarded custom objects

No

Use only for backward-compatible legacy lineage registration scenarios. Not recommended for new onboarding.

The caller maintains its own custom--prefixed IDs

Prerequisites

Before you call these OpenAPIs, make sure your account and DataWorks edition meet the following requirements.

DataWorks edition

Creating entity definitions, entity objects, custom attributes, and lineage relationships requires DataWorks Professional Edition or higher. Specifically:

  • CreateMetaEntityDef creates a custom entity definition or an extended table entity definition. The operation requires Professional Edition or higher.

  • BatchCreateMetaEntities batch-creates custom entity objects, extended Database objects, and extended Table objects. The operation requires Professional Edition or higher.

  • CreateLineageRelationship registers lineage relationships. The operation requires Professional Edition or higher.

If you only call query operations, refer to the edition requirements stated on each operation's reference page.

Account and role permissions

The sample code uses an AccessKey pair to call OpenAPI. The Alibaba Cloud account, RAM user, or STS identity that owns the AccessKey pair must belong to the target DataWorks tenant and have permission to call DataWorks OpenAPI.

To create custom entity definitions, custom attributes, pure custom entities, and extended table entities, use an account with any of the following permissions:

  • Tenant owner.

  • Tenant administrator.

  • Tenant data-governance administrator.

  • An account that has the DataWorks FullAccess permission.

To update the business attributes of MaxCompute tables or columns that DataWorks has collected, the caller must also be the MaxCompute table owner, or the project owner or project administrator of the corresponding DataWorks project. The Demo in this topic only reads the IDs of collected tables and columns to register lineage and does not modify these collected tables or columns.

Data to prepare before running the Demo

To run the Java Demo, replace the constant UPSTREAM_TABLE_ID in the class with a DataWorks-collected table ID that actually exists under your account. Obtain the ID as follows:

  • Table ID: call ListTables or GetTable and use the Id field in the response.

  • Column ID: call ListColumns or GetColumn and use the Id field in the response.

For tables, columns, datasets, and dataset versions that DataWorks has collected, do not concatenate entity IDs manually. Use the IDs returned by the query operations to avoid lineage registration failures caused by ID-format changes or by the object not being collected yet. Extended tables and extended columns registered through BatchCreateMetaEntities can also be queried later using ListTables, GetTable, ListColumns, or GetColumn to obtain the Id. If you later use these extended tables or columns as lineage endpoints, also prefer IDs returned by the query operations.

If you simply onboard your own object list and lineage list by following the "Batch registration engineering pattern" section, you do not have to use the upstream table example in this Demo. You can pass any upstream and downstream entity IDs that actually exist in your list.

Data preparation templates

Before batch onboarding, prepare at least three structured lists. You can store them as CSV, Excel, or database tables. Field names can be adjusted to match your in-house standards, but the semantics should remain consistent.

Example entity definition list

Field

Description

Example

def_name

Name of the entity definition, used to create the entity type

bi_report

display_name

Name displayed on the page

BI Report

kind

Modeling approach: custom entity or extended table entity

CUSTOM_ENTITY

extend

Whether to extend the DataWorks table model

NONE / TABLE

attribute_defs_json

Regular attribute definitions, stored as a JSON array

[{"Name":"reportCode","Type":"STRING"}]

remark

Business description

Enterprise BI report asset

Example entity object list

Field

Description

Example

entity_type

Entity type

custom_entity-bi_report

name

Entity name

daily_sales_report

comment

Entity description

Daily sales report

attributes_json

Regular attribute values, stored as a JSON object

{"reportCode":"daily_sales_report"}

custom_attributes_json

Custom attribute values, stored as a JSON object

{"sourceSystem":["BI"]}

parent_entity_id

Hierarchical relationship for extended database/table

custom_bi-database:src::report_db

columns_json

Column list for an extended table, written together with the table

[{"name":"order_id","type":"STRING"}]

Example lineage relationship list

Field

Description

Example

src_id

Upstream entity ID

maxcompute-table:::project:default:dwd_order

src_name

Upstream entity name

dwd_order

dst_id

Downstream entity ID

custom_entity-bi_report:daily_sales_report

dst_name

Downstream entity name

daily_sales_report

task_id

ID of the task or process that produced the lineage

dwd_order_to_report

task_type

Task type

custom-table_to_report

task_attributes_json

Task attributes, stored as a JSON object

{"scene":"daily_sync"}

Onboarding flow

The following onboarding flow is recommended. For detailed API parameters, field constraints, error codes, and additional SDK examples, see the API reference pages in the References section.

Step 1: Compile the object and relationship lists

First, compile the object list and relationship list of your enterprise system. The object list typically includes:

  • Object name, such as Order Query API, Sales Report, or External Order Table.

  • Object type, such as API, report, metric, or database table.

  • Business attributes, such as owner, business domain, and source system.

  • For tables or columns, the database name, table name, column name, and column type.

The relationship list typically includes:

  • Upstream object.

  • Downstream object.

  • Relationship type, such as table-produces-report, column-maps-column, or API-writes-table.

  • Task or process that produces the relationship, such as a sync task, an ETL task, or an API call chain.

For batch onboarding, first organize your entity and lineage lists according to the Data preparation templates section, and then proceed to the API call flow.

Step 2: Determine whether the object has been onboarded by DataWorks

If an object is already collected or managed by DataWorks, do not register it again. Use the entity ID returned by the corresponding query operation. Common ways to obtain the ID:

  • Table: call ListTables or GetTable.

  • Column: call ListColumns or GetColumn.

  • Dataset: call ListDatasets or GetDataset.

  • Dataset version: call ListDatasetVersions or GetDatasetVersion.

If the object has not been onboarded by DataWorks yet, choose a registration approach based on the object type:

  • For business objects such as reports, APIs, metrics, or systems: register them as custom entities.

  • For external data assets that have a database/table/column structure: register them as extended table entities.

Step 3: Create entity definitions

Use CreateMetaEntityDef to create the entity type definition.

Custom entities are suitable for business concepts such as APIs, reports, or metrics. Example:

{
  "Name": "customer_api",
  "DisplayName": "Business API",
  "Description": "API object from an enterprise system",
  "AttributeDefs": [
    {
      "Name": "apiCode",
      "DisplayName": "API code",
      "Type": "STRING",
      "IsOptional": false
    },
    {
      "Name": "serviceName",
      "DisplayName": "Service name",
      "Type": "STRING",
      "IsOptional": true
    }
  ]
}

Extended table entities are suitable for describing external database, table, and column objects that DataWorks does not collect automatically. When you create an extended table, we recommend that you also create the corresponding extended Database object and link the table to it through parentMetaEntityId. This builds a Database-to-Table hierarchy that lets you browse external assets in Data Map with the full database/table layout. Example:

{
  "Name": "bi-table",
  "DisplayName": "BI report table",
  "Description": "Database tables and columns from an external BI system",
  "Extend": "TABLE"
}

For detailed field semantics and naming constraints, see the CreateMetaEntityDef reference page.

Step 4: Create reusable custom attributes

To display or filter entities in Data Map by business attributes, use CreateCustomAttribute to create custom attribute definitions. For example:

  • Source system.

  • Business domain.

  • Owner.

  • Data tier.

  • Security level.

Example:

{
  "Id": "custom-attribute:source_system",
  "DisplayName": "Source system",
  "Type": "ENUM",
  "ValueEnums": ["CRM", "ERP", "BI", "CUSTOM_APP"],
  "EntityTypes": ["custom_entity-customer_api"],
  "SearchFilterEnabled": true,
  "DisplayEnabled": true
}

After a custom attribute is created, you can use it when you write custom entities, extended table entities, or update business information on collected tables and columns. In EntityTypes, specify the exact entity types to which the attribute applies. To attach the attribute to extended tables or extended columns, specify the concrete types generated by the corresponding extended entity definitions.

Step 5: Create entity objects

Use BatchCreateMetaEntities to write entity objects.

Example of a custom entity object:

{
  "Entities": [
    {
      "EntityType": "custom_entity-customer_api",
      "Name": "api_001",
      "Comment": "Business order-query API",
      "Attributes": {
        "apiCode": "api_001",
        "serviceName": "order-service"
      },
      "CustomAttributes": {
        "source_system": ["CRM"]
      }
    }
  ]
}
Note

BatchCreateMetaEntities is a batch operation. You must check two levels of success: the outer Success indicates whether the batch request was processed successfully by the server, and the per-record Success in Results indicates whether each entity object was created successfully. In production, do not check only the outer Success. You must also iterate through Results[].Success and record the ErrorMessage for any failed item.

Example of an extended table entity object

Before writing an extended table, first write the database entity of the same extension type, then write the table entity with its parentMetaEntityId pointing to that database. This produces a complete Database -> Table -> Column hierarchy that is easier to browse and manage in Data Map. Because BatchCreateMetaEntities requires all entities in a single batch to share the same EntityType, send the database and the table as two separate requests.

First, write the external database:

{
  "Entities": [
    {
      "EntityType": "custom_bi-database",
      "Name": "report_db",
      "Comment": "External BI report database",
      "Attributes": {
        "parentMetaEntityId": "custom_bi:custom_bi_src",
        "technicalMetadata.location": "external://bi/report_db"
      }
    }
  ]
}

Next, write the external table together with its columns through the columns attribute:

{
  "Entities": [
    {
      "EntityType": "custom_bi-table",
      "Name": "sales_report",
      "Comment": "Sales report result table",
      "Attributes": {
        "parentMetaEntityId": "custom_bi-database:custom_bi_src::report_db",
        "columns": "[{\"name\":\"order_id\",\"type\":\"STRING\"},{\"name\":\"amount\",\"type\":\"DECIMAL\"}]"
      }
    }
  ]
}

You do not need to call BatchCreateMetaEntities separately to create the columns. After the table is written successfully, the external columns can participate in column-level lineage using their extended column IDs. You can obtain extended table and column IDs by calling ListTables, GetTable, ListColumns, or GetColumn. If you know the ID rule for an extended entity, you can also construct the ID, for example custom_bi-column:custom_bi_src::report_db::sales_report:order_id.

When the write operation succeeds, the response returns the entity ID. Use this ID for subsequent lineage registration, detail queries, and entity updates.

Step 6: Register lineage relationships

Use CreateLineageRelationship to register the upstream-to-downstream relationship between entities. The direction of a lineage edge is always upstream SrcEntity -> downstream DstEntity.

Example 1: From a DataWorks-collected table to a custom API.

{
  "SrcEntity": {
    "Id": "mysql-table:rm-xxx::sales_db::orders",
    "Name": "orders"
  },
  "DstEntity": {
    "Id": "custom_entity-customer_api:api_001",
    "Name": "api_001"
  },
  "Task": {
    "Id": "table_to_api_001",
    "Type": "custom-lineage-task",
    "Attributes": {
      "scene": "table_to_api"
    }
  }
}

Example 2: From a DataWorks-collected table to an external extended table.

{
  "SrcEntity": {
    "Id": "maxcompute-table:::prod_project:default:dwd_order",
    "Name": "dwd_order"
  },
  "DstEntity": {
    "Id": "custom_bi-table:custom_bi_src::report_db::sales_report",
    "Name": "sales_report"
  },
  "Task": {
    "Id": "dwd_order_to_sales_report",
    "Type": "custom-table-lineage",
    "Attributes": {
      "scene": "external_bi_table"
    }
  }
}

Example 3: From a DataWorks-collected column to an external extended column.

{
  "SrcEntity": {
    "Id": "maxcompute-column:::prod_project:default:dwd_order:order_id",
    "Name": "order_id"
  },
  "DstEntity": {
    "Id": "custom_bi-column:custom_bi_src::report_db::sales_report:order_id",
    "Name": "order_id"
  },
  "Task": {
    "Id": "dwd_order_id_to_report_order_id",
    "Type": "custom-column-lineage",
    "Attributes": {
      "scene": "external_bi_column"
    }
  }
}

For column-level lineage, SrcEntity and DstEntity use column entity IDs. Column IDs can come from ListColumns or GetColumn and cover both DataWorks-collected columns and registered extended-table columns. For detailed request parameters and response fields, see the CreateLineageRelationship reference page.

Step 7: Verify the registration result and consume the lineage

After registration, we recommend that you perform two types of verification.

Verify that entities were written successfully:

  • Custom entity: call GetMetaEntity.

  • Extended table: call GetTable.

  • Extended column: call GetColumn.

  • DataWorks-collected objects: call the corresponding Get operation.

Note

After an entity is created successfully, there may be about 1 second of visibility lag on the query side. If you cannot query the entity immediately after writing, wait briefly and retry.

Verify that lineage was written successfully:

  • Query downstream: call ListLineages with SrcEntityId.

  • Query upstream: call ListLineages with DstEntityId.

  • To return relationship details, set NeedAttachRelationship=true.

Note

There may be a brief index lag after lineage is registered. If you cannot query the lineage immediately after writing, wait several seconds and retry.

After basic verification, you can use lineage data for daily analysis:

  • Upstream lineage tracing: starting from a target entity, call ListLineages recursively against upstream nodes to answer "where does the data behind this report or table come from?"

  • Downstream impact analysis: starting from a changed entity, call ListLineages to query downstream and aggregate by downstream entity type and count to answer "which reports, APIs, or external tables are affected by a change to this table or column?"

  • Broken-link detection: compare your batch-registered entity list with the lineage endpoint list to find orphan entities that have no upstream or downstream, as candidates for additional registration or cleanup.

  • Lineage health checks: regularly check that entities still exist, that lineage relationships are not duplicated, that lineage endpoints are queryable, and that batch writes have no failed items, and produce a health-check report.

The multi-hop impact analysis can be implemented as follows:

queue = [changedEntityId]
visited = set()
while queue is not empty:
    current = queue.pop()
    downstream = ListLineages(SrcEntityId=current)
    for entity in downstream:
        if entity.id not in visited:
            visited.add(entity.id)
            queue.push(entity.id)

Emit entity types, entity names, and impact paths from visited.

In your batch onboarding job, persist the entity IDs and lineage relationship IDs that were registered successfully in each run. For subsequent impact analysis, broken-link detection, or incremental updates, you can then diff against this result table using the query operations, without re-parsing the original business-system data.

Batch registration engineering pattern

When onboarding scales from a few objects to hundreds of reports, thousands of external tables, or tens of thousands of lineage edges, design the registration process as a repeatable, resumable, and auditable batch job.

Recommended write order

For batch onboarding, we recommend the following execution order:

  1. Create or confirm entity definitions by calling CreateMetaEntityDef.

  2. Create or confirm custom attributes by calling CreateCustomAttribute.

  3. Write extended Database objects by calling BatchCreateMetaEntities.

  4. Write extended Table objects together with their Columns.

  5. Write pure custom entities, such as reports, APIs, and metrics.

  6. Register lineage relationships by calling CreateLineageRelationship.

  7. Run query-based verification and generate an onboarding result report.

Batching, retry, and resumable runs

BatchCreateMetaEntities is the batch write operation. Each request can write at most 5 entities, and all entities in a single batch must share the same EntityType. In production, first group the rows by EntityType and then split each group into batches of 5.

CreateLineageRelationship writes one lineage edge per call. For large lineage volumes, control the concurrency and apply backoff-and-retry for throttling or transient service errors.

We recommend the following error-handling pattern:

for batch in splitByEntityType(rows, size=5):
    resp = BatchCreateMetaEntities(batch)
    if resp.Success is not true:
        write_batch_failed(batch, resp.RequestId, resp.Message)
        continue

    for result in resp.Results:
        if result.Success is true:
            mark_done(result.Id)
        else:
            write_row_failed(result.Id, result.ErrorMessage, resp.RequestId)
Note

Check both levels of success: the outer Success indicates whether the request was processed successfully by the server, and Results[].Success indicates whether each entity was written successfully. Do not assume all entities succeeded because the outer Success=true.

Recommendations for resumable runs:

  • Use stable business IDs for entities and lineage relationships so that the same identifiers are produced across runs.

  • After each batch write, record the successful items, failed items, RequestId, failure reasons, and original row numbers.

  • On a rerun, skip items that already succeeded and retry only failed or changed items.

  • Before a run, use query operations such as GetMetaEntity, GetCustomAttribute, or ListLineageRelationships to confirm whether the target objects already exist.

  • 4xx data errors usually require fixing the list before retrying. Throttling, network jitter, and transient service errors can use a bounded backoff-and-retry policy.

For daily client-side sync jobs, keep a "desired state table" and a "last-successful write result table". For incremental sync, process only new and changed objects. For periodic full reconciliations, diff the desired state table against the DataWorks query results to detect missing, duplicated, or deprecated relationships and then re-register or clean them up.

Page effects

After running the Demo, you can see the following effects in DataWorks Data Map. The page layout may vary depending on account permissions, region, Data Map version, and the registered data.

Custom entities become searchable and filterable

After custom entity definitions and entity objects are registered, you can search for the corresponding entities in Data Map. If the entity definition enables search-filter capability on its attributes, the left pane shows the corresponding filters (for example, filtering report entities by business domain, source system, or owner).

Entity detail page shows attribute information

On the detail page of a custom entity, you can view the basic information, regular attributes, and custom attributes. For example, the detail page of a BI report entity shows fields such as report code, business domain, owner, and source system.

Custom attributes are managed in one place

Custom attributes created through CreateCustomAttribute appear on the custom attribute management page of Data Map, where you can review their names, types, and applicable entity types for easy maintenance and reuse.

Extended table entities join the table asset search

After extended Database, Table, and Column objects are registered, external tables join the table asset view of Data Map and can be filtered by data source type and database, presented together with DataWorks-collected tables.

Table-level and column-level lineage in the lineage graph

After "table to custom entity", "table to table", and "column to column" lineage is registered, you can view the upstream and downstream relationships on the lineage page. For example, a sales summary table can simultaneously link to a custom report entity and to an extended table entity, with column-level lineage drilling down.

Java SDK example

The following is a runnable Java Demo that covers both of the following:

  • Registering a BI report custom entity and establishing the lineage from a DataWorks-collected table to that report.

  • Registering an external extended table and establishing table-to-table and column-to-column lineage.

The business identifiers in the Demo are kept as constants at the top of the class. Before you use this Demo, replace them with the actual upstream table IDs, column IDs, report names, and external database/table names under your account. Before the first run, also confirm that the entity definitions, custom attributes, and entity objects in the example do not yet exist under your account, or change them to identifiers that are unique in your organization.

We recommend that you call ListTables or GetTable to obtain a real upstream table ID, and call ListColumns or GetColumn to obtain a real upstream column ID, and then run the Demo.

1. Prepare the project

Project layout:

custom-metadata-lineage-demo
├── pom.xml
└── src
    └── main
        └── java
            └── CustomMetadataLineageDemo.java

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>custom-metadata-lineage-demo</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>com.aliyun</groupId>
      <artifactId>dataworks_public20240518</artifactId>
      <version>${dataworks.sdk.version}</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
          <mainClass>CustomMetadataLineageDemo</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

2. Write the Demo

Save the following code as src/main/java/CustomMetadataLineageDemo.java.

import com.aliyun.dataworks_public20240518.Client;
import com.aliyun.dataworks_public20240518.models.BatchCreateMetaEntitiesRequest;
import com.aliyun.dataworks_public20240518.models.BatchCreateMetaEntitiesResponse;
import com.aliyun.dataworks_public20240518.models.CreateCustomAttributeRequest;
import com.aliyun.dataworks_public20240518.models.CreateLineageRelationshipRequest;
import com.aliyun.dataworks_public20240518.models.CreateMetaEntityDefRequest;
import com.aliyun.dataworks_public20240518.models.GetCustomAttributeRequest;
import com.aliyun.dataworks_public20240518.models.GetCustomAttributeResponse;
import com.aliyun.dataworks_public20240518.models.GetMetaEntityDefRequest;
import com.aliyun.dataworks_public20240518.models.GetMetaEntityDefResponse;
import com.aliyun.dataworks_public20240518.models.LineageEntity;
import com.aliyun.dataworks_public20240518.models.LineageTask;
import com.aliyun.dataworks_public20240518.models.MetaEntityAttributeDef;
import com.aliyun.dataworks_public20240518.models.MetaEntityWriteResult;
import com.aliyun.teaopenapi.models.Config;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CustomMetadataLineageDemo {
    private static final String REGION_ID = "cn-shanghai";

    // DataWorks-collected table ID. Replace with the result of ListTables/GetTable.
    private static final String UPSTREAM_TABLE_ID = "maxcompute-table:::prod_project:default:dwd_sales_order";
    private static final String UPSTREAM_TABLE_NAME = "dwd_sales_order";

    // BI report example.
    private static final String REPORT_ENTITY_TYPE = "custom_entity-bi_report";
    private static final String REPORT_ENTITY_NAME = "daily_sales_report";

    // External extended table example.
    private static final String EXT_PREFIX = "warehouse";
    private static final String EXT_SOURCE = "custom_external_bi_src";
    private static final String EXT_DATABASE = "analytics_db";
    private static final String EXT_TABLE = "ads_sales_summary";
    private static final String EXT_TABLE_ID =
            "custom_" + EXT_PREFIX + "-table:" + EXT_SOURCE + "::" + EXT_DATABASE + "::" + EXT_TABLE;
    private static final String EXT_ORDER_ID_COLUMN_ID =
            "custom_" + EXT_PREFIX + "-column:" + EXT_SOURCE + "::" + EXT_DATABASE + "::" + EXT_TABLE + ":order_id";
    private static final String EXT_AMOUNT_COLUMN_ID =
            "custom_" + EXT_PREFIX + "-column:" + EXT_SOURCE + "::" + EXT_DATABASE + "::" + EXT_TABLE + ":amount";

    public static void main(String[] args) throws Exception {
        Client client = new Client(new Config()
                .setAccessKeyId(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"))
                .setAccessKeySecret(System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"))
                .setRegionId(REGION_ID));

        if (System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID") == null
                || System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET") == null) {
            throw new IllegalArgumentException(
                    "Please set ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET first");
        }

        String reportId = registerBiReport(client);
        registerExternalTable(client);
        createLineage(client, UPSTREAM_TABLE_ID, UPSTREAM_TABLE_NAME,
                reportId, REPORT_ENTITY_NAME, "dw_table_to_bi_report", "custom-table_to_report");
        createLineage(client, UPSTREAM_TABLE_ID, UPSTREAM_TABLE_NAME,
                EXT_TABLE_ID, EXT_TABLE, "dw_table_to_external_table", "custom-table_to_table");
        createLineage(client, EXT_ORDER_ID_COLUMN_ID, "order_id",
                EXT_AMOUNT_COLUMN_ID, "amount", "ext_col_to_ext_col", "custom-column_to_column");

        System.out.println("Custom entities, extended table, and lineage registered.");
    }

    private static String registerBiReport(Client client) throws Exception {
        ensureMetaEntityDef(client, REPORT_ENTITY_TYPE, new CreateMetaEntityDefRequest()
                .setName("bi_report")
                .setDisplayName("BI report")
                .setDescription("Enterprise BI report asset")
                .setExtend("NONE")
                .setAttributeDefs(Arrays.asList(
                        new MetaEntityAttributeDef()
                                .setName("reportCode").setDisplayName("Report code").setType("STRING").setIsOptional(false),
                        new MetaEntityAttributeDef()
                                .setName("reportUrl").setDisplayName("Report URL").setType("STRING").setIsOptional(true)
                )));

        ensureCustomAttribute(client, "custom-attribute:sourceSystem", new CreateCustomAttributeRequest()
                .setId("custom-attribute:sourceSystem")
                .setDisplayName("Source system")
                .setType("ENUM")
                .setValueEnums(Arrays.asList("BI", "CRM", "ERP"))
                .setEntityTypes(Collections.singletonList(REPORT_ENTITY_TYPE))
                .setDisplayEnabled(true)
                .setSearchFilterEnabled(true));

        Map<String, String> attrs = new HashMap<String, String>();
        attrs.put("reportCode", REPORT_ENTITY_NAME);
        attrs.put("reportUrl", "https://bi.example.com/reports/daily_sales");

        Map<String, List<String>> customAttrs = new HashMap<String, List<String>>();
        customAttrs.put("sourceSystem", Collections.singletonList("BI"));

        String reportId = createOneEntity(client,
                new BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities()
                        .setEntityType(REPORT_ENTITY_TYPE)
                        .setName(REPORT_ENTITY_NAME)
                        .setComment("Daily sales report")
                        .setAttributes(attrs)
                        .setCustomAttributes(customAttrs));
        Thread.sleep(1000L); // After successful creation, the query side may have about 1s of visibility lag.
        return reportId;
    }

    private static void registerExternalTable(Client client) throws Exception {
        ensureMetaEntityDef(client, "custom_" + EXT_PREFIX + "-table", new CreateMetaEntityDefRequest()
                .setName(EXT_PREFIX + "-table")
                .setDisplayName("External warehouse table")
                .setDescription("External tables not collected by DataWorks automatically")
                .setExtend("TABLE"));

        Map<String, String> dbAttrs = new HashMap<String, String>();
        dbAttrs.put("parentMetaEntityId", "custom_" + EXT_PREFIX + ":" + EXT_SOURCE);
        dbAttrs.put("technicalMetadata.location", "external://" + EXT_DATABASE);
        createOneEntity(client, new BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities()
                .setEntityType("custom_" + EXT_PREFIX + "-database")
                .setName(EXT_DATABASE)
                .setComment("External analytics database")
                .setAttributes(dbAttrs));

        Map<String, String> tableAttrs = new HashMap<String, String>();
        tableAttrs.put("parentMetaEntityId", "custom_" + EXT_PREFIX + "-database:" + EXT_SOURCE + "::" + EXT_DATABASE);
        tableAttrs.put("tableType", "TABLE");
        tableAttrs.put("columns", "["
                + "{\"name\":\"order_id\",\"type\":\"STRING\",\"comment\":\"Order ID\",\"position\":1},"
                + "{\"name\":\"amount\",\"type\":\"DECIMAL\",\"comment\":\"Sales amount\",\"position\":2}"
                + "]");
        createOneEntity(client, new BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities()
                .setEntityType("custom_" + EXT_PREFIX + "-table")
                .setName(EXT_TABLE)
                .setComment("External sales summary table")
                .setAttributes(tableAttrs));
        Thread.sleep(1000L);
    }

    private static String createOneEntity(
            Client client,
            BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities entity) throws Exception {
        BatchCreateMetaEntitiesResponse response = client.batchCreateMetaEntities(
                new BatchCreateMetaEntitiesRequest().setEntities(Collections.singletonList(entity)));
        if (response.getBody() == null || !Boolean.TRUE.equals(response.getBody().getSuccess())) {
            throw new RuntimeException("BatchCreateMetaEntities request failed");
        }
        MetaEntityWriteResult result = response.getBody().getResults().get(0);
        if (!Boolean.TRUE.equals(result.getSuccess())) {
            if (isAlreadyExists(result.getErrorMessage())) {
                String existingId = buildExpectedEntityId(entity);
                System.out.println("Entity already exists, reusing: " + existingId);
                return existingId;
            }
            throw new RuntimeException("Entity creation failed: " + result.getErrorMessage());
        }
        System.out.println("Entity created: " + result.getId());
        return result.getId();
    }

    private static String buildExpectedEntityId(
            BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities entity) {
        String entityType = entity.getEntityType();
        String name = entity.getName();
        if (entityType.startsWith("custom_entity-")) {
            return entityType + ":" + name;
        }

        Map<String, String> attributes = entity.getAttributes();
        String parentMetaEntityId = attributes == null ? null : attributes.get("parentMetaEntityId");
        if (entityType.endsWith("-database") && parentMetaEntityId != null) {
            String metaSource = parentMetaEntityId.substring(parentMetaEntityId.indexOf(':') + 1);
            return entityType + ":" + metaSource + "::" + name;
        }
        if (entityType.endsWith("-table") && parentMetaEntityId != null) {
            String parent = parentMetaEntityId.substring(parentMetaEntityId.indexOf(':') + 1);
            return entityType + ":" + parent + "::" + name;
        }

        throw new IllegalArgumentException("Cannot derive existing entity ID from type and attributes: " + entityType + "/" + name);
    }

    private static void createLineage(
            Client client,
            String srcId,
            String srcName,
            String dstId,
            String dstName,
            String taskId,
            String taskType) throws Exception {
        client.createLineageRelationship(new CreateLineageRelationshipRequest()
                .setSrcEntity(new LineageEntity().setId(srcId).setName(srcName))
                .setDstEntity(new LineageEntity().setId(dstId).setName(dstName))
                .setTask(new LineageTask().setId(taskId).setType(taskType)));
        System.out.println("Lineage created: " + srcId + " -> " + dstId);
    }

    private static void ensureMetaEntityDef(
            Client client,
            String entityType,
            CreateMetaEntityDefRequest request) throws Exception {
        if (metaEntityDefExists(client, entityType)) {
            System.out.println("Entity definition already exists, skipped: " + entityType);
            return;
        }
        client.createMetaEntityDef(request);
    }

    private static boolean metaEntityDefExists(Client client, String entityType) throws Exception {
        try {
            GetMetaEntityDefResponse response = client.getMetaEntityDef(
                    new GetMetaEntityDefRequest().setEntityType(entityType));
            return response.getBody() != null
                    && Boolean.TRUE.equals(response.getBody().getSuccess())
                    && response.getBody().getMetaEntityDef() != null;
        } catch (Exception e) {
            if (isNotFound(e)) {
                return false;
            }
            throw e;
        }
    }

    private static void ensureCustomAttribute(
            Client client,
            String id,
            CreateCustomAttributeRequest request) throws Exception {
        if (customAttributeExists(client, id)) {
            System.out.println("Custom attribute already exists, skipped: " + id);
            return;
        }
        client.createCustomAttribute(request);
    }

    private static boolean customAttributeExists(Client client, String id) throws Exception {
        try {
            GetCustomAttributeResponse response = client.getCustomAttribute(
                    new GetCustomAttributeRequest().setId(id));
            return response.getBody() != null
                    && Boolean.TRUE.equals(response.getBody().getSuccess())
                    && response.getBody().getCustomAttribute() != null;
        } catch (Exception e) {
            if (isNotFound(e)) {
                return false;
            }
            throw e;
        }
    }

    private static boolean isNotFound(Exception e) {
        String message = e.getMessage();
        if (message == null) {
            return false;
        }
        String lowerMessage = message.toLowerCase();
        return lowerMessage.contains("not found")
                || lowerMessage.contains("not exist")
                || lowerMessage.contains("not_exists");
    }

    private static boolean isAlreadyExists(String message) {
        if (message == null) {
            return false;
        }
        String lowerMessage = message.toLowerCase();
        return lowerMessage.contains("already exists") || lowerMessage.contains("already exist");
    }
}

3. Run the Demo

Configure the access credentials before running. The sample code reads the AccessKey pair from environment variables to avoid hardcoding the secret.

export ALIBABA_CLOUD_ACCESS_KEY_ID="your-access-key-id"
export ALIBABA_CLOUD_ACCESS_KEY_SECRET="your-access-key-secret"

Execute:

export DATAWORKS_SDK_VERSION="latest-version"
export JAVA_VERSION="8"
mvn -Ddataworks.sdk.version="${DATAWORKS_SDK_VERSION}" \
    -Djava.version="${JAVA_VERSION}" \
    compile exec:java

Notes:

  • The Demo follows the creation flow. Before you run it, change the entity types, entity names, and external database/table names at the top of the class to identifiers that are not yet used under your account.

  • BatchCreateMetaEntities requires checking both the outer Success and each Results[].Success. The createOneEntity helper in the sample already covers these checks.

  • After an entity is created successfully, the query side may have about 1 second of visibility lag. The sample adds a brief wait after creation. In production, replace it with a bounded retry.

4. Core code snippets for batch onboarding

The Demo above runs through the end-to-end flow. For production batch onboarding, prepare your entity object list and lineage relationship list as described in the Data preparation templates section, and convert them into List<EntityRow> and List<LineageRow> as input to the batch methods.

Here, EntityRow maps to the entity object list and LineageRow maps to the lineage relationship list. If your list comes from CSV, Excel, or a database, first parse attributes_json, custom_attributes_json, and task_attributes_json into Map objects, then populate the row objects. The snippets below are core fragments that you can embed in the Demo project. They are not a complete CSV/Excel reader; implement file reading and JSON parsing according to your internal engineering standards.

When you add the following code to the CustomMetadataLineageDemo class, also add these imports:

import java.util.ArrayList;
import java.util.LinkedHashMap;

Batch-create entities:

private static BatchWriteResult batchCreateEntities(Client client, List<EntityRow> entityRows) throws Exception {
    BatchWriteResult report = new BatchWriteResult();

    // BatchCreateMetaEntities requires each batch to share the same EntityType, so group by EntityType first.
    Map<String, List<EntityRow>> grouped = new LinkedHashMap<String, List<EntityRow>>();
    for (EntityRow row : entityRows) {
        if (!grouped.containsKey(row.entityType)) {
            grouped.put(row.entityType, new ArrayList<EntityRow>());
        }
        grouped.get(row.entityType).add(row);
    }

    for (Map.Entry<String, List<EntityRow>> entry : grouped.entrySet()) {
        List<EntityRow> sameTypeRows = entry.getValue();
        for (int from = 0; from < sameTypeRows.size(); from += 5) {
            int to = Math.min(from + 5, sameTypeRows.size());
            List<EntityRow> rowBatch = sameTypeRows.subList(from, to);

            List<BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities> requestEntities =
                    new ArrayList<BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities>();
            for (EntityRow row : rowBatch) {
                requestEntities.add(row.toRequestEntity());
            }

            BatchCreateMetaEntitiesResponse response = client.batchCreateMetaEntities(
                    new BatchCreateMetaEntitiesRequest().setEntities(requestEntities));
            if (response.getBody() == null || !Boolean.TRUE.equals(response.getBody().getSuccess())) {
                for (EntityRow row : rowBatch) {
                    report.failed.add("entity=" + row.name + ", reason=batch request failed");
                }
                continue;
            }

            List<MetaEntityWriteResult> results = response.getBody().getResults();
            if (results == null || results.size() != rowBatch.size()) {
                for (EntityRow row : rowBatch) {
                    report.failed.add("entity=" + row.name + ", reason=unexpected batch result");
                }
                continue;
            }
            for (int i = 0; i < results.size(); i++) {
                MetaEntityWriteResult result = results.get(i);
                if (Boolean.TRUE.equals(result.getSuccess())) {
                    report.successIds.add(result.getId());
                } else {
                    EntityRow row = rowBatch.get(i);
                    report.failed.add("entity=" + row.name + ", reason=" + result.getErrorMessage());
                }
            }
        }
    }

    return report;
}

Batch-register lineage relationships:

private static BatchWriteResult batchCreateLineages(Client client, List<LineageRow> lineages) {
    BatchWriteResult report = new BatchWriteResult();
    for (LineageRow row : lineages) {
        boolean success = false;
        String lastError = null;

        for (int attempt = 1; attempt <= 3; attempt++) {
            try {
                LineageTask task = new LineageTask().setId(row.taskId).setType(row.taskType);
                if (row.taskAttributes != null && !row.taskAttributes.isEmpty()) {
                    task.setAttributes(row.taskAttributes);
                }

                client.createLineageRelationship(new CreateLineageRelationshipRequest()
                        .setSrcEntity(new LineageEntity().setId(row.srcId).setName(row.srcName))
                        .setDstEntity(new LineageEntity().setId(row.dstId).setName(row.dstName))
                        .setTask(task));
                report.successIds.add(row.srcId + " -> " + row.dstId);
                success = true;
                break;
            } catch (Exception e) {
                lastError = e.getMessage();
                try {
                    Thread.sleep(1000L * attempt);
                } catch (InterruptedException interrupted) {
                    Thread.currentThread().interrupt();
                    lastError = interrupted.getMessage();
                    break;
                }
            }
        }

        if (!success) {
            report.failed.add("lineage=" + row.srcId + " -> " + row.dstId + ", reason=" + lastError);
        }
    }
    return report;
}

Helper classes:

private static class EntityRow {
    // Maps to entity_type, name, comment in the entity object list.
    private String entityType;
    private String name;
    private String comment;

    // Maps to attributes_json and custom_attributes_json.
    private Map<String, String> attributes = new HashMap<String, String>();
    private Map<String, List<String>> customAttributes = new HashMap<String, List<String>>();

    // Maps to parent_entity_id and columns_json, mainly used for extended Database/Table.
    private String parentEntityId;
    private String columnsJson;

    private BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities toRequestEntity() {
        Map<String, String> mergedAttributes = new HashMap<String, String>();
        if (attributes != null) {
            mergedAttributes.putAll(attributes);
        }
        if (parentEntityId != null && parentEntityId.length() > 0) {
            mergedAttributes.put("parentMetaEntityId", parentEntityId);
        }
        if (columnsJson != null && columnsJson.length() > 0) {
            mergedAttributes.put("columns", columnsJson);
        }

        return new BatchCreateMetaEntitiesRequest.BatchCreateMetaEntitiesRequestEntities()
                .setEntityType(entityType)
                .setName(name)
                .setComment(comment)
                .setAttributes(mergedAttributes)
                .setCustomAttributes(customAttributes);
    }
}

private static class BatchWriteResult {
    private final List<String> successIds = new ArrayList<String>();
    private final List<String> failed = new ArrayList<String>();
}

private static class LineageRow {
    private String srcId;
    private String srcName;
    private String dstId;
    private String dstName;
    private String taskId;
    private String taskType;
    private Map<String, String> taskAttributes = new HashMap<String, String>();
}

In production, persist BatchWriteResult.failed to a file, database, or message queue as input for resumable runs and manual fix-ups. Do not retry every failure indiscriminately. Issues such as parameter errors, missing entity types, or missing lineage endpoints require fixing the data list first, then rerunning.

Best practices

Prefer registering objects as DataWorks custom entity objects

Non-onboarded custom-{Type}:{Identifier} objects can participate in lineage registration directly, but they are harder to manage afterwards. For new onboarding, we recommend that you:

  1. Create an entity definition with CreateMetaEntityDef.

  2. Create entity objects with BatchCreateMetaEntities.

  3. Use the returned entity ID for lineage registration.

This lets you view entity details, maintain attributes, reuse custom attributes, and support subsequent governance in Data Map.

Distinguish custom entities from extended table entities

If an object is a business concept such as a report, API, metric, system, or data product, register it as a custom entity. If an object has a well-defined database/table/column structure and DataWorks does not collect it automatically, register it as an extended table entity.

When you register an extended table entity, also create the corresponding extended Database object first, then create the Table and inline Columns. This preserves the original database/table/column hierarchy of the source data and produces an asset view in Data Map that is closer to the real data structure.

Warning

Do not use BatchCreateMetaEntities to create pre-defined entities managed by DataWorks collectors, such as MaxCompute, DLF, or EMR entities. For collected objects, use the entity IDs returned by the query operations. For external tables that are not collected, use extended table entities.

Confirm parameters on the API reference page

The examples above illustrate API composition and the onboarding flow. For field mandatoriness, naming rules, enum values, error codes, and SDK code, see the corresponding API reference pages.

Operations and maintenance

After custom entities and lineage relationships are onboarded, maintain them as a continuous sync pipeline rather than a one-off import. Common operations are as follows:

Operation

Recommended API

Description

Update the display name, description, or attribute definitions of an entity definition

UpdateMetaEntityDef

Use for entity-model evolution. Assess compatibility with existing entity data before modification.

Update a custom attribute definition

UpdateCustomAttribute

Use to adjust display, filtering, or enum settings.

Update a custom entity object or an extended table entity object

UpdateMetaEntity

Use to update the description, regular attributes, and custom attributes of an entity.

Update the business metadata of a table or column

UpdateTableBusinessMetadata / UpdateColumnBusinessMetadata

Use to maintain README content and custom attributes on tables and columns.

Delete deprecated lineage

DeleteLineageRelationship

First call ListLineageRelationships to find the relationships to delete.

Delete a deprecated entity definition

DeleteMetaEntityDef

Confirm there are no remaining entities or lineage relationships in use before deletion, and use force-delete with caution.

When lineage changes, we recommend that you first generate a "desired lineage list" from your source system, then call ListLineageRelationships to query the existing relationships in DataWorks. Reconcile by:

  • Present in the desired list but missing in DataWorks: call CreateLineageRelationship to add.

  • Missing in the desired list but still in DataWorks: call DeleteLineageRelationship to clean up.

  • Present on both sides but with changed task information: delete the old relationship first, then create the new one.

If your source system adds new objects every day, use incremental sync. If your source system has many historical edits or manually maintained data, run periodic full reconciliations to avoid drift in DataWorks.

Troubleshooting

Symptom

Likely cause

Suggested fix

Entity type does not exist

The entity definition has not been created yet, or has just been created and is not visible on the query side

Call CreateMetaEntityDef first and wait briefly before writing entities

Entity write request succeeds but some entities fail

Only the outer Success was checked; Results[].Success was ignored

Iterate through Results, persist failed items, and re-register according to the error reasons

Lineage endpoint does not exist

The upstream or downstream entity ID is incorrect, or the entity was just created and is not yet queryable

Use operations such as GetMetaEntity, GetTable, or GetColumn to verify the endpoint ID, and retry after a wait if needed

Column-level lineage registration fails

The column ID was concatenated manually with an error, or the extended-table columns were not registered with the table

For collected columns, use IDs returned by ListColumns or GetColumn; for extended columns, confirm that columns was written together with the table

Batch calls are throttled

Concurrency is too high or too many requests in a short period

Reduce concurrency and apply exponential backoff for throttling errors

Duplicated or stale lineage appears after a rerun

No stable IDs and no desired-state comparison were used

Persist successful results; before a rerun, query existing relationships and skip or delete-then-recreate them

Custom attribute is not filterable on the page

The attribute definition has not enabled search filtering, or the applicable entity types do not include the target type

Check SearchFilterEnabled and EntityTypes; update the custom attribute definition if needed

Need to diagnose batch write failures

Failure information lacks context for locating the issue

Record the RequestId, original row number, entity ID, lineage endpoint ID, and error message of the failed request. When opening a ticket or troubleshooting, provide the RequestId first

A newly written object cannot be queried for a short time on the page or query API

Brief index lag on the query side

Wait 1 to several seconds and retry; in batch jobs, use bounded retries instead of declaring failure immediately

Common onboarding patterns

Pattern 1: Onboard BI reports to Data Map

Suitable when you want to view which tables a report depends on, or which reports are affected when a table changes. Recommended steps:

  1. Use CreateMetaEntityDef to create a report entity definition.

  2. Use CreateCustomAttribute to create attributes such as business domain, owner, and source system.

  3. Use BatchCreateMetaEntities to batch-write the report entities.

  4. Use ListTables and ListColumns to obtain the DataWorks-collected table and column IDs.

  5. Use CreateLineageRelationship to register table-to-report and column-to-column lineage.

  6. Use ListLineages to verify the upstream and downstream chains.

Pattern 2: Onboard external database tables to Data Map

Suitable when your enterprise has third-party databases or external system tables that DataWorks does not collect automatically, but you want to include them in a unified asset view. Recommended steps:

  1. Use CreateMetaEntityDef to create the extended table definition.

  2. Use BatchCreateMetaEntities to write the external database first, then write the external table together with its inline columns.

  3. Use CreateCustomAttribute to add business attributes such as source system and data domain.

  4. Use CreateLineageRelationship to establish lineage between DataWorks tables and external tables.

  5. Use GetTable, GetColumn, and ListLineages to verify the result.

Pattern 3: Add lineage between DataWorks-collected assets

Suitable when your enterprise has external pipelines or legacy pipelines that you want to register in Data Map. Recommended steps:

  1. Use APIs such as ListTables, ListColumns, and ListDatasets to obtain entity IDs.

  2. Call CreateLineageRelationship according to the actual upstream-to-downstream direction.

  3. Use ListLineages to verify.

In this pattern, you do not need to create custom entity definitions or write entity objects.

References