GithubHelp home page GithubHelp logo

Comments (21)

mp911de avatar mp911de commented on May 19, 2024 3

I think dropping the first execute() and providing sql(String) and sql(Supplier) as entry methods on DatabaseClient should be fine.

I could see client.sql(“insert ...”)......execute() being clearer than execute/fetch.
After all, inserts don’t “fetch”.

That's not entirely true. INSERT statements produce at least an affected count (same for UPDATE and DELETE) and inserts can produce generated keys that are tabular data. DatabaseClient provides with sql(…) an operator to execute arbitrary SQL which can be all sorts of SQL and after all we're interested in the following outcomes:

  • Completion (success/exceptional) via DatabaseClient.sql(…).then()
  • Affected row count via DatabaseClient.sql(…).fetch().rowsUpdated()
  • Tabular results via DatabaseClient.sql(…).fetch().all()/.first()

A note on insert() and select() methods: These methods are not complete yet, we miss update() and delete().

We provide support on two levels: Arbitrary SQL execution and assisted statement execution. insert() and select() are to provide entity-focussed data access that allows usage of auditing, optimistic locking and other Spring Data features. These methods are not intended for direct SQL usage but SQL is created by Spring Data R2DBC in a manner similar to other Spring Data modules.

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024 1

@mp911de Thanks for your response

However, the core objection remains unanswered. As it is neither about calling builder (or) not, nor whether the intermediate objects are mutable (or) immutable, rather its about method names breaking intuition

Also, intuition has nothing to do with whether we are writing declarative (or) imperative programming

