Skip to content

Conversation

alexandraoberaigner
Copy link
Contributor

@alexandraoberaigner alexandraoberaigner commented Sep 19, 2025

This PR

this re-adds hook data support avoiding breaking changes in the binary (we introduced with v1.18.0)

Related Issues

#1472

###Notes

With v1.18.1 we reverted the hook data feature:

revert hook data to resolve bytecode incompatibility (#1613) (9845601)

@alexandraoberaigner alexandraoberaigner requested review from a team as code owners September 19, 2025 12:24
Copy link
Contributor

Summary of Changes

Hello @alexandraoberaigner, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request reintroduces and refines the support for HookData within the OpenFeature SDK, allowing individual hooks to maintain state across their lifecycle stages during a flag evaluation. This feature was previously removed in v1.18.0 and is now brought back without introducing breaking changes to the binary. The implementation ensures that each hook instance receives its own isolated data store, enabling more complex and stateful hook logic.

Highlights

  • Reintroduction of Hook Data Support: The pull request re-enables the ability for hooks to store and retrieve data across their various execution stages during a flag evaluation, a feature previously removed in v1.18.0.
  • New HookData Interface and Implementation: A new HookData interface is introduced, along with a DefaultHookData implementation, providing a mechanism for hooks to maintain state isolated to a single flag evaluation.
  • Enhanced HookContext: The HookContext class is updated to include a HookData field, allowing hooks to access their specific data store throughout the evaluation process.
  • Updated Hook Lifecycle Management: The HookSupport class is refactored to manage HookData instances, ensuring they are created per hook and passed correctly through the before, after, error, and finallyAfter stages.
  • Comprehensive Testing: New unit tests for HookData and HookContextWithData are added, and existing HookSupport tests are extended to validate the correct isolation and persistence of hook data across stages.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request re-introduces hook data support, which is a great addition for allowing hooks to maintain state across execution stages. The implementation is solid, with good test coverage for the new functionality. I've identified a few areas for improvement, mainly concerning code clarity, consistency, and a potential thread-safety issue in the HookData implementation. My detailed comments and suggestions are provided below.

@alexandraoberaigner alexandraoberaigner changed the title feat: add hook data support [DRAFT] feat: add hook data support Sep 19, 2025
@alexandraoberaigner alexandraoberaigner force-pushed the feat/hook-data-support branch 2 times, most recently from 9d41b69 to 0761dd1 Compare September 22, 2025 11:08
@alexandraoberaigner alexandraoberaigner changed the title [DRAFT] feat: add hook data support feat: add hook data support Sep 22, 2025
Signed-off-by: Alexandra Oberaigner <[email protected]>
Signed-off-by: Alexandra Oberaigner <[email protected]>
alexandraoberaigner and others added 5 commits September 23, 2025 14:25
Signed-off-by: Alexandra Oberaigner <[email protected]>
Signed-off-by: Guido Breitenhuber <[email protected]>
Signed-off-by: Guido Breitenhuber <[email protected]>
Signed-off-by: Guido Breitenhuber <[email protected]>
Copy link
Member

@aepfli aepfli left a comment

Choose a reason for hiding this comment

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

I do like the object based approach, but i have a hard time supporting the changes how hooksupport/hookexecutor is created for each execution. We are bound now for testing, and have no easy way to replace this implementation with a different one.

new SharedHookContext(key, type, this.getMetadata(), provider.getMetadata(), defaultValue);

var evalContext = mergeEvaluationContext(ctx);
hookExecutor = HookExecutor.create(mergedHooks, sharedHookContext, evalContext, hints);
Copy link
Member

Choose a reason for hiding this comment

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

[issue] Static constructors for object regularly used adds a lot of complexity for testing, we are now heavily bound during the testing of the OpenFeatureClient to the hookExecutor, mocking is only possible with advanced tooling manipulating the bytecode heavily. Although i like the idea of creating one executor per evaluation, i think this adds more complexity to the code now, and maintainability is decreased. Especially our hotpath should be easy to test without dependencies. Hence for clarity i am oppose introducing static creator methods for logic related objects.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pls let me know if the current changes are going into the direction you had in mind :)

Copy link
Member

Choose a reason for hiding this comment

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

it is going into the direction, but does not solve the issue, that our logic is still tightly coupled. We should keep the initialization within the constructor of our openFeatureClient, and split logic and data into two separate objects.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pls rereview

Optional.ofNullable(hook.before(hookContext, hints)).orElse(Optional.empty());
if (returnedEvalContext.isPresent()) {
// update shared evaluation context for all hooks
setEvaluationContext(evaluationContext.merge(returnedEvalContext.get()));
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should collect all hook contexts in a list and merge them only after the loop. This would mean that the context changes of a hook cannot be read by another hook. Not sure if this would be spec compliant though

Copy link
Contributor Author

@alexandraoberaigner alexandraoberaigner Sep 25, 2025

Choose a reason for hiding this comment

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

I couldn't find something in the spec right away, but with merging it right away we will be at least consistent with what is implemented in dotnet

details.setErrorMessage(e.getMessage());
enrichDetailsWithErrorDefaults(defaultValue, details);
hookSupport.errorHooks(type, afterHookContext, e, mergedHooks, hints);
hookSupport.executeErrorHooks(e);
Copy link
Contributor

Choose a reason for hiding this comment

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

add non null check to hookSupport

Copy link
Contributor Author

Choose a reason for hiding this comment

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

does this still apply? & if so, can you elaborate how/why you would add the null check?

return sharedContext.getProviderMetadata();
}

@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "Intentional exposure of hookData")
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder why this is needed. HookData is already public

Copy link
Contributor Author

Choose a reason for hiding this comment

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

spotbugs complained... 🤔

* Encapsulates data for hook execution per flag evaluation
*/
@Getter
class HookSupportData {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see the value of separating HookSupport and HookSupportData. We need to create a new object for every flag evaluation anyway

Copy link
Contributor Author

@alexandraoberaigner alexandraoberaigner Sep 25, 2025

Choose a reason for hiding this comment

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

This was my solution to @aepfli's comment here. It should simplify testing

Signed-off-by: Alexandra Oberaigner <[email protected]>
Signed-off-by: Alexandra Oberaigner <[email protected]>
Copy link

codecov bot commented Sep 25, 2025

Codecov Report

❌ Patch coverage is 84.32836% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.37%. Comparing base (3ef41f5) to head (ab2a549).
⚠️ Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
...in/java/dev/openfeature/sdk/SharedHookContext.java 50.00% 8 Missing ⚠️
...main/java/dev/openfeature/sdk/DefaultHookData.java 73.68% 5 Missing ⚠️
src/main/java/dev/openfeature/sdk/HookContext.java 81.81% 0 Missing and 4 partials ⚠️
...in/java/dev/openfeature/sdk/OpenFeatureClient.java 90.00% 0 Missing and 2 partials ⚠️
src/main/java/dev/openfeature/sdk/ObjectHook.java 0.00% 1 Missing ⚠️
src/main/java/dev/openfeature/sdk/Pair.java 87.50% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1620      +/-   ##
============================================
+ Coverage     92.06%   92.37%   +0.31%     
- Complexity      488      519      +31     
============================================
  Files            46       51       +5     
  Lines          1184     1272      +88     
  Branches        105      114       +9     
============================================
+ Hits           1090     1175      +85     
- Misses           62       63       +1     
- Partials         32       34       +2     
Flag Coverage Δ
unittests 92.37% <84.32%> (+0.31%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

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.

4 participants