本文介绍了使用mongodb-reactive的Reactive Spring Boot应用程序中的多租户的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们如何使用Mongodb-reactive存储库在Spring Webflux中创建多租户应用程序?

How can we create a multi-tenant application in spring webflux using Mongodb-reactive repository?

我在网络上找不到用于响应式应用程序的任何完整资源.所有可用资源均用于非反应性应用程序.

I cannot find any complete resources on the web for reactive applications. all the resources available are for non-reactive applications.

更新:

在非响应式应用程序中,我们曾经将上下文数据存储在ThreadLocal中,但由于响应式应用程序存在线程切换,因此无法做到这一点.有一种方法可以在WebFilter的反应堆上下文中存储上下文信息,但是我不知道如何在ReactiveMongoDatabaseFactory类中保存该数据.

In a non-reactive application, we used to store contextual data in ThreadLocal but this cannot be done with reactive applications as there is thread switching. There is a way to store contextual info in reactor Context inside a WebFilter, But I don't how get hold of that data in ReactiveMongoDatabaseFactory class.

谢谢.

推荐答案

我能够使用mangodb在Spring Reactive应用程序中实现多租户.负责实现的主要类是:自定义MongoDbFactory类,用于捕获租户信息的WebFilter类(而不是Servlet Filter)和用于存储租户信息的ThreadLocal类.流程非常简单:

I was able to Implement Multi-Tenancy in Spring Reactive application using mangodb. Main classes responsible for realizing were: Custom MongoDbFactory class, WebFilter class (instead of Servlet Filter) for capturing tenant info and a ThreadLocal class for storing tenant info. Flow is very simple:

  1. 从WebFilter的请求中捕获与租户相关的信息,并在ThreadLocal中进行设置.在这里,我使用标题发送租户信息:X-Tenant
  2. 实施自定义MondoDbFactory类并重写getMongoDatabase()方法,以基于ThreadLocal类中可用的当前租户返回数据库.
  1. Capture Tenant related info from the request in WebFilter and set it in ThreadLocal. Here I am sending Tenant info using header: X-Tenant
  2. Implement Custom MondoDbFactory class and override getMongoDatabase() method to return database based on current tenant available in ThreadLocal class.

源代码为:

CurrentTenantHolder.java

CurrentTenantHolder.java

package com.jazasoft.demo;

public class CurrentTenantHolder {
    private static final ThreadLocal<String> currentTenant = new InheritableThreadLocal<>();

    public static String get() {
        return currentTenant.get();
    }

    public static void set(String tenant) {
        currentTenant.set(tenant);
    }

    public static String remove() {
        synchronized (currentTenant) {
            String tenant = currentTenant.get();
            currentTenant.remove();
            return tenant;
        }
    }
}

TenantContextWebFilter.java

TenantContextWebFilter.java

package com.example.demo;

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Component
public class TenantContextWebFilter implements WebFilter {

    public static final String TENANT_HTTP_HEADER = "X-Tenant";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        if (request.getHeaders().containsKey(TENANT_HTTP_HEADER)) {
            String tenant = request.getHeaders().getFirst(TENANT_HTTP_HEADER);
            CurrentTenantHolder.set(tenant);
        }
        return chain.filter(exchange).doOnSuccessOrError((Void v, Throwable throwable) -> CurrentTenantHolder.remove());
    }
}

MultiTenantMongoDbFactory.java

MultiTenantMongoDbFactory.java

package com.example.demo;

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoDatabase;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;


public class MultiTenantMongoDbFactory extends SimpleReactiveMongoDatabaseFactory {
    private final String defaultDatabase;

    public MultiTenantMongoDbFactory(MongoClient mongoClient, String databaseName) {
        super(mongoClient, databaseName);
        this.defaultDatabase = databaseName;
    }


    @Override
    public MongoDatabase getMongoDatabase() throws DataAccessException {
        final String tlName = CurrentTenantHolder.get();
        final String dbToUse = (tlName != null ? tlName : this.defaultDatabase);
        return super.getMongoDatabase(dbToUse);
    }
}

MongoDbConfig.java

MongoDbConfig.java

package com.example.demo;

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.ReactiveMongoClientFactoryBean;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;

@Configuration
public class MongoDbConfig {

    @Bean
    public ReactiveMongoTemplate reactiveMongoTemplate(MultiTenantMongoDbFactory multiTenantMongoDbFactory) {
        return new ReactiveMongoTemplate(multiTenantMongoDbFactory);
    }

    @Bean
    public MultiTenantMongoDbFactory multiTenantMangoDbFactory(MongoClient mongoClient) {
        return new MultiTenantMongoDbFactory(mongoClient, "test1");
    }

    @Bean
    public ReactiveMongoClientFactoryBean mongoClient() {
        ReactiveMongoClientFactoryBean clientFactory = new ReactiveMongoClientFactoryBean();
        clientFactory.setHost("localhost");
        return clientFactory;
    }
}

更新:

在反应流中,由于请求未绑定到单个线程,我们无法再将上下文信息存储在ThreadLocal中,因此,这不是正确的解决方案.

In reactive-stream we cannot store contextual information in ThreadLocal any more as the request is not tied to a single thread, So, This is not the correct solution.

但是,上下文信息可以像这样在WebFilter中存储反应堆上下文. chain.filter(exchange).subscriberContext(context -> context.put("tenant", tenant));.问题是如何在ReactiveMongoDatabaseFactory实现类中掌握此上下文信息.

However, Contextual information can be stored reactor Context in WebFilter like this. chain.filter(exchange).subscriberContext(context -> context.put("tenant", tenant));. Problem is how do get hold of this contextual info in ReactiveMongoDatabaseFactory implementation class.

这篇关于使用mongodb-reactive的Reactive Spring Boot应用程序中的多租户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-15 16:34