From 95a77658eba4a63301060223e0a6db023ebf0d6e Mon Sep 17 00:00:00 2001 From: Chris Price Date: Thu, 11 Jul 2024 09:38:11 -0700 Subject: [PATCH] chore: add docs examples for PreviewStorageClient (#384) * chore: add docs examples for PreviewStorageClient --- .github/workflows/ci.yml | 18 +++ .github/workflows/on-push-to-main-branch.yml | 18 +++ .gitignore | 1 - examples/README.md | 10 -- examples/README.template.md | 13 ++ examples/cache-with-aws/build.gradle.kts | 2 +- examples/cache/build.gradle.kts | 2 +- .../doc_examples/DocExamplesJavaAPIs.java | 4 +- examples/lambda/docker/build.gradle.kts | 4 +- examples/settings.gradle.kts | 1 + examples/storage/README.template.md | 34 ++++ examples/storage/build.gradle.kts | 64 ++++++++ .../momento/client/example/BasicExample.java | 117 ++++++++++++++ .../example/doc_examples/CheatSheet.java | 16 ++ .../doc_examples/DocExamplesJavaAPIs.java | 146 ++++++++++++++++++ .../storage/src/main/resources/logback.xml | 17 ++ examples/token/build.gradle.kts | 2 +- examples/topic/build.gradle.kts | 2 +- 18 files changed, 452 insertions(+), 19 deletions(-) delete mode 100644 examples/README.md create mode 100644 examples/README.template.md create mode 100644 examples/storage/README.template.md create mode 100644 examples/storage/build.gradle.kts create mode 100644 examples/storage/src/main/java/momento/client/example/BasicExample.java create mode 100644 examples/storage/src/main/java/momento/client/example/doc_examples/CheatSheet.java create mode 100644 examples/storage/src/main/java/momento/client/example/doc_examples/DocExamplesJavaAPIs.java create mode 100644 examples/storage/src/main/resources/logback.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6d5805a..0b0ede22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,24 @@ jobs: sdk_language: Java dev_docs_slug: java + - name: Verify examples README generation + uses: momentohq/standards-and-practices/github-actions/oss-readme-template@gh-actions-v2 + with: + project_status: official + project_stability: beta + project_type: other + template_file: ./examples/README.template.md + output_file: ./examples/README.md + + - name: Verify Storage examples README generation + uses: momentohq/standards-and-practices/github-actions/oss-readme-template@gh-actions-v2 + with: + project_status: official + project_stability: beta + project_type: other + template_file: ./examples/storage/README.template.md + output_file: ./examples/storage/README.md + - name: Commitlint and Other Shared Build Steps uses: momentohq/standards-and-practices/github-actions/shared-build@gh-actions-v1 env: diff --git a/.github/workflows/on-push-to-main-branch.yml b/.github/workflows/on-push-to-main-branch.yml index 23b8bf92..47980817 100644 --- a/.github/workflows/on-push-to-main-branch.yml +++ b/.github/workflows/on-push-to-main-branch.yml @@ -21,3 +21,21 @@ jobs: project_type: sdk sdk_language: Java dev_docs_slug: java + + - name: Verify Storage examples README generation + uses: momentohq/standards-and-practices/github-actions/generate-and-commit-oss-readme@gh-actions-v2 + with: + project_status: official + project_stability: beta + project_type: other + template_file: ./examples/storage/README.template.md + output_file: ./examples/storage/README.md + + - name: Verify examples README generation + uses: momentohq/standards-and-practices/github-actions/generate-and-commit-oss-readme@gh-actions-v2 + with: + project_status: official + project_stability: beta + project_type: other + template_file: ./examples/README.template.md + output_file: ./examples/README.md diff --git a/.gitignore b/.gitignore index e00185e6..db9d3e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,3 @@ examples/lib/.classpath examples/lib/.project examples/lib/bin .vscode/ -*logback.xml diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 3e59a09c..00000000 --- a/examples/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Java Client SDK - -## Examples - -All examples are in different subdirectories for different use-cases, and minimizes the dependencies required to run them. - -- Examples to get started with Momento: - - https://github.com/momentohq/client-sdk-java/tree/main/examples/cache -- Examples that uses Momento with one or more AWS integrations, such as AWS Secrets Manager to store your Momento auth token: - - https://github.com/momentohq/client-sdk-java/tree/main/examples/cache-with-aws \ No newline at end of file diff --git a/examples/README.template.md b/examples/README.template.md new file mode 100644 index 00000000..038621d5 --- /dev/null +++ b/examples/README.template.md @@ -0,0 +1,13 @@ +{{ ossHeader }} + +# Momento Java SDK Examples + +## Examples + +All examples are in different subdirectories for different use-cases, and minimizes the dependencies required to run them. + +- [Examples to get started with Momento Cache](https://github.com/momentohq/client-sdk-java/tree/main/examples/cache) +- [Examples that uses Momento Cache with one or more AWS integrations, such as AWS Secrets Manager to store your Momento auth token](https://github.com/momentohq/client-sdk-java/tree/main/examples/cache-with-aws) +- [Examples to get started with Momento Storage](https://github.com/momentohq/client-sdk-java/tree/main/examples/storage) + +{{ ossFooter }} diff --git a/examples/cache-with-aws/build.gradle.kts b/examples/cache-with-aws/build.gradle.kts index fc817ceb..87f9af59 100644 --- a/examples/cache-with-aws/build.gradle.kts +++ b/examples/cache-with-aws/build.gradle.kts @@ -17,7 +17,7 @@ repositories { } dependencies { - implementation("software.momento.java:sdk:1.11.0") + implementation("software.momento.java:sdk:1.14.1") // For examples to store secrets in AWS Secrets Manager implementation("software.amazon.awssdk:secretsmanager:2.20.93") diff --git a/examples/cache/build.gradle.kts b/examples/cache/build.gradle.kts index 05042d76..26eb05b4 100644 --- a/examples/cache/build.gradle.kts +++ b/examples/cache/build.gradle.kts @@ -17,7 +17,7 @@ repositories { } dependencies { - implementation("software.momento.java:sdk:1.11.0") + implementation("software.momento.java:sdk:1.14.1") implementation("com.google.guava:guava:31.1-android") diff --git a/examples/cache/src/main/java/momento/client/example/doc_examples/DocExamplesJavaAPIs.java b/examples/cache/src/main/java/momento/client/example/doc_examples/DocExamplesJavaAPIs.java index 02198804..00e9068d 100644 --- a/examples/cache/src/main/java/momento/client/example/doc_examples/DocExamplesJavaAPIs.java +++ b/examples/cache/src/main/java/momento/client/example/doc_examples/DocExamplesJavaAPIs.java @@ -5,7 +5,7 @@ import momento.sdk.CacheClient; import momento.sdk.auth.CredentialProvider; import momento.sdk.config.Configurations; -import momento.sdk.exceptions.AlreadyExistsException; +import momento.sdk.exceptions.MomentoErrorCode; import momento.sdk.responses.cache.DeleteResponse; import momento.sdk.responses.cache.GetResponse; import momento.sdk.responses.cache.IncrementResponse; @@ -95,7 +95,7 @@ public static void example_API_CreateCache(CacheClient cacheClient) { if (response instanceof CacheCreateResponse.Success) { System.out.println("Cache 'test-cache' created"); } else if (response instanceof CacheCreateResponse.Error error) { - if (error.getCause() instanceof AlreadyExistsException) { + if (error.getErrorCode() == MomentoErrorCode.ALREADY_EXISTS_ERROR) { System.out.println("Cache 'test-cache' already exists"); } else { throw new RuntimeException( diff --git a/examples/lambda/docker/build.gradle.kts b/examples/lambda/docker/build.gradle.kts index 7b55b052..6a7a7553 100644 --- a/examples/lambda/docker/build.gradle.kts +++ b/examples/lambda/docker/build.gradle.kts @@ -12,7 +12,7 @@ repositories { dependencies { implementation("com.amazonaws:aws-lambda-java-core:1.2.1") - implementation("software.momento.java:sdk:1.11.0") + implementation("software.momento.java:sdk:1.14.1") } tasks.jar { @@ -25,4 +25,4 @@ tasks.shadowJar { archiveBaseName.set("docker") archiveClassifier.set("all") mergeServiceFiles() -} \ No newline at end of file +} diff --git a/examples/settings.gradle.kts b/examples/settings.gradle.kts index 803eadb1..35d139af 100644 --- a/examples/settings.gradle.kts +++ b/examples/settings.gradle.kts @@ -25,3 +25,4 @@ include("cache-with-aws") include("lambda:docker") include("token") include("topic") +include("storage") diff --git a/examples/storage/README.template.md b/examples/storage/README.template.md new file mode 100644 index 00000000..87db4d9d --- /dev/null +++ b/examples/storage/README.template.md @@ -0,0 +1,34 @@ +{{ ossHeader }} + +# Momento Java SDK - Storage Client Examples + +## Running the Examples + +- You do not need gradle to be installed +- JDK 14 or above is required to run the example +- To get started with Momento you will need a Momento API key. You can get one from the + [Momento Console](https://console.gomomento.com). + +### Basic + +```bash +MOMENTO_API_KEY= ./gradlew basic +``` + +Example Code: [BasicExample.java](src/main/java/momento/client/example/BasicExample.java) + +## Using the Java SDK in your project + +### Gradle Configuration + +Update your Gradle build to include the components + +#### build.gradle.kts + +```kotlin +dependencies { + implementation("software.momento.java:sdk:1.x.x") +} +``` + +{{ ossFooter }} diff --git a/examples/storage/build.gradle.kts b/examples/storage/build.gradle.kts new file mode 100644 index 00000000..04c75e16 --- /dev/null +++ b/examples/storage/build.gradle.kts @@ -0,0 +1,64 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/7.2/userguide/building_java_projects.html + */ + +plugins { + application + id("com.diffplug.spotless") version "5.15.1" +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + implementation("software.momento.java:sdk:1.14.1") + + // Logging framework to log and enable logging in the Momento client. + implementation("ch.qos.logback:logback-classic:1.4.7") + + // Use JUnit Jupiter for testing. + testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") +} + +spotless { + java { + removeUnusedImports() + googleJavaFormat("1.11.0") + } +} + +tasks.test { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} + +task("basic", JavaExec::class) { + description = "Run the basic example" + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("momento.client.example.BasicExample") +} + +task("docExamples", JavaExec::class) { + description = "Validate that the API doc examples run" + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("momento.client.example.doc_examples.DocExamplesJavaAPIs") +} + +task("docCheatSheet", JavaExec::class) { + description = "Validate that the doc cheat sheet runs" + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("momento.client.example.doc_examples.CheatSheet") +} + +task("docsTasks") { + dependsOn("docCheatSheet") + dependsOn("docExamples") +} + +task("prepareKotlinBuildScriptModel") {} diff --git a/examples/storage/src/main/java/momento/client/example/BasicExample.java b/examples/storage/src/main/java/momento/client/example/BasicExample.java new file mode 100644 index 00000000..b62eab18 --- /dev/null +++ b/examples/storage/src/main/java/momento/client/example/BasicExample.java @@ -0,0 +1,117 @@ +package momento.client.example; + +import java.nio.charset.StandardCharsets; +import momento.sdk.PreviewStorageClient; +import momento.sdk.auth.CredentialProvider; +import momento.sdk.config.StorageConfigurations; +import momento.sdk.exceptions.StoreAlreadyExistsException; +import momento.sdk.responses.storage.CreateStoreResponse; +import momento.sdk.responses.storage.GetResponse; +import momento.sdk.responses.storage.ListStoresResponse; +import momento.sdk.responses.storage.StoreInfo; + +public class BasicExample { + + private static final String API_KEY_ENV_VAR = "MOMENTO_API_KEY"; + + private static final String STORE_NAME = "store"; + private static final String KEY = "key"; + private static final String VALUE = "value"; + + public static void main(String[] args) { + printStartBanner(); + + final CredentialProvider credentialProvider = CredentialProvider.fromEnvVar(API_KEY_ENV_VAR); + + try (final var client = + new PreviewStorageClient(credentialProvider, StorageConfigurations.Laptop.latest())) { + + createStore(client, STORE_NAME); + + listStores(client); + + System.out.printf("Putting key '%s', value '%s'%n", KEY, VALUE); + // store a string value + client.put(STORE_NAME, KEY, VALUE).join(); + + // you can also store values of type byte[], long, or double: + byte[] bytesValue = "value".getBytes(StandardCharsets.UTF_8); + client.put(STORE_NAME, "bytes-key", bytesValue).join(); + client.put(STORE_NAME, "long-key", 42L).join(); + client.put(STORE_NAME, "double-key", 3.14).join(); + + System.out.printf("Getting value for key '%s'%n", KEY); + + final GetResponse getResponse = client.get(STORE_NAME, KEY).join(); + + // simplified style, if you are confident the value exists and is a String: + final String value = getResponse.valueWhenFound().get().getString().get(); + + // pattern-matching style, more suitable for production code: + if (getResponse instanceof GetResponse.Found hit) { + // if you're confident it's a String: + System.out.printf("Found value for key '%s': '%s'%n", KEY, hit.value().getString().get()); + + // you're not sure of the type: + switch (hit.value().getType()) { + case BYTE_ARRAY -> { + System.out.println("Got a byte array: " + hit.value().getByteArray().get()); + } + case STRING -> { + System.out.println("Got a string: " + hit.value().getString().get()); + } + case LONG -> { + System.out.println("Got a long: " + hit.value().getLong().get()); + } + case DOUBLE -> { + System.out.println("Got a double: " + hit.value().getDouble().get()); + } + } + } else if (getResponse instanceof GetResponse.NotFound) { + System.out.println("Found no value for key " + KEY); + } else if (getResponse instanceof GetResponse.Error error) { + System.out.printf( + "Unable to look up value for key '%s' with error %s\n", KEY, error.getErrorCode()); + System.out.println(error.getMessage()); + } + } + printEndBanner(); + } + + private static void createStore(PreviewStorageClient storageClient, String storeName) { + final CreateStoreResponse createResponse = storageClient.createStore(storeName).join(); + if (createResponse instanceof CreateStoreResponse.Error error) { + if (error.getCause() instanceof StoreAlreadyExistsException) { + System.out.println("Store with name '" + storeName + "' already exists."); + } else { + System.out.println("Unable to create store with error " + error.getErrorCode()); + System.out.println(error.getMessage()); + } + } + } + + private static void listStores(PreviewStorageClient storageClient) { + System.out.println("Listing caches:"); + final ListStoresResponse listResponse = storageClient.listStores().join(); + if (listResponse instanceof ListStoresResponse.Success success) { + for (StoreInfo storeInfo : success.getStores()) { + System.out.println(storeInfo.getName()); + } + } else if (listResponse instanceof ListStoresResponse.Error error) { + System.out.println("Unable to list caches with error " + error.getErrorCode()); + System.out.println(error.getMessage()); + } + } + + private static void printStartBanner() { + System.out.println("******************************************************************"); + System.out.println("Basic Example Start"); + System.out.println("******************************************************************"); + } + + private static void printEndBanner() { + System.out.println("******************************************************************"); + System.out.println("Basic Example End"); + System.out.println("******************************************************************"); + } +} diff --git a/examples/storage/src/main/java/momento/client/example/doc_examples/CheatSheet.java b/examples/storage/src/main/java/momento/client/example/doc_examples/CheatSheet.java new file mode 100644 index 00000000..20803e66 --- /dev/null +++ b/examples/storage/src/main/java/momento/client/example/doc_examples/CheatSheet.java @@ -0,0 +1,16 @@ +package momento.client.example.doc_examples; + +import momento.sdk.PreviewStorageClient; +import momento.sdk.auth.CredentialProvider; +import momento.sdk.config.StorageConfigurations; + +public class CheatSheet { + public static void main(String[] args) { + try (final var storageClient = + new PreviewStorageClient( + CredentialProvider.fromEnvVar("MOMENTO_API_KEY"), + StorageConfigurations.Laptop.latest())) { + // ... + } + } +} diff --git a/examples/storage/src/main/java/momento/client/example/doc_examples/DocExamplesJavaAPIs.java b/examples/storage/src/main/java/momento/client/example/doc_examples/DocExamplesJavaAPIs.java new file mode 100644 index 00000000..fdf7c6d2 --- /dev/null +++ b/examples/storage/src/main/java/momento/client/example/doc_examples/DocExamplesJavaAPIs.java @@ -0,0 +1,146 @@ +package momento.client.example.doc_examples; + +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; +import momento.sdk.PreviewStorageClient; +import momento.sdk.auth.CredentialProvider; +import momento.sdk.config.StorageConfigurations; +import momento.sdk.exceptions.StoreAlreadyExistsException; +import momento.sdk.responses.storage.*; + +public class DocExamplesJavaAPIs { + + @SuppressWarnings("EmptyTryBlock") + public static void example_API_Storage_InstantiateClient() { + try (PreviewStorageClient storageClient = + new PreviewStorageClient( + CredentialProvider.fromEnvVar("MOMENTO_API_KEY"), + StorageConfigurations.Laptop.latest())) { + // ... + } + } + + public static void example_API_Storage_CreateStore(PreviewStorageClient storageClient) { + final CreateStoreResponse response = storageClient.createStore("test-store").join(); + if (response instanceof CreateStoreResponse.Success) { + System.out.println("Store 'test-store' created"); + } else if (response instanceof CreateStoreResponse.Error error) { + if (error.getCause() instanceof StoreAlreadyExistsException) { + System.out.println("Store 'test-store' already exists"); + } else { + throw new RuntimeException( + "An error occurred while attempting to create store 'test-store': " + + error.getErrorCode(), + error); + } + } + } + + public static void example_API_Storage_DeleteStore(PreviewStorageClient storageClient) { + final DeleteStoreResponse response = storageClient.deleteStore("test-store").join(); + if (response instanceof DeleteStoreResponse.Success) { + System.out.println("Store 'test-store' deleted"); + } else if (response instanceof DeleteStoreResponse.Error error) { + throw new RuntimeException( + "An error occurred while attempting to delete store 'test-store': " + + error.getErrorCode(), + error); + } + } + + public static void example_API_Storage_ListStores(PreviewStorageClient storageClient) { + final ListStoresResponse response = storageClient.listStores().join(); + if (response instanceof ListStoresResponse.Success success) { + final String stores = + success.getStores().stream().map(StoreInfo::getName).collect(Collectors.joining("\n")); + System.out.println("Stores:\n" + stores); + } else if (response instanceof ListStoresResponse.Error error) { + throw new RuntimeException( + "An error occurred while attempting to list stores: " + error.getErrorCode(), error); + } + } + + public static void example_API_Storage_Put(PreviewStorageClient storageClient) { + // this example illustrates how to store a String value + final PutResponse response = storageClient.put("test-store", "test-key", "test-value").join(); + if (response instanceof PutResponse.Success) { + System.out.println("Key 'test-key' stored successfully"); + } else if (response instanceof PutResponse.Error error) { + throw new RuntimeException( + "An error occurred while attempting to store key 'test-key' in store 'test-store': " + + error.getErrorCode(), + error); + } + + // Momento Storage also supports storing values of type byte[], long, and double: + byte[] bytesValue = "test-byte-array-value".getBytes(StandardCharsets.UTF_8); + storageClient.put("test-store", "test-byte-array-key", bytesValue).join(); + storageClient.put("test-store", "test-integer-key", 42L).join(); + storageClient.put("test-store", "test-double-key", 42.0).join(); + } + + public static void example_API_Storage_Get(PreviewStorageClient storageClient) { + final GetResponse response = storageClient.get("test-store", "test-key").join(); + + // simplified style to access the value, if you're confident the value exists and you know the + // type. + // The optionals in this chain will throw exceptions when you call `.get()` if the item did not + // exist in the store, or is another type besides a String + final String value = response.valueWhenFound().get().getString().get(); + + // Or, you can use pattern-matching for more production-safe code: + if (response instanceof GetResponse.Found found) { + // if you know the value is a String: + String stringValue = + found + .value() + .getString() + .orElseThrow(() -> new RuntimeException("Value was not a String!")); + // if you don't know the type of the value: + switch (found.value().getType()) { + case STRING -> System.out.println("String value: " + found.value().getString().get()); + case BYTE_ARRAY -> System.out.println( + "Byte array value: " + found.value().getByteArray().get()); + case LONG -> System.out.println("Long value: " + found.value().getLong().get()); + case DOUBLE -> System.out.println("Double value: " + found.value().getDouble().get()); + } + } else if (response instanceof GetResponse.NotFound) { + System.out.println("Key 'test-key' was not found in store 'test-store'"); + } else if (response instanceof GetResponse.Error error) { + throw new RuntimeException( + "An error occurred while attempting to get key 'test-key' from store 'test-store': " + + error.getErrorCode(), + error); + } + } + + public static void example_API_Storage_Delete(PreviewStorageClient storageClient) { + final DeleteResponse response = storageClient.delete("test-store", "test-key").join(); + if (response instanceof DeleteResponse.Success) { + System.out.println("Key 'test-key' deleted successfully"); + } else if (response instanceof DeleteResponse.Error error) { + throw new RuntimeException( + "An error occurred while attempting to delete key 'test-key' from store 'test-store': " + + error.getErrorCode(), + error); + } + } + + public static void main(String[] args) { + example_API_Storage_InstantiateClient(); + try (final PreviewStorageClient storageClient = + new PreviewStorageClient( + CredentialProvider.fromEnvVar("MOMENTO_API_KEY"), + StorageConfigurations.Laptop.latest())) { + + example_API_Storage_CreateStore(storageClient); + example_API_Storage_DeleteStore(storageClient); + example_API_Storage_CreateStore(storageClient); + example_API_Storage_ListStores(storageClient); + + example_API_Storage_Put(storageClient); + example_API_Storage_Get(storageClient); + example_API_Storage_Delete(storageClient); + } + } +} diff --git a/examples/storage/src/main/resources/logback.xml b/examples/storage/src/main/resources/logback.xml new file mode 100644 index 00000000..29d89338 --- /dev/null +++ b/examples/storage/src/main/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n + + + + + + + diff --git a/examples/token/build.gradle.kts b/examples/token/build.gradle.kts index 9e62f61d..4d7404a5 100644 --- a/examples/token/build.gradle.kts +++ b/examples/token/build.gradle.kts @@ -17,7 +17,7 @@ repositories { } dependencies { - implementation("software.momento.java:sdk:1.11.0") + implementation("software.momento.java:sdk:1.14.1") implementation("com.google.guava:guava:31.1-android") diff --git a/examples/topic/build.gradle.kts b/examples/topic/build.gradle.kts index 21706ea1..2cd05c71 100644 --- a/examples/topic/build.gradle.kts +++ b/examples/topic/build.gradle.kts @@ -17,7 +17,7 @@ repositories { } dependencies { - implementation("software.momento.java:sdk:1.11.0") + implementation("software.momento.java:sdk:1.14.1") implementation("com.google.guava:guava:31.1-android")