Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Implementation:Datahub project Datahub EntityClient Get Mutable

From Leeroopedia


Field Value
Implementation Name EntityClient Get and Mutable
Type API Doc
Status Active
Last Updated 2026-02-10
Repository Datahub_project_Datahub
Source Files EntityClient.java (Lines 460-533), Entity.java (Lines 385-437)

Overview

The get() method on EntityClient fetches an entity from the DataHub server as a read-only object, and the mutable() method on Entity creates a writable copy for modifications. Together, these methods implement the read-modify-write pattern for existing entities.

Source Reference: EntityClient.get()

File: metadata-integration/java/datahub-client/src/main/java/datahub/client/v2/operations/EntityClient.java (Lines 460-533)

@Nonnull
public <T extends Entity> T get(@Nonnull String urn, @Nonnull Class<T> entityClass)
    throws IOException, ExecutionException, InterruptedException {

    // Create URN object
    com.linkedin.common.urn.Urn urnObj = com.linkedin.common.urn.Urn.createFromString(urn);

    // Create entity instance to get default aspects
    T entity = createEntityInstance(urnObj, entityClass);

    // Get default aspects and fetch with them
    List<Class<? extends RecordTemplate>> defaultAspects = entity.getDefaultAspects();
    return get(urn, entityClass, defaultAspects);
}

@Nonnull
public <T extends Entity> T get(
    @Nonnull String urn,
    @Nonnull Class<T> entityClass,
    @Nonnull List<Class<? extends RecordTemplate>> aspects)
    throws IOException, ExecutionException, InterruptedException {

    com.linkedin.common.urn.Urn urnObj = com.linkedin.common.urn.Urn.createFromString(urn);

    // Build aspect names for single batch fetch
    List<String> aspectNames = new ArrayList<>();
    Map<String, Class<? extends RecordTemplate>> aspectClassMap = new HashMap<>();
    for (Class<? extends RecordTemplate> aspectClass : aspects) {
        String aspectName = getAspectName(aspectClass);
        aspectNames.add(aspectName);
        aspectClassMap.put(aspectName, aspectClass);
    }

    // Fetch all aspects in a single API call
    Map<String, RecordTemplate> aspectCache = fetchAspects(urnObj, aspectNames, aspectClassMap);

    // Create entity with loaded aspects (read-only by default)
    T entityWithAspects = createEntityInstance(urnObj, entityClass, aspectCache);
    entityWithAspects.bindToClient(this, config.getMode());

    return entityWithAspects;
}

Source Reference: Entity.mutable()

File: metadata-integration/java/datahub-client/src/main/java/datahub/client/v2/entity/Entity.java (Lines 385-437)

The base Entity class provides a default mutable() implementation using reflection, but entity subclasses override it with dedicated copy constructors:

Dataset.mutable() (Line 182)

@Override
@Nonnull
public Dataset mutable() {
    if (!readOnly) {
        return this;  // Already mutable, return self (idempotent)
    }
    return new Dataset(this);  // Copy constructor
}

Dataset Copy Constructor (Lines 137-148)

protected Dataset(@Nonnull Dataset other) {
    super(
        other.urn,           // Shared: URN
        other.cache,         // Shared: aspect cache
        other.client,        // Shared: client reference
        other.mode,          // Shared: operation mode
        new HashMap<>(),     // Fresh: pending patches
        new ArrayList<>(),   // Fresh: pending MCPs
        new HashMap<>(),     // Fresh: patch builders
        false,               // Not dirty
        false);              // Not read-only (mutable)
}

Entity Base mutable() (Lines 385-437)

@Nonnull
@SuppressWarnings("unchecked")
public <T extends Entity> T mutable() {
    if (!readOnly) {
        return (T) this;  // Already mutable
    }
    // Reflection-based copy with shared cache but independent mutation tracking
    // Sets readOnly=false, dirty=false on the copy
    ...
}

getAspectLazy()

File: metadata-integration/java/datahub-client/src/main/java/datahub/client/v2/entity/Entity.java (Lines 491-528)

