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

Add support for CLIENT TRACKINGINFO #2862

Merged
merged 10 commits into from
Aug 8, 2024
Merged

Conversation

tishun
Copy link
Collaborator

@tishun tishun commented May 24, 2024

Closes #2761

Make sure that:

  • You have read the contribution guidelines.
  • You have created a feature request first to discuss your contribution intent. Please reference the feature request ticket number in the pull request.
  • You use the code formatters provided here and have them applied to your changes. Don’t submit any formatting related changes.
  • You submit test cases (unit or integration tests) that back your changes.

@tishun tishun changed the title Topic/tishun/issue 2761 Add support for CLIENT TRACKINGINFO May 24, 2024
@tishun tishun self-assigned this May 24, 2024
@tishun tishun added the type: feature A new feature label May 24, 2024
@tishun tishun added this to the 7.x milestone May 24, 2024
@tishun tishun marked this pull request as ready for review May 24, 2024 15:36
Copy link
Collaborator

@uglide uglide left a comment

Choose a reason for hiding this comment

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

We should return Map<String, String> to make the response usable even without a parser.

@tishun
Copy link
Collaborator Author

tishun commented May 29, 2024

This is what I initially went with, but the existing CommandOutput abstractions could not handle a Map result with nested Array. Both the MapOutput and the GenericMapOutput could not handle this.

The only place we had a similar parsing done is with the CLUSTER SLOTS and I replicated this approach.

@mp911de do we have a way to handle this or should I create a new CommandOutput?

@mp911de
Copy link
Collaborator

mp911de commented Jun 4, 2024

@mp911de do we have a way to handle this or should I create a new CommandOutput?

Have you tried ObjectOutput? An alternative could be ArrayOutput. If both do not work, then a custom CommandOutput would be the last alternative.

@tishun
Copy link
Collaborator Author

tishun commented Jun 5, 2024

Sooo folks I'd love to hear from you what you think about the latest solution.

I already had a discussion with @uglide on that, but would love to hear any comments.

Obviously it has some downsides, but I personally find it a good compromise between what we have as a solution right now and having an actual mapping to a domain object. The latter would also incur some more heavy duty maintenance on our part.

@mp911de
Copy link
Collaborator

mp911de commented Jun 6, 2024

The new output is pretty complex and its result seems pretty complex to understand as well. A Object or List<Object> return is typically much easier to consume as it would contain Redis primitives (Lists, numbers, strings) that are more common than the approach used here.

@tishun
Copy link
Collaborator Author

tishun commented Jun 6, 2024

The new output is pretty complex and its result seems pretty complex to understand as well. A Object or List<Object> return is typically much easier to consume as it would contain Redis primitives (Lists, numbers, strings) that are more common than the approach used here.

I agree. It is a tradeoff.

The proposed solution is supposed to be used (mainly) in conjunction with the parser logic. Further down the line the parser could be extended to generate stubs based on some schema definition. For a strongly typed language such as Java this is the only working way to consume an API of a service, that has complex types and could be changed down the line.

Jedis has a model object for complex return types, but this makes the design tightly coupled with the current API. Any change to the API would break the existing model.

I was aiming for a middle ground where we have a utility to help the user consume the logic in a simple way (see the integration test), but also allow them to parse the information themselves if the model changes.

A good example for this is if another value is added to the Map. The consumers of the driver could start polling the new value without having to wait until we release a new version of the parser.

So we gat a bit of more stability for the price of having to deal with this interim object that is a bit complex to use.

@tishun
Copy link
Collaborator Author

tishun commented Jun 7, 2024

We should return Map<String, String> to make the response usable even without a parser.

@uglide what is your take on this solution?

@tishun
Copy link
Collaborator Author

tishun commented Jul 11, 2024

ping @uglide :)

@tishun tishun modified the milestones: 7.x, 6.5.0.RELEASE Jul 13, 2024
Copy link
Collaborator

@atakavci atakavci left a comment

Choose a reason for hiding this comment

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

just left a comment around use of getDynamicMap.

other than that, if we already good with having an explicit parser;

I was aiming for a middle ground where we have a utility to help the user consume the logic in a simple way (see the integration test), but also allow them to parse the information themselves if the model changes.

is it possible to keep the return types as simple/plain as possible and do all the heavy work to construct the strongly typed object (besides the parser logic) inside the parser. I 'd suggest to keep DynamicAggregateData and all subclasses as reusable stuff and hide all them inside a particular parser.

In case some client interested in parsing themselves, required bits would be still available. Besides, this way they wont have to construct a complete AggregateData if they interested in just a small part of it.
does it makes sense ?

@tishun
Copy link
Collaborator Author

