Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I configure spring r2dbc to use separate read-only and read-write DB urls?

I have a Spring Webflux application with the "org.springframework.boot:spring-boot-starter-data-r2dbc" dependency for the DB connection.
I also have a postgres cluster containing master and read-only replica. Both have separate URLs.
I am looking for an option to configure the app to use both these urls accordingly.
What is the best way to do this?

like image 550
fyrkov Avatar asked Sep 06 '25 07:09

fyrkov


1 Answers

Following this PR from @mp911de I created a custom AbstractRoutingConnectionFactory which can route to different datasources depending on the specific key in Reactor's context.

public class ClusterConnectionFactory extends AbstractRoutingConnectionFactory {

    @Override
    protected Mono<Object> determineCurrentLookupKey() {
        return Mono.deferContextual(Mono::just)
                .filter(it -> it.hasKey("CONNECTION_MODE"))
                .map(it -> it.get("CONNECTION_MODE"));
    }
}
@Configuration
public class ClusterConnectionFactoryConfiguration {

    @Bean
    public ConnectionFactory routingConnectionFactory() {

        var clusterConnFactory = new ClusterConnectionFactory();

        var connectionFactories = Map.of(
            ConnectionMode.READ_WRITE, getDefaultConnFactory(),
            ConnectionMode.READ_ONLY, getReadOnlyConnFactory()
        );

        clusterConnFactory.setTargetConnectionFactories(connectionFactories);
        clusterConnFactory.setDefaultTargetConnectionFactory(getDefaultConnFactory());

        return clusterConnFactory;
    }

    // In this example I used Postgres
    private ConnectionFactory getDefaultConnFactory() {
        return new PostgresqlConnectionFactory(
                PostgresqlConnectionConfiguration.builder()...build());
    }

    private ConnectionFactory getReadOnlyConnFactory() { 
      // similar to the above but pointing to the read-only replica
    }

    public enum ConnectionMode { // auxiliary enum as a key
      READ_WRITE,
      READ_ONLY
    }

}

Then I had to extend my repository methods with this contextual info like

public <S extends Entity> Mono<UUID> save(final S entity) {
    return repository.save(entity)
            .contextWrite(context -> context.put("CONNECTION_MODE", READ_WRITE));

This works, but unfortunately doesn't look good in the sense that it is not declarative and interferes with reactive chains.

I would be glad if someone suggests a better solution.

like image 165
fyrkov Avatar answered Sep 07 '25 19:09

fyrkov