3

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?

fyrkov
  • 2,245
  • 16
  • 41

1 Answers1

4

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.

fyrkov
  • 2,245
  • 16
  • 41