tishun commented Aug 7, 2024

Everyone,

after a 30min discussion with @atakavci I realized we are definitely over-engineering this and only making it complex to the users of the driver as a result. The final version simply parses the data behind the scenes and returns a nice domain object to the user. The existing infrastructure is now facilitating the parsing of future complex objects, buty without exposing any of the complexity to the API.

Why did we decide to not expose the parsing to the users of the driver:

  • first and most important of all - making the assumption that the server would return a different data structure (and thus - obviously - has introduced breaking changes) and the users of the driver would still opt to use the old version of the driver (not adapted to the server's breaking changes) is a bold assumption
  • additionally the users of the Lettuce driver already have a way to send custom commands in case the existing coverage is not sufficient, so they do not need yet another facility that would give them alternatives of parsing the data
  • last but not least I agree with @mp911de that the data needs to be easy to use by the user

Any thoughts are welcome, but I feel confident right now that this is the best solution. Feel free to prove me wrong :)

@mp911de
Copy link
Collaborator

mp911de commented Aug 7, 2024

Hiding the inner Output is a good thing and it will allow for future revision without breaking calling code. I would strongly advise to investigate on a generic output that is driven by the Redis response structure allowing for capturing the response. A Output wrapper could then apply the parsing.

@atakavci
Copy link
Collaborator

atakavci commented Aug 7, 2024

first of, i am all good with typed object as return value.
but let me share my 2 cents here as well and may be contradict with myself 😬 while doing that;

  • i think using a new version of client api with an old version of server is not a bold assumption,, there are reasonable amount of organizations where these two things are in responsibility of separate people/department with different timelines.

  • imho, what we have as conclusion is only one step away from 'ideal'. simplicity is a key quality of a successful api/driver i believe, and i am choosing this for simplicity. maintenance and compatibility should stay as a burden on our side.

  • referring to the 'ideal' word above, solution with DynamicAggregateData would not be over-engineered when the time comes and we had this first round with a request/issue from users trying to access some part of the server response which is not exposed with the current return type.

And thanks for the discussion, i am happy we had it.

@tishun
Copy link
Collaborator Author

tishun commented Aug 8, 2024

Hiding the inner Output is a good thing and it will allow for future revision without breaking calling code. I would strongly advise to investigate on a generic output that is driven by the Redis response structure allowing for capturing the response. A Output wrapper could then apply the parsing.

Hey Mark, apologies, not sure If I get this right, let me rephrase it and see if we are on the same page.
Your recommendation is to have a multipurpose generic output class that is able to handle all possible permutations of the command results and then use wrapper outputs to apply any parsing / mapping to Java types.

Is that correct? If so - this makes sense. I can create another issue to track this work.

@tishun
Copy link
Collaborator Author

tishun commented Aug 8, 2024

Just for the sake of argument (I mostly agree with your comments):

  • i think using a new version of client api with an old version of server is not a bold assumption,, there are reasonable amount of organizations where these two things are in responsibility of separate people/department with different timelines.

I agree that it is not impossible for the situation to arise where two teams / companies working on the same solution end up with different version of the client and server. However I would be surprised if - instead of upgrading the driver - they decide to parse the result themselves. First of all they would have to now maintain this new code and change it again in the future when it breaks (or remove it when they upgrade the driver). Second of all - in terms of effort and stability - it is the harder thing to do.

Even then they are still given the opportunity to use the custom commands functionality to manually process the result.

  • imho, what we have as conclusion is only one step away from 'ideal'. simplicity is a key quality of a successful api/driver i believe, and i am choosing this for simplicity. maintenance and compatibility should stay as a burden on our side.

Here is where I disagree, but we could agree to disagree :) To me this is the ideal solution. I realize it incurs maintenance and compatibility but IMHO this is the responsibility of the driver. Somebody has to pay the price and the driver makes most sense - otherwise all the users of the driver would have to pay that price themselves, which is less than ideal.

  • referring to the 'ideal' word above, solution with DynamicAggregateData would not be over-engineered when the time comes and we had this first round with a request/issue from users trying to access some part of the server response which is not exposed with the current return type.

You may be right, depending on what such a solution would bring to the users of the driver, compared to the custom commands. If it is yet another way or them to parse things manually it does not add any value. If it helps them more than that then it might be meaningful.

And thanks for the discussion, i am happy we had it.

Absolutely. I have the feeling we will have more of these :)

@tishun tishun merged commit 103636c into redis:main Aug 8, 2024
5 checks passed
@tishun tishun deleted the topic/tishun/issue-2761 branch August 8, 2024 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature A new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for CLIENT TRACKINGINFO
4 participants