APIs might call them *Specs/*Builders, but end of the day they are storing the information about how to build the reactive chain when the actual subscription happens. My only objection is, doing so should not break intuition

As we all know, code is written once, read multiple times, if we follow some naming convention that does not break this intuition, its better for users. We need to break intuition with extreme care. I dont know if there is a good reason for breaking this intuition, other than using this convention (action name like select) can shorten method names

Also, since we use IDE, it does not matter if the method name is few characters long

Do you agree on whether looking at the code with action names that dont actually do any action breaks intuition?

from spring-data-r2dbc.

gregturn avatar gregturn commented on May 19, 2024 1

To be fair, Reactor has toStream to clarify you are going, you know, to a Stream. And to differentiate from it's fromStream method, which indicates the opposite direction.

from spring-data-r2dbc.

sdeleuze avatar sdeleuze commented on May 19, 2024 1

@thekalinga For get and post, these points have been discussed extensively with the team, and in the context of a builder or a DSL I think this is acceptable. The server DSL has GET and POST capitalized for making that more obvious, but it seemed weird for the client API so we ended up with that kind of method.

@mp911de query() is not so bad, but maybe we could think something even more natural by removing a step in the API if that's possible in term of design.

databaseClient.sql("INSERT INTO ...")...
databaseClient.select("SELECT ...")...

This kind of generic purpose SQL at the same level than specialized ones seems attractive to me, any thoughts?

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

Same applies to select & insert aswell

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

Also, looks like the actual call to db does not get initiated when fetch method is executed rather when the actual subscription occurs like any other reactive stream. So we need to rename this too so as not to give the user wrong intuition

from spring-data-r2dbc.

NorbertSandor avatar NorbertSandor commented on May 19, 2024

Same applies to select & insert aswell

I'm not sure of this... Maybe it would be enough to start the chain with a more specifically named method like you suggested.

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

I'm implying using selectBuilder/forSelect for select just like for execute. I assume thats what you meant aswell

from spring-data-r2dbc.

mp911de avatar mp911de commented on May 19, 2024

Thanks a lot for raising the issue.

DatabaseClient and WebClient use a naming scheme for functional-reactive operators that let you compose a functional pipeline. Execution (the actual doing) happens much later upon subscription.

this sounds & feels like we are making GET call when we are making it (as per intuition),

The intuition originates in a different programming model. In imperative programming method names should translate to actions, to do something. We're in functional-reactive programming here. In functional-reactive programming, we expect methods to create/provide a functional stage that is appended to form a functional sequence.

DatabaseClient was created to align with WebClient and to provide the same look and feel for database access.

Please note that both client implementations are not builders – builders keep a shared mutable state to build a final object. DatabaseClient and WebClient produce immutable intermediate objects that are safe to be shared.

We're happy about receiving naming suggestions that fit into the functional-reactive programming context. We won't go back to an imperative variant.

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

I even go so far as to say, since declarative programming is relatively difficult to follow when compared to imperative, we should avoid making it confusing by avoiding (atleast sidestepping) action method names unless we are certain it performs a specific action

For e.g, take Mono.block(). Tho its part of reactive API, From method name, we can be certain that it blocks the current thread [I know its slightly an outlier, but it explains the crux of the problem] & is an action

from spring-data-r2dbc.

mp911de avatar mp911de commented on May 19, 2024

I think there's a point to this discussion and I have a feeling that it's mostly around the execute method. We're aiming towards a readable DSL. Looking at the example above gives us exactly that:

databaseClient.execute()
  .sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
  .bind("$1", 42055)
  .bind("$2", "Description")
  .bindNull("$3", Integer.class)
  .fetch()

Translates into "execute SQL INSERT …, bind parameters and then fetch the result as first/all" elements.

The same goes for WebClient:

webClient.get()
  .uri("http://host/{name}", name)
  .retrieve()
  .bodyToMono(Details.class);

Get some data from http://… and retrieve it as Details.

Note: get is a method name and can also be used as verb. Same goes for post and delete, only WebClient.method(…) breaks the scheme.

Playing a bit with naming and nesting, we could end up with things like:

databaseClient.newExecute()
  .sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
  …
 .fetch()
databaseClient.query()
  .sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
  …
 .fetch()
databaseClient.forSql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
  …
 .fetch()

Adding prefixes here and there can improve naming in the first place. It adds a bulkhead that can help guide new users. Once used to this API style, users will find that bulkhead rather hinders readability.
With DatabaseClient and WebClient we're creating a new fluent approach to access data and HTTP resources. Each API/programming model requires a learning curve, and we do not imply a know-nothing approach that would enable using the API. Rather, learning from the ref docs and Javadoc is the way to explore the API.

I've two further thoughts here:

  1. What is the actual problem are we trying to solve by changing naming?
  2. What is the alternative?

Further references:

from spring-data-r2dbc.

gregturn avatar gregturn commented on May 19, 2024

I was getting the impression is that this is more about the deferred execution of reactive streams not being clearly visible. It implies that execute should be something like executeUponSubscription.

Since ALL of RS programming is deferred execution, we'd have to apply this to every part of this fluent API, and that would clearly get tedious at a certain point. Especially after new users have gotten the hang of RS.

I feel such nomenclature adds no more readability than if RestTemplate.exchange were put inside a Java 8 Consumer via lambda call. RestTemplate.exchange, upon execution, does its thing. WHEN it does it is dependent upon context outside the exchange method itself. The same could be said for WebClient of DatabaseClient. WHEN it happens isn't pertinent to the name.

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

@mp911de @gregturn

Sure,

I agree everything is context dependent, but looking at this databaseClient.execute()....fetch() feels so unnatural (ofcourse, its subjective) when it never executes & does not fetch unless someone subscribes in the future

I'm using RxJava for almost 2 years now & Reactor for atleast a year. AFAIK, there are no APIs from those two libraries that sacrifice readability (compose & transform are exceptions, which will be renamed in the future) unlike web-flux, reactor-netty & the current library

Compare these three & see which of them accurately says (more accurately does not say) what it does & when it does

databaseClient.execute()
  .sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
  .bind("$1", 42055)
  .bind("$2", "Description")
  .bindNull("$3", Integer.class)
  .fetch()

vs

databaseClient.forSql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
  .bind("$1", 42055)
  .bind("$2", "Description")
  .bindNull("$3", Integer.class)
  .done()

vs

databaseClient.forExecute()
  .sql("INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)")
  .bind("$1", 42055)
  .bind("$2", "Description")
  .bindNull("$3", Integer.class)
  .done()

Please note that this is just a quick suggestion I could think of that does not confuse the users, but may be if we think about it a bit, we might comeup with a compromise that does not confuse at the same time try and not sacrifice the attributes you are trying to preserve in the new API

Also note that I'm only trying to use this as a starting point to make sure that 90-95% of developers who comes to reactive programming from regular Java/Spring programming should need feel method does not do what it says in the name

I agree with the direction to make the API fluent (I too am in favor of it), but not at the cost of intuition

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

ADBA APIs countOperation [Used for both insert & update statement] (or) rowOperation [used for select statement] also looks reasonably OK when its comes to intuition, as it they dont specify/imply when the method gets executed, but execute() & fetch() imply an operation that will happen now, not sometime in the future

While thinking about it, I was looking at the method bind. Even that method looks a bit off, but not as off as execute & fetch as when binding happens is usually immaterial from the point of view of the developer (as we dont have lazy evaluation of bound arguments as of now in the BindingSpec API)

Since this is a new area for most of us, there are no recipes to follow when it comes to naming things in this reactive world. May be we can attempt to comeup with some initial direction when it comes to this

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

Adding prefixes here and there can improve naming in the first place. It adds a bulkhead that can help guide new users. Once used to this API style, users will find that bulkhead rather hinders readability.

Take Flux.toStream for example. they explicitly added to prefix for readability reasons. They could have verywell named it as Flux.stream aswell, but dint do it for readability reasons AFAIK

Edit: I'm not sure if I'm completely accurate about above analysis, but it feels to be natural to name methods so that they accurately represent what they do

from spring-data-r2dbc.

sdeleuze avatar sdeleuze commented on May 19, 2024

I was about to raise a similar issue for execute, I tend to think there is something to be done here to make the API less confusing. That's quite visible with the Coroutines support I have built: client.execute().sql("CREATE TABLE ...").execute(). The second execute I have added at Coroutines support level seems ok to me, the first one seems confusing.

fetch is maybe also confusing, but I need to give it more thoughts.

For the remarks about WebClient, get, post are HTTP verbs and are here to describe the request we want to send, so I tend t think this is ok.

I will try to think about proposals for a better API.

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

@gregturn

To be fair, Reactor has toStream to clarify you are going, you know, to a Stream. And to differentiate from it's fromStream method, which indicates the opposite direction.

Agree with you. Must be the reason behind it. But it can be stream(Stream) instead of fromStream(Stream) & stream() instead toStream() if the goal was to keep method names compact

Looks like I am wrong on Reactor API (+ RxJava2 aswell). When I looked at reactor API if any of the operators looks like they are actions (some thing that we expect to be done immediately). I found retry, repeat, collect* (somewhat), count (somewhat) & others that also to resemble ACTION names. The only excuse I can make for them is "They are Flux operators who operate on streams & not flux builders" 😉

Here we are using action names that does nothing but record the intent of how actual Publisher should look like with method names resembling actions. To be fair, since this space is quite new and the naming conventions are not explored like every other thing in Java, we will need to make a start and will need to comeup with some guidelines to make sure the API does not look too bloated with longMethodNames while at the sametime does not give the wrong impression with confusing names

PS: I'm using the term builder loosely to refer to an object/place that records the information to build the final flux at somepoint of the time in the future. This need not be traditional builder pattern

from spring-data-r2dbc.

thekalinga avatar thekalinga commented on May 19, 2024

@sdeleuze

The problem with action methods like get is they implicitly break general convention we use in programming in general. From my point of view, get & post calls imply that we are attempting to make a call, not creating a builder that will make a flux in the future

As I said before, looks like we all have our individual personnel preferences when it comes to which one looks intuitive and which one does not. May be time for all of us to stir the pot lil bit & see if we can make a small start in coming up with general guidelines in this new space

from spring-data-r2dbc.

gregturn avatar gregturn commented on May 19, 2024

I could see client.sql(“insert ...”)......execute() being clearer than execute/fetch.

After all, inserts don’t “fetch”.

When exploring the API, what is clearer than the IDE showing you sql in the drop down? I haven’t been a fan of bind, but it’s an industry nomenclature so it should stay. And execute seems like a clear terminator

I don’t see the next to embed the deferred concept into the naming convention. I feel I’ve done enough RS that I understand the “action” is happening when you subscribe.

from spring-data-r2dbc.

gregturn avatar gregturn commented on May 19, 2024

I'm curious how this would pan out in light of this comment on r2dbc-h2.

Do we need some enum at the SPI level that explicitly demarks insert/update/whatever, and that the clients then leverage?

from spring-data-r2dbc.

mp911de avatar mp911de commented on May 19, 2024

I don't think that we need an operation enum. The standard is somewhat overseeable but we have a lot of other operations (like CREATE TABLE, ALTER INDEX).

Re r2dbc/r2dbc-h2#9: I think drivers will have to inspect the query to perform vendor-specific API calls (see here and here for examples).

from spring-data-r2dbc.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.