Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: API layer with RedisClient #737

Merged

Conversation

acarbonetto
Copy link
Contributor

@acarbonetto acarbonetto commented Dec 29, 2023

Depends on #717.

Issue #, if available:

API layer for: #589

Description of changes:

In this PR, we're adding an API layer that creates a RedisClient async-ly using a RedisConfiguration. It then connects to Redis using the ConnectionManager. There are several builders in the RedisClient that use different configurations (localhost, host and port, or a full config object).

Out of Scope

  • Command Manager: sending requests to the command manager
  • All command calls will be added in a follow-up PR.
  • Transaction commands will be added in a follow-up PR.

Use cases

// connect to Redis
CompletableFuture<babushka.client.api.RedisClient> redisClientConnection =
    babushka.client.api.RedisClient.CreateClient(configuration);

// resolve the Future and get a RedisClient
babushka.client.api.RedisClient redisClient = redisClientConnection.get();
image

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;

/** Factory class for creating Glide/Redis-client connections */
Copy link
Contributor

Choose a reason for hiding this comment

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

this class is the actual connection, not just the factory.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It should say it's the standalone redis client

Copy link
Contributor Author

@acarbonetto acarbonetto Jan 2, 2024

Choose a reason for hiding this comment

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

Updated in d23001f.

// verify
// no exception
assertNull(result.get());
verify(channel).connect(eq(expectedProtobufConnectionRequest));
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand what these tests actually test. What kind of behavior breakage will this test catch?

I don't see the value for quite a few of these tests. They seem to just be passthrough for the mock,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They test that the protobuf connection request is built as expected.
They will break if the protobuf connection requests change in a way that is unexpected.

}

@Test
public void CheckBabushkaResponse_ConstantResponse_returnsSuccessfully()
Copy link
Contributor

Choose a reason for hiding this comment

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

where are you testing that the constant response was passed to the user?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: On a connection, the user receives a RedisClient with a completed CompletableFuture. They don't see the constant response. If we did that, we would have to change our CreateClient method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add unit tests for CreateClient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tests added under 09fd5c7

private final AtomicInteger requestId = new AtomicInteger(0);

/**
* Storage of Futures to handle responses. Map key is callback id, which starts from 1.<br>
Copy link
Contributor

Choose a reason for hiding this comment

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

remove
throughout

Copy link
Contributor Author

@acarbonetto acarbonetto Jan 2, 2024

Choose a reason for hiding this comment

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

Updated in d23001f.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;

/** Factory class for creating Glide/Redis-client connections */
Copy link
Collaborator

Choose a reason for hiding this comment

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

It should say it's the standalone redis client

}

/**
* Async (non-blocking) connection to Redis.
Copy link
Collaborator

Choose a reason for hiding this comment

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

should be changed to something like:
Creates an async (non-blocking) client for Redis in Standalone mode.

Copy link
Contributor

@shachlanAmazon shachlanAmazon Jan 2, 2024

Choose a reason for hiding this comment

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

in general, the user-facing term we use is client, not connection. That is, unless we're discussing a concrete connection to a single node, or the UDS connection (which is mostly discussed internally and isn't user facing).

Copy link
Contributor Author

@acarbonetto acarbonetto Jan 2, 2024

Choose a reason for hiding this comment

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

Updated in d23001f.

java/client/src/main/java/glide/api/RedisClient.java Outdated Show resolved Hide resolved
java/client/src/main/java/glide/api/RedisClient.java Outdated Show resolved Hide resolved
import lombok.Singular;
import lombok.experimental.SuperBuilder;

/** Represents the configuration settings for a Redis client. */
Copy link
Collaborator

Choose a reason for hiding this comment

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

should be changed to something like-
Represents the shared configuration settings for both Standalone and Cluster Redis clients.

Copy link
Contributor Author

@acarbonetto acarbonetto Jan 2, 2024

Choose a reason for hiding this comment

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

Updated in d23001f.


CompletableFuture<Response> channel;

public CompletableFuture<Void> closeConnection() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Adding to Shachar's question - why CommandManager has closeConnection function?

} else if (response.hasConstantResponse()) {
return null;
} else if (response.hasRespPointer()) {
throw new RuntimeException("Unexpected data in response");
Copy link
Collaborator

Choose a reason for hiding this comment

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

why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a connection request, we only handle the error scenario and the OK successful response. This would be unexpected... I don't know what to do with the data...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In conversation today, we decided that a ClosingException would be best (and cancel all the waiting responses).
I'll add a TODO and fix it under https://github.com/orgs/Bit-Quill/projects/4/views/6?pane=issue&itemId=48063887

} else if (response.hasRespPointer()) {
throw new RuntimeException("Unexpected data in response");
}
// TODO commented out due to #710 https://github.com/aws/babushka/issues/710
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we remove this todo?
This PR changing connection response to OK was merged:
#711

Copy link
Contributor Author

@acarbonetto acarbonetto Jan 2, 2024

