Skip to content

Add initial extstore types#2900

Open
cconstable wants to merge 8 commits into
masterfrom
extstore-foundation
Open

Add initial extstore types#2900
cconstable wants to merge 8 commits into
masterfrom
extstore-foundation

Conversation

@cconstable
Copy link
Copy Markdown

@cconstable cconstable commented Jun 4, 2026

What was changed

Adds the @Experimental External Storage API surface under io.temporal.payload.storage. Type
definitions only.

Types:

  • StorageDriver: driver contract (async store/retrieve, getName/getType).
  • StorageDriverClaim: claim-check token for an externally stored payload.
  • StorageDriverSelector: chooses a driver per payload, or keeps it inline.
  • StorageDriverStoreContext / StorageDriverRetrieveContext: driver-call contexts.
  • StorageDriverTargetInfo plus StorageDriverWorkflowInfo / StorageDriverActivityInfo: best-effort,
    store-side target identity.
  • ExternalStorage (plus Builder): config for drivers, optional selector, and size threshold.

Notes:

  • No WorkflowClientOptions setter yet.
  • Builder validation throws IllegalStateException. Stable diagnostic codes are a follow-up.

Why?

  • First part of part of Large Payloads -> External Storage work

Checklist

  • Added tests.

/** Configuration for offloading large payloads to external storage. */
@Experimental
public final class ExternalStorage {
private static final int DEFAULT_PAYLOAD_SIZE_THRESHOLD = 256 * 1024;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept this private to be consistent with TS. However, I had to remove DEFAULT_PAYLOAD_SIZE_THRESHOLD links from the doc comments so they would actually resolved (and not be broken). Seems reasonable to let users knows what the default is in the doc comments so I added a "Defaults to 256 KiB" but this feels a little worse to me than just making this public. Looking for feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this package-private (so just remove the private keyword) and then generate the docs via javadoc with -package? Haven't tried it myself and not sure what else might become docuemented.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think promoting to package is reasonable. One consideration for making it public is being able to reference it in generated docs e.g. on the setThreshold method "The default value is {@link ExternalStorage.DEFAULT_PAYLOAD_SIZE_THRESHOLD" instead of what I have now: "Defaults to 256 KiB".

@cconstable cconstable changed the title Add initial extstore types. Add initial extstore types Jun 4, 2026
return this;
}

public ExternalStorage build() {
Copy link
Copy Markdown
Author

@cconstable cconstable Jun 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, these all throw state exceptions (which are tested). In a subsequent PR, I would like to replace these with stable diagnostic codes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these in particular, do we need diagnostic codes? The messages are fairly clear as to what the issue is and prevent the use of external storage at startup rather than having to report during execution runtime.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a proponent of best-effort, stable diagnostic codes if the mechanism for adding them very easy. I have a little write-up about this that I wanted to share but the tl;dr is I think we should make a best effort to tag anything that's actionable if for nothing else it supports automated tooling (e.g. AI) and feels like a healthy industry trend (hand wavy gesture). I would not have felt this way 4 years ago.

@cconstable cconstable marked this pull request as ready for review June 4, 2026 20:34
@cconstable cconstable requested a review from a team as a code owner June 4, 2026 20:34

/** Context passed to {@link StorageDriver#retrieve}. */
@Experimental
public final class StorageDriverRetrieveContext {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about if making this and StorageDriverStoreContext interfaces instead of finalized classes. Might make it easier for us internally define the implementations and thus refactor/update them as necessary. In either case, there is burden on driver authors who write unit tests for their drivers to have to adapt to either changing APIs on classes or interfaces, but interfaces would allow them to implement however they deem necessary to fulfill the context contract.

private Builder() {}

/** At least one driver is required. When more than one is set, a selector is also required. */
public Builder setDrivers(@Nonnull List<StorageDriver> drivers) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we document that when calling setDrivers or setDriver that the last call wins? Not sure if that is clear, especially in the case of calling setDriver multiple times.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good builder pattern call out 😄 I'll update.

}

@Test(expected = IllegalStateException.class)
public void multipleDriversRequireSelector() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe some more tests validating that calling setDriver and setDrivers in various orders only allows the last call to win.

Copy link
Copy Markdown
Contributor

@jmaeagle99 jmaeagle99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments but overall looks good. Please hold off until a Java owner reviews and approves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants