Comments (21)
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.
@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 *Spec
s/*Builder
s, 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.
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.
@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.
Same applies to select
& insert
aswell
from spring-data-r2dbc.
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.
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.
I'm implying using selectBuilder
/forSelect
for select
just like for execute
. I assume thats what you meant aswell
from spring-data-r2dbc.
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.
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.
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:
- What is the actual problem are we trying to solve by changing naming?
- What is the alternative?
Further references:
from spring-data-r2dbc.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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)
- Release 1.5.14 (2021.2.14)
- Upgrade to Maven Wrapper 3.9.3
- Release 1.5.15 (2021.2.15)
- Upgrade to Maven Wrapper 3.9.4
- Release 1.5.16 (2021.2.16)
- Release 1.5.17 (2021.2.17)
- Model entity need to add custom Encryption on insert/update and decrypt on read HOT 4
- Upgrade to Maven Wrapper 3.9.5
- Release 1.5.18 (2021.2.18)
- Enum is not properly mapped until custom converter created HOT 5
- NativeParameterUtils doesn't support collection with nullable values HOT 7
- Issue with @Transient HOT 3
- Manage transaction name when use @Transactional annotatnion HOT 1
- Transaction rollback does not work when using jOOQ HOT 1
- Custom converter in R2dbcEntityOperations breaks nested mappings
- Caused by: io.r2dbc.postgresql.ExceptionFactory$PostgresqlBadGrammarException: [42883] operator does not exist: traffic_type = character varying
- ConditionalGenericConverter is not properly handled by Spring Data R2DBC HOT 1
- Save entity with custom UUID identifier HOT 2
- `bindProperties` throws error when entities contain enum fields HOT 1
- IN clause not working HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from spring-data-r2dbc.