Class Attribute<T>

Type Parameters:
T - the Java value type stored in the Attribute

public final class Attribute<T> extends PersistenceDefinition
Defines one durable, typed value stored with a Flow execution.

Declare Attributes once on the Flow, include them in its PersistenceSchema, and use the same object from Step and RPC code. Values are serialized using the Class supplied at definition time. A parameterized value type such as List<String> cannot be supplied directly because List.class does not retain its element type. Wrap the value in a concrete class whose field is a List<String>, or use String[] when an array fits the data model.

private final Attribute<Integer> counter =
        Attribute.define("counter", Integer.class);
private final Attribute<Tags> tags =
        Attribute.define("tags", Tags.class);
private final Attribute<String[]> tagArray =
        Attribute.define("tag-array", String[].class);

static final class Tags {
    public List<String> values;
}

public StepDecision execute(Context context, Command input) {
    counter.set(context, counter.get(context) + 1);
    return StepDecision.gracefulComplete();
}
  • Method Details

    • define

      public static <T> Attribute<T> define(String name, Class<T> valueType)
      Defines an Attribute without a search index.
      Type Parameters:
      T - the Java value type
      Parameters:
      name - the stable Attribute name; must not be blank or contain /
      valueType - the concrete Java class used to serialize and deserialize values
      Returns:
      the typed Attribute definition
      Throws:
      IllegalArgumentException - if name is null, blank, or contains /
      NullPointerException - if valueType is null
    • define

      public static <T> Attribute<T> define(String name, Class<T> valueType, AttributeIndex index)
      Defines an Attribute with a search index.
      Type Parameters:
      T - the Java value type
      Parameters:
      name - the stable Attribute name; must not be blank or contain /
      valueType - the concrete Java class used to serialize and deserialize values
      index - the search-index definition, or null for no index
      Returns:
      the typed Attribute definition
      Throws:
      IllegalArgumentException - if name is null, blank, or contains /
      NullPointerException - if valueType is null
    • syncToAttributeStore

      public Attribute<T> syncToAttributeStore()
      Returns a definition whose writes are projected through the Flow's Attribute Store.

      The projection is asynchronous and latest-state only. A projection failure does not roll back the Flow Attribute. Deleting this Attribute projects SQL NULL when the target column permits it.

      Returns:
      a new immutable Attribute definition with Attribute Store synchronization enabled
      Throws:
      IllegalStateException - if this definition uses hardDeleteOnNull()
    • hardDeleteOnNull

      public Attribute<T> hardDeleteOnNull()
      Returns a definition whose value is stored only in the Dex blob store and is permanently deleted when the Attribute is deleted or set to null.

      Use this option for short-lived secrets, such as an API key or an access token, that a Flow needs for a limited time. A regular Attribute value is recorded in Flow history. A hard-delete Attribute value is never recorded in Flow history. Dex stores it only in the blob store and records only a reference in Flow history. Calling delete(Context) or set(Context, Object) with null permanently deletes the stored value. After the deletion, the Attribute reads as absent.

      Only string and object values are supported. Object values include JSON-mapped classes and byte[]. Numeric and Boolean value types, such as Integer, Long, Double, and Boolean, are rejected when this option is applied. When the declared value type is broader, such as Object or Number, a write whose value maps to a number or a Boolean is rejected when it is written. Writing null is always allowed.

      This option cannot be combined with an AttributeIndex or with syncToAttributeStore(). Client.waitForAttributeMatch(String, Attribute, AttributeMatch, WaitForAttributeOptions) rejects a hard-delete Attribute before it contacts the Server. Use an RPC that reads the Attribute when a caller needs the value.

      The Dex Server must have an enabled blob store and must support Server protocol version 3 or later. A Step, RPC, StartFlow, or SubFlow start that writes this Attribute fails with IllegalStateException when the negotiated Server protocol is older. The Server rejects the write when its blob store is disabled. Steps and RPC handlers receive the value inline. The SDK never stores a hard-delete value in the local BlobCache.

      Know these limitations before you store a secret:

      • Each Flow keeps one stored copy per Attribute key. Every write overwrites that copy in place.
      • A Step or RPC attempt writes or deletes the stored value before Dex commits the attempt's result. A retried attempt, other Steps, and other readers can therefore observe a write or a deletion from an attempt that later failed.
      • When concurrent Steps or RPCs write the same key, the final stored value may not be the value from the last committed write. When the order matters, use a single writer, or make every Step and RPC that writes the key hold an AttributeLock on it. A failed locked attempt can still overwrite the value.
      • A Client.startFlow(Flow, String, I) call or SubFlow.run(Class, I) start that fails because the Flow ID is already running still overwrites the running Flow's value. With a null initial value, it deletes the running Flow's value.
      • Executions that reuse a Flow ID share the stored value.
      • Client.timeTravel(String, TimeTravelOptions) does not restore a deleted value. A deleted value reads as absent.
      • A Flow retry or a restarted SubFlow starts from the original initial value. If an earlier run deleted that value, the new run reads it as absent.
      • Removing hardDeleteOnNull() from a definition leaves the stored value in place until Dex removes the Flow. Delete the value with the hard-delete definition first.
      • A value remains in the blob store until it is deleted, or until Flow retention removes the Flow and blob cleanup runs.
      • Blob store backups and bucket versioning can retain copies after deletion. Configure them separately when deleted secrets must not survive.
      final class ProviderSyncFlow implements Flow<String> {
          final Attribute<String> apiKey =
                  Attribute.define("api-key", String.class).hardDeleteOnNull();
          private final SyncWithProvider sync = new SyncWithProvider(apiKey);
      
          @Override
          public StepList<String> getSteps() {
              return StepList.startStep(sync);
          }
      
          @Override
          public PersistenceSchema getPersistenceSchema() {
              return PersistenceSchema.of(apiKey);
          }
      }
      
      final class SyncWithProvider implements Step<String> {
          private final Attribute<String> apiKey;
      
          SyncWithProvider(final Attribute<String> apiKey) {
              this.apiKey = apiKey;
          }
      
          @Override
          public StepDecision execute(final Context context, final String accountId) {
              final String key = apiKey.get(context);
              ProviderApi.sync(accountId, key);
              // Permanently delete the secret once it is no longer needed.
              apiKey.delete(context);
              return StepDecision.gracefulComplete();
          }
      }
      
      // Start the Flow with the secret as an initial Attribute value.
      client.startFlow(
              flow,
              "provider-sync-42",
              "account-42",
              StartFlowOptions.newBuilder()
                      .addAttribute(flow.apiKey, System.getenv("PROVIDER_API_KEY"))
                      .build());
      
      Returns:
      a new immutable Attribute definition with hard deletion on null enabled
      Throws:
      IllegalStateException - if this definition has an index, uses syncToAttributeStore(), or declares a numeric or Boolean value type
    • get

      public T get(Context context)
      Reads this Attribute from the current invocation.
      Parameters:
      context - the Step or RPC invocation context
      Returns:
      the current typed value, or null when no value is stored
    • set

      public void set(Context context, T value)
      Writes this Attribute in the current invocation.
      Parameters:
      context - the Step or RPC invocation context
      value - the typed value to persist; null deletes the value
      Throws:
      IllegalArgumentException - if this is a hardDeleteOnNull() Attribute and the value maps to a number or a Boolean
    • delete

      public void delete(Context context)
      Deletes this Attribute in the current invocation.
      Parameters:
      context - the Step or RPC invocation context