@Nullable
public <T extends RecordTemplate> T getAspectLazy(@Nonnull Class<T> aspectClass) {
    String aspectName = getAspectName(aspectClass);

    // Try cache first
    T cached = cache.get(aspectName, aspectClass, ReadMode.ALLOW_DIRTY);
    if (cached != null) { return cached; }

    // Skip lazy-load if entity has pending mutations
    if (dirty && client != null) { return null; }

    // Fetch from server if client is bound
    if (client != null) {
        AspectWithMetadata<T> aspectWithMetadata = client.getAspect(urn, aspectClass);
        T aspect = aspectWithMetadata.getAspect();
        if (aspect != null) {
            cache.put(aspectName, aspect, AspectSource.SERVER, false);
        }
        return aspect;
    }
    return null;
}

Method Signatures

EntityClient.get() (default aspects)

public <T extends Entity> T get(@Nonnull String urn, @Nonnull Class<T> entityClass)
    throws IOException, ExecutionException, InterruptedException

EntityClient.get() (specified aspects)

public <T extends Entity> T get(
    @Nonnull String urn,
    @Nonnull Class<T> entityClass,
    @Nonnull List<Class<? extends RecordTemplate>> aspects)
    throws IOException, ExecutionException, InterruptedException

Entity.mutable()

public <T extends Entity> T mutable()

Accessed via:

// Fetch read-only entity
Dataset dataset = client.entities().get(urnString, Dataset.class);

// Create mutable copy
Dataset mutable = dataset.mutable();

I/O Contract

get()

Input:

Output: A read-only entity instance with:

  • Pre-loaded aspects from the server (in the WriteTrackingAspectCache)
  • readOnly = true (mutation methods throw ReadOnlyEntityException)
  • Bound to the client for lazy loading of additional aspects

Exceptions:

  • IOException -- if the entity does not exist or network error occurs
  • ExecutionException -- if the server returns an error response
  • InterruptedException -- if the thread is interrupted while waiting

mutable()

Input: A read-only entity instance (from get())

Output: A mutable entity instance with:

  • Shared aspect cache (reads see the same pre-loaded data as the original)
  • Independent mutation tracking (fresh pending patches, MCPs, and patch builders)
  • readOnly = false, dirty = false

If called on an already mutable entity, returns the same instance (idempotent).

getAspectLazy()

Input: An aspect class (e.g., GlobalTags.class)

Output: The aspect instance, or null if not available. Loading behavior:

  • Returns cached aspect if present (even if dirty)
  • Skips server fetch if entity has pending mutations (dirty flag)
  • Fetches from server and caches if client is bound and aspect is not cached
  • Returns null if no client is bound and aspect is not cached

Usage Examples

Read-Modify-Write Pattern

// Phase 1: Read (immutable)
Dataset dataset = client.entities().get(
    "urn:li:dataset:(urn:li:dataPlatform:snowflake,my_table,PROD)",
    Dataset.class);

dataset.isReadOnly();    // true
dataset.getDescription(); // Works fine - reads from cache

// Phase 2: Mutable copy
Dataset mutable = dataset.mutable();
mutable.isReadOnly();    // false
mutable.isMutable();     // true

// Phase 3: Modify and persist
mutable.addTag("verified");
mutable.setDescription("Updated description");
mutable.addOwner("urn:li:corpuser:johndoe", OwnershipType.DATA_OWNER);

client.entities().upsert(mutable);

Fetch with Specific Aspects

Dataset dataset = client.entities().get(
    urnString,
    Dataset.class,
    List.of(DatasetProperties.class, GlobalTags.class));

Lazy Loading

Dataset dataset = client.entities().get(urnString, Dataset.class);

// SchemaMetadata is not in default aspects, but will be lazy-loaded on access
SchemaMetadata schema = dataset.getSchema();
// Triggers: client.getAspect(urn, SchemaMetadata.class) under the hood

Shared vs Independent State

When mutable() is called, the following table shows which state is shared between the original and the mutable copy:

State Shared? Reason
URN Shared Identity is immutable
Aspect cache (WriteTrackingAspectCache) Shared Reads see the same server data
Client reference Shared Both use the same server connection
Operation mode Shared Both use the same mode (SDK/INGESTION)
Pending patches (pendingPatches) Independent (fresh empty) Mutations are independent
Pending MCPs (pendingMCPs) Independent (fresh empty) Mutations are independent
Patch builders (patchBuilders) Independent (fresh empty) Mutations are independent
Dirty flag Independent (false) Copy starts clean
Read-only flag Independent (false) Copy is mutable

Related

Knowledge Sources

Domains

Data_Integration, Metadata_Management, Java_SDK

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment