Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,55 @@ concurrency:
cancel-in-progress: true

jobs:
checks:
java:
name: Java ${{ matrix.java }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- java: "11"
goal: "-Dmaven.test.skip=true clean package"
- java: "25"
goal: "clean verify"

steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up Java
uses: actions/setup-java@v5
with:
java-version: "24"
java-version: ${{ matrix.java }}
distribution: temurin
cache: maven

- name: Build and test Maven reactor
run: mvn --batch-mode clean verify
run: mvn --batch-mode ${{ matrix.goal }}

- name: Verify published artifact boundaries
run: bash scripts/verify-artifacts.sh

example:
name: Featurevisor example-1
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Java
uses: actions/setup-java@v5
with:
java-version: "25"
distribution: temurin
cache: maven

- name: Build SDK
run: mvn --batch-mode clean install

- name: Set up Node.js
uses: actions/setup-node@v7
with:
Expand Down
34 changes: 30 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Set up Java and Maven publishing
uses: actions/setup-java@v5
with:
java-version: "24"
java-version: "25"
distribution: temurin
cache: maven
server-id: github
Expand All @@ -36,14 +36,40 @@ jobs:
id: version
shell: bash
run: |
if [[ ! "$GITHUB_REF_NAME" =~ ^v3\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Expected a Featurevisor Java v3 semantic version tag" >&2
exit 1
fi
version="${GITHUB_REF_NAME#v}"
if ! grep -Fq "<revision>$version</revision>" pom.xml; then
echo "Tag $GITHUB_REF_NAME does not match the Maven revision" >&2
exit 1
fi
if ! grep -Fq "<version>$version</version>" README.md; then
echo "README installation version does not match $GITHUB_REF_NAME" >&2
exit 1
fi
if ! grep -Fq "version = \"$version\"" featurevisor-sdk/src/main/java/com/featurevisor/cli/CLI.java; then
echo "CLI version does not match $GITHUB_REF_NAME" >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Publishing Featurevisor Java $version"

- name: Build, test, and publish all artifacts
- name: Build and test all artifacts
run: >-
mvn --batch-mode
-Drevision=${{ steps.version.outputs.version }}
clean verify

- name: Verify published artifact boundaries
run: bash scripts/verify-artifacts.sh

- name: Publish all artifacts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
mvn --batch-mode
-Drevision=${{ steps.version.outputs.version }}
clean deploy
-DskipTests
deploy
34 changes: 21 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Add the Featurevisor Java SDK as a dependency with your desired version:
<dependency>
<groupId>com.featurevisor</groupId>
<artifactId>featurevisor-java</artifactId>
<version>0.1.0</version>
<version>3.0.0</version>
</dependency>
</dependencies>
```
Expand Down Expand Up @@ -133,6 +133,8 @@ Featurevisor f = Featurevisor.createFeaturevisor(

Most applications only need `Featurevisor.createFeaturevisor`, the `Featurevisor` instance type, and `Featurevisor.FeaturevisorOptions`. Public extension and observability types include `FeaturevisorModule`, `FeaturevisorDiagnostic`, and the datafile model types.

Concurrent evaluations are safe after an instance is configured. Do not call state-changing methods such as `setDatafile`, `setContext`, `setSticky`, `addModule`, `removeModule`, or `close` concurrently with evaluations or with each other. Apply those changes from a serialized update path. Module, event, and diagnostic callbacks must synchronize mutable state that they capture.

## Initialization

The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/building-datafiles/) content directly:
Expand Down Expand Up @@ -572,7 +574,7 @@ You can listen to these events that can occur at various stages in your applicat
### `datafile_set`

```java
Runnable unsubscribe = f.on("datafile_set", (event) -> {
FeaturevisorUnsubscribe unsubscribe = f.on(FeaturevisorEventName.DATAFILE_SET, (event) -> {
String revision = (String) event.get("revision"); // new revision
String previousRevision = (String) event.get("previousRevision");
Boolean revisionChanged = (Boolean) event.get("revisionChanged"); // true if revision has changed
Expand All @@ -586,7 +588,7 @@ Runnable unsubscribe = f.on("datafile_set", (event) -> {
});

// stop listening to the event
unsubscribe.run();
unsubscribe.unsubscribe();
```

The `features` array will contain keys of features that have either been:
Expand All @@ -600,7 +602,7 @@ compared to the previous datafile content that existed in the SDK instance.
### `context_set`

```java
Runnable unsubscribe = f.on("context_set", (event) -> {
FeaturevisorUnsubscribe unsubscribe = f.on(FeaturevisorEventName.CONTEXT_SET, (event) -> {
Boolean replaced = (Boolean) event.get("replaced"); // true if context was replaced
@SuppressWarnings("unchecked")
Map<String, Object> context = (Map<String, Object>) event.get("context"); // the new context
Expand All @@ -612,7 +614,7 @@ Runnable unsubscribe = f.on("context_set", (event) -> {
### `sticky_set`

```java
Runnable unsubscribe = f.on("sticky_set", (event) -> {
FeaturevisorUnsubscribe unsubscribe = f.on(FeaturevisorEventName.STICKY_SET, (event) -> {
Boolean replaced = (Boolean) event.get("replaced"); // true if sticky features got replaced
@SuppressWarnings("unchecked")
List<String> features = (List<String>) event.get("features"); // list of all affected feature keys
Expand All @@ -624,7 +626,7 @@ Runnable unsubscribe = f.on("sticky_set", (event) -> {
### `error`

```java
Emitter.UnsubscribeFunction unsubscribe = f.on(Emitter.EventName.ERROR, (event) -> {
FeaturevisorUnsubscribe unsubscribe = f.on(FeaturevisorEventName.ERROR, (event) -> {
FeaturevisorDiagnostic diagnostic = (FeaturevisorDiagnostic) event.get("diagnostic");
System.err.println(diagnostic.getMessage());
});
Expand All @@ -638,16 +640,16 @@ Besides logging with debug level enabled, you can also get more details about ho

```java
// flag
Map<String, Object> evaluation = f.evaluateFlag(featureKey, context);
Evaluation evaluation = f.evaluateFlag(featureKey, context);

// variation
Map<String, Object> evaluation = f.evaluateVariation(featureKey, context);
Evaluation evaluation = f.evaluateVariation(featureKey, context);

// variable
Map<String, Object> evaluation = f.evaluateVariable(featureKey, variableKey, context);
Evaluation evaluation = f.evaluateVariable(featureKey, variableKey, context);
```

The returned object will always contain the following properties:
The returned `Evaluation` exposes the following properties:

- `featureKey`: the feature key
- `reason`: the reason how the value was evaluated
Expand Down Expand Up @@ -749,6 +751,8 @@ FeaturevisorModule module = new FeaturevisorModule("diagnostic-module")

## Child instance

A child snapshots the parent keys that exist when it is spawned. Child values win for those keys. Parent keys introduced later are still inherited. Calling `close()` removes both child-owned listeners and subscriptions delegated to the parent.

When dealing with purely client-side applications, it is understandable that there is only one user involved, like in browser or mobile applications.

But when using Featurevisor SDK in server-side applications, where a single server instance can handle multiple user requests simultaneously, it is important to isolate the context for each request.
Expand All @@ -774,8 +778,11 @@ Similar to parent SDK, child instances also support several additional methods:

- `setContext`
- `setSticky`
- `evaluateFlag`
- `isEnabled`
- `evaluateVariation`
- `getVariation`
- `evaluateVariable`
- `getVariable`
- `getVariableBoolean`
- `getVariableString`
Expand Down Expand Up @@ -916,9 +923,10 @@ $ make verify-artifacts

### Releasing

- Manually create a new release on [GitHub](https://github.com/featurevisor/featurevisor-java/releases)
- Tag it with a prefix of `v`, like `v1.0.0`
- GitHub Actions publishes the parent POM, Java SDK, and OpenFeature provider to [GitHub Packages](https://github.com/orgs/featurevisor/packages?repo_name=featurevisor-java)
1. Merge the release changes into `main`.
2. Tag the release with a `v` prefix, such as `v3.0.0`, and push the tag.
3. GitHub Actions verifies and publishes the parent POM, Java SDK, and OpenFeature provider to [GitHub Packages](https://github.com/orgs/featurevisor/packages?repo_name=featurevisor-java).
4. Create the corresponding [GitHub release](https://github.com/featurevisor/featurevisor-java/releases).

## License

Expand Down
Loading