postgresqlspring-webfluxspring-data-r2dbcr2dbc

Difference between Spring R2DBC using EntityCallbacks vs Mapping and use case?


I am trying to understand the difference between R2DBC EntityCallbacks and R2DBC Mapping. I am trying to use it in a Multitenant application, where I would like to map the TenantId using a web filter and request context holder to set it before making the DB call.

In the above context which will be the better approach so that handling the tenant id is separated from the business logic for the current endpoint?


Solution

  • For database/R2dbc multitenant support, there is an AbstractRoutingConnectionFactory which can be used to switch the certain tenant database from the client request.

    public class TenantAwareConnectionFactory extends AbstractRoutingConnectionFactory {
        @Override
        protected Mono<Object> determineCurrentLookupKey() {
            return CurrentTenantIdHolder.getTenantId().map(id -> (Object) id);
        }
    }
    

    We use CurrentTenantIdHolder to hold the tenant id from request context.

    public class CurrentTenantIdHolder {
    
        public static final String TENANT_ID = CurrentTenantIdHolder.class.getName() + ".TENANT_ID";
    
        public static Context withTenantId(String id) {
            return Context.of(TENANT_ID, Mono.just(id));
        }
    
        public static Mono<String> getTenantId() {
            return Mono.deferContextual(contextView -> {
                        if (contextView.hasKey(TENANT_ID)) {
                            return contextView.get(TENANT_ID);
                        }
                        return Mono.empty();
                    }
            );
    
        }
    
        public static Function<Context, Context> clearContext() {
            return (context) -> context.delete(TENANT_ID);
        }
    }
    

    Use a filter to set up the incoming tenant id.

    @Component
    public class TenantFilter implements WebFilter {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
            var value = exchange.getRequest().getHeaders().getFirst("X-Tenant-Id");
            if (StringUtils.hasText(value)) {
                return chain.filter(exchange)
                        .contextWrite(CurrentTenantIdHolder.withTenantId(value));
            }
            return chain.filter(exchange);
        }
    }
    

    Here I assume you are using a header X-Tenant-Id to identify the incoming tenant. You can use query parameter, or sub domain, etc.

    Check the complete example here: https://github.com/hantsy/spring-puzzles/blob/master/multi-tenancy-r2dbc/