From 38791cfbd454bc8dc38b36cc15920f01b20161f2 Mon Sep 17 00:00:00 2001 From: acarbonetto Date: Wed, 1 Nov 2023 03:38:43 -0700 Subject: [PATCH 01/40] Add raw-ffi design Signed-off-by: acarbonetto --- docs/design-raw-ffi.md | 150 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 docs/design-raw-ffi.md diff --git a/docs/design-raw-ffi.md b/docs/design-raw-ffi.md new file mode 100644 index 0000000000..95de11d142 --- /dev/null +++ b/docs/design-raw-ffi.md @@ -0,0 +1,150 @@ +# Babushka Socket Listener + +## Sequence Diagram - Unix Domain Socket Manager + +**Summary**: The Babushka "UDS" solution uses a socket manager to redis-client worker threads, and UDS to manage the communication +between the wrapper and redis-client threads. This works well because we allow the socket to manage the communication. This +results in simple/fast communication. But the worry is that the unix sockets can become a bottleneck for data-intensive communication; +ie read/write buffer operations become the bottleneck. + +_Observation_: We noticed that we are creating a fixed number of Babushka/Redis client connections based on the number of CPUs available. +It would be better to configure this thread count, or default it to the number of CPUs available. + +```mermaid +sequenceDiagram + +participant Wrapper as Client-Wrapper +participant ffi as Babushka FFI +participant manager as Babushka impl +participant worker as Tokio Worker +participant SocketListener as Socket Listener +participant Socket as Unix Domain Socket +participant Client as Redis + +activate Wrapper +activate Socket +activate Client +Wrapper ->>+ ffi: connect_to_redis +ffi ->>+ manager: start_socket_listener(init_callback) + manager ->> worker: Create Tokio::Runtime (count: CPUs) + activate worker + worker ->> SocketListener: listen_on_socket(init_callback) + SocketListener ->> SocketListener: loop: listen_on_client_stream + activate SocketListener + manager -->> ffi: socket_path +ffi -->>- Wrapper: socket_path + SocketListener -->> Client: BabushkaClient::new + SocketListener -->> Socket: UnixStreamListener::new +Wrapper ->> Socket: write buffer (socket_path) +Socket -->> Wrapper: + SocketListener ->> SocketListener: handle_request + SocketListener ->> Socket: read_values_loop(client_listener, client) + Socket -->> SocketListener: + SocketListener ->> Client: send(request) + Client -->> SocketListener: ClientUsageResult + SocketListener ->> Socket: write_result + Socket -->> SocketListener: +Wrapper ->> Socket: read buffer (socket_path) + Socket -->> Wrapper: +Wrapper ->> Wrapper: Result +Wrapper ->> ffi: close_connection + ffi ->> manager: close_connection + manager ->>- worker: close + worker ->>SocketListener: close + deactivate SocketListener + deactivate worker +deactivate Wrapper +deactivate Client +deactivate Socket +``` + +## Elements +* **Wrapper**: Our Babushka wrapper that exposes a client API (java, python, node, etc) +* **Babushka FFI**: Foreign Function Interface definitions from our wrapper to our Rust Babushka-Core +* **Babushka impl**: public interface layer and thread manager +* **Tokio Worker**: Tokio worker threads (number of CPUs) +* **SocketListener**: listens for work from the Socket, and handles commands +* **Unix Domain Socket**: Unix Domain Socket to handle communication +* **Redis**: Our data store + +## (Current) Raw-FFI Benchmark Test + +**Summary**: We copied the C# benchmarking implementation, and discovered that it wasn't using the Babushka/Redis client +at all, but instead spawning a single worker thread to connect to Redis using a general Rust Redis client. + +```mermaid +sequenceDiagram + +participant Wrapper as Client-Wrapper +participant ffi as Babushka FFI +participant worker as Tokio Worker +participant Client as Redis + +activate Wrapper +activate Client +Wrapper ->>+ ffi: create_connection + ffi ->>+ worker: Create Tokio::Runtime (count: 1) + worker ->> Client: new Redis::Client + ffi -->> Wrapper: Connection +Wrapper ->> ffi: command (GET/SET) + ffi ->>+ worker: Runtime::spawn + worker ->> Client: Connection::clone(command) + Client -->> worker: Result + worker -->> ffi: success_callback +ffi ->> Wrapper: async Result +deactivate Wrapper +deactivate Client +``` + +## Sequence Diagram - Managed Raw-FFI Client + +**Summary**: Following the socket listener/manager solution, we can create a [event manager](https://en.wikipedia.org/wiki/Reactor_pattern) +on the Rust side that will spawn worker threads available to execute event commands on-demand. FFI calls will petition the +worker thread manager for work request. + +_Expectation_: According to Shachar, it is our understanding that having a Tokio thead manager on the Rust side, and an event +manager on the Wrapper-side will create a lot of busy-waiting between the two thread managers. + +_Observation_: Go routines seems to have a decent solution using channels. Instead of waiting, we can close the threads +on the wrapper since, and (awaken) push the threads back to the channel once the Tokio threads are completed. + +```mermaid +sequenceDiagram + +participant Wrapper as Client-Wrapper +participant ffi as Babushka FFI +participant manager as Babushka impl +participant worker as Tokio Worker +participant RuntimeManager as Runtime Manager +participant Client as Redis + +activate Wrapper +activate Client +Wrapper ->>+ ffi: connect_to_redis +ffi ->>+ manager: start_thread_manager(init_callback) + manager ->> worker: Create Tokio::Runtime (count: CPUs) + activate worker + worker ->> RuntimeManager: wait_for_work(init_callback) + RuntimeManager ->> RuntimeManager: loop: wait + activate RuntimeManager + manager -->> ffi: callback +ffi -->>- Wrapper: callback + RuntimeManager -->> Client: BabushkaClient::new + +Wrapper ->> ffi: command: get(key) + ffi ->> manager: command: get(key) + manager ->> worker: command: get(key) + worker ->> Client: send(command) + Client -->> worker: Result + worker -->> ffi: success_callback +ffi ->> Wrapper: async Result + +Wrapper ->> ffi: close_connection + ffi ->> manager: close_connection + manager ->>- worker: close + worker ->>RuntimeManager: close + deactivate RuntimeManager + deactivate worker +deactivate Wrapper +deactivate Client +``` \ No newline at end of file From 29e22441d7e58388b34d20b8949d865db6d74713 Mon Sep 17 00:00:00 2001 From: acarbonetto Date: Wed, 1 Nov 2023 15:12:58 -0700 Subject: [PATCH 02/40] Update to add Shachars changes Signed-off-by: acarbonetto --- docs/design-raw-ffi.md | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/design-raw-ffi.md b/docs/design-raw-ffi.md index 95de11d142..331651613a 100644 --- a/docs/design-raw-ffi.md +++ b/docs/design-raw-ffi.md @@ -1,6 +1,8 @@ # Babushka Socket Listener -## Sequence Diagram - Unix Domain Socket Manager +## Unix Domain Socket Manager + +### Sequence Diagram **Summary**: The Babushka "UDS" solution uses a socket manager to redis-client worker threads, and UDS to manage the communication between the wrapper and redis-client threads. This works well because we allow the socket to manage the communication. This @@ -58,7 +60,7 @@ deactivate Client deactivate Socket ``` -## Elements +### Elements * **Wrapper**: Our Babushka wrapper that exposes a client API (java, python, node, etc) * **Babushka FFI**: Foreign Function Interface definitions from our wrapper to our Rust Babushka-Core * **Babushka impl**: public interface layer and thread manager @@ -67,11 +69,13 @@ deactivate Socket * **Unix Domain Socket**: Unix Domain Socket to handle communication * **Redis**: Our data store -## (Current) Raw-FFI Benchmark Test +## Raw-FFI Benchmark Test **Summary**: We copied the C# benchmarking implementation, and discovered that it wasn't using the Babushka/Redis client at all, but instead spawning a single worker thread to connect to Redis using a general Rust Redis client. +### (Current) Sequence Diagram for test + ```mermaid sequenceDiagram @@ -96,6 +100,35 @@ deactivate Wrapper deactivate Client ``` +### (New) Sequence Diagram for test + +Shachar [updated C# benchmark tests](https://github.com/aws/babushka/pull/559/files) to connect to babushka core, instead +of a redis client. + +```mermaid +sequenceDiagram + +participant Wrapper as Client-Wrapper +participant ffi as Babushka FFI +participant worker as Tokio Worker +participant Client as Redis + +activate Wrapper +activate Client +Wrapper ->>+ ffi: create_connection + ffi ->>+ worker: Create Tokio::Runtime (count: 1) + worker ->> Client: BabushkaClient::new + ffi -->> Wrapper: Connection +Wrapper ->> ffi: cmd[GET/SET] + ffi ->>+ worker: Runtime::spawn + worker ->> Client: Connection::clone.req_packed_command(cmd) + Client -->> worker: Result + worker -->> ffi: success_callback +ffi ->> Wrapper: async Result +deactivate Wrapper +deactivate Client +``` + ## Sequence Diagram - Managed Raw-FFI Client **Summary**: Following the socket listener/manager solution, we can create a [event manager](https://en.wikipedia.org/wiki/Reactor_pattern) From 32b751e56b1494992139ec6e3b4d1f9507bd7c64 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Mon, 20 Nov 2023 09:24:24 -0800 Subject: [PATCH 03/40] Update design documentation Signed-off-by: Andrew Carbonetto --- docs/design-raw-ffi.md | 272 +++++++++++++++++++++++------------------ 1 file changed, 154 insertions(+), 118 deletions(-) diff --git a/docs/design-raw-ffi.md b/docs/design-raw-ffi.md index 331651613a..10711a8b6a 100644 --- a/docs/design-raw-ffi.md +++ b/docs/design-raw-ffi.md @@ -1,16 +1,58 @@ -# Babushka Socket Listener +# Babushka Core Wrappers -## Unix Domain Socket Manager +## Summary -### Sequence Diagram +The Babushka client enables Redis users connect to Redis using a variety of languages and supported commands. The client +uses a performant core to establish and manage connections, and a thin wrapper layer written in various languages, to +communicate -**Summary**: The Babushka "UDS" solution uses a socket manager to redis-client worker threads, and UDS to manage the communication -between the wrapper and redis-client threads. This works well because we allow the socket to manage the communication. This -results in simple/fast communication. But the worry is that the unix sockets can become a bottleneck for data-intensive communication; -ie read/write buffer operations become the bottleneck. +The following document discusses two primary communication protocol architectures for wrapping the babushka clients, and +how Java-Babushka and Go-Babushka use each of those protocols differently by taking advantage of language-specific attributes. -_Observation_: We noticed that we are creating a fixed number of Babushka/Redis client connections based on the number of CPUs available. -It would be better to configure this thread count, or default it to the number of CPUs available. +### References + +* Babushka - communication protocol architecture: describes high-level approaches to communication protocols. + +# Unix Domain Socket Manager Connector + +## High-Level Design + +**Summary**: The Babushka "UDS" solution uses a socket listener to manage rust-to-wrapper worker threads, and unix domain sockets +to deliver package between the wrapper and redis-client threads. This works well because we allow the unix sockets to pass messages +through the OS and is very performant. This results in simple/fast communication. But the concern is that the unix sockets can +become a bottleneck for data-intensive communication; ie read/write buffer operations. + +```mermaid +stateDiagram-v2 + direction LR + + JavaWrapper: Java Wrapper + UnixDomainSocket: Unix Domain Socket + RustCore: Rust-Core + + [*] --> JavaWrapper: User + JavaWrapper --> UnixDomainSocket + UnixDomainSocket --> JavaWrapper + RustCore --> UnixDomainSocket + UnixDomainSocket --> RustCore + RustCore --> Redis +``` + +## Decision to use UDS Sockets for a Java-Babushka Wrapper + +The decision to use Unix Domain Sockets (UDS) to manage the Java-wrapper to Babushka Redis-client communication was two-fold, as opposed to a raw-FFI solution: +1. Java contains an efficient socket protocol library (netty.io) that provides a highly configurable environment to manage sockets. +2. Java objects serialization/de-serialization is an expensive operation, and a performing multiple io operations between raw-ffi calls would be inefficient. + +### Decision Log + +| Protocol | Details | Pros | Cons | +|----------------------------------------------|---------------------------------------------------------|-----------------------------|----------------------------------------------------| +| Unix Domain Sockets (jni/netty) | JNI to submit commands; netty.io for message passing | netty.io standard lib; | complex configuration; limited by socket interface | +| Raw-FFI (JNA, uniffi-rs, j4rs, interoptopus) | JNA to submit commands; protobuf for message processing | reusable in other languages | Slow performance and uses JNI under the hood; | +| Panama/jextract | Performance similar to a raw-ffi using JNI | | lacks early Java support (JDK 18+); prototype | + +### Sequence Diagram ```mermaid sequenceDiagram @@ -24,10 +66,9 @@ participant Socket as Unix Domain Socket participant Client as Redis activate Wrapper -activate Socket activate Client -Wrapper ->>+ ffi: connect_to_redis -ffi ->>+ manager: start_socket_listener(init_callback) +Wrapper -)+ ffi: connect_to_redis +ffi -)+ manager: start_socket_listener(init_callback) manager ->> worker: Create Tokio::Runtime (count: CPUs) activate worker worker ->> SocketListener: listen_on_socket(init_callback) @@ -35,149 +76,144 @@ ffi ->>+ manager: start_socket_listener(init_callback) activate SocketListener manager -->> ffi: socket_path ffi -->>- Wrapper: socket_path - SocketListener -->> Client: BabushkaClient::new - SocketListener -->> Socket: UnixStreamListener::new -Wrapper ->> Socket: write buffer (socket_path) -Socket -->> Wrapper: - SocketListener ->> SocketListener: handle_request - SocketListener ->> Socket: read_values_loop(client_listener, client) - Socket -->> SocketListener: - SocketListener ->> Client: send(request) - Client -->> SocketListener: ClientUsageResult - SocketListener ->> Socket: write_result - Socket -->> SocketListener: -Wrapper ->> Socket: read buffer (socket_path) + SocketListener -->> Socket: UnixStreamListener::new + activate Socket + SocketListener -->> Client: BabushkaClient::new +Wrapper ->> Socket: connect + Socket -->> Wrapper: +loop single_request + Wrapper ->> Socket: netty.writeandflush (protobuf.redis_request) Socket -->> Wrapper: -Wrapper ->> Wrapper: Result -Wrapper ->> ffi: close_connection - ffi ->> manager: close_connection - manager ->>- worker: close - worker ->>SocketListener: close - deactivate SocketListener - deactivate worker -deactivate Wrapper -deactivate Client -deactivate Socket + Wrapper ->> Wrapper: wait + SocketListener ->> SocketListener: handle_request + SocketListener ->> Socket: read_values_loop(client_listener, client) + Socket -->> SocketListener: + SocketListener ->> Client: send(request) + Client -->> SocketListener: ClientUsageResult + SocketListener ->> Socket: write_result + Socket -->> SocketListener: + Wrapper ->> Socket: netty.read (protobuf.response) + Socket -->> Wrapper: + Wrapper ->> Wrapper: Result +end +Wrapper ->> Socket: close() +Wrapper ->> SocketListener: shutdown + SocketListener ->> Socket: close() + deactivate Socket + SocketListener ->> Client: close() + SocketListener -->> Wrapper: + deactivate SocketListener + deactivate worker + deactivate Wrapper + deactivate Client ``` ### Elements * **Wrapper**: Our Babushka wrapper that exposes a client API (java, python, node, etc) * **Babushka FFI**: Foreign Function Interface definitions from our wrapper to our Rust Babushka-Core * **Babushka impl**: public interface layer and thread manager -* **Tokio Worker**: Tokio worker threads (number of CPUs) +* **Tokio Worker**: Tokio worker threads (number of CPUs) * **SocketListener**: listens for work from the Socket, and handles commands * **Unix Domain Socket**: Unix Domain Socket to handle communication * **Redis**: Our data store -## Raw-FFI Benchmark Test +## Wrapper-to-Core Connector with raw-FFI calls -**Summary**: We copied the C# benchmarking implementation, and discovered that it wasn't using the Babushka/Redis client -at all, but instead spawning a single worker thread to connect to Redis using a general Rust Redis client. - -### (Current) Sequence Diagram for test +**Summary**: Foreign Function Calls are simple to implement inter-language calls. The setup between Golang and the Rust-core +is fairly simple using the well-supported CGO library. While sending language calls is easy, setting it up in an async manner +is more complex because of the requirement to handle async callbacks. Golang as a simple, light-weight solution, using goroutines +and channels, to pass callbacks and execution between langugages. ```mermaid -sequenceDiagram - -participant Wrapper as Client-Wrapper -participant ffi as Babushka FFI -participant worker as Tokio Worker -participant Client as Redis - -activate Wrapper -activate Client -Wrapper ->>+ ffi: create_connection - ffi ->>+ worker: Create Tokio::Runtime (count: 1) - worker ->> Client: new Redis::Client - ffi -->> Wrapper: Connection -Wrapper ->> ffi: command (GET/SET) - ffi ->>+ worker: Runtime::spawn - worker ->> Client: Connection::clone(command) - Client -->> worker: Result - worker -->> ffi: success_callback -ffi ->> Wrapper: async Result -deactivate Wrapper -deactivate Client +stateDiagram-v2 + direction LR + + Wrapper: Golang Wrapper + FFI: Foreign Function Interface + RustCore: Rust-Core + + [*] --> Wrapper: User + Wrapper --> FFI + FFI --> Wrapper + RustCore --> FFI + FFI --> RustCore + RustCore --> Redis ``` -### (New) Sequence Diagram for test +## Decision to use Raw-FFI calls directly to Rust-Core for Golang Wrapper -Shachar [updated C# benchmark tests](https://github.com/aws/babushka/pull/559/files) to connect to babushka core, instead -of a redis client. +### Decision Log -```mermaid -sequenceDiagram +***TODO*** -participant Wrapper as Client-Wrapper -participant ffi as Babushka FFI -participant worker as Tokio Worker -participant Client as Redis +| Protocol | Details | Pros | Cons | +|----------------------------------------------|---------|------|------| +| Unix Domain Sockets (jni/netty) | | | | +| Raw-FFI (JNA, uniffi-rs, j4rs, interoptopus) | | | | -activate Wrapper -activate Client -Wrapper ->>+ ffi: create_connection - ffi ->>+ worker: Create Tokio::Runtime (count: 1) - worker ->> Client: BabushkaClient::new - ffi -->> Wrapper: Connection -Wrapper ->> ffi: cmd[GET/SET] - ffi ->>+ worker: Runtime::spawn - worker ->> Client: Connection::clone.req_packed_command(cmd) - Client -->> worker: Result - worker -->> ffi: success_callback -ffi ->> Wrapper: async Result -deactivate Wrapper -deactivate Client -``` +## Sequence Diagram - Raw-FFI Client -## Sequence Diagram - Managed Raw-FFI Client +**Summary**: If we make direct calls through FFI from our Wrapper to Rust, we can initiate commands to Redis. This allows us +to make on-demand calls directly to Rust-core solution. Since the calls are async, we need to manage and populate a callback +object with the response and a payload. -**Summary**: Following the socket listener/manager solution, we can create a [event manager](https://en.wikipedia.org/wiki/Reactor_pattern) -on the Rust side that will spawn worker threads available to execute event commands on-demand. FFI calls will petition the -worker thread manager for work request. +We will need to avoid busy waits while waiting on the async response. The wrapper and Rust-core languages independently track +threads. On the Rust side, they use a Tokio runtime to manage threads. When the Rust-core is complete, and returning a Response, +we can use the Callback object to re-awake the wrapper thread manager and continue work. -_Expectation_: According to Shachar, it is our understanding that having a Tokio thead manager on the Rust side, and an event -manager on the Wrapper-side will create a lot of busy-waiting between the two thread managers. +Go routines have a performant solution using light-weight go-routines and channels. Instead of busy-waiting, we awaken by +pushing goroutines to the result channel once the Tokio threads send back a callback. + +### Sequence Diagram -_Observation_: Go routines seems to have a decent solution using channels. Instead of waiting, we can close the threads -on the wrapper since, and (awaken) push the threads back to the channel once the Tokio threads are completed. ```mermaid sequenceDiagram participant Wrapper as Client-Wrapper +participant channel as Result Channel participant ffi as Babushka FFI participant manager as Babushka impl participant worker as Tokio Worker -participant RuntimeManager as Runtime Manager participant Client as Redis activate Wrapper activate Client -Wrapper ->>+ ffi: connect_to_redis +Wrapper -)+ ffi: create_connection(connection_settings) ffi ->>+ manager: start_thread_manager(init_callback) manager ->> worker: Create Tokio::Runtime (count: CPUs) activate worker - worker ->> RuntimeManager: wait_for_work(init_callback) - RuntimeManager ->> RuntimeManager: loop: wait - activate RuntimeManager - manager -->> ffi: callback -ffi -->>- Wrapper: callback - RuntimeManager -->> Client: BabushkaClient::new - -Wrapper ->> ffi: command: get(key) - ffi ->> manager: command: get(key) - manager ->> worker: command: get(key) - worker ->> Client: send(command) + manager -->> Wrapper: Ok(BabushkaClient) + worker ->> Client: BabushkaClient::new + worker ->> worker: wait_for_work(init_callback) + +loop single_request +Wrapper ->> channel: make channel + activate channel +Wrapper -) ffi: command: single_command(protobuf.redis_request, &channel) +Wrapper ->> channel: wait + ffi ->> manager: cmd(protobuf.redis_request) + manager ->> worker: command: cmd(protobuf.redis_request) + worker ->> Client: send(command, args) Client -->> worker: Result - worker -->> ffi: success_callback -ffi ->> Wrapper: async Result - -Wrapper ->> ffi: close_connection - ffi ->> manager: close_connection - manager ->>- worker: close - worker ->>RuntimeManager: close - deactivate RuntimeManager - deactivate worker -deactivate Wrapper -deactivate Client -``` \ No newline at end of file + worker -->> ffi: Ok(protobuf.response) + ffi -->> channel: Ok(protobuf.response) +channel ->> Wrapper: protobuf.response +Wrapper ->> channel: close + deactivate channel +end + +Wrapper -) worker: close_connection + worker -->> Wrapper: + deactivate worker + deactivate Wrapper + deactivate Client +``` + +### Elements +* **Client-Wrapper**: Our Babushka wrapper that exposes a client API (Go, etc) +* **Result Channel**: Goroutine channel on the Babushka Wrapper +* **Babushka FFI**: Foreign Function Interface definitions from our wrapper to our Rust Babushka-Core +* **Babushka impl**: public interface layer and thread manager +* **Tokio Worker**: Tokio worker threads (number of CPUs) +* **Redis**: Our data store \ No newline at end of file From 799b2487c96f1f8cfee7b5a9275ef9d4d298a873 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Mon, 20 Nov 2023 11:57:55 -0800 Subject: [PATCH 04/40] Update docs Signed-off-by: Andrew Carbonetto --- docs/design-raw-ffi.md | 43 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/docs/design-raw-ffi.md b/docs/design-raw-ffi.md index 10711a8b6a..387f69e11e 100644 --- a/docs/design-raw-ffi.md +++ b/docs/design-raw-ffi.md @@ -2,25 +2,22 @@ ## Summary -The Babushka client enables Redis users connect to Redis using a variety of languages and supported commands. The client -uses a performant core to establish and manage connections, and a thin wrapper layer written in various languages, to -communicate +The Babushka client allows Redis users to connect to Redis using a variety of commands through a thin-client optimized for +various languages. The client uses a performant core to establish and manage connections and communicate with Redis. The thin-client +wrapper talks to the core using an FFI (foreign function interface) to Rust. -The following document discusses two primary communication protocol architectures for wrapping the babushka clients, and -how Java-Babushka and Go-Babushka use each of those protocols differently by taking advantage of language-specific attributes. - -### References - -* Babushka - communication protocol architecture: describes high-level approaches to communication protocols. +The following document discusses two primary communication protocol architectures for wrapping the Babushka clients. Specifically, +it details how Java-Babushka and Go-Babushka each use a different protocol and describes the advantages of each language-specific approach. # Unix Domain Socket Manager Connector ## High-Level Design **Summary**: The Babushka "UDS" solution uses a socket listener to manage rust-to-wrapper worker threads, and unix domain sockets -to deliver package between the wrapper and redis-client threads. This works well because we allow the unix sockets to pass messages -through the OS and is very performant. This results in simple/fast communication. But the concern is that the unix sockets can -become a bottleneck for data-intensive communication; ie read/write buffer operations. +to deliver command requests between the wrapper and redis-client threads. This works well because we allow the unix sockets to pass messages and manage threads +through the OS, and unix sockets are very performant. This results in simple/fast communication. The risk to avoid is that +unix sockets can become a bottleneck for data-intensive commands, and the library can spend too much time waiting on I/O +blocking operations. ```mermaid stateDiagram-v2 @@ -40,7 +37,7 @@ stateDiagram-v2 ## Decision to use UDS Sockets for a Java-Babushka Wrapper -The decision to use Unix Domain Sockets (UDS) to manage the Java-wrapper to Babushka Redis-client communication was two-fold, as opposed to a raw-FFI solution: +The decision to use Unix Domain Sockets (UDS) to manage the Java-wrapper to Babushka Redis-client communication was two-fold, vs a raw-FFI solution: 1. Java contains an efficient socket protocol library (netty.io) that provides a highly configurable environment to manage sockets. 2. Java objects serialization/de-serialization is an expensive operation, and a performing multiple io operations between raw-ffi calls would be inefficient. @@ -50,7 +47,7 @@ The decision to use Unix Domain Sockets (UDS) to manage the Java-wrapper to Babu |----------------------------------------------|---------------------------------------------------------|-----------------------------|----------------------------------------------------| | Unix Domain Sockets (jni/netty) | JNI to submit commands; netty.io for message passing | netty.io standard lib; | complex configuration; limited by socket interface | | Raw-FFI (JNA, uniffi-rs, j4rs, interoptopus) | JNA to submit commands; protobuf for message processing | reusable in other languages | Slow performance and uses JNI under the hood; | -| Panama/jextract | Performance similar to a raw-ffi using JNI | | lacks early Java support (JDK 18+); prototype | +| Panama/jextract | Performance similar to a raw-ffi using JNI | modern | lacks early Java support (JDK 18+); prototype | ### Sequence Diagram @@ -119,10 +116,10 @@ Wrapper ->> SocketListener: shutdown ## Wrapper-to-Core Connector with raw-FFI calls -**Summary**: Foreign Function Calls are simple to implement inter-language calls. The setup between Golang and the Rust-core +**Summary**: Foreign Function Interface (FFI) calls are simple to implement, cross-language calls. The setup between Golang and the Rust-core is fairly simple using the well-supported CGO library. While sending language calls is easy, setting it up in an async manner -is more complex because of the requirement to handle async callbacks. Golang as a simple, light-weight solution, using goroutines -and channels, to pass callbacks and execution between langugages. +requires that we handle async callbacks. Golang has a simple, light-weight solution to that, using goroutines and channels, +to pass callbacks and execution between the languages. ```mermaid stateDiagram-v2 @@ -144,12 +141,14 @@ stateDiagram-v2 ### Decision Log -***TODO*** +The decision to use raw FFI request from Golang to Rust-core was straight forward: +1. Golang contains goroutines as an alternative, lightweight, and performant solution serves as an obvious solution to pass request, even at scale. + -| Protocol | Details | Pros | Cons | -|----------------------------------------------|---------|------|------| -| Unix Domain Sockets (jni/netty) | | | | -| Raw-FFI (JNA, uniffi-rs, j4rs, interoptopus) | | | | +| Protocol | Details | Pros | Cons | +|--------------------------|---------|--------------------------------------------------------|--------------------------------------| +| Unix Domain Sockets | | UDS performance; consistent protocol between languages | complex configuration | +| Raw-FFI (CGO/goroutines) | | simplified and light-weight interface | separate management for each request | ## Sequence Diagram - Raw-FFI Client From a86e42416ca8c3c23823ca96977b7d05cdf5ff25 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Tue, 21 Nov 2023 15:11:25 -0800 Subject: [PATCH 05/40] Add API design doc --- docs/design-api.md | 524 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 524 insertions(+) create mode 100644 docs/design-api.md diff --git a/docs/design-api.md b/docs/design-api.md new file mode 100644 index 0000000000..8c7012b7d6 --- /dev/null +++ b/docs/design-api.md @@ -0,0 +1,524 @@ +API Design + +# Client Wrapper API design doc + +## API requirements: +- The API will be thread-safe +- The API will accept as inputs all of RESP3 types. +- The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. + +## Command Interface + +### Unix Domain Socket solution +For clients based on Unix Domain Sockets (UDS), we will simply use the existing protobuf messages for creating a connection, sending requests, and receiving responses. Supported commands are enumerated in the [protobuf definition for requests](https://github.com/Bit-Quill/babushka/blob/main/babushka-core/src/protobuf/redis_request.proto) and we may add more in the future, although the `CustomCommand` request type is also adequate for all commands. As defined in the [protobuf definition for responses](https://github.com/Bit-Quill/babushka/blob/main/babushka-core/src/protobuf/response.proto), client wrappers will receive data as a pointer, which can be passed to Rust to marshal the data back into the wrapper language’s native data types. + +### Raw FFI solution +For clients using a raw FFI solution, in Rust, we will expose a general command that is able to take any command and arguments as strings. + +We have 2 options for passing the command, arguments, and any additional configuration to the Rust core from the wrapper language: + +#### Protobuf +The wrapper language will pass the commands, arguments, and configuration as protobuf messages using the same definitions as in the UDS solution. + +Pros: +- We get to reuse the protobuf definitions, meaning fewer files to update if we make changes to the protobuf definitions +- May be simpler to implement compared to the C data types solution, since we do not need to define our own C data types + +Cons: +- There is additional overhead from marshalling data to and from protobuf, which could impact performance significantly + +#### C Data Types +The wrapper language will pass commands, arguments, and configuration as C data types. + +Pros: +- No additional overhead from marshalling to and from protobuf, so this should perform better +- May be simpler to implement compared to protobuf solution, since it can be tricky to construct protobuf messages in a performant way and we have to add a varint to the messages as well + +Cons: +- Would add an additional file to maintain containing the C definitions (only one file though, since we could share between all raw FFI solutions), which we would need to update every time we want to update the existing protobuf definitions + +We will be testing both approaches to see which is easier to implement, as well as the performance impact before deciding on a solution. + +To marshal Redis data types back into the corresponding types for the wrapper language, we will convert them into appropriate C types, which can then be translated by the wrapper language into its native data types. + +In our client wrappers, we will expose separate methods for each supported command. We do not need to validate data types for inputs, because Redis will do that for us. + +Supported Commands +ACL CAT +ACL DELUSER +ACL DRYRUN +ACL GENPASS +ACL GETUSER +ACL LIST +ACL LOAD +ACL LOG +ACL SAVE +ACL SETUSER +ACL USERS +ACL WHOAMI +APPEND +ASKING +AUTH +BF.ADD +BF.CARD +BF.EXISTS +BF.INFO +BF.INSERT +BF.LOADCHUNK +BF.MADD +BF.MEXISTS +BF.RESERVE +BF.SCANDUMP +BGREWRITEAOF +BGSAVE +BITCOUNT +BITFIELD +BITFIELD_RO +BITOP +BITPOS +BLMOVE +BLMPOP +BLPOP +BRPOP +BRPOPLPUSH +BZMPOP +BZPOPMAX +BZPOPMIN +CF.ADD +CF.ADDNX +CF.COUNT +CF.DEL +CF.EXISTS +CF.INFO +CF.INSERT +CF.INSERTNX +CF.LOADCHUNK +CF.MEXISTS +CF.RESERVE +CF.SCANDUMP +CLIENT CACHING +CLIENT GETNAME +CLIENT GETREDIR +CLIENT ID +CLIENT INFO +CLIENT KILL +CLIENT LIST +CLIENT NO-EVICT +CLIENT NO-TOUCH +CLIENT PAUSE +CLIENT REPLY +CLIENT SETINFO +CLIENT SETNAME +CLIENT TRACKING +CLIENT TRACKINGINFO +CLIENT UNBLOCK +CLIENT UNPAUSE +CLUSTER ADDSLOTS +CLUSTER ADDSLOTSRANGE +CLUSTER BUMPEPOCH +CLUSTER COUNT-FAILURE-REPORTS +CLUSTER COUNTKEYSINSLOT +CLUSTER DELSLOTS +CLUSTER DELSLOTSRANGE +CLUSTER FAILOVER +CLUSTER FLUSHSLOTS +CLUSTER FORGET +CLUSTER GETKEYSINSLOT +CLUSTER INFO +CLUSTER KEYSLOT +CLUSTER LINKS +CLUSTER MEET +CLUSTER MYID +CLUSTER MYSHARDID +CLUSTER NODES +CLUSTER REPLICAS +CLUSTER REPLICATE +CLUSTER RESET +CLUSTER SAVECONFIG +CLUSTER SET-CONFIG-EPOCH +CLUSTER SETSLOT +CLUSTER SHARDS +CLUSTER SLAVES +CLUSTER SLOTS +CMS.INCRBY +CMS.INFO +CMS.INITBYDIM +CMS.INITBYPROB +CMS.MERGE +CMS.QUERY +COMMAND +COMMAND COUNT +COMMAND DOCS +COMMAND GETKEYS +COMMAND GETKEYSANDFLAGS +COMMAND INFO +COMMAND LIST +CONFIG GET +CONFIG RESETSTAT +CONFIG REWRITE +CONFIG SET +COPY +DBSIZE +DECR +DECRBY +DEL +DISCARD +DUMP +ECHO +EVAL +EVAL_RO +EVALSHA +EVALSHA_RO +EXEC +EXISTS +EXPIRE +EXPIREAT +EXPIRETIME +FAILOVER +FCALL +FCALL_RO +FLUSHALL +FLUSHDB +FT._LIST +FT.AGGREGATE +FT.ALIASADD +FT.ALIASDEL +FT.ALIASUPDATE +FT.ALTER +FT.CONFIG GET +FT.CONFIG SET +FT.CREATE +FT.CURSOR DEL +FT.CURSOR READ +FT.DICTADD +FT.DICTDEL +FT.DICTDUMP +FT.DROPINDEX +FT.EXPLAIN +FT.EXPLAINCLI +FT.INFO +FT.PROFILE +FT.SEARCH +FT.SPELLCHECK +FT.SUGADD +FT.SUGDEL +FT.SUGGET +FT.SUGLEN +FT.SYNDUMP +FT.SYNUPDATE +FT.TAGVALS +FUNCTION DELETE +FUNCTION DUMP +FUNCTION FLUSH +FUNCTION KILL +FUNCTION LIST +FUNCTION LOAD +FUNCTION RESTORE +FUNCTION STATS +GEOADD +GEODIST +GEOHASH +GEOPOS +GEORADIUS +GEORADIUS_RO +GEORADIUSBYMEMBER +GEORADIUSBYMEMBER_RO +GEOSEARCH +GEOSEARCHSTORE +GET +GETBIT +GETDEL +GETEX +GETRANGE +GETSET +HDEL +HELLO +HEXISTS +HGET +HGETALL +HINCRBY +HINCRBYFLOAT +HKEYS +HLEN +HMGET +HMSET +HRANDFIELD +HSCAN +HSET +HSETNX +HSTRLEN +HVALS +INCR +INCRBY +INCRBYFLOAT +INFO +JSON.ARRAPPEND +JSON.ARRINDEX +JSON.ARRINSERT +JSON.ARRLEN +JSON.ARRPOP +JSON.ARRTRIM +JSON.CLEAR +JSON.DEBUG +JSON.DEBUG MEMORY +JSON.DEL +JSON.FORGET +JSON.GET +JSON.MERGE +JSON.MGET +JSON.MSET +JSON.NUMINCRBY +JSON.NUMMULTBY +JSON.OBJKEYS +JSON.OBJLEN +JSON.RESP +JSON.SET +JSON.STRAPPEND +JSON.STRLEN +JSON.TOGGLE +JSON.TYPE +KEYS +LASTSAVE +LATENCY DOCTOR +LATENCY GRAPH +LATENCY HISTOGRAM +LATENCY HISTORY +LATENCY LATEST +LATENCY RESET +LCS +LINDEX +LINSERT +LLEN +LMOVE +LMPOP +LOLWUT +LPOP +LPOS +LPUSH +LPUSHX +LRANGE +LREM +LSET +LTRIM +MEMORY DOCTOR +MEMORY MALLOC-STATS +MEMORY PURGE +MEMORY STATS +MEMORY USAGE +MGET +MIGRATE +MODULE LIST +MODULE LOAD +MODULE LOADEX +MODULE UNLOAD +MONITOR +MOVE +MSET +MSETNX +MULTI +OBJECT ENCODING +OBJECT FREQ +OBJECT IDLETIME +OBJECT REFCOUNT +PERSIST +PEXPIRE +PEXPIREAT +PEXPIRETIME +PFADD +PFCOUNT +PFDEBUG +PFMERGE +PFSELFTEST +PING +PSETEX +PSUBSCRIBE +PSYNC +PTTL +PUBLISH +PUBSUB CHANNELS +PUBSUB NUMPAT +PUBSUB NUMSUB +PUBSUB SHARDCHANNELS +PUBSUB SHARDNUMSUB +PUNSUBSCRIBE +QUIT +RANDOMKEY +READONLY +READWRITE +RENAME +RENAMENX +REPLCONF +REPLICAOF +RESET +RESTORE +RESTORE-ASKING +ROLE +RPOP +RPOPLPUSH +RPUSH +RPUSHX +SADD +SAVE +SCAN +SCARD +SCRIPT DEBUG +SCRIPT EXISTS +SCRIPT FLUSH +SCRIPT KILL +SCRIPT LOAD +SDIFF +SDIFFSTORE +SELECT +SET +SETBIT +SETEX +SETNX +SETRANGE +SHUTDOWN +SINTER +SINTERCARD +SINTERSTORE +SISMEMBER +SLAVEOF +SLOWLOG GET +SLOWLOG LEN +SLOWLOG RESET +SMEMBERS +SMISMEMBER +SMOVE +SORT +SORT_RO +SPOP +SPUBLISH +SRANDMEMBER +SREM +SSCAN +SSUBSCRIBE +STRLEN +SUBSCRIBE +SUBSTR +SUNION +SUNIONSTORE +SUNSUBSCRIBE +SWAPDB +SYNC +TDIGEST.ADD +TDIGEST.BYRANK +TDIGEST.BYREVRANK +TDIGEST.CDF +TDIGEST.CREATE +TDIGEST.INFO +TDIGEST.MAX +TDIGEST.MERGE +TDIGEST.MIN +TDIGEST.QUANTILE +TDIGEST.RANK +TDIGEST.RESET +TDIGEST.REVRANK +TDIGEST.TRIMMED_MEAN +TFCALL +TFCALLASYNC +TFUNCTION DELETE +TFUNCTION LIST +TFUNCTION LOAD +TIME +TOPK.ADD +TOPK.COUNT +TOPK.INCRBY +TOPK.INFO +TOPK.LIST +TOPK.QUERY +TOPK.RESERVE +TOUCH +TS.ADD +TS.ALTER +TS.CREATE +TS.CREATERULE +TS.DECRBY +TS.DEL +TS.DELETERULE +TS.GET +TS.INCRBY +TS.INFO +TS.MADD +TS.MGET +TS.MRANGE +TS.MREVRANGE +TS.QUERYINDEX +TS.RANGE +TS.REVRANGE +TTL +TYPE +UNLINK +UNSUBSCRIBE +UNWATCH +WAIT +WAITAOF +WATCH +XACK +XADD +XAUTOCLAIM +XCLAIM +XDEL +XGROUP CREATE +XGROUP CREATECONSUMER +XGROUP DELCONSUMER +XGROUP DESTROY +XGROUP SETID +XINFO CONSUMERS +XINFO GROUPS +XINFO STREAM +XLEN +XPENDING +XRANGE +XREAD +XREADGROUP +XREVRANGE +XSETID +XTRIM +ZADD +ZCARD +ZCOUNT +ZDIFF +ZDIFFSTORE +ZINCRBY +ZINTER +ZINTERCARD +ZINTERSTORE +ZLEXCOUNT +ZMPOP +ZMSCORE +ZPOPMAX +ZPOPMIN +ZRANDMEMBER +ZRANGE +ZRANGEBYLEX +ZRANGEBYSCORE +ZRANGESTORE +ZRANK +ZREM +ZREMRANGEBYLEX +ZREMRANGEBYRANK +ZREMRANGEBYSCORE +ZREVRANGE +ZREVRANGEBYLEX +ZREVRANGEBYSCORE +ZREVRANK +ZSCAN +ZSCORE +ZUNION +ZUNIONSTORE + +## Errors +ClosingError: Errors that report that the client has closed and is no longer usable. + +RedisError: Errors that were reported during a request. + +TimeoutError: Errors that are thrown when a request times out. + +ExecAbortError: Errors that are thrown when a transaction is aborted. + +ConnectionError: Errors that are thrown when a connection disconnects. These errors can be temporary, as the client will attempt to reconnect. + +Errors returned are subject to change as we update the protobuf definitions. + From 265daa0b51bc839964a9e085da48eba46b6ea428 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Tue, 21 Nov 2023 15:17:55 -0800 Subject: [PATCH 06/40] Update section on supported commands in API design doc --- docs/design-api.md | 467 +-------------------------------------------- 1 file changed, 2 insertions(+), 465 deletions(-) diff --git a/docs/design-api.md b/docs/design-api.md index 8c7012b7d6..f7bd1425ee 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -43,471 +43,8 @@ To marshal Redis data types back into the corresponding types for the wrapper la In our client wrappers, we will expose separate methods for each supported command. We do not need to validate data types for inputs, because Redis will do that for us. -Supported Commands -ACL CAT -ACL DELUSER -ACL DRYRUN -ACL GENPASS -ACL GETUSER -ACL LIST -ACL LOAD -ACL LOG -ACL SAVE -ACL SETUSER -ACL USERS -ACL WHOAMI -APPEND -ASKING -AUTH -BF.ADD -BF.CARD -BF.EXISTS -BF.INFO -BF.INSERT -BF.LOADCHUNK -BF.MADD -BF.MEXISTS -BF.RESERVE -BF.SCANDUMP -BGREWRITEAOF -BGSAVE -BITCOUNT -BITFIELD -BITFIELD_RO -BITOP -BITPOS -BLMOVE -BLMPOP -BLPOP -BRPOP -BRPOPLPUSH -BZMPOP -BZPOPMAX -BZPOPMIN -CF.ADD -CF.ADDNX -CF.COUNT -CF.DEL -CF.EXISTS -CF.INFO -CF.INSERT -CF.INSERTNX -CF.LOADCHUNK -CF.MEXISTS -CF.RESERVE -CF.SCANDUMP -CLIENT CACHING -CLIENT GETNAME -CLIENT GETREDIR -CLIENT ID -CLIENT INFO -CLIENT KILL -CLIENT LIST -CLIENT NO-EVICT -CLIENT NO-TOUCH -CLIENT PAUSE -CLIENT REPLY -CLIENT SETINFO -CLIENT SETNAME -CLIENT TRACKING -CLIENT TRACKINGINFO -CLIENT UNBLOCK -CLIENT UNPAUSE -CLUSTER ADDSLOTS -CLUSTER ADDSLOTSRANGE -CLUSTER BUMPEPOCH -CLUSTER COUNT-FAILURE-REPORTS -CLUSTER COUNTKEYSINSLOT -CLUSTER DELSLOTS -CLUSTER DELSLOTSRANGE -CLUSTER FAILOVER -CLUSTER FLUSHSLOTS -CLUSTER FORGET -CLUSTER GETKEYSINSLOT -CLUSTER INFO -CLUSTER KEYSLOT -CLUSTER LINKS -CLUSTER MEET -CLUSTER MYID -CLUSTER MYSHARDID -CLUSTER NODES -CLUSTER REPLICAS -CLUSTER REPLICATE -CLUSTER RESET -CLUSTER SAVECONFIG -CLUSTER SET-CONFIG-EPOCH -CLUSTER SETSLOT -CLUSTER SHARDS -CLUSTER SLAVES -CLUSTER SLOTS -CMS.INCRBY -CMS.INFO -CMS.INITBYDIM -CMS.INITBYPROB -CMS.MERGE -CMS.QUERY -COMMAND -COMMAND COUNT -COMMAND DOCS -COMMAND GETKEYS -COMMAND GETKEYSANDFLAGS -COMMAND INFO -COMMAND LIST -CONFIG GET -CONFIG RESETSTAT -CONFIG REWRITE -CONFIG SET -COPY -DBSIZE -DECR -DECRBY -DEL -DISCARD -DUMP -ECHO -EVAL -EVAL_RO -EVALSHA -EVALSHA_RO -EXEC -EXISTS -EXPIRE -EXPIREAT -EXPIRETIME -FAILOVER -FCALL -FCALL_RO -FLUSHALL -FLUSHDB -FT._LIST -FT.AGGREGATE -FT.ALIASADD -FT.ALIASDEL -FT.ALIASUPDATE -FT.ALTER -FT.CONFIG GET -FT.CONFIG SET -FT.CREATE -FT.CURSOR DEL -FT.CURSOR READ -FT.DICTADD -FT.DICTDEL -FT.DICTDUMP -FT.DROPINDEX -FT.EXPLAIN -FT.EXPLAINCLI -FT.INFO -FT.PROFILE -FT.SEARCH -FT.SPELLCHECK -FT.SUGADD -FT.SUGDEL -FT.SUGGET -FT.SUGLEN -FT.SYNDUMP -FT.SYNUPDATE -FT.TAGVALS -FUNCTION DELETE -FUNCTION DUMP -FUNCTION FLUSH -FUNCTION KILL -FUNCTION LIST -FUNCTION LOAD -FUNCTION RESTORE -FUNCTION STATS -GEOADD -GEODIST -GEOHASH -GEOPOS -GEORADIUS -GEORADIUS_RO -GEORADIUSBYMEMBER -GEORADIUSBYMEMBER_RO -GEOSEARCH -GEOSEARCHSTORE -GET -GETBIT -GETDEL -GETEX -GETRANGE -GETSET -HDEL -HELLO -HEXISTS -HGET -HGETALL -HINCRBY -HINCRBYFLOAT -HKEYS -HLEN -HMGET -HMSET -HRANDFIELD -HSCAN -HSET -HSETNX -HSTRLEN -HVALS -INCR -INCRBY -INCRBYFLOAT -INFO -JSON.ARRAPPEND -JSON.ARRINDEX -JSON.ARRINSERT -JSON.ARRLEN -JSON.ARRPOP -JSON.ARRTRIM -JSON.CLEAR -JSON.DEBUG -JSON.DEBUG MEMORY -JSON.DEL -JSON.FORGET -JSON.GET -JSON.MERGE -JSON.MGET -JSON.MSET -JSON.NUMINCRBY -JSON.NUMMULTBY -JSON.OBJKEYS -JSON.OBJLEN -JSON.RESP -JSON.SET -JSON.STRAPPEND -JSON.STRLEN -JSON.TOGGLE -JSON.TYPE -KEYS -LASTSAVE -LATENCY DOCTOR -LATENCY GRAPH -LATENCY HISTOGRAM -LATENCY HISTORY -LATENCY LATEST -LATENCY RESET -LCS -LINDEX -LINSERT -LLEN -LMOVE -LMPOP -LOLWUT -LPOP -LPOS -LPUSH -LPUSHX -LRANGE -LREM -LSET -LTRIM -MEMORY DOCTOR -MEMORY MALLOC-STATS -MEMORY PURGE -MEMORY STATS -MEMORY USAGE -MGET -MIGRATE -MODULE LIST -MODULE LOAD -MODULE LOADEX -MODULE UNLOAD -MONITOR -MOVE -MSET -MSETNX -MULTI -OBJECT ENCODING -OBJECT FREQ -OBJECT IDLETIME -OBJECT REFCOUNT -PERSIST -PEXPIRE -PEXPIREAT -PEXPIRETIME -PFADD -PFCOUNT -PFDEBUG -PFMERGE -PFSELFTEST -PING -PSETEX -PSUBSCRIBE -PSYNC -PTTL -PUBLISH -PUBSUB CHANNELS -PUBSUB NUMPAT -PUBSUB NUMSUB -PUBSUB SHARDCHANNELS -PUBSUB SHARDNUMSUB -PUNSUBSCRIBE -QUIT -RANDOMKEY -READONLY -READWRITE -RENAME -RENAMENX -REPLCONF -REPLICAOF -RESET -RESTORE -RESTORE-ASKING -ROLE -RPOP -RPOPLPUSH -RPUSH -RPUSHX -SADD -SAVE -SCAN -SCARD -SCRIPT DEBUG -SCRIPT EXISTS -SCRIPT FLUSH -SCRIPT KILL -SCRIPT LOAD -SDIFF -SDIFFSTORE -SELECT -SET -SETBIT -SETEX -SETNX -SETRANGE -SHUTDOWN -SINTER -SINTERCARD -SINTERSTORE -SISMEMBER -SLAVEOF -SLOWLOG GET -SLOWLOG LEN -SLOWLOG RESET -SMEMBERS -SMISMEMBER -SMOVE -SORT -SORT_RO -SPOP -SPUBLISH -SRANDMEMBER -SREM -SSCAN -SSUBSCRIBE -STRLEN -SUBSCRIBE -SUBSTR -SUNION -SUNIONSTORE -SUNSUBSCRIBE -SWAPDB -SYNC -TDIGEST.ADD -TDIGEST.BYRANK -TDIGEST.BYREVRANK -TDIGEST.CDF -TDIGEST.CREATE -TDIGEST.INFO -TDIGEST.MAX -TDIGEST.MERGE -TDIGEST.MIN -TDIGEST.QUANTILE -TDIGEST.RANK -TDIGEST.RESET -TDIGEST.REVRANK -TDIGEST.TRIMMED_MEAN -TFCALL -TFCALLASYNC -TFUNCTION DELETE -TFUNCTION LIST -TFUNCTION LOAD -TIME -TOPK.ADD -TOPK.COUNT -TOPK.INCRBY -TOPK.INFO -TOPK.LIST -TOPK.QUERY -TOPK.RESERVE -TOUCH -TS.ADD -TS.ALTER -TS.CREATE -TS.CREATERULE -TS.DECRBY -TS.DEL -TS.DELETERULE -TS.GET -TS.INCRBY -TS.INFO -TS.MADD -TS.MGET -TS.MRANGE -TS.MREVRANGE -TS.QUERYINDEX -TS.RANGE -TS.REVRANGE -TTL -TYPE -UNLINK -UNSUBSCRIBE -UNWATCH -WAIT -WAITAOF -WATCH -XACK -XADD -XAUTOCLAIM -XCLAIM -XDEL -XGROUP CREATE -XGROUP CREATECONSUMER -XGROUP DELCONSUMER -XGROUP DESTROY -XGROUP SETID -XINFO CONSUMERS -XINFO GROUPS -XINFO STREAM -XLEN -XPENDING -XRANGE -XREAD -XREADGROUP -XREVRANGE -XSETID -XTRIM -ZADD -ZCARD -ZCOUNT -ZDIFF -ZDIFFSTORE -ZINCRBY -ZINTER -ZINTERCARD -ZINTERSTORE -ZLEXCOUNT -ZMPOP -ZMSCORE -ZPOPMAX -ZPOPMIN -ZRANDMEMBER -ZRANGE -ZRANGEBYLEX -ZRANGEBYSCORE -ZRANGESTORE -ZRANK -ZREM -ZREMRANGEBYLEX -ZREMRANGEBYRANK -ZREMRANGEBYSCORE -ZREVRANGE -ZREVRANGEBYLEX -ZREVRANGEBYSCORE -ZREVRANK -ZSCAN -ZSCORE -ZUNION -ZUNIONSTORE +## Supported Commands +We will be supporting all Redis commands. Commands with higher usage will be prioritized. ## Errors ClosingError: Errors that report that the client has closed and is no longer usable. From 8d43095eee18b3b5ee5f41839e87dabf30144d77 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 22 Nov 2023 02:53:47 -0800 Subject: [PATCH 07/40] Update API design doc with more details --- docs/design-api.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/design-api.md b/docs/design-api.md index f7bd1425ee..e09f76bfd7 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -3,14 +3,18 @@ API Design # Client Wrapper API design doc ## API requirements: -- The API will be thread-safe -- The API will accept as inputs all of RESP3 types. +- The API will be thread-safe. +- The API will accept as inputs all of [RESP3 types](https://redis.io/docs/reference/protocol-spec/). - The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. ## Command Interface ### Unix Domain Socket solution -For clients based on Unix Domain Sockets (UDS), we will simply use the existing protobuf messages for creating a connection, sending requests, and receiving responses. Supported commands are enumerated in the [protobuf definition for requests](https://github.com/Bit-Quill/babushka/blob/main/babushka-core/src/protobuf/redis_request.proto) and we may add more in the future, although the `CustomCommand` request type is also adequate for all commands. As defined in the [protobuf definition for responses](https://github.com/Bit-Quill/babushka/blob/main/babushka-core/src/protobuf/response.proto), client wrappers will receive data as a pointer, which can be passed to Rust to marshal the data back into the wrapper language’s native data types. +For clients based on Unix Domain Sockets (UDS), we will simply use the existing protobuf messages for creating a connection, sending requests, and receiving responses. Supported commands are enumerated in the [protobuf definition for requests](../babushka-core/src/protobuf/redis_request.proto) and we may add more in the future, although the `CustomCommand` request type is also adequate for all commands. As defined in the [protobuf definition for responses](../babushka-core/src/protobuf/response.proto), client wrappers will receive data as a pointer, which can be passed to Rust to marshal the data back into the wrapper language’s native data types. + +Transactions will be handled by adding a list of `Command`s to the protobuf request. The response will be a `redis::Value::Bulk`, which should be handled in the same Rust function that marshals the data back into the wrapper language's native data types. This is handled by storing the results in a collection type native to the wrapper language. + +When running Redis in Cluster Mode, several routing options will be provided. These are all specified in the protobuf request. ### Raw FFI solution For clients using a raw FFI solution, in Rust, we will expose a general command that is able to take any command and arguments as strings. @@ -20,6 +24,8 @@ We have 2 options for passing the command, arguments, and any additional configu #### Protobuf The wrapper language will pass the commands, arguments, and configuration as protobuf messages using the same definitions as in the UDS solution. +Transactions will be handled by adding a list of `Command`s to the protobuf request. The response will be a `redis::Value::Bulk`, which can be marshalled into a C array of values before being passed from Rust to the wrapper language. The wrapper language is responsible for converting the array of results to its own native collection type. + Pros: - We get to reuse the protobuf definitions, meaning fewer files to update if we make changes to the protobuf definitions - May be simpler to implement compared to the C data types solution, since we do not need to define our own C data types @@ -30,6 +36,8 @@ Cons: #### C Data Types The wrapper language will pass commands, arguments, and configuration as C data types. +Transactions will be handled by passing a C array of an array of arguments to Rust from the wrapper language. The response will be a `redis::Value::Bulk`, which can be marshalled in the same way as explained in the protobuf solution. + Pros: - No additional overhead from marshalling to and from protobuf, so this should perform better - May be simpler to implement compared to protobuf solution, since it can be tricky to construct protobuf messages in a performant way and we have to add a varint to the messages as well @@ -41,10 +49,17 @@ We will be testing both approaches to see which is easier to implement, as well To marshal Redis data types back into the corresponding types for the wrapper language, we will convert them into appropriate C types, which can then be translated by the wrapper language into its native data types. -In our client wrappers, we will expose separate methods for each supported command. We do not need to validate data types for inputs, because Redis will do that for us. - ## Supported Commands -We will be supporting all Redis commands. Commands with higher usage will be prioritized. +We will be supporting all Redis commands. Commands with higher usage will be prioritized, as determined by usage numbers from AWS ElastiCache usage logs. + +## Command Input and Output Types +Two different methods of sending requests will be supported. + +### No Redis Type Validation +We will expose an `executeRaw` method that does no validation of the input types or command on the client side, leaving it up to Redis to reject the request should it be malformed. This gives the user the flexibility to send any type of request they want, including ones not officially supported yet. + +### With Redis Type Validation +We will expose separate methods for each supported command, and will attempt to validate the inputs for each of these methods. ## Errors ClosingError: Errors that report that the client has closed and is no longer usable. From 11ea2c70a9e85d6ce5b0bdada6af3c87d33d567b Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 22 Nov 2023 08:06:04 -0800 Subject: [PATCH 08/40] Update type handling policy in API design doc --- docs/design-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design-api.md b/docs/design-api.md index e09f76bfd7..e2729137ba 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -59,7 +59,7 @@ Two different methods of sending requests will be supported. We will expose an `executeRaw` method that does no validation of the input types or command on the client side, leaving it up to Redis to reject the request should it be malformed. This gives the user the flexibility to send any type of request they want, including ones not officially supported yet. ### With Redis Type Validation -We will expose separate methods for each supported command, and will attempt to validate the inputs for each of these methods. +We will expose separate methods for each supported command, and will attempt to validate the inputs for each of these methods. We may leverage the compiler for the wrapper language to validate the types of the command arguments, or, for non-statically typed languages, we may try to implement this using explicit checks. ## Errors ClosingError: Errors that report that the client has closed and is no longer usable. From d7325a11400056046bb16413e5c3d2033e2010cf Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Wed, 22 Nov 2023 14:00:11 -0800 Subject: [PATCH 09/40] Push update Signed-off-by: Andrew Carbonetto --- docs/design-raw-ffi.md | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/docs/design-raw-ffi.md b/docs/design-raw-ffi.md index 387f69e11e..43ecfad52f 100644 --- a/docs/design-raw-ffi.md +++ b/docs/design-raw-ffi.md @@ -17,37 +17,39 @@ it details how Java-Babushka and Go-Babushka each use a different protocol and d to deliver command requests between the wrapper and redis-client threads. This works well because we allow the unix sockets to pass messages and manage threads through the OS, and unix sockets are very performant. This results in simple/fast communication. The risk to avoid is that unix sockets can become a bottleneck for data-intensive commands, and the library can spend too much time waiting on I/O -blocking operations. +blocking operations. ```mermaid stateDiagram-v2 direction LR - JavaWrapper: Java Wrapper + Wrapper: Wrapper UnixDomainSocket: Unix Domain Socket RustCore: Rust-Core - [*] --> JavaWrapper: User - JavaWrapper --> UnixDomainSocket - UnixDomainSocket --> JavaWrapper + [*] --> Wrapper: User + Wrapper --> UnixDomainSocket + UnixDomainSocket --> Wrapper RustCore --> UnixDomainSocket UnixDomainSocket --> RustCore RustCore --> Redis + Redis --> RustCore ``` ## Decision to use UDS Sockets for a Java-Babushka Wrapper -The decision to use Unix Domain Sockets (UDS) to manage the Java-wrapper to Babushka Redis-client communication was two-fold, vs a raw-FFI solution: -1. Java contains an efficient socket protocol library (netty.io) that provides a highly configurable environment to manage sockets. -2. Java objects serialization/de-serialization is an expensive operation, and a performing multiple io operations between raw-ffi calls would be inefficient. +The decision to use Unix Domain Sockets (UDS) to manage the Java-wrapper to Babushka Redis-client communication was thus: +1. Java contains an efficient socket protocol library ([netty.io](https://netty.io/)) that provides a highly configurable environment to manage sockets. +2. Java objects serialization/de-serialization is an expensive operation, and a performing multiple io operations between raw-ffi calls would be inefficient. +3. The async FFI requests with callbacks requires that we manage multiple runtimes (Rust and Java Thread management), and JNI does not provide an out-of-box solution for this. ### Decision Log -| Protocol | Details | Pros | Cons | -|----------------------------------------------|---------------------------------------------------------|-----------------------------|----------------------------------------------------| -| Unix Domain Sockets (jni/netty) | JNI to submit commands; netty.io for message passing | netty.io standard lib; | complex configuration; limited by socket interface | -| Raw-FFI (JNA, uniffi-rs, j4rs, interoptopus) | JNA to submit commands; protobuf for message processing | reusable in other languages | Slow performance and uses JNI under the hood; | -| Panama/jextract | Performance similar to a raw-ffi using JNI | modern | lacks early Java support (JDK 18+); prototype | +| Protocol | Details | Pros | Cons | +|----------------------------------------------|-------------------------------------------------------------|-----------------------------|----------------------------------------------------| +| Unix Domain Sockets (jni/netty) | JNI to submit commands; netty.io for message passing; async | netty.io standard lib; | complex configuration; limited by socket interface | +| Raw-FFI (JNA, uniffi-rs, j4rs, interoptopus) | FFI to submit commands; Rust for message processing | reusable in other languages | slow performance and uses JNI under the hood | +| Panama/jextract | Performance similar to a raw-ffi using JNI | modern | lacks early Java support (JDK 18+); prototype | ### Sequence Diagram @@ -55,8 +57,8 @@ The decision to use Unix Domain Sockets (UDS) to manage the Java-wrapper to Babu sequenceDiagram participant Wrapper as Client-Wrapper -participant ffi as Babushka FFI -participant manager as Babushka impl +participant ffi as FFI +participant manager as Rust-Core participant worker as Tokio Worker participant SocketListener as Socket Listener participant Socket as Unix Domain Socket @@ -66,11 +68,12 @@ activate Wrapper activate Client Wrapper -)+ ffi: connect_to_redis ffi -)+ manager: start_socket_listener(init_callback) - manager ->> worker: Create Tokio::Runtime (count: CPUs) + manager -) worker: Create Tokio::Runtime (count: CPUs) activate worker worker ->> SocketListener: listen_on_socket(init_callback) SocketListener ->> SocketListener: loop: listen_on_client_stream activate SocketListener + SocketListener -->> manager: manager -->> ffi: socket_path ffi -->>- Wrapper: socket_path SocketListener -->> Socket: UnixStreamListener::new @@ -111,7 +114,7 @@ Wrapper ->> SocketListener: shutdown * **Babushka impl**: public interface layer and thread manager * **Tokio Worker**: Tokio worker threads (number of CPUs) * **SocketListener**: listens for work from the Socket, and handles commands -* **Unix Domain Socket**: Unix Domain Socket to handle communication +* **Unix Domain Socket**: Unix Domain Socket to handle incoming requests and response payloads between Rust-Core and Wrapper * **Redis**: Our data store ## Wrapper-to-Core Connector with raw-FFI calls @@ -144,6 +147,8 @@ stateDiagram-v2 The decision to use raw FFI request from Golang to Rust-core was straight forward: 1. Golang contains goroutines as an alternative, lightweight, and performant solution serves as an obvious solution to pass request, even at scale. +Due to lightweight thread management solution, we chose a solution that scales quickly and requires less configuration to achieve a performant solution +on par with existing industrial standards ([go-redis](https://github.com/redis/go-redis)). | Protocol | Details | Pros | Cons | |--------------------------|---------|--------------------------------------------------------|--------------------------------------| From fe1690a35943ebc36dd9da15f0092551d420a2ed Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Fri, 24 Nov 2023 03:13:34 -0800 Subject: [PATCH 10/40] Update API design doc with Routing info --- docs/design-api.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/design-api.md b/docs/design-api.md index e2729137ba..52d3d0d123 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -14,7 +14,7 @@ For clients based on Unix Domain Sockets (UDS), we will simply use the existing Transactions will be handled by adding a list of `Command`s to the protobuf request. The response will be a `redis::Value::Bulk`, which should be handled in the same Rust function that marshals the data back into the wrapper language's native data types. This is handled by storing the results in a collection type native to the wrapper language. -When running Redis in Cluster Mode, several routing options will be provided. These are all specified in the protobuf request. +When running Redis in Cluster Mode, several routing options will be provided. These are all specified in the protobuf request. The various options are detaield below in the ["Routing Options" section](#routing-options). We will also provide a separate client for handling Cluster Mode responses, which will convert the list of values and nodes into a map, as is done in existing client wrappers. ### Raw FFI solution For clients using a raw FFI solution, in Rust, we will expose a general command that is able to take any command and arguments as strings. @@ -26,6 +26,8 @@ The wrapper language will pass the commands, arguments, and configuration as pro Transactions will be handled by adding a list of `Command`s to the protobuf request. The response will be a `redis::Value::Bulk`, which can be marshalled into a C array of values before being passed from Rust to the wrapper language. The wrapper language is responsible for converting the array of results to its own native collection type. +Cluster Mode support is the same here as in the UDS solution detailed above. + Pros: - We get to reuse the protobuf definitions, meaning fewer files to update if we make changes to the protobuf definitions - May be simpler to implement compared to the C data types solution, since we do not need to define our own C data types @@ -38,6 +40,8 @@ The wrapper language will pass commands, arguments, and configuration as C data Transactions will be handled by passing a C array of an array of arguments to Rust from the wrapper language. The response will be a `redis::Value::Bulk`, which can be marshalled in the same way as explained in the protobuf solution. +For Cluster Mode support, [routing options](#routing-options) will be defined as C enums and structs. Like in the protobuf solution, we will provide a separate client for handling Cluster Mode responses, which will convert the list of values and nodes into a map. + Pros: - No additional overhead from marshalling to and from protobuf, so this should perform better - May be simpler to implement compared to protobuf solution, since it can be tricky to construct protobuf messages in a performant way and we have to add a varint to the messages as well @@ -49,17 +53,20 @@ We will be testing both approaches to see which is easier to implement, as well To marshal Redis data types back into the corresponding types for the wrapper language, we will convert them into appropriate C types, which can then be translated by the wrapper language into its native data types. +## Routing Options +We will be supporting routing Redis requests to all nodes, all primary nodes, or a random node. For more specific routing to a node, we will also allow sending a request to a primary or replica node with a specified hash slot or key. When the wrapper given a key route, the key is passed to the Rust core, which will find the corresponding hash slot for it. + ## Supported Commands We will be supporting all Redis commands. Commands with higher usage will be prioritized, as determined by usage numbers from AWS ElastiCache usage logs. -## Command Input and Output Types +## Command Input Types Two different methods of sending requests will be supported. ### No Redis Type Validation We will expose an `executeRaw` method that does no validation of the input types or command on the client side, leaving it up to Redis to reject the request should it be malformed. This gives the user the flexibility to send any type of request they want, including ones not officially supported yet. ### With Redis Type Validation -We will expose separate methods for each supported command, and will attempt to validate the inputs for each of these methods. We may leverage the compiler for the wrapper language to validate the types of the command arguments, or, for non-statically typed languages, we may try to implement this using explicit checks. +We will expose separate methods for each supported command, and will attempt to validate the inputs for each of these methods. There will be a separate version of each method for transactions, as well as another version for Cluster Mode clients. We may leverage the compiler for the wrapper language to validate the types of the command arguments for statically typed languages. Since wrappers should be as lightweight as possible, we most likely will not be performing any checks for proper typing for non-statically typed languages. ## Errors ClosingError: Errors that report that the client has closed and is no longer usable. @@ -74,3 +81,10 @@ ConnectionError: Errors that are thrown when a connection disconnects. These err Errors returned are subject to change as we update the protobuf definitions. +## Java Specific Details +We will be using the UDS solution for communication between the wrapper and the Rust core. This thin layer is implemented using the [jni-rs library](https://github.com/jni-rs/jni-rs) to start the socket listener and marshal Redis values into native Java data types. + +Errors in Rust are represented as Algebraic Data Types, which are not supported in Java by default (at least not in the versions of Java we want to support). Instead, we utilise the [jni-rs library](https://github.com/jni-rs/jni-rs) to throw Java `Exception`s where we receive errors from Redis. + +## Golang Specific Details +We will be using a raw FFI solution for communication between the wrapper and the Rust core. TODO: Add more details here From 744e92fcf420df25bb10b7d3b6e088f132ae89f3 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Fri, 24 Nov 2023 11:53:40 -0800 Subject: [PATCH 11/40] Add example showing how executeRaw would work to API design doc --- docs/design-api.md | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/design-api.md b/docs/design-api.md index 52d3d0d123..7dffc65f31 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -4,7 +4,7 @@ API Design ## API requirements: - The API will be thread-safe. -- The API will accept as inputs all of [RESP3 types](https://redis.io/docs/reference/protocol-spec/). +- The API will accept as inputs all of [RESP3 types](https://redis.io/docs/reference/protocol-spec/). NOTE: Requires further discussion, since RESP3 isn't supported by redis-rs yet - The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. ## Command Interface @@ -36,7 +36,7 @@ Cons: - There is additional overhead from marshalling data to and from protobuf, which could impact performance significantly #### C Data Types -The wrapper language will pass commands, arguments, and configuration as C data types. +The wrapper language will pass commands, arguments, and configuration as C data types. Transactions will be handled by passing a C array of an array of arguments to Rust from the wrapper language. The response will be a `redis::Value::Bulk`, which can be marshalled in the same way as explained in the protobuf solution. @@ -51,7 +51,18 @@ Cons: We will be testing both approaches to see which is easier to implement, as well as the performance impact before deciding on a solution. -To marshal Redis data types back into the corresponding types for the wrapper language, we will convert them into appropriate C types, which can then be translated by the wrapper language into its native data types. +To marshal Redis data types back into the corresponding types for the wrapper language, we will convert them into appropriate C types, which can then be translated by the wrapper language into its native data types. Here is what a Redis result might look like: +``` +typedef struct redisValue { + enum {NIL, INT, DATA, STATUS, BULK, OKAY} kind; + union Payload { + long intValue; + unsigned char *dataValue; + char *statusValue; + struct redisValue *bulkValue; + } payload; +} RedisValue +``` ## Routing Options We will be supporting routing Redis requests to all nodes, all primary nodes, or a random node. For more specific routing to a node, we will also allow sending a request to a primary or replica node with a specified hash slot or key. When the wrapper given a key route, the key is passed to the Rust core, which will find the corresponding hash slot for it. @@ -59,12 +70,25 @@ We will be supporting routing Redis requests to all nodes, all primary nodes, or ## Supported Commands We will be supporting all Redis commands. Commands with higher usage will be prioritized, as determined by usage numbers from AWS ElastiCache usage logs. -## Command Input Types +## Type Validation Two different methods of sending requests will be supported. ### No Redis Type Validation We will expose an `executeRaw` method that does no validation of the input types or command on the client side, leaving it up to Redis to reject the request should it be malformed. This gives the user the flexibility to send any type of request they want, including ones not officially supported yet. +For example, if a user wants to implement support for the Redis ZADD command in Java, their implementation might look something like this: +```java +public Long zadd(K key, double score, V member) throws RequestException { + string[] args = { key.toString(), score.toString(), member.toString() }; + return (Long) executeRaw(args); +} +``` + +where `executeRaw` has the following signature: +```java +public Object executeRaw(string[] args) throws RequestException +``` + ### With Redis Type Validation We will expose separate methods for each supported command, and will attempt to validate the inputs for each of these methods. There will be a separate version of each method for transactions, as well as another version for Cluster Mode clients. We may leverage the compiler for the wrapper language to validate the types of the command arguments for statically typed languages. Since wrappers should be as lightweight as possible, we most likely will not be performing any checks for proper typing for non-statically typed languages. From 56ebe90d15d6743a2b0e3a9395ae821e73162b5b Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Sun, 26 Nov 2023 11:01:03 -0800 Subject: [PATCH 12/40] Add Redis to Java and Go encoding Signed-off-by: Andrew Carbonetto --- docs/design-raw-ffi.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/design-raw-ffi.md b/docs/design-raw-ffi.md index 43ecfad52f..fb36948ae8 100644 --- a/docs/design-raw-ffi.md +++ b/docs/design-raw-ffi.md @@ -82,6 +82,9 @@ ffi -->>- Wrapper: socket_path Wrapper ->> Socket: connect Socket -->> Wrapper: loop single_request + Wrapper ->> ffi: java_arg_to_redis + ffi -->> Wrapper: + Wrapper -> Wrapper: pack protobuf.redis_request Wrapper ->> Socket: netty.writeandflush (protobuf.redis_request) Socket -->> Wrapper: Wrapper ->> Wrapper: wait @@ -94,7 +97,9 @@ loop single_request Socket -->> SocketListener: Wrapper ->> Socket: netty.read (protobuf.response) Socket -->> Wrapper: - Wrapper ->> Wrapper: Result + Wrapper ->> ffi: redis_value_to_java + ffi -->> Wrapper: + Wrapper ->> Wrapper: unpack protobuf.response end Wrapper ->> Socket: close() Wrapper ->> SocketListener: shutdown @@ -108,6 +113,13 @@ Wrapper ->> SocketListener: shutdown deactivate Client ``` +### Discussion +* `redis_value_to_java`: This ffi call is necessary to evaluate the Redis::Value response that Redis returns to Rust-core, +and needs to be converted to a `JObject` before it can be evaluated by Java. We are looking for alternatives to this call +to avoid an unnecessary ffi call. +* `java_arg_to_redis`: This ffi call is currently unnecessary, because all arguments sent are Strings. + + ### Elements * **Wrapper**: Our Babushka wrapper that exposes a client API (java, python, node, etc) * **Babushka FFI**: Foreign Function Interface definitions from our wrapper to our Rust Babushka-Core @@ -200,9 +212,9 @@ Wrapper ->> channel: wait manager ->> worker: command: cmd(protobuf.redis_request) worker ->> Client: send(command, args) Client -->> worker: Result - worker -->> ffi: Ok(protobuf.response) - ffi -->> channel: Ok(protobuf.response) -channel ->> Wrapper: protobuf.response + worker -->> ffi: Ok(protobuf.response) + ffi -->> channel: Ok(protobuf.response) +channel ->> Wrapper: protobuf.response Wrapper ->> channel: close deactivate channel end From 64e034a337f0b95a1ffd54ba64f21bf0fda16e44 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Mon, 27 Nov 2023 09:11:45 -0800 Subject: [PATCH 13/40] Change to supporting RESP2 instead of RESP3 for now --- docs/design-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design-api.md b/docs/design-api.md index 7dffc65f31..7b0101a1a6 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -4,7 +4,7 @@ API Design ## API requirements: - The API will be thread-safe. -- The API will accept as inputs all of [RESP3 types](https://redis.io/docs/reference/protocol-spec/). NOTE: Requires further discussion, since RESP3 isn't supported by redis-rs yet +- The API will accept as inputs all of [RESP2 types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md). - The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. ## Command Interface From 2d688c1cca49d8d225eae1b305b972ba685e013d Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Mon, 27 Nov 2023 12:43:15 -0800 Subject: [PATCH 14/40] Add go and java-specific language Signed-off-by: Andrew Carbonetto --- docs/design-raw-ffi.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/design-raw-ffi.md b/docs/design-raw-ffi.md index fb36948ae8..4e7d450710 100644 --- a/docs/design-raw-ffi.md +++ b/docs/design-raw-ffi.md @@ -56,7 +56,7 @@ The decision to use Unix Domain Sockets (UDS) to manage the Java-wrapper to Babu ```mermaid sequenceDiagram -participant Wrapper as Client-Wrapper +participant Wrapper as Java-Wrapper participant ffi as FFI participant manager as Rust-Core participant worker as Tokio Worker @@ -121,7 +121,7 @@ to avoid an unnecessary ffi call. ### Elements -* **Wrapper**: Our Babushka wrapper that exposes a client API (java, python, node, etc) +* **Java-Wrapper**: Our Babushka wrapper that exposes a client API (java, python, node, etc) * **Babushka FFI**: Foreign Function Interface definitions from our wrapper to our Rust Babushka-Core * **Babushka impl**: public interface layer and thread manager * **Tokio Worker**: Tokio worker threads (number of CPUs) @@ -182,11 +182,10 @@ pushing goroutines to the result channel once the Tokio threads send back a call ### Sequence Diagram - ```mermaid sequenceDiagram -participant Wrapper as Client-Wrapper +participant Wrapper as Go-Wrapper participant channel as Result Channel participant ffi as Babushka FFI participant manager as Babushka impl @@ -226,8 +225,14 @@ Wrapper -) worker: close_connection deactivate Client ``` +### Discussion + +Message format interface: When passing messages between the Go-wrapper and Rust-core, we need to use a language-idiomatic +format. Protobuf, for example, passes messages in wire-frame. We could also pass messages using a custom C datatype. +Protobuf is available, but the overhead to encode and decode messages may make a custom C datatype more worthwhile. + ### Elements -* **Client-Wrapper**: Our Babushka wrapper that exposes a client API (Go, etc) +* **Go-Wrapper**: Our Babushka wrapper that exposes a client API (Go, etc) * **Result Channel**: Goroutine channel on the Babushka Wrapper * **Babushka FFI**: Foreign Function Interface definitions from our wrapper to our Rust Babushka-Core * **Babushka impl**: public interface layer and thread manager From f6b702bb827eddef62422274a2735bd01ecf4846 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Wed, 29 Nov 2023 04:43:21 -0800 Subject: [PATCH 15/40] Clean up section on supported commands in API design doc --- docs/design-api.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/design-api.md b/docs/design-api.md index 7b0101a1a6..222d670866 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -4,7 +4,7 @@ API Design ## API requirements: - The API will be thread-safe. -- The API will accept as inputs all of [RESP2 types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md). +- The API will accept as inputs all of [RESP2 types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md). We plan to add support for RESP3 types when they are available. - The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. ## Command Interface @@ -17,7 +17,9 @@ Transactions will be handled by adding a list of `Command`s to the protobuf requ When running Redis in Cluster Mode, several routing options will be provided. These are all specified in the protobuf request. The various options are detaield below in the ["Routing Options" section](#routing-options). We will also provide a separate client for handling Cluster Mode responses, which will convert the list of values and nodes into a map, as is done in existing client wrappers. ### Raw FFI solution -For clients using a raw FFI solution, in Rust, we will expose a general command that is able to take any command and arguments as strings. +For clients using a raw FFI solution, in Rust, we will expose a general command that is able to take any command and arguments as strings. + +Like in the UDS solution, we will support a separate client for Cluster Mode. We have 2 options for passing the command, arguments, and any additional configuration to the Rust core from the wrapper language: @@ -70,11 +72,10 @@ We will be supporting routing Redis requests to all nodes, all primary nodes, or ## Supported Commands We will be supporting all Redis commands. Commands with higher usage will be prioritized, as determined by usage numbers from AWS ElastiCache usage logs. -## Type Validation -Two different methods of sending requests will be supported. +Two different methods of sending commands will be supported: -### No Redis Type Validation -We will expose an `executeRaw` method that does no validation of the input types or command on the client side, leaving it up to Redis to reject the request should it be malformed. This gives the user the flexibility to send any type of request they want, including ones not officially supported yet. +### Custom Command +We will expose an `executeRaw` method that does no validation of the input types or command on the client side, leaving it up to Redis to reject the command should it be malformed. This gives the user the flexibility to send any type of command they want, including ones not officially supported yet. For example, if a user wants to implement support for the Redis ZADD command in Java, their implementation might look something like this: ```java @@ -89,8 +90,8 @@ where `executeRaw` has the following signature: public Object executeRaw(string[] args) throws RequestException ``` -### With Redis Type Validation -We will expose separate methods for each supported command, and will attempt to validate the inputs for each of these methods. There will be a separate version of each method for transactions, as well as another version for Cluster Mode clients. We may leverage the compiler for the wrapper language to validate the types of the command arguments for statically typed languages. Since wrappers should be as lightweight as possible, we most likely will not be performing any checks for proper typing for non-statically typed languages. +### Explicitly Supported Command +We will expose separate methods for each supported command. There will be a separate version of each method for transactions, as well as another version for Cluster Mode clients. For statically typed languages, we will leverage the compiler of the wrapper language to validate the types of the command arguments as much as possible. Since wrappers should be as lightweight as possible, we will be performing very few to no checks for proper typing for non-statically typed languages. ## Errors ClosingError: Errors that report that the client has closed and is no longer usable. From af4e2a4532a59a81f26553f3caabda489790d9d6 Mon Sep 17 00:00:00 2001 From: jonathanl-bq <72158117+jonathanl-bq@users.noreply.github.com> Date: Wed, 29 Nov 2023 04:46:25 -0800 Subject: [PATCH 16/40] Fix typo in API design doc Co-authored-by: Andrew Carbonetto --- docs/design-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design-api.md b/docs/design-api.md index 222d670866..bde9ed3d97 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -14,7 +14,7 @@ For clients based on Unix Domain Sockets (UDS), we will simply use the existing Transactions will be handled by adding a list of `Command`s to the protobuf request. The response will be a `redis::Value::Bulk`, which should be handled in the same Rust function that marshals the data back into the wrapper language's native data types. This is handled by storing the results in a collection type native to the wrapper language. -When running Redis in Cluster Mode, several routing options will be provided. These are all specified in the protobuf request. The various options are detaield below in the ["Routing Options" section](#routing-options). We will also provide a separate client for handling Cluster Mode responses, which will convert the list of values and nodes into a map, as is done in existing client wrappers. +When running Redis in Cluster Mode, several routing options will be provided. These are all specified in the protobuf request. The various options are detailed below in the ["Routing Options" section](#routing-options). We will also provide a separate client for handling Cluster Mode responses, which will convert the list of values and nodes into a map, as is done in existing client wrappers. ### Raw FFI solution For clients using a raw FFI solution, in Rust, we will expose a general command that is able to take any command and arguments as strings. From df00c54098dbb1c13150742e62b09ac4cbd297a5 Mon Sep 17 00:00:00 2001 From: jonathanl-bq <72158117+jonathanl-bq@users.noreply.github.com> Date: Thu, 30 Nov 2023 10:04:33 -0800 Subject: [PATCH 17/40] Update docs/design-api.md Co-authored-by: Andrew Carbonetto --- docs/design-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design-api.md b/docs/design-api.md index bde9ed3d97..a30d2e6701 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -10,7 +10,7 @@ API Design ## Command Interface ### Unix Domain Socket solution -For clients based on Unix Domain Sockets (UDS), we will simply use the existing protobuf messages for creating a connection, sending requests, and receiving responses. Supported commands are enumerated in the [protobuf definition for requests](../babushka-core/src/protobuf/redis_request.proto) and we may add more in the future, although the `CustomCommand` request type is also adequate for all commands. As defined in the [protobuf definition for responses](../babushka-core/src/protobuf/response.proto), client wrappers will receive data as a pointer, which can be passed to Rust to marshal the data back into the wrapper language’s native data types. +For clients based on Unix Domain Sockets (UDS), we will use the existing Rust-core protobuf messages for creating a connection, sending requests, and receiving responses. Supported commands are enumerated in the [protobuf definition for requests](../babushka-core/src/protobuf/redis_request.proto) and we will support them as they are added in the future. Note: the `CustomCommand` request type is also adequate for all commands. As defined in the [protobuf definition for responses](../babushka-core/src/protobuf/response.proto), client wrappers will receive data as a pointer, which can be passed to Rust to marshal the data back into the wrapper language’s native data types. Transactions will be handled by adding a list of `Command`s to the protobuf request. The response will be a `redis::Value::Bulk`, which should be handled in the same Rust function that marshals the data back into the wrapper language's native data types. This is handled by storing the results in a collection type native to the wrapper language. From ce3eb6caa36cded8e9822d7cda3c2d5f0225b967 Mon Sep 17 00:00:00 2001 From: Jonathan Louie Date: Thu, 30 Nov 2023 10:16:29 -0800 Subject: [PATCH 18/40] Add some more details to API design --- docs/design-api.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/design-api.md b/docs/design-api.md index a30d2e6701..04b60fcfb4 100644 --- a/docs/design-api.md +++ b/docs/design-api.md @@ -10,7 +10,7 @@ API Design ## Command Interface ### Unix Domain Socket solution -For clients based on Unix Domain Sockets (UDS), we will use the existing Rust-core protobuf messages for creating a connection, sending requests, and receiving responses. Supported commands are enumerated in the [protobuf definition for requests](../babushka-core/src/protobuf/redis_request.proto) and we will support them as they are added in the future. Note: the `CustomCommand` request type is also adequate for all commands. As defined in the [protobuf definition for responses](../babushka-core/src/protobuf/response.proto), client wrappers will receive data as a pointer, which can be passed to Rust to marshal the data back into the wrapper language’s native data types. +For clients based on Unix Domain Sockets (UDS), we will use the existing Rust-core protobuf messages for creating a connection, sending requests, and receiving responses. Supported commands are enumerated in the [protobuf definition for requests](../babushka-core/src/protobuf/redis_request.proto) and we will support them as they are added in the future. Note: the `CustomCommand` request type is also adequate for all commands. As defined in the [protobuf definition for responses](../babushka-core/src/protobuf/response.proto), client wrappers will receive data as a pointer, which can be passed to Rust to marshal the data back into the wrapper language’s native data types. In the future, we plan on optimizing this approach such that small data is returned as an object and large data is returned as a pointer in order to reduce the overhead of FFI calls in the case of small data. Transactions will be handled by adding a list of `Command`s to the protobuf request. The response will be a `redis::Value::Bulk`, which should be handled in the same Rust function that marshals the data back into the wrapper language's native data types. This is handled by storing the results in a collection type native to the wrapper language. @@ -112,4 +112,6 @@ We will be using the UDS solution for communication between the wrapper and the Errors in Rust are represented as Algebraic Data Types, which are not supported in Java by default (at least not in the versions of Java we want to support). Instead, we utilise the [jni-rs library](https://github.com/jni-rs/jni-rs) to throw Java `Exception`s where we receive errors from Redis. ## Golang Specific Details -We will be using a raw FFI solution for communication between the wrapper and the Rust core. TODO: Add more details here +We will be using a raw FFI solution for communication between the wrapper and the Rust core. The FFI layer is implemented using `cgo`. + +Golang does not support Algebraic Data Types, so errors will need to be returned as struct values. From 28c672bafec3ff36946f6cca19252b0fe45fd761 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Wed, 13 Dec 2023 00:21:45 -0800 Subject: [PATCH 19/40] Add java design documentation Signed-off-by: Andrew Carbonetto --- docs/design-java-api.md | 51 +++++++++++++++++++ docs/img/design-java-api-detailed-level.svg | 4 ++ docs/img/design-java-api-high-level.svg | 4 ++ .../design-java-api-sequence-datatypes.svg | 4 ++ 4 files changed, 63 insertions(+) create mode 100644 docs/design-java-api.md create mode 100644 docs/img/design-java-api-detailed-level.svg create mode 100644 docs/img/design-java-api-high-level.svg create mode 100644 docs/img/design-java-api-sequence-datatypes.svg diff --git a/docs/design-java-api.md b/docs/design-java-api.md new file mode 100644 index 0000000000..e1d048ea01 --- /dev/null +++ b/docs/design-java-api.md @@ -0,0 +1,51 @@ +Java API Design + +# Java API Design documentation + +## Overview + +This document is available to demonstrate the high-level and detailed design elements of the Java-Wrapper client library +interface. Specifically, it demonstrates how requests are received from the user, and responses with typing are delivered +back to the user. + +# High-Level Architecture + +## Presentation + +![Architecture Overview](img/design-java-api-high-level.svg) + +## Responsibilities + +At a high-level the Java wrapper client has 3 layers: +1. The API layer that is exposed to the user +2. The service layer that deals with data mapping between the client models and data access models +3. The data access layer that is responsible for sending and receiving data from the Redis service + +# API Detailed Design + +## Presentation + +![API Design](img/design-java-api-detailed-level.svg) + +## Responsibilities + +1. A client library that can receive Redis service configuration, and connect to a standalone and clustered Redis service +2. Once connected, the client library can send single command requests to the Redis service +3. Once connected, the client library can send transactional/multi-command request to the Redis service +4. Success and Error feedback is returned to the user +5. Route descriptions are returned from cluster Redis services +6. The payload data in either RESP2 or RESP3 format is returned with the response + +# Response and Payload typing + +## Presentation + +![API Request and Response typing](img/design-java-api-sequence-datatypes.svg) + +## Responsibilities + +1. Data typing and verification is performed for known commands +2. Data is returned as a payload in the RedisResponse object on a success response +3. If no data payload is requested, the service returns an OK constant response +4. Otherwise, the service will cast to the specified type on a one-for-one mapping based on the command +5. If the casting fails, the Java-wrapper will report an Error \ No newline at end of file diff --git a/docs/img/design-java-api-detailed-level.svg b/docs/img/design-java-api-detailed-level.svg new file mode 100644 index 0000000000..9bc00efcbb --- /dev/null +++ b/docs/img/design-java-api-detailed-level.svg @@ -0,0 +1,4 @@ + + + +
babushka.api.commands
babushka.api.commands
babushka.api.c.BaseCommands
babushka.api.c.BaseCommands
+ exec(Transaction): CFuture<Response<T>>
+ exec(Transaction): CFuture<Response<T>>
+ exec(Command): CFuture<Response<T>>
+ exec(Command): CFuture<Response<T>>
+ ping(): CFuture<Response<Void>>
+ ping(): CFuture<Response<Void>>
babushka.api.c.StringCommands
babushka.api.c.StringCommands
+ getString(String): Response<String>
+ getString(String): Response<Strin...
...
...
babushka.api.c.SortedSetCommands
babushka.api.c.SortedSetCommands
babushka.api.RedisClient
babushka.api.RedisClient
+ Create(Config): RedisClient
+ Create(Config): RedisClient
babushka.api.RedisClusterClient
babushka.api.RedisClusterClient
+ Create(Config): RedisClient
+ Create(Config): RedisClient
babushka.api.Transaction
babushka.api.Transaction
+ commands: List<Command>
+ commands: List<Command>
- responseHandler: RedisBaseResponse
- responseHandler: RedisBaseResponse
babushka.api.Command
babushka.api.Command
+ requestType: RequestType
+ requestType: RequestType
+ arguments: List<String>
+ arguments: List<String>
- responseHandler: RedisBaseResponse
- responseHandler: RedisBaseResponse
babushka.api.BaseClient
babushka.api.BaseClient
+ configuration: BaseClientConfiguration
+ configuration: BaseClientConfiguration
+ commands: BaseClientCommands
+ commands: BaseClientCommands
+ connection: ConnectionManager
+ connection: ConnectionManager
+ Create(Config): BaseClient
+ Create(Config): BaseClient
+ CreateAndConnectToRedis(Config): BaseClient
+ CreateAndConnectToRedis(Config): BaseClient
+ isConnected(): boolean
+ isConnected(): boolean
+ connectToRedis(): boolean
+ connectToRedis(): boolean
+ asyncConnectToRedis(): CFuture(Response)
+ asyncConnectToRedis(): CFuture(Response)
babushka.api.BaseClientConfiguration
babushka.api.BaseClientConfiguration
+ addresses: List<Address>
+ addresses: List<Address>
+ useTLS: boolean
+ useTLS: boolean
+ credentials: Credentials
+ credentials: Credentials
+ requestTimeout: long
+ requestTimeout: long
+ readFrom: ReadFromType
+ readFrom: ReadFromType
1
1
Extend all of
Extend all of
RedisClientConfiguration
RedisClientConfiguration
+ databaseId: integer
+ databaseId: integer
+ connectionBackoff: RetryStrategy
+ connectionBackoff: RetryStrategy
RedisClusterClientConfiguration
RedisClusterClientConfiguration
1
1
Extends
Extends
Extends
Extends
babushka.api.exceptions.RedisException
babushka.api.exceptions.RedisException
+ message: String
+ message: String
+ name: String
+ name: String
babushka.api.exceptions.ClosingException
babushka.api.exceptions.ClosingException
babushka.api.exceptions.RequestException
babushka.api.exceptions.RequestException
babushka.api.exceptions.TimeoutException
babushka.api.exceptions.TimeoutException
babushka.api.exceptions.ExecAbortException
babushka.api.exceptions.ExecAbortException
babushka.api.exceptions.ConnectionException
babushka.api.exceptions.ConnectionException
Command interfaces:
BaseRedisCommands, RedisAclCommands, RedisClusterCommands, RedisFunctionCommands, RedisGeoCommands, RedisHashCommands, RedisHLLCommands, RedisKeyCommands, RedisListCommands, RedisScriptingCommands, RedisServerCommands, RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, RedisStringCommands, RedisTransactionalCommands
Command interfaces:...
...
...
babushka.api.RedisBaseResponse
babushka.api.RedisBaseResponse
+ handleResponse(resp): Void
+ handleResponse(resp): Void
+ handleResp3Response(resp): Void
+ handleResp3Response(resp): Void
babushka.api.RedisHashResponse
babushka.api.RedisHashResponse
+ handleResponse(resp): HashMap
+ handleResponse(resp): HashMap
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
babushka.api.RedisTransactionResponse
babushka.api.RedisTransactionResponse
+ handleResponse(resp): List<Object>
+ handleResponse(resp): List<Object>
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
babushka.api.RedisClusterResponse
babushka.api.RedisClusterResponse
+ handleResponse(resp): ClusterValue
+ handleResponse(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handlers: List<BaseRedisResponse>
+ handlers: List<BaseRedisResponse>
babushka.api.ClusterValue
babushka.api.ClusterValue
+ routedValue: HashMap<Object>
+ routedValue: HashMap<Object>
+ singleValue: Object
+ singleValue: Object

Check is_single_response
Check is_single_response
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/img/design-java-api-high-level.svg b/docs/img/design-java-api-high-level.svg new file mode 100644 index 0000000000..115b193282 --- /dev/null +++ b/docs/img/design-java-api-high-level.svg @@ -0,0 +1,4 @@ + + + +
Client API
Client API

RedisClient.java


+ create(Configuration): RedisClient

+ close(): void

+ connect(): void

RedisClient.java...
babushka.api
babushka.api

RedisClientConfiguration.java



RedisClientConfiguration.java...
Controllers
Controllers
babushka.managers
babushka.managers

ConnectionManager.java


+ connectToRedis

+ closeRedisConnection

ConnectionManager.java...

CommandManager.java


+ createRedisRequest

CommandManager.java...

CallbackManager.java


+ requestId: AtomicInteger

+ connectionRequest: Deque

+ callbacks: Map<Int, CompFuture>

+ registerConnection(CompFuture)

+ registerRequest(CompFuture)


CallbackManager.java...
Models
Models
babushka.models.protobuf
babushka.models.protobuf
protobuf generated content
protobuf generated content

ConnectionRequest.java



ConnectionRequest.java...

RedisRequest.java



RedisRequest.java...

Response.java



Response.java...
BaseRedisResponse
BaseRedisResponse
babushka.models.api
babushka.models.api
ClosingError
ClosingError
RequestError
RequestError
TimeoutError
TimeoutError
ExecAbortError
ExecAbortError
ConnectionError
ConnectionError
Connections
Connections
babushka.connectors
babushka.connectors

SocketConnection.java


- getSocket

- isMacOs

- Channel channel

- EventLoopGroup group

- NettySocketManager INSTANCE

+ getInstance(): NettySocketManager

+ write(Msg)

+ writeAndFlush(Msg)

+ close()

class ShutdownHook


SocketConnection.java...
babushka.connection.handlers
babushka.connection.handlers

ChannelHandler.java


+ initChannel

ChannelHandler.java...

ChannelInboundHandler.java


+ channelRead

ChannelInboundHandler.java...

ChannelOutboundHandler.java


+ write

ChannelOutboundHandler.java...

ChannelInitializer.java


+ connect()

+ close()

ChannelInitializer.java...
babushka.ffi.resolvers
babushka.ffi.resolvers

SocketListenerResolver.java


+ startSocketListenerExternal

SocketListenerResolver.java...
Socket
Socket
FFI.Resolvers
FFI.Resolvers

RedisValueResolver.java


+ valueFromPointer(Long ptr): Obj

RedisValueResolver.java...

LoggingResolver.java


+ log(String): Obj

+ init(): Obj

LoggingResolver.java...
lib.rs
lib.rs

Examples

babushka.client.RedisClient redisClient = glider.client.Builder.CreateClient();
redisClient.connectToRedis(host, port); 

redisClient.isConnected(); // true
babushka.client.Commands redisCommands = 
    babushka.client.Builder.GetCommands(
redisClient);

babushka.client.SyncCommands redisSync = 
    babushka.client.Builder.GetSyncCommands(
        redisConnection);
Response res = redisCommands.get(key);
res.getValue();  // maybe this waits
Awaiter.awaitAll(res1, res2, res3); // list of values? 
redisSync.get(key); // String

TODO: 
• Response object
• ERROR 
Examples...
TODO: Add support in the future to allow for multiple socket connection paths. 
TODO: Add support in the fut...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/img/design-java-api-sequence-datatypes.svg b/docs/img/design-java-api-sequence-datatypes.svg new file mode 100644 index 0000000000..2426b75269 --- /dev/null +++ b/docs/img/design-java-api-sequence-datatypes.svg @@ -0,0 +1,4 @@ + + + +
:RedisBaseCommand
:RedisBaseCommand
:RedisStringCommand
:RedisStringCommand
:RedisClientCommands
:RedisClientCommands
get(key)
get(key)
get(key)
get(key)
exec(Request.GET, List(key))
exec(Request.GET, List(key))
:SocketConnector
:SocketConnector
submitNewCommand
(Command, <String>)
submitNewCommand...
write(protobuf.RedisRequest)
write(protobuf.RedisRequest)
:CallbackManager
:CallbackManager
:CommandManager
:CommandManager
registerRequest
(Command)
registerRequest...
callbackId
callbackId
handleResponse
(protobuf.RedisResponse)
handleResponse...
BaseRedisResponse<String>
BaseRedisResponse<String>
:CompletableFuture
:CompletableFuture
completeRequest(protobuf.Response)
completeRequest(protobuf.Response)
supplyAsync()
supplyAsync()
CompletableFuture<BaseRedisResponse<String>>
CompletableFuture<BaseRedisResponse<String>>
get()
get()
thenApplyAsync
(protobuf.RedisResponse)
thenApplyAsync...
:BaseRedisResponse
:BaseRedisResponse
(String)(value_from_pointer)
(String)(value_...
BaseRedisResponse<String>
BaseRedisResponse<String>
new
new
CompletableFuture<BaseRedisResponse<String>>
CompletableFuture<BaseRedisResponse<String>>
completeAsync()
completeAsync()
Text is not SVG - cannot display
\ No newline at end of file From 91490bc11a92bb083d772b34fd01a11b1f3190da Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Thu, 14 Dec 2023 19:04:07 -0800 Subject: [PATCH 20/40] Add use-cases as examples of using the API Signed-off-by: Andrew Carbonetto --- docs/design-java-api.md | 140 ++++++++++++++++++++ docs/img/design-java-api-detailed-level.svg | 2 +- docs/img/design-java-api-high-level.svg | 2 +- 3 files changed, 142 insertions(+), 2 deletions(-) diff --git a/docs/design-java-api.md b/docs/design-java-api.md index e1d048ea01..b2e228c44b 100644 --- a/docs/design-java-api.md +++ b/docs/design-java-api.md @@ -8,6 +8,146 @@ This document is available to demonstrate the high-level and detailed design ele interface. Specifically, it demonstrates how requests are received from the user, and responses with typing are delivered back to the user. +# Use Cases + +### Case 1: Connect to RedisClient + +```java +// create a client configuration for a standalone client and connect +babushka.client.api.RedisClientConfiguration configuration = + babushka.client.api.RedisClientConfiguration.builder() + .address(babushka.client.api.Addresses.builder() + .host(host) + .port(port) + .build()) + .useTls(true) + .build(); + +// connect to Redis +CompletableFuture redisClientConnection = + babushka.client.api.RedisClient.CreateClient(configuration); + +// resolve the Future and get a RedisClient +babushka.client.api.RedisClient redisClient = redisClientConnection.get(); +redisClient.isConnected(); // true +``` + +### Case 2: Connection to RedisClient fails +```java +// create a client configuration for a standalone client - check connection +CompletableFuture redisClientConnection = + babushka.client.api.RedisClient.CreateClient(configuration); + +// resolve the Future and get a RedisClient that is not connected +babushka.client.api.RedisClient redisClient = redisClientConnection.get(); +if (!redisClient.isConnected()) { + throw new RuntimeException("Failed to connect to Redis: " + redisClient.getConnectionError().getMessage()); +} +``` + +### Case 3: Disconnect from RedisClient +TODO: confirm that close should be an async call +```java +CompletableFuture redisClientCloseConnection = redisClient.close(); +redisClient = redisClientCloseConnection.get(); +redisClient.isConnected(); // false +``` + +### Case 4: Connect to RedisClusterClient +```java +// create a client configuration for a standalone client and connect +babushka.client.api.RedisClusterClientConfiguration configuration = + babushka.client.api.RedisClusterClientConfiguration.builder() + .address(babushka.client.api.Addresses.builder() + .host(address_one) + .port(port_one) + .build()) + .address(babushka.client.api.Addresses.builder() + .host(address_two) + .port(port_two) + .build()) + .useTls(true) + .build(); + +// connect to Redis +CompletableFuture redisClusterClientConnection = + babushka.client.api.RedisClusterClient.CreateClient(configuration); + +// resolve the Future and get a RedisClusterClient +babushka.client.api.RedisClusterClient redisClusterClient = redisClusterClientConnection.get(); +redisClusterClient.isConnected(); // true +``` + +### Case 5: Connect to RedisClient and receive RESP2 responses +To confirm: send `.resp2(true)` in the configuration + +### Case ___: Get(key) from connected RedisClient +```java +CompletableFuture getRequest = redisClient.get("apples"); +RedisStringResponse getResponse = getRequest.get(); +getResponse.isSuccessful(); // true +String getValue = getResponse.getValue(); +``` + +### Case ___: Set(key, value) from connected RedisClient +```java +CompletableFuture setRequest = redisClient.set("apples", "oranges"); +RedisVoidResponse setResponse = setRequest.get(); +setResponse.isSuccessful(); // true +getResponse.isOk(); // true +getResponse.toString(); // "Ok" +``` + +### Case ___: Get(key) from a disconnected RedisClient +Throw a babushka.api.models.exceptions.ConnectionException if the RedisClient is closed/disconnected + +### Case ___: Send customCommand to RedisClient +```java + +CompletableFuture customCommandRequest = redisClient.customCommand(StringCommands.GETSTRING, "apples"); +RedisBaseResponse customCommandResponse = customCommandRequest.get(); +if (customCommandResponse.isSuccessful()) { + switch(customCommandResponse.getValueType()) { + STRING: + String customCommandValue = (String) customCommandResponse.getValue(); + break; + DEFAULT: + throw new RuntimeException("Unexpected value type returned"); + } +} +``` +### Case ___: Send transaction to RedisClient +TODO + +### Case ___: Send get request to a RedisClusterClient with one address +TODO + +### Case ___: Send get request to a RedisClusterClient with multiple addresses +TODO + +### Case ___: Request is interrupted +```java +CompletableFuture getRequest = redisClient.get("apples"); +try{ + RedisStringResponse getResponse=getRequest.get(); // throws InterruptedException +} catch (InterruptedException interruptedException) { + redisClient.isConnected(); // false +} +``` + +### Case ___: Request timesout +```java +CompletableFuture getRequest = redisClient.get("apples"); +RedisStringResponse getResponse = getRequest.get(); // times out +if (getResponse.isError()) { + if (getResponse.getErrorType() == TIMEOUT_ERROR) { + babushka.client.api.exceptions.TimeoutException timeoutException = getResponse.getTimeoutException(); + System.err.out("Timeout Exception: " + timeoutException.getMessage()); + throw timeoutException; + } +} +``` + # High-Level Architecture ## Presentation diff --git a/docs/img/design-java-api-detailed-level.svg b/docs/img/design-java-api-detailed-level.svg index 9bc00efcbb..f2163395a9 100644 --- a/docs/img/design-java-api-detailed-level.svg +++ b/docs/img/design-java-api-detailed-level.svg @@ -1,4 +1,4 @@ -
babushka.api.commands
babushka.api.commands
babushka.api.c.BaseCommands
babushka.api.c.BaseCommands
+ exec(Transaction): CFuture<Response<T>>
+ exec(Transaction): CFuture<Response<T>>
+ exec(Command): CFuture<Response<T>>
+ exec(Command): CFuture<Response<T>>
+ ping(): CFuture<Response<Void>>
+ ping(): CFuture<Response<Void>>
babushka.api.c.StringCommands
babushka.api.c.StringCommands
+ getString(String): Response<String>
+ getString(String): Response<Strin...
...
...
babushka.api.c.SortedSetCommands
babushka.api.c.SortedSetCommands
babushka.api.RedisClient
babushka.api.RedisClient
+ Create(Config): RedisClient
+ Create(Config): RedisClient
babushka.api.RedisClusterClient
babushka.api.RedisClusterClient
+ Create(Config): RedisClient
+ Create(Config): RedisClient
babushka.api.Transaction
babushka.api.Transaction
+ commands: List<Command>
+ commands: List<Command>
- responseHandler: RedisBaseResponse
- responseHandler: RedisBaseResponse
babushka.api.Command
babushka.api.Command
+ requestType: RequestType
+ requestType: RequestType
+ arguments: List<String>
+ arguments: List<String>
- responseHandler: RedisBaseResponse
- responseHandler: RedisBaseResponse
babushka.api.BaseClient
babushka.api.BaseClient
+ configuration: BaseClientConfiguration
+ configuration: BaseClientConfiguration
+ commands: BaseClientCommands
+ commands: BaseClientCommands
+ connection: ConnectionManager
+ connection: ConnectionManager
+ Create(Config): BaseClient
+ Create(Config): BaseClient
+ CreateAndConnectToRedis(Config): BaseClient
+ CreateAndConnectToRedis(Config): BaseClient
+ isConnected(): boolean
+ isConnected(): boolean
+ connectToRedis(): boolean
+ connectToRedis(): boolean
+ asyncConnectToRedis(): CFuture(Response)
+ asyncConnectToRedis(): CFuture(Response)
babushka.api.BaseClientConfiguration
babushka.api.BaseClientConfiguration
+ addresses: List<Address>
+ addresses: List<Address>
+ useTLS: boolean
+ useTLS: boolean
+ credentials: Credentials
+ credentials: Credentials
+ requestTimeout: long
+ requestTimeout: long
+ readFrom: ReadFromType
+ readFrom: ReadFromType
1
1
Extend all of
Extend all of
RedisClientConfiguration
RedisClientConfiguration
+ databaseId: integer
+ databaseId: integer
+ connectionBackoff: RetryStrategy
+ connectionBackoff: RetryStrategy
RedisClusterClientConfiguration
RedisClusterClientConfiguration
1
1
Extends
Extends
Extends
Extends
babushka.api.exceptions.RedisException
babushka.api.exceptions.RedisException
+ message: String
+ message: String
+ name: String
+ name: String
babushka.api.exceptions.ClosingException
babushka.api.exceptions.ClosingException
babushka.api.exceptions.RequestException
babushka.api.exceptions.RequestException
babushka.api.exceptions.TimeoutException
babushka.api.exceptions.TimeoutException
babushka.api.exceptions.ExecAbortException
babushka.api.exceptions.ExecAbortException
babushka.api.exceptions.ConnectionException
babushka.api.exceptions.ConnectionException
Command interfaces:
BaseRedisCommands, RedisAclCommands, RedisClusterCommands, RedisFunctionCommands, RedisGeoCommands, RedisHashCommands, RedisHLLCommands, RedisKeyCommands, RedisListCommands, RedisScriptingCommands, RedisServerCommands, RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, RedisStringCommands, RedisTransactionalCommands
Command interfaces:...
...
...
babushka.api.RedisBaseResponse
babushka.api.RedisBaseResponse
+ handleResponse(resp): Void
+ handleResponse(resp): Void
+ handleResp3Response(resp): Void
+ handleResp3Response(resp): Void
babushka.api.RedisHashResponse
babushka.api.RedisHashResponse
+ handleResponse(resp): HashMap
+ handleResponse(resp): HashMap
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
babushka.api.RedisTransactionResponse
babushka.api.RedisTransactionResponse
+ handleResponse(resp): List<Object>
+ handleResponse(resp): List<Object>
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
babushka.api.RedisClusterResponse
babushka.api.RedisClusterResponse
+ handleResponse(resp): ClusterValue
+ handleResponse(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handlers: List<BaseRedisResponse>
+ handlers: List<BaseRedisResponse>
babushka.api.ClusterValue
babushka.api.ClusterValue
+ routedValue: HashMap<Object>
+ routedValue: HashMap<Object>
+ singleValue: Object
+ singleValue: Object

Check is_single_response
Check is_single_response
Text is not SVG - cannot display
\ No newline at end of file +
babushka.api.commands
babushka.api.commands
babushka.api.c.BaseCommands
babushka.api.c.BaseCommands
- exec(Transaction): CFuture<RedisBaseResponse<T>>
- exec(Transaction): CFuture<RedisBaseResponse<T>>
- customCommand(String, List): CFuture<RedisBaseResponse<T>>
- customCommand(String, List): CFuture<RedisBaseResponse<T>>
- exec(Command): CFuture<RedisBaseResponse<T>>
- exec(Command): CFuture<RedisBaseResponse<T>>
+ ping(): CFuture<RedisBaseResponse<Void>>
+ ping(): CFuture<RedisBaseResponse<Void>>
babushka.api.c.StringCommands
babushka.api.c.StringCommands
+ get(String): RedisStringResponse<String>
+ get(String): RedisStringResponse<String>
...
...
babushka.api.c.SortedSetCommands
babushka.api.c.SortedSetCommands
babushka.api.RedisClient
babushka.api.RedisClient
+ Create(Config): RedisClient
+ Create(Config): RedisClient
babushka.api.RedisClusterClient
babushka.api.RedisClusterClient
+ Create(Config): RedisClient
+ Create(Config): RedisClient
babushka.api.BaseClient
babushka.api.BaseClient
+ configuration: BaseClientConfiguration
+ configuration: BaseClientConfiguration
+ commands: BaseClientCommands
+ commands: BaseClientCommands
+ connection: ConnectionManager
+ connection: ConnectionManager
+ Create(Config): BaseClient
+ Create(Config): BaseClient
+ CreateAndConnectToRedis(Config): BaseClient
+ CreateAndConnectToRedis(Config): BaseClient
+ isConnected(): boolean
+ isConnected(): boolean
+ connectToRedis(): boolean
+ connectToRedis(): boolean
+ asyncConnectToRedis(): CFuture(Response)
+ asyncConnectToRedis(): CFuture(Response)
babushka.api.BaseClientConfiguration
babushka.api.BaseClientConfiguration
+ addresses: List<Address>
+ addresses: List<Address>
+ useTLS: boolean
+ useTLS: boolean
+ credentials: Credentials
+ credentials: Credentials
+ requestTimeout: long
+ requestTimeout: long
+ readFrom: ReadFromType
+ readFrom: ReadFromType
1
1
Extend all of
Extend all of
RedisClientConfiguration
RedisClientConfiguration
+ databaseId: integer
+ databaseId: integer
+ connectionBackoff: RetryStrategy
+ connectionBackoff: RetryStrategy
RedisClusterClientConfiguration
RedisClusterClientConfiguration
1
1
Extends
Extends
Extends
Extends
b.a.models.exceptions.RedisException
b.a.models.exceptions.RedisException
+ message: String
+ message: String
+ name: String
+ name: String
b.a.m.exceptions.ClosingException
b.a.m.exceptions.ClosingException
b.a.m.exceptions.RequestException
b.a.m.exceptions.RequestException
b.a.m.exceptions.TimeoutException
b.a.m.exceptions.TimeoutException
b.a.m.exceptions.ExecAbortException
b.a.m.exceptions.ExecAbortException
b.a.m.exceptions.ConnectionException
b.a.m.exceptions.ConnectionException
Command interfaces:
BaseRedisCommands, RedisAclCommands, RedisClusterCommands, RedisFunctionCommands, RedisGeoCommands, RedisHashCommands, RedisHLLCommands, RedisKeyCommands, RedisListCommands, RedisScriptingCommands, RedisServerCommands, RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, RedisStringCommands, RedisTransactionalCommands
Command interfaces:...
...
...
babushka.api.models.RedisBaseResponse
babushka.api.models.RedisBaseResponse
+ handleResponse(resp): Void
+ handleResponse(resp): Void
+ handleResp3Response(resp): Void
+ handleResp3Response(resp): Void
b.a..models.RedisHashResponse
b.a..models.RedisHashResponse
+ handleResponse(resp): HashMap
+ handleResponse(resp): HashMap
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
b.a.models.RedisTransactionResponse
b.a.models.RedisTransactionResponse
+ handleResponse(resp): List<Object>
+ handleResponse(resp): List<Object>
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
b.a.models.RedisClusterResponse
b.a.models.RedisClusterResponse
+ handleResponse(resp): ClusterValue
+ handleResponse(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handlers: List<BaseRedisResponse>
+ handlers: List<BaseRedisResponse>
babushka.api.ClusterValue
babushka.api.ClusterValue
+ routedValue: HashMap<Object>
+ routedValue: HashMap<Object>
+ singleValue: Object
+ singleValue: Object

Check is_single_response
Check is_single_response
babushka.api.c.models.Transaction
babushka.api.c.models.Transaction
+ commands: List<Command>
+ commands: List<Command>
- responseHandler: RedisBaseResponse
- responseHandler: RedisBaseResponse
babushka.api.c.models.Command
babushka.api.c.models.Command
+ requestType: RequestType
+ requestType: RequestType
+ arguments: List<String>
+ arguments: List<String>
- responseHandler: RedisBaseResponse
- responseHandler: RedisBaseResponse
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/img/design-java-api-high-level.svg b/docs/img/design-java-api-high-level.svg index 115b193282..448e4dfd76 100644 --- a/docs/img/design-java-api-high-level.svg +++ b/docs/img/design-java-api-high-level.svg @@ -1,4 +1,4 @@ -
Client API
Client API

RedisClient.java


+ create(Configuration): RedisClient

+ close(): void

+ connect(): void

RedisClient.java...
babushka.api
babushka.api

RedisClientConfiguration.java



RedisClientConfiguration.java...
Controllers
Controllers
babushka.managers
babushka.managers

ConnectionManager.java


+ connectToRedis

+ closeRedisConnection

ConnectionManager.java...

CommandManager.java


+ createRedisRequest

CommandManager.java...

CallbackManager.java


+ requestId: AtomicInteger

+ connectionRequest: Deque

+ callbacks: Map<Int, CompFuture>

+ registerConnection(CompFuture)

+ registerRequest(CompFuture)


CallbackManager.java...
Models
Models
babushka.models.protobuf
babushka.models.protobuf
protobuf generated content
protobuf generated content

ConnectionRequest.java



ConnectionRequest.java...

RedisRequest.java



RedisRequest.java...

Response.java



Response.java...
BaseRedisResponse
BaseRedisResponse
babushka.models.api
babushka.models.api
ClosingError
ClosingError
RequestError
RequestError
TimeoutError
TimeoutError
ExecAbortError
ExecAbortError
ConnectionError
ConnectionError
Connections
Connections
babushka.connectors
babushka.connectors

SocketConnection.java


- getSocket

- isMacOs

- Channel channel

- EventLoopGroup group

- NettySocketManager INSTANCE

+ getInstance(): NettySocketManager

+ write(Msg)

+ writeAndFlush(Msg)

+ close()

class ShutdownHook


SocketConnection.java...
babushka.connection.handlers
babushka.connection.handlers

ChannelHandler.java


+ initChannel

ChannelHandler.java...

ChannelInboundHandler.java


+ channelRead

ChannelInboundHandler.java...

ChannelOutboundHandler.java


+ write

ChannelOutboundHandler.java...

ChannelInitializer.java


+ connect()

+ close()

ChannelInitializer.java...
babushka.ffi.resolvers
babushka.ffi.resolvers

SocketListenerResolver.java


+ startSocketListenerExternal

SocketListenerResolver.java...
Socket
Socket
FFI.Resolvers
FFI.Resolvers

RedisValueResolver.java


+ valueFromPointer(Long ptr): Obj

RedisValueResolver.java...

LoggingResolver.java


+ log(String): Obj

+ init(): Obj

LoggingResolver.java...
lib.rs
lib.rs

Examples

babushka.client.RedisClient redisClient = glider.client.Builder.CreateClient();
redisClient.connectToRedis(host, port); 

redisClient.isConnected(); // true
babushka.client.Commands redisCommands = 
    babushka.client.Builder.GetCommands(
redisClient);

babushka.client.SyncCommands redisSync = 
    babushka.client.Builder.GetSyncCommands(
        redisConnection);
Response res = redisCommands.get(key);
res.getValue();  // maybe this waits
Awaiter.awaitAll(res1, res2, res3); // list of values? 
redisSync.get(key); // String

TODO: 
• Response object
• ERROR 
Examples...
TODO: Add support in the future to allow for multiple socket connection paths. 
TODO: Add support in the fut...
Text is not SVG - cannot display
\ No newline at end of file +
Client API
Client API

RedisClient.java


+ create(Configuration): RedisClient

+ isConnected(): boolean

RedisClient.java...
babushka.api
babushka.api

RedisClientConfiguration.java



RedisClientConfiguration.java...
Controllers
Controllers
babushka.managers
babushka.managers

ConnectionManager.java


+ connectToRedis

+ closeRedisConnection

ConnectionManager.java...

CommandManager.java


+ createRedisRequest

CommandManager.java...

CallbackManager.java


+ requestId: AtomicInteger

+ connectionRequest: Deque

+ callbacks: Map<Int, CompFuture>

+ registerConnection(CompFuture)

+ registerRequest(CompFuture)


CallbackManager.java...
Models
Models
babushka.connections.models.protobuf
babushka.connections.models.protobuf
protobuf generated content
protobuf generated content

ConnectionRequest.java



ConnectionRequest.java...

RedisRequest.java



RedisRequest.java...

Response.java



Response.java...
BaseRedisResponse
BaseRedisResponse
babushka.api.models
babushka.api.models
ClosingError
ClosingError
RequestError
RequestError
TimeoutError
TimeoutError
ExecAbortError
ExecAbortError
ConnectionError
ConnectionError
Connections
Connections
babushka.connectors
babushka.connectors

SocketConnection.java


- getSocket

- isMacOs

- Channel channel

- EventLoopGroup group

- NettySocketManager INSTANCE

+ getInstance(): NettySocketManager

+ write(Msg)

+ writeAndFlush(Msg)

+ close()

class ShutdownHook


SocketConnection.java...
babushka.connection.handlers
babushka.connection.handlers

ChannelHandler.java


+ initChannel

ChannelHandler.java...

ChannelInboundHandler.java


+ channelRead

ChannelInboundHandler.java...

ChannelOutboundHandler.java


+ write

ChannelOutboundHandler.java...

ChannelInitializer.java


+ connect()

+ close()

ChannelInitializer.java...
babushka.ffi.resolvers
babushka.ffi.resolvers

SocketListenerResolver.java


+ startSocketListenerExternal

SocketListenerResolver.java...
Socket
Socket
FFI.Resolvers
FFI.Resolvers

RedisValueResolver.java


+ valueFromPointer(Long ptr): Obj

RedisValueResolver.java...

LoggingResolver.java


+ log(String): Obj

+ init(): Obj

LoggingResolver.java...
lib.rs
lib.rs
Text is not SVG - cannot display
\ No newline at end of file From 7058b0286759106ed1765e118942c9ab5e4c9250 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Wed, 20 Dec 2023 00:21:04 -0800 Subject: [PATCH 21/40] Add more examples; return Type directly Signed-off-by: Andrew Carbonetto --- docs/design-java-api.md | 159 ++++++++++++------ docs/img/design-java-api-detailed-level.svg | 2 +- .../design-java-api-sequence-datatypes.svg | 2 +- 3 files changed, 109 insertions(+), 54 deletions(-) diff --git a/docs/design-java-api.md b/docs/design-java-api.md index b2e228c44b..f58da485f3 100644 --- a/docs/design-java-api.md +++ b/docs/design-java-api.md @@ -20,7 +20,7 @@ babushka.client.api.RedisClientConfiguration configuration = .host(host) .port(port) .build()) - .useTls(true) + .useTLS(true) .build(); // connect to Redis @@ -29,28 +29,31 @@ CompletableFuture redisClientConnection = // resolve the Future and get a RedisClient babushka.client.api.RedisClient redisClient = redisClientConnection.get(); -redisClient.isConnected(); // true ``` -### Case 2: Connection to RedisClient fails +### Case 2: Connection to RedisClient fails with ConnectionException ```java // create a client configuration for a standalone client - check connection CompletableFuture redisClientConnection = babushka.client.api.RedisClient.CreateClient(configuration); // resolve the Future and get a RedisClient that is not connected -babushka.client.api.RedisClient redisClient = redisClientConnection.get(); -if (!redisClient.isConnected()) { - throw new RuntimeException("Failed to connect to Redis: " + redisClient.getConnectionError().getMessage()); +try{ + babushka.client.api.RedisClient redisClient=redisClientConnection.get(); +} catch (babushka.client.api.model.exceptions.RedisException redisException){ + if (redisException instanceOf babushka.client.api.model.exceptions.ConnectionException) { + throw new RuntimeException("Failed to connect to Redis: " + redisException.getMessage()); + } } ``` -### Case 3: Disconnect from RedisClient -TODO: confirm that close should be an async call +### Case 3: Try RedisClient with resource ```java -CompletableFuture redisClientCloseConnection = redisClient.close(); -redisClient = redisClientCloseConnection.get(); -redisClient.isConnected(); // false +try (RedisClient redisClient = RedisClient.CreateClient(configuration).get()) { + // use the RedisClient +} catch (babushka.client.api.model.exceptions.RedisException redisException) { + throw new RuntimeException("RedisClient failed with: " + redisException.getMessage()); +} ``` ### Case 4: Connect to RedisClusterClient @@ -66,7 +69,7 @@ babushka.client.api.RedisClusterClientConfiguration configuration = .host(address_two) .port(port_two) .build()) - .useTls(true) + .useTLS(true) .build(); // connect to Redis @@ -75,75 +78,127 @@ CompletableFuture redisClusterClientConn // resolve the Future and get a RedisClusterClient babushka.client.api.RedisClusterClient redisClusterClient = redisClusterClientConnection.get(); -redisClusterClient.isConnected(); // true ``` -### Case 5: Connect to RedisClient and receive RESP2 responses -To confirm: send `.resp2(true)` in the configuration +### Case 5: Connect to RedisClient and receive RESP2 responses (Future) +```java +// create a client configuration for a standalone client and connect +babushka.client.api.RedisClientConfiguration configuration = + babushka.client.api.RedisClientConfiguration.builder() + .address(babushka.client.api.Addresses.builder() + .host(host) + .port(port) + .build()) + .useTLS(true) + .useRESP2(true) + .build(); + +// connect to Redis +CompletableFuture redisClientConnection = + babushka.client.api.RedisClient.CreateClient(configuration); + +// resolve the Future and get a RedisClient +babushka.client.api.RedisClient redisClient = redisClientConnection.get(); +``` -### Case ___: Get(key) from connected RedisClient +### Case 6: Get(key) from connected RedisClient ```java -CompletableFuture getRequest = redisClient.get("apples"); -RedisStringResponse getResponse = getRequest.get(); -getResponse.isSuccessful(); // true -String getValue = getResponse.getValue(); +CompletableFuture getRequest = redisClient.get("apples"); +String getValueStr = getRequest.get(); ``` -### Case ___: Set(key, value) from connected RedisClient +### Case 7: Set(key, value) from connected RedisClient ```java -CompletableFuture setRequest = redisClient.set("apples", "oranges"); -RedisVoidResponse setResponse = setRequest.get(); -setResponse.isSuccessful(); // true -getResponse.isOk(); // true -getResponse.toString(); // "Ok" +CompletableFuture setRequest = redisClient.set("apples", "oranges"); +setRequest.get(); // returns null when complete + +SetOptions setOptions = SetOptions.builder() + .returnOldValue(true) // returns a String + .build(); +CompletableFuture setRequest = redisClient.set("apples", "oranges", setOptions); +String oldValue = setRequest.get(); // returns a string unless .returnOldValue() is not true ``` -### Case ___: Get(key) from a disconnected RedisClient +### Case 8: Get(key) from a disconnected RedisClient Throw a babushka.api.models.exceptions.ConnectionException if the RedisClient is closed/disconnected - -### Case ___: Send customCommand to RedisClient ```java +try { + CompletableFuture getRequest = redisClient.get("apples"); + String getValueStr = getRequest.get(); +} catch (babushka.client.api.model.exceptions.RedisException redisException) { + // handle RedisException + throw new RuntimeException("RedisClient get failed with: " + redisException.getMessage()); +} +``` -CompletableFuture customCommandRequest = redisClient.customCommand(StringCommands.GETSTRING, "apples"); -RedisBaseResponse customCommandResponse = customCommandRequest.get(); -if (customCommandResponse.isSuccessful()) { - switch(customCommandResponse.getValueType()) { +### Case 9: Send customCommand to RedisClient and receive a RedisFuture (CompleteableFuture wrapper) +```java +// returns an Object: custom command requests don't have an associated return type +RedisFuture customCommandRequest = redisClient.customCommand(StringCommands.GETSTRING, "apples"); +Object objectResponse = customCommandRequest.get(); +if (customCommandRequest.isDone()) { + switch(customCommandRequest.getValueType()) { STRING: - String customCommandValue = (String) customCommandResponse.getValue(); + String stringResponse = customCommandRequest.getString(); break; DEFAULT: throw new RuntimeException("Unexpected value type returned"); } } ``` -### Case ___: Send transaction to RedisClient -TODO -### Case ___: Send get request to a RedisClusterClient with one address -TODO +### Case 10: Send transaction to RedisClient +```java +// submit three commands in a single transaction to Redis +Command getApplesRequest = Command.builder() + .requestType(GETSTRING) + .arguments(new String[]{apples"}) + .build(); +Command getPearsRequest = Command.builder() + .requestType(GETSTRING) + .arguments(new String[]{"pears"}) + .build(); +Command setCherriesRequest = Command.builder() + .requestType(SETSTRING) + .arguments(new String[]{"cherries", "Bing"}) + .build(); -### Case ___: Send get request to a RedisClusterClient with multiple addresses -TODO +Transaction transaction = Transaction.builder() + .command(getAppleRequest) + .command(getPearRequest) + .command(setCherryRequest) + .build(); -### Case ___: Request is interrupted +CompletableFuture> transactionRequest = redisClient.exec(transaction); +List transactionResponse = transactionRequest.get(); + +// TODO: verify that if we use the RedisFuture wrapper, can we receive the typing for each object +``` + +### Case 11: Send get request to a RedisClusterClient with one address +// TODO + +### Case 12: Send get request to a RedisClusterClient with multiple addresses +// TODO + +### Case 13: Request is interrupted ```java -CompletableFuture getRequest = redisClient.get("apples"); +CompletableFuture getRequest = redisClient.get("apples"); try{ - RedisStringResponse getResponse=getRequest.get(); // throws InterruptedException + RedisStringResponse getResponse = getRequest.get(); // throws InterruptedException } catch (InterruptedException interruptedException) { - redisClient.isConnected(); // false + throw new RuntimeException("RedisClient was interrupted: " + interruptedException.getMessage()); } ``` -### Case ___: Request timesout +### Case 14: Request timesout ```java -CompletableFuture getRequest = redisClient.get("apples"); -RedisStringResponse getResponse = getRequest.get(); // times out -if (getResponse.isError()) { - if (getResponse.getErrorType() == TIMEOUT_ERROR) { - babushka.client.api.exceptions.TimeoutException timeoutException = getResponse.getTimeoutException(); - System.err.out("Timeout Exception: " + timeoutException.getMessage()); - throw timeoutException; +CompletableFuture getRequest = redisClient.get("apples"); +try{ + RedisStringResponse getResponse = getRequest.get(); // throws TimeoutException +} catch (babushka.client.api.model.exceptions.RedisException redisException) { + if (redisException instanceOf babushka.client.api.model.exceptions.TimeoutException) { + throw new RuntimeException("RedisClient timedout: " + redisException.getMessage()); } } ``` diff --git a/docs/img/design-java-api-detailed-level.svg b/docs/img/design-java-api-detailed-level.svg index f2163395a9..95aa1ca29e 100644 --- a/docs/img/design-java-api-detailed-level.svg +++ b/docs/img/design-java-api-detailed-level.svg @@ -1,4 +1,4 @@ -
babushka.api.commands
babushka.api.commands
babushka.api.c.BaseCommands
babushka.api.c.BaseCommands
- exec(Transaction): CFuture<RedisBaseResponse<T>>
- exec(Transaction): CFuture<RedisBaseResponse<T>>
- customCommand(String, List): CFuture<RedisBaseResponse<T>>
- customCommand(String, List): CFuture<RedisBaseResponse<T>>
- exec(Command): CFuture<RedisBaseResponse<T>>
- exec(Command): CFuture<RedisBaseResponse<T>>
+ ping(): CFuture<RedisBaseResponse<Void>>
+ ping(): CFuture<RedisBaseResponse<Void>>
babushka.api.c.StringCommands
babushka.api.c.StringCommands
+ get(String): RedisStringResponse<String>
+ get(String): RedisStringResponse<String>
...
...
babushka.api.c.SortedSetCommands
babushka.api.c.SortedSetCommands
babushka.api.RedisClient
babushka.api.RedisClient
+ Create(Config): RedisClient
+ Create(Config): RedisClient
babushka.api.RedisClusterClient
babushka.api.RedisClusterClient
+ Create(Config): RedisClient
+ Create(Config): RedisClient
babushka.api.BaseClient
babushka.api.BaseClient
+ configuration: BaseClientConfiguration
+ configuration: BaseClientConfiguration
+ commands: BaseClientCommands
+ commands: BaseClientCommands
+ connection: ConnectionManager
+ connection: ConnectionManager
+ Create(Config): BaseClient
+ Create(Config): BaseClient
+ CreateAndConnectToRedis(Config): BaseClient
+ CreateAndConnectToRedis(Config): BaseClient
+ isConnected(): boolean
+ isConnected(): boolean
+ connectToRedis(): boolean
+ connectToRedis(): boolean
+ asyncConnectToRedis(): CFuture(Response)
+ asyncConnectToRedis(): CFuture(Response)
babushka.api.BaseClientConfiguration
babushka.api.BaseClientConfiguration
+ addresses: List<Address>
+ addresses: List<Address>
+ useTLS: boolean
+ useTLS: boolean
+ credentials: Credentials
+ credentials: Credentials
+ requestTimeout: long
+ requestTimeout: long
+ readFrom: ReadFromType
+ readFrom: ReadFromType
1
1
Extend all of
Extend all of
RedisClientConfiguration
RedisClientConfiguration
+ databaseId: integer
+ databaseId: integer
+ connectionBackoff: RetryStrategy
+ connectionBackoff: RetryStrategy
RedisClusterClientConfiguration
RedisClusterClientConfiguration
1
1
Extends
Extends
Extends
Extends
b.a.models.exceptions.RedisException
b.a.models.exceptions.RedisException
+ message: String
+ message: String
+ name: String
+ name: String
b.a.m.exceptions.ClosingException
b.a.m.exceptions.ClosingException
b.a.m.exceptions.RequestException
b.a.m.exceptions.RequestException
b.a.m.exceptions.TimeoutException
b.a.m.exceptions.TimeoutException
b.a.m.exceptions.ExecAbortException
b.a.m.exceptions.ExecAbortException
b.a.m.exceptions.ConnectionException
b.a.m.exceptions.ConnectionException
Command interfaces:
BaseRedisCommands, RedisAclCommands, RedisClusterCommands, RedisFunctionCommands, RedisGeoCommands, RedisHashCommands, RedisHLLCommands, RedisKeyCommands, RedisListCommands, RedisScriptingCommands, RedisServerCommands, RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, RedisStringCommands, RedisTransactionalCommands
Command interfaces:...
...
...
babushka.api.models.RedisBaseResponse
babushka.api.models.RedisBaseResponse
+ handleResponse(resp): Void
+ handleResponse(resp): Void
+ handleResp3Response(resp): Void
+ handleResp3Response(resp): Void
b.a..models.RedisHashResponse
b.a..models.RedisHashResponse
+ handleResponse(resp): HashMap
+ handleResponse(resp): HashMap
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
b.a.models.RedisTransactionResponse
b.a.models.RedisTransactionResponse
+ handleResponse(resp): List<Object>
+ handleResponse(resp): List<Object>
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
b.a.models.RedisClusterResponse
b.a.models.RedisClusterResponse
+ handleResponse(resp): ClusterValue
+ handleResponse(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handlers: List<BaseRedisResponse>
+ handlers: List<BaseRedisResponse>
babushka.api.ClusterValue
babushka.api.ClusterValue
+ routedValue: HashMap<Object>
+ routedValue: HashMap<Object>
+ singleValue: Object
+ singleValue: Object

Check is_single_response
Check is_single_response
babushka.api.c.models.Transaction
babushka.api.c.models.Transaction
+ commands: List<Command>
+ commands: List<Command>
- responseHandler: RedisBaseResponse
- responseHandler: RedisBaseResponse
babushka.api.c.models.Command
babushka.api.c.models.Command
+ requestType: RequestType
+ requestType: RequestType
+ arguments: List<String>
+ arguments: List<String>
- responseHandler: RedisBaseResponse
- responseHandler: RedisBaseResponse
Text is not SVG - cannot display
\ No newline at end of file +
babushka.api.commands
babushka.api.commands
babushka.api.c.BaseCommands
babushka.api.c.BaseCommands
- exec(Transaction): CFuture<T>
- exec(Transaction): CFuture<T>
- customCommand(String, List): CFuture<T>
- customCommand(String, List): CFuture<T>
- exec(Command): CFuture<T>
- exec(Command): CFuture<T>
babushka.api.c.StringCommands
babushka.api.c.StringCommands
+ get(String): CompleteableFuture<String>
+ get(String): CompleteableFuture<String>
+ ping(String): CFuture<String>
+ ping(String): CFuture<String>
babushka.api.c.SortedSetCommands
babushka.api.c.SortedSetCommands
babushka.api.RedisClient
babushka.api.RedisClient
+ CreateClient(RedisClientConfiguration): CFuture<RedisClient>
+ CreateClient(RedisClientConfiguration): CFuture<RedisClient>
babushka.api.RedisClusterClient
babushka.api.RedisClusterClient
+ CreateClient(RedisClusterClientConfiguration): CFuture<RedisClusterClient>
+ CreateClient(RedisClusterClientConfiguration): CFuture<RedisClusterClient>
babushka.api.BaseClient
babushka.api.BaseClient
+ configuration: BaseClientConfiguration
+ configuration: BaseClientConfiguration
+ connection: ConnectionManager
+ connection: ConnectionManager
+ CreateClient(Config): CFuture<BaseClient>
+ CreateClient(Config): CFuture<BaseClient>
+ isConnected(): boolean
+ isConnected(): boolean
+ close(): CFuture<RedisClient>
+ close(): CFuture<RedisClient>
+ hasError(): boolean
+ hasError(): boolean
+ getError(): @nullable RedisException
+ getError(): @nullable RedisException
babushka.api.BaseClientConfiguration
babushka.api.BaseClientConfiguration
+ addresses: List<Address>
+ addresses: List<Address>
+ useTLS: boolean
+ useTLS: boolean
+ credentials: Credentials
+ credentials: Credentials
+ requestTimeout: long
+ requestTimeout: long
+ readFrom: ReadFromType
+ readFrom: ReadFromType
1
1
Extend all of
Extend all of
RedisClientConfiguration
RedisClientConfiguration
+ databaseId: integer
+ databaseId: integer
+ connectionBackoff: RetryStrategy
+ connectionBackoff: RetryStrategy
RedisClusterClientConfiguration
RedisClusterClientConfiguration
1
1
Extends
Extends
b.a.models.exceptions.RedisException
b.a.models.exceptions.RedisException
+ message: String
+ message: String
+ name: String
+ name: String
b.a.m.exceptions.ClosingException
b.a.m.exceptions.ClosingException
b.a.m.exceptions.RequestException
b.a.m.exceptions.RequestException
b.a.m.exceptions.TimeoutException
b.a.m.exceptions.TimeoutException
b.a.m.exceptions.ExecAbortException
b.a.m.exceptions.ExecAbortException
b.a.m.exceptions.ConnectionException
b.a.m.exceptions.ConnectionException
Command interfaces:
BaseRedisCommands, RedisAclCommands, RedisClusterCommands, RedisFunctionCommands, RedisGeoCommands, RedisHashCommands, RedisHLLCommands, RedisKeyCommands, RedisListCommands, RedisScriptingCommands, RedisServerCommands, RedisSetCommands, RedisSortedSetCommands, RedisStreamCommands, RedisStringCommands, RedisTransactionalCommands
Command interfaces:...
...
...
b.a.models.RedisTransactionResponse
b.a.models.RedisTransactionResponse
+ handleResponse(resp): List<Object>
+ handleResponse(resp): List<Object>
+ handleResp3Response(resp): HashMap
+ handleResp3Response(resp): HashMap
b.a.models.RedisClusterResponse
b.a.models.RedisClusterResponse
+ handleResponse(resp): ClusterValue
+ handleResponse(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handleResp3Response(resp): ClusterValue
+ handlers: List<Object>
+ handlers: List<Object>
babushka.api.ClusterValue
babushka.api.ClusterValue
+ routedValue: HashMap<Object>
+ routedValue: HashMap<Object>
+ singleValue: Object
+ singleValue: Object

Check is_single_response
Check is_single_response
babushka.api.c.models.Transaction
babushka.api.c.models.Transaction
+ commands: List<Command>
+ commands: List<Command>
- responseHandler: RedisTra..Response
- responseHandler: RedisTra..Response
babushka.api.c.models.Command
babushka.api.c.models.Command
+ requestType: RequestType
+ requestType: RequestType
+ arguments: List<String>
+ arguments: List<String>
- responseHandler: T
- responseHandler: T
Extends
Extends
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/img/design-java-api-sequence-datatypes.svg b/docs/img/design-java-api-sequence-datatypes.svg index 2426b75269..94bdeee069 100644 --- a/docs/img/design-java-api-sequence-datatypes.svg +++ b/docs/img/design-java-api-sequence-datatypes.svg @@ -1,4 +1,4 @@ -
:RedisBaseCommand
:RedisBaseCommand
:RedisStringCommand
:RedisStringCommand
:RedisClientCommands
:RedisClientCommands
get(key)
get(key)
get(key)
get(key)
exec(Request.GET, List(key))
exec(Request.GET, List(key))
:SocketConnector
:SocketConnector
submitNewCommand
(Command, <String>)
submitNewCommand...
write(protobuf.RedisRequest)
write(protobuf.RedisRequest)
:CallbackManager
:CallbackManager
:CommandManager
:CommandManager
registerRequest
(Command)
registerRequest...
callbackId
callbackId
handleResponse
(protobuf.RedisResponse)
handleResponse...
BaseRedisResponse<String>
BaseRedisResponse<String>
:CompletableFuture
:CompletableFuture
completeRequest(protobuf.Response)
completeRequest(protobuf.Response)
supplyAsync()
supplyAsync()
CompletableFuture<BaseRedisResponse<String>>
CompletableFuture<BaseRedisResponse<String>>
get()
get()
thenApplyAsync
(protobuf.RedisResponse)
thenApplyAsync...
:BaseRedisResponse
:BaseRedisResponse
(String)(value_from_pointer)
(String)(value_...
BaseRedisResponse<String>
BaseRedisResponse<String>
new
new
CompletableFuture<BaseRedisResponse<String>>
CompletableFuture<BaseRedisResponse<String>>
completeAsync()
completeAsync()
Text is not SVG - cannot display
\ No newline at end of file +
:RedisBaseCommand
:RedisBaseCommand
:RedisClientCommands
:RedisClientCommands
exec(args)
exec(args)
exec(RequestType, args)
exec(RequestType, args)
submitNewCommand
(Command, <T>)
submitNewCommand...
:CommandManager
:CommandManager
:CompletableFuture
:CompletableFuture
getResponseHandler(<T>)
getResponseHandler(<T>)
<T>
<T>
supplyAsync()
supplyAsync()
CompletableFuture<BaseRedisResponse<T>>
CompletableFuture<BaseRedisResponse<T>>
get()
get()
:BaseRedisResponse
:BaseRedisResponse
(T)(value_from_pointer)
(T)(value_from_...
<T>
<T>
protobuf.RedisRequest
protobuf.RedisRequest
protobuf.RedisResponse
protobuf.RedisResponse
handleResponse
(protobuf.RedisResponse)
handleResponse...
Socket Read/Write
Socket Read/Write
Text is not SVG - cannot display
\ No newline at end of file From f339390968803e40ff4847d709a9deca01abaf70 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Wed, 20 Dec 2023 13:38:04 -0800 Subject: [PATCH 22/40] Update customCommand use case --- docs/design-java-api.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/design-java-api.md b/docs/design-java-api.md index f58da485f3..ae19c19dcc 100644 --- a/docs/design-java-api.md +++ b/docs/design-java-api.md @@ -134,12 +134,16 @@ try { ### Case 9: Send customCommand to RedisClient and receive a RedisFuture (CompleteableFuture wrapper) ```java // returns an Object: custom command requests don't have an associated return type -RedisFuture customCommandRequest = redisClient.customCommand(StringCommands.GETSTRING, "apples"); +CompletableFuture customCommandRequest = redisClient.customCommand(StringCommands.GETSTRING, new String[]{"apples"}); Object objectResponse = customCommandRequest.get(); -if (customCommandRequest.isDone()) { - switch(customCommandRequest.getValueType()) { +String stringResponse = (String) objectResponse; + +// We can use the RedisFuture wrapper to determine the typing instead +RedisFuture customCommandRedisFuture = redisClient.customCommand(StringCommands.GETSTRING, new String[]{"apples"}); +if (customCommandRedisFuture.get()) { + switch(customCommandRedisFuture.getValueType()) { STRING: - String stringResponse = customCommandRequest.getString(); + String stringResponse = customCommandRedisFuture.getString(); break; DEFAULT: throw new RuntimeException("Unexpected value type returned"); @@ -243,4 +247,4 @@ At a high-level the Java wrapper client has 3 layers: 2. Data is returned as a payload in the RedisResponse object on a success response 3. If no data payload is requested, the service returns an OK constant response 4. Otherwise, the service will cast to the specified type on a one-for-one mapping based on the command -5. If the casting fails, the Java-wrapper will report an Error \ No newline at end of file +5. If the casting fails, the Java-wrapper will report an Error From c4a13daf7c1098b8122fe260bdfe56a51ba22ec1 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Wed, 20 Dec 2023 13:52:55 -0800 Subject: [PATCH 23/40] Update transactional use-cases --- docs/design-java-api.md | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/design-java-api.md b/docs/design-java-api.md index ae19c19dcc..d3af0571ad 100644 --- a/docs/design-java-api.md +++ b/docs/design-java-api.md @@ -140,15 +140,17 @@ String stringResponse = (String) objectResponse; // We can use the RedisFuture wrapper to determine the typing instead RedisFuture customCommandRedisFuture = redisClient.customCommand(StringCommands.GETSTRING, new String[]{"apples"}); -if (customCommandRedisFuture.get()) { - switch(customCommandRedisFuture.getValueType()) { - STRING: - String stringResponse = customCommandRedisFuture.getString(); +RedisResponse redisResponseObject = customCommandRedisFuture.getRedisResponse(); +// same as .get() +// Object objectResponse = redisResponseObject.getValue(); +switch(redisResponseObject.getValueType()) { + STRING: + String stringResponse = redisResponseObject.getString(); break; - DEFAULT: + DEFAULT: throw new RuntimeException("Unexpected value type returned"); - } } + ``` ### Case 10: Send transaction to RedisClient @@ -174,9 +176,26 @@ Transaction transaction = Transaction.builder() .build(); CompletableFuture> transactionRequest = redisClient.exec(transaction); -List transactionResponse = transactionRequest.get(); - -// TODO: verify that if we use the RedisFuture wrapper, can we receive the typing for each object +List genericResponse = transactionRequest.get(); +String firstResponse = (String) genericResponse.get(0); +String secondResponse = (String) genericResponse.get(1); +genericResponse.get(2); // returns null + +// calls .get() and returns a list of RedisResponse objects that can be type-checked +List transactionResponse = transactionRequest.getRedisResponseList(); + +// We can use the RedisFuture wrapper to determine the typing of each object in the list +transactionResponse.foreach( + r -> { switch(r.getValue()) { + VOID: + break; + STRING: + String stringResponse = r.getString(); + break; + DEFAULT: + throw new RuntimeException("Unexpected value type returned"); + } +}); ``` ### Case 11: Send get request to a RedisClusterClient with one address From 8bb74ba8e5b7e5bba65b592da0f64c9bad889b36 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Mon, 22 Jan 2024 17:37:39 -0800 Subject: [PATCH 24/40] Add Go API documentation --- docs/design-go-api.md | 218 +++++++++++++++++++++++++++++++++++++ docs/img/design-go-api.svg | 3 + 2 files changed, 221 insertions(+) create mode 100644 docs/design-go-api.md create mode 100644 docs/img/design-go-api.svg diff --git a/docs/design-go-api.md b/docs/design-go-api.md new file mode 100644 index 0000000000..ae30725a97 --- /dev/null +++ b/docs/design-go-api.md @@ -0,0 +1,218 @@ +# Go API Design documentation + +## Overview + +This document presents the high-level user API for the Go-Wrapper client library. Specifically, it demonstrates how the user connects to Redis, executes requests, receives responses, and checks for errors. + +## Requirements + +- The minimum supported Go version will be 1.18. This version was chosen because it added support for generics, including type constraints and type sets +- The API will be thread-safe. +- The API will accept as inputs all of [RESP2 types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md). We plan to add support for RESP3 types when they are available. +- The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. + +# Use Cases + +### Case 1: Create Redis client and connect + +```go +var config *StandaloneClientConfiguration = glide.NewClientConfiguration() + WithAddress(glide.AddressInfo{Host: host, Port: port}). + WithUseTLS(true) + +// Create a client and connect +var client *glide.RedisClient +var err error +client, err = glide.CreateClient(config) +``` + +### Case 2: Connection to Redis fails with ConnectionError +```go +var client *glide.RedisClient +var err error +client, err := glide.CreateClient(config) + +// User can check specifically for a ConnectionError: +if err != nil { + connErr, isConnError := err.(glide.ConnectionError) + if isConnError { + log.Fatal("Failed to connect to Redis: " + connErr.Error()) + } +} + +// Or user can simply log the error message: +if err != nil { + log.Fatal("Redis client failed with: " + err.Error()) +} +``` + +### Case 3: Connect to Redis with deferred cleanup +```go +var client *glide.RedisClient +var err error +client, err = glide.CreateClient(config) +if err != nil { + log.Fatal("Redis client failed with: " + err.Error()) +} + +defer client.Close() + +// continue using client... +``` + +### Case 4: Connect to Redis cluster +```go +var config *glide.ClusterClientConfiguration +config = glide.NewClusterClientConfiguration(). + WithAddress(glide.AddressInfo{Host: host1, Port: port1}). + WithAddress(glide.AddressInfo{Host: host2, Port: port2}). + WithUseTLS(true) + +var client *glide.RedisClusterClient +var err error +client, err = glide.CreateClusterClient(config) +``` + +### Case 5: Get(key) from connected RedisClient +```go +result, err := client.Get("apples") +``` + +### Case 6: Set(key, value) from connected RedisClient +```go +// Without setOptions +err := client.Set("apples", "oranges") + +// With setOptions +var setOptions *glide.SetOptions +setOptions = glide.NewSetOptions(). + WithReturnOldValue(true) +oldValue, err := client.SetWithOptions("apples", "oranges", setOptions) +``` + +### Case 7: Get(key) from a disconnected RedisClient +Return a glide.ConnectionError if the RedisClient is closed/disconnected +```go +result, err := client.Get("apples") +if err != nil { + connErr, isConnErr := err.(glide.ConnectionError) + if isConnErr { + log.Fatal("RedisClient get failed with: " + connErr.Error()) + } +} +``` + +### Case 8: Send custom command to RedisClient +```go +var result interface{} +var err error + +result, err = client.CustomCommand([]{"GET", "apples"}) +if err != nil { + log.Fatal("RedisClient failed to execute custom command with: " + err.Error()) +} + +strResult, isString := result.(string) +if !isString { + log.Fatal("Expected result to be of type string but the actual type was: " + reflect.TypeOf(result)) +} +``` + +### Case 9: Send transaction to RedisClient +```go +transaction := glide.NewTransaction() +transaction.Get("apples") +transaction.Get("pears") +transaction.Set("cherries", "Bing") + +var result []interface{} +var err error +result, err = client.Exec(transaction) +if err != nil { + log.Fatal("Redis client transaction failed with: " + err.Error()) +} + +firstResponse := result[0] +secondResponse := result[1] +thirdResponse := result[2] // evaluates to nil +``` + +### Case 10: Send Get request to a RedisClusterClient with one address +```go +var config *glide.ClusterClientConfiguration +config = glide.NewClusterClientConfiguration(). + WithAddress(glide.AddressInfo{Host: host, Port: port}). + WithUseTLS(true) + +// Create a client and connect +var client *glide.RedisClusterClient +var err error +client, err = glide.CreateClusterClient(config) +if err != nil { + log.Fatal("Redis client failed with: " + err.Error()) +} + +result, err := client.Get("apples") +``` + +### Case 11: Send Get request to a RedisClusterClient with multiple addresses +```go +var config *glide.ClusterClientConfiguration +config = glide.NewClusterClientConfiguration(). + WithAddress(glide.AddressInfo{Host: host1, Port: port1}). + WithAddress(glide.AddressInfo{Host: host2, Port: port2}). + WithUseTLS(true) + +// Create a client and connect +var client *glide.RedisClusterClient +var err error +client, err = glide.CreateClusterClient(config) +if err != nil { + log.Fatal("Redis client failed with: " + err.Error()) +} + +result, err := client.GetWithRoutes("apples", glide.NewRoute(glide.AllNodes)) +``` + +### Case 12: Request times out, user retries up to a predetermined limit +```go +numRetries := 3 + +for { + attempts := 0 + result, err := client.Get("apples") + + if err == nil { + break + } + + timeoutErr, isTimeoutErr := err.(glide.TimeoutError) + if isTimeoutErr { + fmt.Println("RedisClient Get encountered a TimeoutError") + } else { + log.Fatal("RedisClient Get failed with: " + err.Error()) + } + + attempts += 1 + if attempts == numRetries { + log.Fatal("RedisClient Get request hit the retry limit") + } +} +``` + +### Case 13: Get(key) encounters a ClosingError +```go +result, err := client.Get("apples") // ClosingError is detected internally, client will internally close and perform any necessary cleanup steps +if err != nil { + closingErr, isClosingErr := err.(glide.ClosingError) + if isClosingErr { + log.Fatal("RedisClient get failed with: " + closingErr.Error()) + } +} +``` + +# API Design + +## Presentation + +![API Design](img/design-go-api.svg) diff --git a/docs/img/design-go-api.svg b/docs/img/design-go-api.svg new file mode 100644 index 0000000000..13debfe5c0 --- /dev/null +++ b/docs/img/design-go-api.svg @@ -0,0 +1,3 @@ + + +glide.baseRedisClientconfig: *ClientConfigurationGet(string): string, errorSet(string, string): errorSetWithOptions(string, string, SetOptions): string, errorClose(): errorcreateBaseClient(*ClientConfiguration): *BaseRedisClient, errorexecuteCommand(*protobuf.RedisRequest): protobuf.RedisResponse, errorglide.RedisClientbaseClient: *BaseRedisClientCreateClient(*StandaloneClientConfiguration): *RedisClient, errorExec(*StandaloneTransaction): []interface{}, error
implements
implements
glide.baseClientConfigurationaddresses: []AddressInfouseTLS: boolcredentials:  AuthenticationOptionsreadFrom: ReadFromStrategyclientCreationTimeout: intresponseTimeout: inttoProtobufConnRequest(): *protobuf.ConnectionRequestglide.RedisClusterClientbaseClient: *BaseRedisClientCreateClusterClient(*ClusterClientConfiguration): *RedisClusterClient, errorExec(*ClusterTransaction): []interface{}, errorExecWithRoutes(*ClusterTransaction, Routes): []interface, errorSetWithRoutes(string, string, Routes): errorSetWithOptionsAndRoutes(string, string, SetOptions, Routes): string, error
implements
implements
glide.StandaloneClientConfigurationbaseConfig: *BaseClientConfigurationbackoffStrategy: *BackoffStrategydatabaseId: intNewClientConfiguration(): *StandaloneClientConfigurationWithAddress(AddressInfo): *StandaloneClientConfigurationWithUseTLS(bool): *StandaloneClientConfigurationWithCredentials(*AuthenticationOptions): *StandaloneClientConfigurationWithReadFromStrategy(ReadFromStrategy): *StandaloneClientConfigurationWithClientCreationTimeout(int): *StandaloneClientConfigurationWithResponseTimeout(int): *StandaloneClientConfigurationWithBackoffStrategy(*BackoffStrategy): *StandaloneClientConfigurationWithDatabaseId(int): *StandaloneClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequest
implements
implements
glide.ClusterClientConfigurationbaseConfig: *BaseClientConfigurationNewClusterClientConfiguration(): *ClusterClientConfigurationWithAddress(AddressInfo): *ClusterClientConfigurationWithUseTLS(bool): *ClusterClientConfigurationWithCredentials(*AuthenticationOptions): *ClusterClientConfigurationWithReadFromStrategy(ReadFromStrategy): *ClusterClientConfigurationWithClientCreationTimeout(int): *ClusterClientConfigurationWithResponseTimeout(int): *ClusterClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestinterface glide.ClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestglide.ClosingErrorredisError: RedisErrorglide.RequestErrorredisError: RedisErrorglide.TimeoutErrorredisError: RedisErrorglide.ExecAbortErrorredisError: RedisErrorglide.ConnectionErrorredisError: RedisErrorglide.RedisErrormessage: StringError(): str
Text is not SVG - cannot display
\ No newline at end of file From eece5f92105a7275bbc8a8b23463785baea067be Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Mon, 22 Jan 2024 17:39:26 -0800 Subject: [PATCH 25/40] add missing period --- docs/design-go-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design-go-api.md b/docs/design-go-api.md index ae30725a97..2c70cab60a 100644 --- a/docs/design-go-api.md +++ b/docs/design-go-api.md @@ -6,7 +6,7 @@ This document presents the high-level user API for the Go-Wrapper client library ## Requirements -- The minimum supported Go version will be 1.18. This version was chosen because it added support for generics, including type constraints and type sets +- The minimum supported Go version will be 1.18. This version was chosen because it added support for generics, including type constraints and type sets. - The API will be thread-safe. - The API will accept as inputs all of [RESP2 types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md). We plan to add support for RESP3 types when they are available. - The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. From 73ba55b439cc665336539d01c83d18cbd086cd7a Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 23 Jan 2024 15:42:39 -0800 Subject: [PATCH 26/40] Address PR feedback --- docs/design-go-api.md | 59 ++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/docs/design-go-api.md b/docs/design-go-api.md index 2c70cab60a..24c50eb387 100644 --- a/docs/design-go-api.md +++ b/docs/design-go-api.md @@ -8,7 +8,7 @@ This document presents the high-level user API for the Go-Wrapper client library - The minimum supported Go version will be 1.18. This version was chosen because it added support for generics, including type constraints and type sets. - The API will be thread-safe. -- The API will accept as inputs all of [RESP2 types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md). We plan to add support for RESP3 types when they are available. +- The API will accept as inputs all [RESP3 types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md). - The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. # Use Cases @@ -26,17 +26,17 @@ var err error client, err = glide.CreateClient(config) ``` -### Case 2: Connection to Redis fails with ConnectionError +### Case 2: Connection to Redis fails with ClosingError ```go var client *glide.RedisClient var err error client, err := glide.CreateClient(config) -// User can check specifically for a ConnectionError: +// User can check specifically for a ClosingError: if err != nil { - connErr, isConnError := err.(glide.ConnectionError) - if isConnError { - log.Fatal("Failed to connect to Redis: " + connErr.Error()) + closingErr, isClosingErr := err.(glide.ClosingError) + if isClosingErr { + log.Fatal("Failed to connect to Redis: " + closingErr.Error()) } } @@ -155,7 +155,7 @@ if err != nil { result, err := client.Get("apples") ``` -### Case 11: Send Get request to a RedisClusterClient with multiple addresses +### Case 11: Send Ping request to a RedisClusterClient with multiple addresses ```go var config *glide.ClusterClientConfiguration config = glide.NewClusterClientConfiguration(). @@ -171,42 +171,37 @@ if err != nil { log.Fatal("Redis client failed with: " + err.Error()) } -result, err := client.GetWithRoutes("apples", glide.NewRoute(glide.AllNodes)) -``` +// Without message or route +result, err := client.Ping() -### Case 12: Request times out, user retries up to a predetermined limit -```go -numRetries := 3 +// With message +result, err := client.PingWithMessage("Ping received") -for { - attempts := 0 - result, err := client.Get("apples") +// With route +result, err := client.PingWithRoute(glide.NewRoute(glide.AllNodes)) - if err == nil { - break - } +// With message and route +result, err := client.PingWithMessageAndRoute("Ping received", glide.NewRoute(glide.AllNodes)) +``` +### Case 12: Get(key) encounters a TimeoutError +```go +result, err := client.Get("apples") +if err != nil { timeoutErr, isTimeoutErr := err.(glide.TimeoutError) - if isTimeoutErr { - fmt.Println("RedisClient Get encountered a TimeoutError") - } else { - log.Fatal("RedisClient Get failed with: " + err.Error()) - } - - attempts += 1 - if attempts == numRetries { - log.Fatal("RedisClient Get request hit the retry limit") + if isTimeoutErr { + // Handle error as desired } } ``` -### Case 13: Get(key) encounters a ClosingError +### Case 13: Get(key) encounters a ConnectionError ```go -result, err := client.Get("apples") // ClosingError is detected internally, client will internally close and perform any necessary cleanup steps +result, err := client.Get("apples") if err != nil { - closingErr, isClosingErr := err.(glide.ClosingError) - if isClosingErr { - log.Fatal("RedisClient get failed with: " + closingErr.Error()) + connErr, isConnErr := err.(glide.ConnectionError) + if isConnErr { + // Handle error as desired } } ``` From b88cbbd94fba93277c64b559c7189c8132967c29 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 23 Jan 2024 16:36:16 -0800 Subject: [PATCH 27/40] Update struct diagram --- docs/img/design-go-api.svg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/img/design-go-api.svg b/docs/img/design-go-api.svg index 13debfe5c0..647954eb81 100644 --- a/docs/img/design-go-api.svg +++ b/docs/img/design-go-api.svg @@ -1,3 +1,4 @@ + -glide.baseRedisClientconfig: *ClientConfigurationGet(string): string, errorSet(string, string): errorSetWithOptions(string, string, SetOptions): string, errorClose(): errorcreateBaseClient(*ClientConfiguration): *BaseRedisClient, errorexecuteCommand(*protobuf.RedisRequest): protobuf.RedisResponse, errorglide.RedisClientbaseClient: *BaseRedisClientCreateClient(*StandaloneClientConfiguration): *RedisClient, errorExec(*StandaloneTransaction): []interface{}, error
implements
implements
glide.baseClientConfigurationaddresses: []AddressInfouseTLS: boolcredentials:  AuthenticationOptionsreadFrom: ReadFromStrategyclientCreationTimeout: intresponseTimeout: inttoProtobufConnRequest(): *protobuf.ConnectionRequestglide.RedisClusterClientbaseClient: *BaseRedisClientCreateClusterClient(*ClusterClientConfiguration): *RedisClusterClient, errorExec(*ClusterTransaction): []interface{}, errorExecWithRoutes(*ClusterTransaction, Routes): []interface, errorSetWithRoutes(string, string, Routes): errorSetWithOptionsAndRoutes(string, string, SetOptions, Routes): string, error
implements
implements
glide.StandaloneClientConfigurationbaseConfig: *BaseClientConfigurationbackoffStrategy: *BackoffStrategydatabaseId: intNewClientConfiguration(): *StandaloneClientConfigurationWithAddress(AddressInfo): *StandaloneClientConfigurationWithUseTLS(bool): *StandaloneClientConfigurationWithCredentials(*AuthenticationOptions): *StandaloneClientConfigurationWithReadFromStrategy(ReadFromStrategy): *StandaloneClientConfigurationWithClientCreationTimeout(int): *StandaloneClientConfigurationWithResponseTimeout(int): *StandaloneClientConfigurationWithBackoffStrategy(*BackoffStrategy): *StandaloneClientConfigurationWithDatabaseId(int): *StandaloneClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequest
implements
implements
glide.ClusterClientConfigurationbaseConfig: *BaseClientConfigurationNewClusterClientConfiguration(): *ClusterClientConfigurationWithAddress(AddressInfo): *ClusterClientConfigurationWithUseTLS(bool): *ClusterClientConfigurationWithCredentials(*AuthenticationOptions): *ClusterClientConfigurationWithReadFromStrategy(ReadFromStrategy): *ClusterClientConfigurationWithClientCreationTimeout(int): *ClusterClientConfigurationWithResponseTimeout(int): *ClusterClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestinterface glide.ClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestglide.ClosingErrorredisError: RedisErrorglide.RequestErrorredisError: RedisErrorglide.TimeoutErrorredisError: RedisErrorglide.ExecAbortErrorredisError: RedisErrorglide.ConnectionErrorredisError: RedisErrorglide.RedisErrormessage: StringError(): str
Text is not SVG - cannot display
\ No newline at end of file +glide.baseRedisClientconfig: *ClientConfigurationGet(string): string, errorSet(string, string): errorSetWithOptions(string, string, SetOptions): string, errorClose(): errorcreateBaseClient(*ClientConfiguration): *BaseRedisClient, errorexecuteCommand(*protobuf.RedisRequest): *RedisResponse, errorglide.RedisClientbaseClient: *BaseRedisClientCreateClient(*StandaloneClientConfiguration): *RedisClient, errorExec(*StandaloneTransaction): []interface{}, error
implements
glide.baseClientConfigurationaddresses: []AddressInfouseTLS: boolcredentials:  AuthenticationOptionsreadFrom: ReadFromStrategyclientCreationTimeout: intresponseTimeout: inttoProtobufConnRequest(): *protobuf.ConnectionRequestglide.RedisClusterClientbaseClient: *BaseRedisClientCreateClusterClient(*ClusterClientConfiguration): *RedisClusterClient, errorExec(*ClusterTransaction): []interface{}, errorExecWithRoute(*ClusterTransaction, Route): []interface, error
implements
glide.StandaloneClientConfigurationbaseConfig: *BaseClientConfigurationbackoffStrategy: *BackoffStrategydatabaseId: intNewClientConfiguration(): *StandaloneClientConfigurationWithAddress(AddressInfo): *StandaloneClientConfigurationWithUseTLS(bool): *StandaloneClientConfigurationWithCredentials(*AuthenticationOptions): *StandaloneClientConfigurationWithReadFromStrategy(ReadFromStrategy): *StandaloneClientConfigurationWithClientCreationTimeout(int): *StandaloneClientConfigurationWithResponseTimeout(int): *StandaloneClientConfigurationWithBackoffStrategy(*BackoffStrategy): *StandaloneClientConfigurationWithDatabaseId(int): *StandaloneClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequest
implements
glide.ClusterClientConfigurationbaseConfig: *BaseClientConfigurationNewClusterClientConfiguration(): *ClusterClientConfigurationWithAddress(AddressInfo): *ClusterClientConfigurationWithUseTLS(bool): *ClusterClientConfigurationWithCredentials(*AuthenticationOptions): *ClusterClientConfigurationWithReadFromStrategy(ReadFromStrategy): *ClusterClientConfigurationWithClientCreationTimeout(int): *ClusterClientConfigurationWithResponseTimeout(int): *ClusterClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestinterface glide.ClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestglide.ClosingErrorredisError: RedisErrorglide.RequestErrorredisError: RedisErrorglide.TimeoutErrorredisError: RedisErrorglide.ExecAbortErrorredisError: RedisErrorglide.ConnectionErrorredisError: RedisErrorglide.RedisErrormessage: StringError(): str
\ No newline at end of file From 30406bf9ac983bd432e74a7b0897629d9fe810b8 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 23 Jan 2024 18:04:32 -0800 Subject: [PATCH 28/40] PR suggestions --- docs/design-go-api.md | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/docs/design-go-api.md b/docs/design-go-api.md index 24c50eb387..a5f30d4eb2 100644 --- a/docs/design-go-api.md +++ b/docs/design-go-api.md @@ -6,7 +6,7 @@ This document presents the high-level user API for the Go-Wrapper client library ## Requirements -- The minimum supported Go version will be 1.18. This version was chosen because it added support for generics, including type constraints and type sets. +- The minimum supported Go version will be 1.18. This version introduces support for generics, including type constraints and type sets. - The API will be thread-safe. - The API will accept as inputs all [RESP3 types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md). - The API will attempt authentication, topology refreshes, reconnections, etc., automatically. In case of failures concrete errors will be returned to the user. @@ -39,25 +39,31 @@ if err != nil { log.Fatal("Failed to connect to Redis: " + closingErr.Error()) } } - -// Or user can simply log the error message: -if err != nil { - log.Fatal("Redis client failed with: " + err.Error()) -} ``` ### Case 3: Connect to Redis with deferred cleanup ```go -var client *glide.RedisClient -var err error -client, err = glide.CreateClient(config) -if err != nil { - log.Fatal("Redis client failed with: " + err.Error()) -} +func connectAndGet(key string) string { + var client *glide.RedisClient + var err error + client, err = glide.CreateClient(config) + if err != nil { + log.Fatal("Redis client failed with: " + err.Error()) + } -defer client.Close() + // client.Close() is executed when the function exits. + // The client is available until the end of the function. + defer client.Close() + + result, err := client.Get(key) + if err != nil { + // If we enter this branch, client.Close() will be executed after logging this message. + log.Fatal("Redis Get failed with: " + err.Error()) + } -// continue using client... + // client.Close() will be executed when the result is returned. + return result +} ``` ### Case 4: Connect to Redis cluster @@ -76,6 +82,7 @@ client, err = glide.CreateClusterClient(config) ### Case 5: Get(key) from connected RedisClient ```go result, err := client.Get("apples") +fmt.Println("The value associated with 'apples' is: " + result) ``` ### Case 6: Set(key, value) from connected RedisClient @@ -91,7 +98,7 @@ oldValue, err := client.SetWithOptions("apples", "oranges", setOptions) ``` ### Case 7: Get(key) from a disconnected RedisClient -Return a glide.ConnectionError if the RedisClient is closed/disconnected +Return a glide.ConnectionError if the RedisClient fails to connect to Redis ```go result, err := client.Get("apples") if err != nil { @@ -132,8 +139,8 @@ if err != nil { log.Fatal("Redis client transaction failed with: " + err.Error()) } -firstResponse := result[0] -secondResponse := result[1] +firstResponse := result[0] // evaluates to a string +secondResponse := result[1] // evaluates to a string thirdResponse := result[2] // evaluates to nil ``` From a25467e3039c356149a9714a2520f09c37929579 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 25 Jan 2024 17:25:31 -0800 Subject: [PATCH 29/40] Add documentation for the Go FFI design --- docs/design-go-api.md | 32 +++++++++++++++++++ docs/img/FFI-conn-sequence.svg | 4 +++ docs/img/FFI-conn-struct-diagram.svg | 4 +++ docs/img/FFI-request-failure-sequence.svg | 4 +++ .../FFI-request-failure-struct-diagram.svg | 4 +++ docs/img/FFI-request-success-sequence.svg | 4 +++ .../FFI-request-success-struct-diagram.svg | 4 +++ 7 files changed, 56 insertions(+) create mode 100644 docs/img/FFI-conn-sequence.svg create mode 100644 docs/img/FFI-conn-struct-diagram.svg create mode 100644 docs/img/FFI-request-failure-sequence.svg create mode 100644 docs/img/FFI-request-failure-struct-diagram.svg create mode 100644 docs/img/FFI-request-success-sequence.svg create mode 100644 docs/img/FFI-request-success-struct-diagram.svg diff --git a/docs/design-go-api.md b/docs/design-go-api.md index a5f30d4eb2..d7b44638c3 100644 --- a/docs/design-go-api.md +++ b/docs/design-go-api.md @@ -218,3 +218,35 @@ if err != nil { ## Presentation ![API Design](img/design-go-api.svg) + +# FFI Design + +## Client creation and connection + +### Sequence diagram + +![FFI Connection Sequence Diagram](img/FFI-conn-sequence.svg) + +### Struct diagram + +![FFI Connection Struct Diagram](img/FFI-conn-struct-diagram.svg) + +## Redis request succeeds + +### Sequence diagram + +![FFI Request Success Sequence Diagram](img/FFI-request-success-sequence.svg) + +### Struct diagram + +![FFI Request Success Struct Diagram](img/FFI-request-success-struct-diagram.svg) + +## Redis request fails + +### Sequence diagram + +![FFI Request Failure Sequence Diagram](img/FFI-request-failure-sequence.svg) + +### Struct diagram + +![FFI Request Failure Struct Diagram](img/FFI-request-failure-struct-diagram.svg) diff --git a/docs/img/FFI-conn-sequence.svg b/docs/img/FFI-conn-sequence.svg new file mode 100644 index 0000000000..b786f4609e --- /dev/null +++ b/docs/img/FFI-conn-sequence.svg @@ -0,0 +1,4 @@ + + + +
Application
Go-Wrapper
Rust
Redis
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
FFI Boundary
RedisClient, RedisError
\ No newline at end of file diff --git a/docs/img/FFI-conn-struct-diagram.svg b/docs/img/FFI-conn-struct-diagram.svg new file mode 100644 index 0000000000..d61ef9bb62 --- /dev/null +++ b/docs/img/FFI-conn-struct-diagram.svg @@ -0,0 +1,4 @@ + + + +
C struct: ConnectionResponse
connPtr: *const c_void
error: RedisErrorFFI
C struct: RedisErrorFFI
message: *const c_char
errorType: ErrorType
enum: ErrorType
ClosingError: int
RequestError: int
TimeoutError: int
ExecAbortError: int
ConnectionError: int
\ No newline at end of file diff --git a/docs/img/FFI-request-failure-sequence.svg b/docs/img/FFI-request-failure-sequence.svg new file mode 100644 index 0000000000..144145036e --- /dev/null +++ b/docs/img/FFI-request-failure-sequence.svg @@ -0,0 +1,4 @@ + + + +
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
Err
send(command, args)
failureCallback(
        RedisErrorFFI, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string (""),
RedisError
RedisResponse (nil),
RedisError
close
RedisResponse (nil),
RedisError
\ No newline at end of file diff --git a/docs/img/FFI-request-failure-struct-diagram.svg b/docs/img/FFI-request-failure-struct-diagram.svg new file mode 100644 index 0000000000..9c170a021f --- /dev/null +++ b/docs/img/FFI-request-failure-struct-diagram.svg @@ -0,0 +1,4 @@ + + + +
C struct: RedisErrorFFI
message: *const c_char
errorType: ErrorType
enum: ErrorType
ClosingError: int
RequestError: int
TimeoutError: int
ExecAbortError: int
ConnectionError: int
\ No newline at end of file diff --git a/docs/img/FFI-request-success-sequence.svg b/docs/img/FFI-request-success-sequence.svg new file mode 100644 index 0000000000..9af5d9a7c9 --- /dev/null +++ b/docs/img/FFI-request-success-sequence.svg @@ -0,0 +1,4 @@ + + + +
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
RedisResponse,
RedisError (nil)
RedisResponse,
RedisError (nil)
Result
send(command, args)
successCallback(
        response RedisResponse, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string,
RedisError (nil)
close
\ No newline at end of file diff --git a/docs/img/FFI-request-success-struct-diagram.svg b/docs/img/FFI-request-success-struct-diagram.svg new file mode 100644 index 0000000000..117dbb9060 --- /dev/null +++ b/docs/img/FFI-request-success-struct-diagram.svg @@ -0,0 +1,4 @@ + + + +
C struct: RedisResponse
value: union RedisValue
valueType: enum RedisValueType
C union: RedisValue
simpleString: []char
bulkString: []char
verbatimString: []char
ok: []char
intVal: int
array: []RedisValue
map: Map
doubleVal: double
boolVal: bool
boolVal: bool
set: []RedisValue
attribute: Map
push: []RedisValue
C struct: Map
keys: [][]char
values: []RedisValue
enum: RedisValueType
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
\ No newline at end of file From 0062ed9bd0d873c8b6b5baa5ebbc803e3b220a67 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Fri, 26 Jan 2024 14:48:47 -0800 Subject: [PATCH 30/40] Update diagrams so that maps and arrays of Redis values include an enum indicating the value type --- docs/img/FFI-request-success-sequence.svg | 2 +- docs/img/FFI-request-success-struct-diagram.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/img/FFI-request-success-sequence.svg b/docs/img/FFI-request-success-sequence.svg index 9af5d9a7c9..20aab5790f 100644 --- a/docs/img/FFI-request-success-sequence.svg +++ b/docs/img/FFI-request-success-sequence.svg @@ -1,4 +1,4 @@ -
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
RedisResponse,
RedisError (nil)
RedisResponse,
RedisError (nil)
Result
send(command, args)
successCallback(
        response RedisResponse, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string,
RedisError (nil)
close
\ No newline at end of file +
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
Result
send(command, args)
successCallback(
        value RedisValue, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string,
RedisError (nil)
RedisResponse,
RedisError (nil)
close
RedisResponse,
RedisError (nil)
\ No newline at end of file diff --git a/docs/img/FFI-request-success-struct-diagram.svg b/docs/img/FFI-request-success-struct-diagram.svg index 117dbb9060..16022769df 100644 --- a/docs/img/FFI-request-success-struct-diagram.svg +++ b/docs/img/FFI-request-success-struct-diagram.svg @@ -1,4 +1,4 @@ -
C struct: RedisResponse
value: union RedisValue
valueType: enum RedisValueType
C union: RedisValue
simpleString: []char
bulkString: []char
verbatimString: []char
ok: []char
intVal: int
array: []RedisValue
map: Map
doubleVal: double
boolVal: bool
boolVal: bool
set: []RedisValue
attribute: Map
push: []RedisValue
C struct: Map
keys: [][]char
values: []RedisValue
enum: RedisValueType
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
\ No newline at end of file +
C struct: RedisValue
value: union RedisValueData
valueType: enum RedisValueType
C union: RedisValueData
simpleString: []char
bulkString: []char
verbatimString: []char
ok: []char
intVal: int
array: []RedisValue
map: Map
doubleVal: double
boolVal: bool
boolVal: bool
set: []RedisValue
attribute: Map
push: []RedisValue
C struct: Map
keys: [][]RedisValue
values: []RedisValue
enum: RedisValueType
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
\ No newline at end of file From ae090e62a5a1d14a8b72e314178f485b61745328 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Fri, 26 Jan 2024 15:24:42 -0800 Subject: [PATCH 31/40] Fix mistakes in the FFI request success struct diagram --- docs/img/FFI-request-success-struct-diagram.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/img/FFI-request-success-struct-diagram.svg b/docs/img/FFI-request-success-struct-diagram.svg index 16022769df..64624ad9b1 100644 --- a/docs/img/FFI-request-success-struct-diagram.svg +++ b/docs/img/FFI-request-success-struct-diagram.svg @@ -1,4 +1,4 @@ -
C struct: RedisValue
value: union RedisValueData
valueType: enum RedisValueType
C union: RedisValueData
simpleString: []char
bulkString: []char
verbatimString: []char
ok: []char
intVal: int
array: []RedisValue
map: Map
doubleVal: double
boolVal: bool
boolVal: bool
set: []RedisValue
attribute: Map
push: []RedisValue
C struct: Map
keys: [][]RedisValue
values: []RedisValue
enum: RedisValueType
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
\ No newline at end of file +
C struct: RedisValue
value: union RedisValueData
valueType: enum RedisValueType
C union: RedisValueData
simpleString: []char
bulkString: []char
verbatimString: []char
ok: []char
intVal: long long int
array: []RedisValue
map: Map
doubleVal: double
boolVal: bool
bigNumber: []char
set: []RedisValue
attribute: Map
push: []RedisValue
C struct: Map
keys: []RedisValue
values: []RedisValue
enum: RedisValueType
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
\ No newline at end of file From a852c04c3c49b827a1a2abd6a77f7ab0c3b9c2f6 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Fri, 26 Jan 2024 16:16:48 -0800 Subject: [PATCH 32/40] Scale up diagrams to be more readable --- docs/img/FFI-conn-sequence.svg | 2 +- docs/img/FFI-conn-struct-diagram.svg | 2 +- docs/img/FFI-request-failure-sequence.svg | 2 +- docs/img/FFI-request-failure-struct-diagram.svg | 2 +- docs/img/FFI-request-success-sequence.svg | 2 +- docs/img/FFI-request-success-struct-diagram.svg | 2 +- docs/img/design-go-api.svg | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/img/FFI-conn-sequence.svg b/docs/img/FFI-conn-sequence.svg index b786f4609e..2733b9f7ba 100644 --- a/docs/img/FFI-conn-sequence.svg +++ b/docs/img/FFI-conn-sequence.svg @@ -1,4 +1,4 @@ -
Application
Go-Wrapper
Rust
Redis
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
FFI Boundary
RedisClient, RedisError
\ No newline at end of file +
Application
Go-Wrapper
Rust
Redis
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
FFI Boundary
RedisClient, RedisError
diff --git a/docs/img/FFI-conn-struct-diagram.svg b/docs/img/FFI-conn-struct-diagram.svg index d61ef9bb62..06b49123b0 100644 --- a/docs/img/FFI-conn-struct-diagram.svg +++ b/docs/img/FFI-conn-struct-diagram.svg @@ -1,4 +1,4 @@ -
C struct: ConnectionResponse
connPtr: *const c_void
error: RedisErrorFFI
C struct: RedisErrorFFI
message: *const c_char
errorType: ErrorType
enum: ErrorType
ClosingError: int
RequestError: int
TimeoutError: int
ExecAbortError: int
ConnectionError: int
\ No newline at end of file +
C struct: ConnectionResponse
connPtr: *const c_void
error: RedisErrorFFI
C struct: RedisErrorFFI
message: *const c_char
errorType: ErrorType
enum: ErrorType
ClosingError: int
RequestError: int
TimeoutError: int
ExecAbortError: int
ConnectionError: int
diff --git a/docs/img/FFI-request-failure-sequence.svg b/docs/img/FFI-request-failure-sequence.svg index 144145036e..ecfc3b7fee 100644 --- a/docs/img/FFI-request-failure-sequence.svg +++ b/docs/img/FFI-request-failure-sequence.svg @@ -1,4 +1,4 @@ -
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
Err
send(command, args)
failureCallback(
        RedisErrorFFI, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string (""),
RedisError
RedisResponse (nil),
RedisError
close
RedisResponse (nil),
RedisError
\ No newline at end of file +
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
Err
send(command, args)
failureCallback(
        RedisErrorFFI, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string (""),
RedisError
RedisResponse (nil),
RedisError
close
RedisResponse (nil),
RedisError
diff --git a/docs/img/FFI-request-failure-struct-diagram.svg b/docs/img/FFI-request-failure-struct-diagram.svg index 9c170a021f..c2159c8384 100644 --- a/docs/img/FFI-request-failure-struct-diagram.svg +++ b/docs/img/FFI-request-failure-struct-diagram.svg @@ -1,4 +1,4 @@ -
C struct: RedisErrorFFI
message: *const c_char
errorType: ErrorType
enum: ErrorType
ClosingError: int
RequestError: int
TimeoutError: int
ExecAbortError: int
ConnectionError: int
\ No newline at end of file +
C struct: RedisErrorFFI
message: *const c_char
errorType: ErrorType
enum: ErrorType
ClosingError: int
RequestError: int
TimeoutError: int
ExecAbortError: int
ConnectionError: int
diff --git a/docs/img/FFI-request-success-sequence.svg b/docs/img/FFI-request-success-sequence.svg index 20aab5790f..7dd9ee0c07 100644 --- a/docs/img/FFI-request-success-sequence.svg +++ b/docs/img/FFI-request-success-sequence.svg @@ -1,4 +1,4 @@ -
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
Result
send(command, args)
successCallback(
        value RedisValue, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string,
RedisError (nil)
RedisResponse,
RedisError (nil)
close
RedisResponse,
RedisError (nil)
\ No newline at end of file +
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
Result
send(command, args)
successCallback(
        value RedisValue, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string,
RedisError (nil)
RedisResponse,
RedisError (nil)
close
RedisResponse,
RedisError (nil)
diff --git a/docs/img/FFI-request-success-struct-diagram.svg b/docs/img/FFI-request-success-struct-diagram.svg index 64624ad9b1..1ca2fb777c 100644 --- a/docs/img/FFI-request-success-struct-diagram.svg +++ b/docs/img/FFI-request-success-struct-diagram.svg @@ -1,4 +1,4 @@ -
C struct: RedisValue
value: union RedisValueData
valueType: enum RedisValueType
C union: RedisValueData
simpleString: []char
bulkString: []char
verbatimString: []char
ok: []char
intVal: long long int
array: []RedisValue
map: Map
doubleVal: double
boolVal: bool
bigNumber: []char
set: []RedisValue
attribute: Map
push: []RedisValue
C struct: Map
keys: []RedisValue
values: []RedisValue
enum: RedisValueType
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
\ No newline at end of file +
C struct: RedisValue
value: union RedisValueData
valueType: enum RedisValueType
C union: RedisValueData
simpleString: []char
bulkString: []char
verbatimString: []char
ok: []char
intVal: long long int
array: []RedisValue
map: Map
doubleVal: double
boolVal: bool
bigNumber: []char
set: []RedisValue
attribute: Map
push: []RedisValue
C struct: Map
keys: []RedisValue
values: []RedisValue
enum: RedisValueType
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
diff --git a/docs/img/design-go-api.svg b/docs/img/design-go-api.svg index 647954eb81..8a027437d4 100644 --- a/docs/img/design-go-api.svg +++ b/docs/img/design-go-api.svg @@ -1,4 +1,4 @@ -glide.baseRedisClientconfig: *ClientConfigurationGet(string): string, errorSet(string, string): errorSetWithOptions(string, string, SetOptions): string, errorClose(): errorcreateBaseClient(*ClientConfiguration): *BaseRedisClient, errorexecuteCommand(*protobuf.RedisRequest): *RedisResponse, errorglide.RedisClientbaseClient: *BaseRedisClientCreateClient(*StandaloneClientConfiguration): *RedisClient, errorExec(*StandaloneTransaction): []interface{}, error
implements
glide.baseClientConfigurationaddresses: []AddressInfouseTLS: boolcredentials:  AuthenticationOptionsreadFrom: ReadFromStrategyclientCreationTimeout: intresponseTimeout: inttoProtobufConnRequest(): *protobuf.ConnectionRequestglide.RedisClusterClientbaseClient: *BaseRedisClientCreateClusterClient(*ClusterClientConfiguration): *RedisClusterClient, errorExec(*ClusterTransaction): []interface{}, errorExecWithRoute(*ClusterTransaction, Route): []interface, error
implements
glide.StandaloneClientConfigurationbaseConfig: *BaseClientConfigurationbackoffStrategy: *BackoffStrategydatabaseId: intNewClientConfiguration(): *StandaloneClientConfigurationWithAddress(AddressInfo): *StandaloneClientConfigurationWithUseTLS(bool): *StandaloneClientConfigurationWithCredentials(*AuthenticationOptions): *StandaloneClientConfigurationWithReadFromStrategy(ReadFromStrategy): *StandaloneClientConfigurationWithClientCreationTimeout(int): *StandaloneClientConfigurationWithResponseTimeout(int): *StandaloneClientConfigurationWithBackoffStrategy(*BackoffStrategy): *StandaloneClientConfigurationWithDatabaseId(int): *StandaloneClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequest
implements
glide.ClusterClientConfigurationbaseConfig: *BaseClientConfigurationNewClusterClientConfiguration(): *ClusterClientConfigurationWithAddress(AddressInfo): *ClusterClientConfigurationWithUseTLS(bool): *ClusterClientConfigurationWithCredentials(*AuthenticationOptions): *ClusterClientConfigurationWithReadFromStrategy(ReadFromStrategy): *ClusterClientConfigurationWithClientCreationTimeout(int): *ClusterClientConfigurationWithResponseTimeout(int): *ClusterClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestinterface glide.ClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestglide.ClosingErrorredisError: RedisErrorglide.RequestErrorredisError: RedisErrorglide.TimeoutErrorredisError: RedisErrorglide.ExecAbortErrorredisError: RedisErrorglide.ConnectionErrorredisError: RedisErrorglide.RedisErrormessage: StringError(): str
\ No newline at end of file +glide.baseRedisClientconfig: *ClientConfigurationGet(string): string, errorSet(string, string): errorSetWithOptions(string, string, SetOptions): string, errorClose(): errorcreateBaseClient(*ClientConfiguration): *BaseRedisClient, errorexecuteCommand(*protobuf.RedisRequest): *RedisResponse, errorglide.RedisClientbaseClient: *BaseRedisClientCreateClient(*StandaloneClientConfiguration): *RedisClient, errorExec(*StandaloneTransaction): []interface{}, error
implements
glide.baseClientConfigurationaddresses: []AddressInfouseTLS: boolcredentials:  AuthenticationOptionsreadFrom: ReadFromStrategyclientCreationTimeout: intresponseTimeout: inttoProtobufConnRequest(): *protobuf.ConnectionRequestglide.RedisClusterClientbaseClient: *BaseRedisClientCreateClusterClient(*ClusterClientConfiguration): *RedisClusterClient, errorExec(*ClusterTransaction): []interface{}, errorExecWithRoute(*ClusterTransaction, Route): []interface, error
implements
glide.StandaloneClientConfigurationbaseConfig: *BaseClientConfigurationbackoffStrategy: *BackoffStrategydatabaseId: intNewClientConfiguration(): *StandaloneClientConfigurationWithAddress(AddressInfo): *StandaloneClientConfigurationWithUseTLS(bool): *StandaloneClientConfigurationWithCredentials(*AuthenticationOptions): *StandaloneClientConfigurationWithReadFromStrategy(ReadFromStrategy): *StandaloneClientConfigurationWithClientCreationTimeout(int): *StandaloneClientConfigurationWithResponseTimeout(int): *StandaloneClientConfigurationWithBackoffStrategy(*BackoffStrategy): *StandaloneClientConfigurationWithDatabaseId(int): *StandaloneClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequest
implements
glide.ClusterClientConfigurationbaseConfig: *BaseClientConfigurationNewClusterClientConfiguration(): *ClusterClientConfigurationWithAddress(AddressInfo): *ClusterClientConfigurationWithUseTLS(bool): *ClusterClientConfigurationWithCredentials(*AuthenticationOptions): *ClusterClientConfigurationWithReadFromStrategy(ReadFromStrategy): *ClusterClientConfigurationWithClientCreationTimeout(int): *ClusterClientConfigurationWithResponseTimeout(int): *ClusterClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestinterface glide.ClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestglide.ClosingErrorredisError: RedisErrorglide.RequestErrorredisError: RedisErrorglide.TimeoutErrorredisError: RedisErrorglide.ExecAbortErrorredisError: RedisErrorglide.ConnectionErrorredisError: RedisErrorglide.RedisErrormessage: StringError(): str
From 50bc3036374bf9995e609d97147a7ce6765c716e Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Mon, 29 Jan 2024 16:53:54 -0800 Subject: [PATCH 33/40] Address PR feedback --- docs/img/FFI-request-failure-sequence.svg | 2 +- docs/img/FFI-request-success-sequence.svg | 2 +- docs/img/FFI-request-success-struct-diagram.svg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/img/FFI-request-failure-sequence.svg b/docs/img/FFI-request-failure-sequence.svg index ecfc3b7fee..642cd9bec5 100644 --- a/docs/img/FFI-request-failure-sequence.svg +++ b/docs/img/FFI-request-failure-sequence.svg @@ -1,4 +1,4 @@ -
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
Err
send(command, args)
failureCallback(
        RedisErrorFFI, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string (""),
RedisError
RedisResponse (nil),
RedisError
close
RedisResponse (nil),
RedisError
+
Go failureCallback
lib.rs
glide-core
Result Channel
executeCommand(protobuf.RedisRequest, &channel)
make channel
cmd(protobuf.RedisRequest)
wait
Redis
send(command, args)
Err
Err
failureCallback(
        RedisErrorFFI, 
        channelPtr C.uintptr_t)
close
RedisResponse (nil),
RedisError
Application
Go-Wrapper
execute command
response (nil),
RedisError
RedisResponse (nil),
RedisError
FFI Boundary
Go code
Rust code
\ No newline at end of file diff --git a/docs/img/FFI-request-success-sequence.svg b/docs/img/FFI-request-success-sequence.svg index 7dd9ee0c07..1f70b415e9 100644 --- a/docs/img/FFI-request-success-sequence.svg +++ b/docs/img/FFI-request-success-sequence.svg @@ -1,4 +1,4 @@ -
Go FFI
Rust
Redis
FFI Boundary
Result Channel
protobuf.RedisRequest
make channel
Result
send(command, args)
successCallback(
        value RedisValue, 
        channelPtr C.uintptr_t)
return
Application
Go-Wrapper
Get(msg string)
string,
RedisError (nil)
RedisResponse,
RedisError (nil)
close
RedisResponse,
RedisError (nil)
+
Go successCallback
lib.rs
glide-core
Result Channel
executeCommand(protobuf.RedisRequest, &channel)
make channel
cmd(protobuf.RedisRequest)
wait
Redis
send(command, args)
Result
Result
successCallback(
        RedisValue, 
        channelPtr C.uintptr_t)
close
RedisResponse,
RedisError (nil)
Application
Go-Wrapper
execute command
response,
RedisError (nil)
RedisResponse,
RedisError (nil)
FFI Boundary
Go code
Rust code
\ No newline at end of file diff --git a/docs/img/FFI-request-success-struct-diagram.svg b/docs/img/FFI-request-success-struct-diagram.svg index 1ca2fb777c..9b6724d890 100644 --- a/docs/img/FFI-request-success-struct-diagram.svg +++ b/docs/img/FFI-request-success-struct-diagram.svg @@ -1,4 +1,4 @@ -
C struct: RedisValue
value: union RedisValueData
valueType: enum RedisValueType
C union: RedisValueData
simpleString: []char
bulkString: []char
verbatimString: []char
ok: []char
intVal: long long int
array: []RedisValue
map: Map
doubleVal: double
boolVal: bool
bigNumber: []char
set: []RedisValue
attribute: Map
push: []RedisValue
C struct: Map
keys: []RedisValue
values: []RedisValue
enum: RedisValueType
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
+
C struct: RedisValue
value: union RedisValueData
valueType: enum RedisValueType
C union: RedisValueData
simpleString: []char
bulkString: []char
verbatimString: []char
intVal: long long int
array: []RedisValue
map: Map
doubleVal: double
boolVal: int
bigNumber: []char
set: []RedisValue
attribute: Attribute
push: Push
C struct: Map
keys: []RedisValue
values: []RedisValue
enum: RedisValueType
Null: int
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
C struct: Attribute
data: *RedisValue
attributes: Map
C struct: Push
pushKind: enum PushKind
data: []RedisValue
enum: PushKind
Invalidate: int
Message: int
PMessage: int
SMessage: int
Unsubscribe: int
PUnsubscribe: int
SUnsubscribe: int
Subscribe: int
PSubscribe: int
SSubscribe: int
\ No newline at end of file From fdd659af7bfdec45749bfc96316762a215df9fe9 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 30 Jan 2024 11:54:29 -0800 Subject: [PATCH 34/40] Increase size of API struct diagram to make it more readable --- docs/img/design-go-api.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/img/design-go-api.svg b/docs/img/design-go-api.svg index 8a027437d4..74ba930161 100644 --- a/docs/img/design-go-api.svg +++ b/docs/img/design-go-api.svg @@ -1,4 +1,4 @@ -glide.baseRedisClientconfig: *ClientConfigurationGet(string): string, errorSet(string, string): errorSetWithOptions(string, string, SetOptions): string, errorClose(): errorcreateBaseClient(*ClientConfiguration): *BaseRedisClient, errorexecuteCommand(*protobuf.RedisRequest): *RedisResponse, errorglide.RedisClientbaseClient: *BaseRedisClientCreateClient(*StandaloneClientConfiguration): *RedisClient, errorExec(*StandaloneTransaction): []interface{}, error
implements
glide.baseClientConfigurationaddresses: []AddressInfouseTLS: boolcredentials:  AuthenticationOptionsreadFrom: ReadFromStrategyclientCreationTimeout: intresponseTimeout: inttoProtobufConnRequest(): *protobuf.ConnectionRequestglide.RedisClusterClientbaseClient: *BaseRedisClientCreateClusterClient(*ClusterClientConfiguration): *RedisClusterClient, errorExec(*ClusterTransaction): []interface{}, errorExecWithRoute(*ClusterTransaction, Route): []interface, error
implements
glide.StandaloneClientConfigurationbaseConfig: *BaseClientConfigurationbackoffStrategy: *BackoffStrategydatabaseId: intNewClientConfiguration(): *StandaloneClientConfigurationWithAddress(AddressInfo): *StandaloneClientConfigurationWithUseTLS(bool): *StandaloneClientConfigurationWithCredentials(*AuthenticationOptions): *StandaloneClientConfigurationWithReadFromStrategy(ReadFromStrategy): *StandaloneClientConfigurationWithClientCreationTimeout(int): *StandaloneClientConfigurationWithResponseTimeout(int): *StandaloneClientConfigurationWithBackoffStrategy(*BackoffStrategy): *StandaloneClientConfigurationWithDatabaseId(int): *StandaloneClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequest
implements
glide.ClusterClientConfigurationbaseConfig: *BaseClientConfigurationNewClusterClientConfiguration(): *ClusterClientConfigurationWithAddress(AddressInfo): *ClusterClientConfigurationWithUseTLS(bool): *ClusterClientConfigurationWithCredentials(*AuthenticationOptions): *ClusterClientConfigurationWithReadFromStrategy(ReadFromStrategy): *ClusterClientConfigurationWithClientCreationTimeout(int): *ClusterClientConfigurationWithResponseTimeout(int): *ClusterClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestinterface glide.ClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestglide.ClosingErrorredisError: RedisErrorglide.RequestErrorredisError: RedisErrorglide.TimeoutErrorredisError: RedisErrorglide.ExecAbortErrorredisError: RedisErrorglide.ConnectionErrorredisError: RedisErrorglide.RedisErrormessage: StringError(): str
+glide.baseRedisClientconfig: *ClientConfigurationGet(string): string, errorSet(string, string): errorSetWithOptions(string, string, SetOptions): string, errorClose(): errorcreateBaseClient(*ClientConfiguration): *BaseRedisClient, errorexecuteCommand(*protobuf.RedisRequest): *RedisResponse, errorglide.RedisClientbaseClient: *BaseRedisClientCreateClient(*StandaloneClientConfiguration): *RedisClient, errorExec(*StandaloneTransaction): []interface{}, error
implements
glide.baseClientConfigurationaddresses: []AddressInfouseTLS: boolcredentials:  AuthenticationOptionsreadFrom: ReadFromStrategyclientCreationTimeout: intresponseTimeout: inttoProtobufConnRequest(): *protobuf.ConnectionRequestglide.RedisClusterClientbaseClient: *BaseRedisClientCreateClusterClient(*ClusterClientConfiguration): *RedisClusterClient, errorExec(*ClusterTransaction): []interface{}, errorExecWithRoute(*ClusterTransaction, Route): []interface, error
implements
glide.StandaloneClientConfigurationbaseConfig: *BaseClientConfigurationbackoffStrategy: *BackoffStrategydatabaseId: intNewClientConfiguration(): *StandaloneClientConfigurationWithAddress(AddressInfo): *StandaloneClientConfigurationWithUseTLS(bool): *StandaloneClientConfigurationWithCredentials(*AuthenticationOptions): *StandaloneClientConfigurationWithReadFromStrategy(ReadFromStrategy): *StandaloneClientConfigurationWithClientCreationTimeout(int): *StandaloneClientConfigurationWithResponseTimeout(int): *StandaloneClientConfigurationWithBackoffStrategy(*BackoffStrategy): *StandaloneClientConfigurationWithDatabaseId(int): *StandaloneClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequest
implements
glide.ClusterClientConfigurationbaseConfig: *BaseClientConfigurationNewClusterClientConfiguration(): *ClusterClientConfigurationWithAddress(AddressInfo): *ClusterClientConfigurationWithUseTLS(bool): *ClusterClientConfigurationWithCredentials(*AuthenticationOptions): *ClusterClientConfigurationWithReadFromStrategy(ReadFromStrategy): *ClusterClientConfigurationWithClientCreationTimeout(int): *ClusterClientConfigurationWithResponseTimeout(int): *ClusterClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestinterface glide.ClientConfigurationtoProtobufConnRequest(): *protobuf.ConnectionRequestglide.ClosingErrorredisError: RedisErrorglide.RequestErrorredisError: RedisErrorglide.TimeoutErrorredisError: RedisErrorglide.ExecAbortErrorredisError: RedisErrorglide.ConnectionErrorredisError: RedisErrorglide.RedisErrormessage: StringError(): str
\ No newline at end of file From c3b7ae6ec32b768a48479fd4e46b1f5ee36aea7d Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 30 Jan 2024 13:24:08 -0800 Subject: [PATCH 35/40] Update request success struct diagram --- docs/img/FFI-request-success-struct-diagram.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/img/FFI-request-success-struct-diagram.svg b/docs/img/FFI-request-success-struct-diagram.svg index 9b6724d890..3eb858064d 100644 --- a/docs/img/FFI-request-success-struct-diagram.svg +++ b/docs/img/FFI-request-success-struct-diagram.svg @@ -1,4 +1,4 @@ -
C struct: RedisValue
value: union RedisValueData
valueType: enum RedisValueType
C union: RedisValueData
simpleString: []char
bulkString: []char
verbatimString: []char
intVal: long long int
array: []RedisValue
map: Map
doubleVal: double
boolVal: int
bigNumber: []char
set: []RedisValue
attribute: Attribute
push: Push
C struct: Map
keys: []RedisValue
values: []RedisValue
enum: RedisValueType
Null: int
SimpleString: int
BulkString: int
VerbatimString: int
OK: int
Int: int
Array: int
Map: int
Double: int
Boolean: int
BigNumber: int
Set: int
Attribute: int
Push: int
C struct: Attribute
data: *RedisValue
attributes: Map
C struct: Push
pushKind: enum PushKind
data: []RedisValue
enum: PushKind
Invalidate: int
Message: int
PMessage: int
SMessage: int
Unsubscribe: int
PUnsubscribe: int
SUnsubscribe: int
Subscribe: int
PSubscribe: int
SSubscribe: int
\ No newline at end of file +
C struct: RedisValue
data: union RedisData
type: enum RedisType
C union: RedisData
simpleString: []char
bulkString: []char
verbatimString: []char
intVal: long long int
array: []RedisValue
map: Map
doubleVal: double
boolVal: short
bigNumber: []char
set: []RedisValue
attribute: Attribute
push: Push
C struct: Map
keys: []RedisValue
values: []RedisValue
enum: RedisType
NIL
SIMPLE_STRING
BULK_STRING
VERBATIM_STRING
OK
INT
ARRAY
MAP
DOUBLE
BOOLEAN
BIG_NUMBER
SET
ATTRIBUTE
PUSH
C struct: Attribute
data: RedisValue
attributes: Map
C struct: Push
pushKind: enum PushKind
data: []RedisValue
enum: PushKind
INVALIDATE
MESSAGE
P_MESSAGE
S_MESSAGE
UNSUBSCRIBE
P_UNSUBSCRIBE
S_UNSUBSCRIBE
SUBSCRIBE
P_SUBSCRIBE
S_SUBSCRIBE
\ No newline at end of file From d5edd9dac1257eecc9d0c35adb20688bb0daabaf Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 30 Jan 2024 14:54:23 -0800 Subject: [PATCH 36/40] Update connection sequence diagram --- docs/img/FFI-conn-sequence.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/img/FFI-conn-sequence.svg b/docs/img/FFI-conn-sequence.svg index 2733b9f7ba..31abbdb7b0 100644 --- a/docs/img/FFI-conn-sequence.svg +++ b/docs/img/FFI-conn-sequence.svg @@ -1,4 +1,4 @@ -
Application
Go-Wrapper
Rust
Redis
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
FFI Boundary
RedisClient, RedisError
+
Application
Go-Wrapper
Rust
Redis
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
RedisClient, RedisError
FFI Boundary
Go code
Rust code
\ No newline at end of file From 3bd84bafb6f3a980b58db6d58144ac5399b7dcba Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 30 Jan 2024 14:56:49 -0800 Subject: [PATCH 37/40] Update connection sequence diagram --- docs/img/FFI-conn-sequence.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/img/FFI-conn-sequence.svg b/docs/img/FFI-conn-sequence.svg index 31abbdb7b0..5113eedba5 100644 --- a/docs/img/FFI-conn-sequence.svg +++ b/docs/img/FFI-conn-sequence.svg @@ -1,4 +1,4 @@ -
Application
Go-Wrapper
Rust
Redis
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
RedisClient, RedisError
FFI Boundary
Go code
Rust code
\ No newline at end of file +
Application
Go-Wrapper
Rust
Redis
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
RedisClient, RedisError
FFI Boundary
Go code
Rust code
\ No newline at end of file From 26bafc1953cdfdc3cda03c209c38e76ab4bfd881 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 31 Jan 2024 13:59:12 -0800 Subject: [PATCH 38/40] Add glide-core to connection sequence diagram --- docs/img/FFI-conn-sequence.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/img/FFI-conn-sequence.svg b/docs/img/FFI-conn-sequence.svg index 5113eedba5..4e5039b333 100644 --- a/docs/img/FFI-conn-sequence.svg +++ b/docs/img/FFI-conn-sequence.svg @@ -1,4 +1,4 @@ -
Application
Go-Wrapper
Rust
Redis
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
RedisClient, RedisError
FFI Boundary
Go code
Rust code
\ No newline at end of file +
Application
Go-Wrapper
lib.rs
glide-core
CreateClient(config StandaloneClientConfig)
protobuf.ConnectionRequest
GlideClient::new
GlideClient
ConnectionResponse
RedisClient, RedisError
FFI Boundary
Go code
Rust code
Redis
GlideClient::new
GlideClient
\ No newline at end of file From 969a896be80a62ff058964660814f23cfe285b94 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Fri, 23 Feb 2024 16:19:22 -0800 Subject: [PATCH 39/40] Update Go use cases with current configuration implementation --- docs/design-go-api.md | 84 ++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/docs/design-go-api.md b/docs/design-go-api.md index d7b44638c3..a1c52bab4b 100644 --- a/docs/design-go-api.md +++ b/docs/design-go-api.md @@ -16,25 +16,27 @@ This document presents the high-level user API for the Go-Wrapper client library ### Case 1: Create Redis client and connect ```go -var config *StandaloneClientConfiguration = glide.NewClientConfiguration() - WithAddress(glide.AddressInfo{Host: host, Port: port}). - WithUseTLS(true) +host := "some_host" +port := 1234 +var config *api.RedisClientConfiguration = api.NewRedisClientConfiguration(). + WithAddress(&api.NodeAddress{&host, &port}). + WithUseTLS(true) // Create a client and connect -var client *glide.RedisClient +var client *api.RedisClient var err error -client, err = glide.CreateClient(config) +client, err = api.CreateClient(config) ``` ### Case 2: Connection to Redis fails with ClosingError ```go -var client *glide.RedisClient +var client *api.RedisClient var err error -client, err := glide.CreateClient(config) +client, err := api.CreateClient(config) // User can check specifically for a ClosingError: if err != nil { - closingErr, isClosingErr := err.(glide.ClosingError) + closingErr, isClosingErr := err.(api.ClosingError) if isClosingErr { log.Fatal("Failed to connect to Redis: " + closingErr.Error()) } @@ -44,9 +46,9 @@ if err != nil { ### Case 3: Connect to Redis with deferred cleanup ```go func connectAndGet(key string) string { - var client *glide.RedisClient + var client *api.RedisClient var err error - client, err = glide.CreateClient(config) + client, err = api.CreateClient(config) if err != nil { log.Fatal("Redis client failed with: " + err.Error()) } @@ -68,15 +70,19 @@ func connectAndGet(key string) string { ### Case 4: Connect to Redis cluster ```go -var config *glide.ClusterClientConfiguration -config = glide.NewClusterClientConfiguration(). - WithAddress(glide.AddressInfo{Host: host1, Port: port1}). - WithAddress(glide.AddressInfo{Host: host2, Port: port2}). +host1 := "host1" +host2 := "host2" +port1 := 1234 +port2 := 5678 +var config *api.RedisClusterClientConfiguration +config = api.NewRedisClusterClientConfiguration(). + WithAddress(&api.NodeAddress{Host: &host1, Port: &port1}). + WithAddress(&api.NodeAddress{Host: &host2, Port: &port2}). WithUseTLS(true) -var client *glide.RedisClusterClient +var client *api.RedisClusterClient var err error -client, err = glide.CreateClusterClient(config) +client, err = api.CreateClusterClient(config) ``` ### Case 5: Get(key) from connected RedisClient @@ -91,18 +97,18 @@ fmt.Println("The value associated with 'apples' is: " + result) err := client.Set("apples", "oranges") // With setOptions -var setOptions *glide.SetOptions -setOptions = glide.NewSetOptions(). +var setOptions *api.SetOptions +setOptions = api.NewSetOptions(). WithReturnOldValue(true) oldValue, err := client.SetWithOptions("apples", "oranges", setOptions) ``` ### Case 7: Get(key) from a disconnected RedisClient -Return a glide.ConnectionError if the RedisClient fails to connect to Redis +Return a api.ConnectionError if the RedisClient fails to connect to Redis ```go result, err := client.Get("apples") if err != nil { - connErr, isConnErr := err.(glide.ConnectionError) + connErr, isConnErr := err.(api.ConnectionError) if isConnErr { log.Fatal("RedisClient get failed with: " + connErr.Error()) } @@ -127,7 +133,7 @@ if !isString { ### Case 9: Send transaction to RedisClient ```go -transaction := glide.NewTransaction() +transaction := api.NewTransaction() transaction.Get("apples") transaction.Get("pears") transaction.Set("cherries", "Bing") @@ -146,15 +152,17 @@ thirdResponse := result[2] // evaluates to nil ### Case 10: Send Get request to a RedisClusterClient with one address ```go -var config *glide.ClusterClientConfiguration -config = glide.NewClusterClientConfiguration(). - WithAddress(glide.AddressInfo{Host: host, Port: port}). +host := "some_host" +port := 1234 +var config *api.ClusterClientConfiguration +config = api.NewClusterClientConfiguration(). + WithAddress(&api.NodeAddress{Host: &host, Port: &port}). WithUseTLS(true) // Create a client and connect -var client *glide.RedisClusterClient +var client *api.RedisClusterClient var err error -client, err = glide.CreateClusterClient(config) +client, err = api.CreateClusterClient(config) if err != nil { log.Fatal("Redis client failed with: " + err.Error()) } @@ -164,16 +172,20 @@ result, err := client.Get("apples") ### Case 11: Send Ping request to a RedisClusterClient with multiple addresses ```go -var config *glide.ClusterClientConfiguration -config = glide.NewClusterClientConfiguration(). - WithAddress(glide.AddressInfo{Host: host1, Port: port1}). - WithAddress(glide.AddressInfo{Host: host2, Port: port2}). +host1 := "host1" +host2 := "host2" +port1 := 1234 +port2 := 5678 +var config *api.ClusterClientConfiguration +config = api.NewClusterClientConfiguration(). + WithAddress(&api.NodeAddress{Host: &host1, Port: &port1}). + WithAddress(&api.NodeAddress{Host: &host2, Port: &port2}). WithUseTLS(true) // Create a client and connect -var client *glide.RedisClusterClient +var client *api.RedisClusterClient var err error -client, err = glide.CreateClusterClient(config) +client, err = api.CreateClusterClient(config) if err != nil { log.Fatal("Redis client failed with: " + err.Error()) } @@ -185,17 +197,17 @@ result, err := client.Ping() result, err := client.PingWithMessage("Ping received") // With route -result, err := client.PingWithRoute(glide.NewRoute(glide.AllNodes)) +result, err := client.PingWithRoute(api.NewRoute(api.AllNodes)) // With message and route -result, err := client.PingWithMessageAndRoute("Ping received", glide.NewRoute(glide.AllNodes)) +result, err := client.PingWithMessageAndRoute("Ping received", api.NewRoute(api.AllNodes)) ``` ### Case 12: Get(key) encounters a TimeoutError ```go result, err := client.Get("apples") if err != nil { - timeoutErr, isTimeoutErr := err.(glide.TimeoutError) + timeoutErr, isTimeoutErr := err.(api.TimeoutError) if isTimeoutErr { // Handle error as desired } @@ -206,7 +218,7 @@ if err != nil { ```go result, err := client.Get("apples") if err != nil { - connErr, isConnErr := err.(glide.ConnectionError) + connErr, isConnErr := err.(api.ConnectionError) if isConnErr { // Handle error as desired } From 3896446f4745ae400ff27fd965b0bc9e68ff99ad Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 27 Feb 2024 13:53:30 -0800 Subject: [PATCH 40/40] Update Go use cases to use config without pointer fields --- docs/design-go-api.md | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/docs/design-go-api.md b/docs/design-go-api.md index a1c52bab4b..501e4aeb45 100644 --- a/docs/design-go-api.md +++ b/docs/design-go-api.md @@ -16,10 +16,8 @@ This document presents the high-level user API for the Go-Wrapper client library ### Case 1: Create Redis client and connect ```go -host := "some_host" -port := 1234 var config *api.RedisClientConfiguration = api.NewRedisClientConfiguration(). - WithAddress(&api.NodeAddress{&host, &port}). + WithAddress(&api.NodeAddress{"some_host", 1234}). WithUseTLS(true) // Create a client and connect @@ -45,7 +43,7 @@ if err != nil { ### Case 3: Connect to Redis with deferred cleanup ```go -func connectAndGet(key string) string { +func connectAndGet(config *RedisClientConfiguration, key string) string { var client *api.RedisClient var err error client, err = api.CreateClient(config) @@ -70,14 +68,10 @@ func connectAndGet(key string) string { ### Case 4: Connect to Redis cluster ```go -host1 := "host1" -host2 := "host2" -port1 := 1234 -port2 := 5678 var config *api.RedisClusterClientConfiguration config = api.NewRedisClusterClientConfiguration(). - WithAddress(&api.NodeAddress{Host: &host1, Port: &port1}). - WithAddress(&api.NodeAddress{Host: &host2, Port: &port2}). + WithAddress(&api.NodeAddress{Host: "host1", Port: 1234}). + WithAddress(&api.NodeAddress{Host: "host2", Port: 1234}). WithUseTLS(true) var client *api.RedisClusterClient @@ -152,11 +146,9 @@ thirdResponse := result[2] // evaluates to nil ### Case 10: Send Get request to a RedisClusterClient with one address ```go -host := "some_host" -port := 1234 var config *api.ClusterClientConfiguration config = api.NewClusterClientConfiguration(). - WithAddress(&api.NodeAddress{Host: &host, Port: &port}). + WithAddress(&api.NodeAddress{Host: "some_host", Port: 1234}). WithUseTLS(true) // Create a client and connect @@ -172,14 +164,10 @@ result, err := client.Get("apples") ### Case 11: Send Ping request to a RedisClusterClient with multiple addresses ```go -host1 := "host1" -host2 := "host2" -port1 := 1234 -port2 := 5678 var config *api.ClusterClientConfiguration config = api.NewClusterClientConfiguration(). - WithAddress(&api.NodeAddress{Host: &host1, Port: &port1}). - WithAddress(&api.NodeAddress{Host: &host2, Port: &port2}). + WithAddress(&api.NodeAddress{Host: "host1", Port: 1234}). + WithAddress(&api.NodeAddress{Host: "host2", Port: 1234}). WithUseTLS(true) // Create a client and connect