Add autoconfiguration for r2dbc-proxy.

Create a proxy of ConnectionFactory when: - r2dbc-proxy is in classpath - property is enabled (enabled by default) - r2dbc-proxy listener beans are defined

Comment From: snicoll

@ttddyy thanks for the PR, can you share a bit more context that lead you to contribute this?

Paging @mp911de for insights.

Comment From: ttddyy

Hi @snicoll

r2dbc-proxy is a library that I wrote to provide callback mechanism to r2dbc-spi classes. It is part of the r2dbc umbrella. One of the usage is observability story for r2dbc, agnostic to actual r2dbc-driver implementations.

r2dbc-proxy creates a proxy of ConnectionFactory with listeners. There are two listener interfaces - ProxyExecutionListener and LifeCycleListener. Listeners are called back with contextual information when Connection, Statement, Batch, etc are invoked.

For example, r2dbc-proxy can be used for getting metrics, creating traces, logging query executions, etc.

The PR is for when those listeners are defined, automatically create a ConnectionFactory proxy and register defined listener beans. This will alleviate manual proxy creation and listener registration. If, for example, spring-cloud-sleuth implements tracing for r2dbc (https://github.com/spring-cloud/spring-cloud-sleuth/issues/1524), they can create a listener bean and this autoconfiguration can pick up such listener and registers to the proxy.

Comment From: mp911de

@ttddyy from my perspective, the proxy integration would make a lot more sense within Spring Cloud Sleuth.

There we could add tracing and metrics support. Likely, it doesn’t make sense to add integrations with the same tool in two places.

Comment From: ttddyy

@mp911de From my view, usage of proxy is more generic. It can be used for metrics and tracing, but also used for different scenarios such as slow query detection, query logging, debugging, etc. In that purpose, it is not limited to spring-cloud-sleuth, and I think integrating to boot autoconfiguration to detect listeners and creating proxy is beneficial, imo.

Comment From: mp911de

The issue we have right now with this PR is that there is too much abstraction without a clear use-case. R2DBC Proxy adds value to the setup, but we need to look also from a perspective of what the integration provides and how it is built.

Providing metrics support out of the box is desirable and likely we should start with metrics as use-case for R2DBC Proxy in Spring Boot. Over time, we can consider additional ones such as general listener support.

Is there already a Micrometer integration for R2DBC Proxy available? On a related note, there's also a ticket in Micrometer asking for SQL metrics.

Comment From: ttddyy

This r2dbc-proxy example populates micrometer metrics a bit.

So, if we want to start with metrics around r2dbc calls, I can write one. We can start what to measure based on the ticket in Micrometer, or let me know if you have list of things to measure.

Comment From: ttddyy

I wrote a sample listener that populate metrics for query execution time per query type(read/write) based on the ticket in Micrometer.

https://github.com/ttddyy/r2dbc-proxy-examples/blob/master/listener-example/src/main/java/io/r2dbc/examples/QueryTimeMetricsExecutionListener.java

Comment From: marcingrzejszczak

Hey @ttddyy , it's Marcin from Spring Cloud Sleuth. How about we move this PR (or part of it) to Spring Cloud Sleuth? We could add an instrumentation to org.springframework.cloud.sleuth.instrument.r2dbc in package scope. Once it turns out that it's working fine and people use it with success, then we could most likely consider pushing it to Boot. WDYT?

Comment From: wilkinsona

Thanks, @marcingrzejszczak. I'm not sure that this needs to go into Sleuth. The Boot team discussed this one recently and, IIRC, the conclusion was that we didn't have any objection to the underlying infrastructure going into Boot and @snicoll was going to have another chat with @mp911de to see if we'd missed something. He's on PTO at the moment so we'll need to wait a bit until he's back.

Comment From: mp911de

From a tracing perspective, it makes sense to have a listener implementation for r2dbc-proxy in Sleuth that can contribute spans based on R2DBC activity once this PR is merged.

Comment From: ttddyy

Hi @marcingrzejszczak

I wrote a sample listener for tracing here long time ago. Looking at it now, I can cleanup a bit. I'll find a time to update it as for a sample listener impl for tracing.

Comment From: marcingrzejszczak

From my perspective, as a Spring Cloud Sleuth user, the easiest way to work with this would be the following

  • add an optional r2dbc proxy
  • create a bean of Listener type
  • assume that Spring Boot would add my listener to the proxy

Comment From: ttddyy

@snicoll Ok, I'll explore the path for modifying ConnectionFactoryConfigurations#createConnectionFactory with ConnectionFactoryOptions. ProxyConnectionFactoryProvider(ConnectionFactoryProvider impl in r2dbc-proxy) understands proxy-listener options. So, if proxy-listener beans are defined, pass them as ConnectionFactoryOptions, then ConnectionFactories#find() would delegate to ProxyConnectionFactoryProvider for actual ProxyConnectionFactory creation with those proxy-listener beans. I think this way is better since it would make more use of r2dbc-spi infrastructure.

Comment From: ttddyy

Hi @snicoll I added a commit 9cccc61004fa4616a02d32b3572149675f3f329b.

This uses ConnectionFactoryOptionsInitializer(via ConnectionFactoryConfigurations#createConnectionFactory) to construct ConnectionFactory options used by ProxyConnectionFactoryProvider(r2bc-proxy). Also, added ProxyListenersProvider interface to avoid direct reference to the classes in r2dbc-proxy to make it work when r2dbc-proxy is not available.

If this looks good, I'll cleanup the commits with tests.

Comment From: mp911de

After discussing with @snicoll we found that ordering of pool/proxy/driver wrapping matters (speaking in URL syntax, r2dbc:pool:proxy:h2 vs. r2dbc:proxy:pool:h2) if someone wants to e.g. measure how many real connections were created/closed by the pool vs. how long a connection is in use by the application.

Comment From: ttddyy

After discussing with @snicoll we found that ordering of pool/proxy/driver wrapping matters (speaking in URL syntax, r2dbc:pool:proxy:h2 vs. r2dbc:proxy:pool:h2) if someone wants to e.g. measure how many real connections were created/closed by the pool vs. how long a connection is in use by the application.

Yes, the ordering matters, and it can be even r2dbc:proxy:pool:proxy:h2.(though, there is a problem specifying listeners for which proxy to apply since there is only one proxyListener option) I consider most of the case proxy is the outer most wrapper since you want to check the Application behavior. If a user wants to proxy the middle ConnectionFactory, the user needs to wrap it manually, but I think that is more advanced use case. For connection-pool in Spring Boot, it may provide a flag in connection-pool settings which would be specific to proxying the connection-pool with some canned proxy listener such as metrics.

Comment From: snicoll

@ttddyy I can see a WIP in your last update and the tests have been commented out. Do you need something from us? Thanks a lot for your effort.

Comment From: ttddyy

@ttddyy I can see a WIP in your last update and the tests have been commented out. Do you need something from us? Thanks a lot for your effort.

@snicoll Thanks for checking. I wanted to check whether the current impl with customizer is ok before I proceed. And since you said ok, I'll finish up the rest of the PR.

Comment From: snicoll

Thanks. Devil in the details as always but I feel we've iterated enough and I can take it from there once you've restored the tests. Thanks again!

Comment From: ttddyy

@snicoll While writing tests, I found using Option has a problem wrapping ConnectionPool with proxy. This is because @Bean method for ConnectionPool wraps the result of createConnectionFactory(). Therefore, proxy cannot wrap the ConnectionPool. So, I fallback to the original plan to make a hook on createConnectionFactory() to post process configured ConnectionFactory. (which I also applied to creating ConnectionPool)

Comment From: jarias

Hi all, I was able to use the proxy with spring-boot 2.3.5 just by adding the dep in the classpath and the following props:

spring:
  r2dbc:
    properties:
      driver: proxy
      protocol: postgresql
      proxyListener: test.foo.bar.MyListener

Comment From: ttddyy

@jarias Looks good. It uses ConnectionFactory Discovery mechanism which is a configuration centric approach. The shortcoming of this approach is that the proxyListener is created outside of the spring and never be a part of the spring context.

In this PR, I am integrating more to spring-boot. So that, proxy listener can be a bean and allowed to be dependency injected.

Comment From: snicoll

In the meantime, Spring Cloud Sleuth has added support for R2DBC instrumentation which was the primary driver for this PR on our side. We haven't really found a way to find an arrangement that would be generic enough to warrant support here so I am going to close this now. We can reconsider based on the experience in Spring Cloud Sleuth or other changes in r2dbc-proxy. Thanks again @ttddyy!