Choose a reason for hiding this comment

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

Updated in d23001f.

acarbonetto and others added 9 commits January 3, 2024 11:13
* Add java client connection layer.

Signed-off-by: Andrew Carbonetto <[email protected]>

---------

Signed-off-by: Yury-Fridlyand <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Co-authored-by: Yury-Fridlyand <[email protected]>
Co-authored-by: SanHalacogluImproving <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
@acarbonetto acarbonetto force-pushed the java/integ_acarbo_refactor_api_createclient branch from 09fd5c7 to 617ab3e Compare January 3, 2024 22:35
Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
Copy link
Contributor

@shachlanAmazon shachlanAmazon left a comment

Choose a reason for hiding this comment

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

round

Signed-off-by: Andrew Carbonetto <[email protected]>
@acarbonetto acarbonetto added the java issues and fixes related to the java client label Jan 5, 2024
java/client/src/main/java/glide/api/RedisClient.java Outdated Show resolved Hide resolved
java/client/src/main/java/glide/api/BaseClient.java Outdated Show resolved Hide resolved
connectionManager.closeConnection().get();
} catch (InterruptedException interruptedException) {
// AutoCloseable functions are strongly advised to avoid throwing InterruptedExceptions
// TODO: marking resources as closed:
Copy link
Collaborator

Choose a reason for hiding this comment

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

restore interrupt

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure this is right.
see: https://github.com/orgs/Bit-Quill/projects/4/views/6?pane=issue&itemId=48063887
Ultimately, it would be best to throw a RedisException/ClosingException after interrupting on all existing responses.

java/src/lib.rs Outdated Show resolved Hide resolved
Signed-off-by: Andrew Carbonetto <[email protected]>
Copy link
Collaborator

@Yury-Fridlyand Yury-Fridlyand left a comment

Choose a reason for hiding this comment

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

Minor things left. Do you have uncommited changes? I had to unresolve few things, because they are not fixed yet.

Signed-off-by: Andrew Carbonetto <[email protected]>
// TODO: Support exception throwing, including interrupted exceptions
return connectionManager
.connectToRedis(config)
.thenApply(ignore -> new RedisClient(connectionManager, commandManager));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
.thenApply(ignore -> new RedisClient(connectionManager, commandManager));
.thenApplyAsync(ignore -> new RedisClient(connectionManager, commandManager));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it necessary to run this in a separate thread?

ChannelHandler channelHandler = buildChannelHandler();
ConnectionManager connectionManager = buildConnectionManager(channelHandler);
CommandManager commandManager = buildCommandManager(channelHandler);
// TODO: Support exception throwing, including interrupted exceptions
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think all exeptions are properly propagated from that async pipeline. They are wrapped by ExectionException. Please, re-check. Maybe you can remove this TODO and/or add a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have a task to add exception handling. It would be 'nicer' if the API supported throwing RedisException, rather than an ExecutionException with a RedisException.
I'm pretty certain we can extend CompletableFutures to handle RedisException, in parallel with ExcutionExceptions and that would provide a better experience for our users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, tests are already included in the follow-up PR, that adds RedisExceptions.

if (msg instanceof Response) {
Response response = (Response) msg;
callbackDispatcher.completeRequest(response);
ctx.fireChannelRead(msg);
Copy link
Collaborator

Choose a reason for hiding this comment

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

why not fireChannelReadComplete?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because that doesn't pass the message to other read handlers. That tells the read pipeline to complete, I believe (documentation on that method is somewhat lacking).

Signed-off-by: Andrew Carbonetto <[email protected]>
@acarbonetto acarbonetto force-pushed the java/integ_acarbo_refactor_api_createclient branch from 6fae6dc to 31bc4c5 Compare January 10, 2024 04:31
Signed-off-by: Andrew Carbonetto <[email protected]>
*
* @return a Future to connect and return a RedisClient
*/
public static CompletableFuture<RedisClient> CreateClient() {
Copy link
Contributor

Choose a reason for hiding this comment

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

please don't add new public functions that don't exist in other wrappers without discussing it with us.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in 0c7d03b

import lombok.experimental.SuperBuilder;

/**
* Configuration settings class for creating a Redis Client. Shared settings for both a standalone
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: correct me if I'm wrong, but isn't it "for both standalone [...]" with the "a"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed in 0c7d03b


@Test
@SneakyThrows
public void createClient_withConfig_successfullyReturnsRedisClient() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I still don't see the value in these tests, but if you insist on having them 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Understood. I think it's useful to have API unit tests that solidify the API contract with the user, even if it's through a static interface.

Signed-off-by: Andrew Carbonetto <[email protected]>
Signed-off-by: Andrew Carbonetto <[email protected]>
@acarbonetto acarbonetto merged commit 2f901b0 into valkey-io:main Jan 11, 2024
3 checks passed
@acarbonetto acarbonetto deleted the java/integ_acarbo_refactor_api_createclient branch February 23, 2024 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
java issues and fixes related to the java client
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants