Spring Boot WebFlux Reactive MongoDB:如何在每个请求上切换数据库? [英] Spring Boot WebFlux Reactive MongoDB: how to switch the database on each request?

查看:671
本文介绍了Spring Boot WebFlux Reactive MongoDB:如何在每个请求上切换数据库?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Spring WebFlux和Reactive MongoDB进行SaaS项目. 它必须是一个MultiTenant应用程序,每个租户都必须使用自己的数据库.

目前,我只是将Reactive MongoDB依赖项添加到pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

然后我扩展了 AbstractReactiveMongoConfiguration ,以提供 MongoClient 和DatabaseName:

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;

@Configuration
public class DatabaseConfiguration extends AbstractReactiveMongoConfiguration {
    @Override
    public MongoClient reactiveMongoClient() {
        System.out.println("ReactiveMongoClient");
        return MongoClients.create();
    }

    @Override
    protected String getDatabaseName() {
        System.out.println("DataBase name");
        return "gateway";
    }
}

整个应用程序是OAuth 2.0资源服务器,我可以从 ReactiveSecurityContextHolder 中的 Authentication 检索 TenantID . /p>

public Mono<String> tenantID() {
    return ReactiveSecurityContextHolder.getContext()
            .switchIfEmpty(Mono.empty())
            .flatMap((securityContext) -> {
                Authentication authentication = securityContext.getAuthentication();

                if (authentication instanceof CustomAuthenticationToken) {
                    CustomAuthenticationToken customAuthenticationToken = (customAuthenticationToken) authentication;
                    Jwt jwt = customAuthenticationToken.getToken();

                    String issuer = jwt.getIssuer().toString();
                    return Mono.justOrEmpty(issuer);
                }

                return Mono.empty();
            });
}

为了根据执行请求的用户(身份验证)切换数据库,下一步是什么?

更新:

几乎接近目标,但几乎是一年前 @ mp911de 说不可能. 想知道现在是否可行. 如何在返回 MongoDatabase 之前实现返回 Mono 的真正的反应性 ReactiveMongoDatabaseFactory ? >

解决方案

几个月前,我遇到了同样的问题,想分享我的解决方法.

解决方案1:自己动手

Spring没有为该问题提供任何现成的解决方案.几个月前,我创建了如何解决该问题的概念证明,并在Github上发布了示例项目.

spring-mongodb-multi-tenancy-example

简而言之:我创建了一个自定义MultiTenancyReactiveMongoTemplate,它基于 subscriberContext 中的 tenantId 在内部委托给实际的ReactiveMongoTemplate. tenantId 是从自定义WebFilter中的http-request标头中提取的,并将其放在 subscriberContext 中.

此解决方法适用于大多数情况,并且还支持自动索引创建和ReactiveMongoRepository的使用.

但是也有一些限制,因为MultiTenancyReactiveMongoTemplateReactiveGridFSTemplate上的Transaction,IndexOps不适用于所提供的解决方案.有些事情可以用其他委派的模板"来实现,但有些事情将永远无法工作,因为这些操作仅返回标量值(没有Publisher),在这种情况下无法访问subscriberContext.

如果您不需要这些功能,则可以使用此解决方案.

解决方案2:

您向上旋转并为每个租户/客户配置服务实例,然后在这些服务之前放置一个反向代理.反向代理决定应使用哪个服务来处理请求. 使用 Spring Cloud Gateway 可以非常容易地实现反向代理.轻松实现谓词,从而决定(例如,基于身份验证令牌)应使用哪种服务.

借助诸如CloudFoundry或Kubernetes之类的技术,协调这些租户特定服务的部署变得不再困难,并且该解决方案还使租户特定的监视,警报甚至计费变得容易.

我们选择了解决方案 2 ,因为总的来说这更容易,并且可以更好地扩展.

I'm working on a SaaS project using Spring WebFlux and Reactive MongoDB. It needs to be a MultiTenant application and each tenant must use its own database.

As for now I just added the Reactive MongoDB dependency to the pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

Then I extended the AbstractReactiveMongoConfiguration in order to provide the MongoClient and the DatabaseName:

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;

@Configuration
public class DatabaseConfiguration extends AbstractReactiveMongoConfiguration {
    @Override
    public MongoClient reactiveMongoClient() {
        System.out.println("ReactiveMongoClient");
        return MongoClients.create();
    }

    @Override
    protected String getDatabaseName() {
        System.out.println("DataBase name");
        return "gateway";
    }
}

The whole application is an OAuth 2.0 Resource Server and I'm able to retrieve the TenantID from the Authentication in the ReactiveSecurityContextHolder.

public Mono<String> tenantID() {
    return ReactiveSecurityContextHolder.getContext()
            .switchIfEmpty(Mono.empty())
            .flatMap((securityContext) -> {
                Authentication authentication = securityContext.getAuthentication();

                if (authentication instanceof CustomAuthenticationToken) {
                    CustomAuthenticationToken customAuthenticationToken = (customAuthenticationToken) authentication;
                    Jwt jwt = customAuthenticationToken.getToken();

                    String issuer = jwt.getIssuer().toString();
                    return Mono.justOrEmpty(issuer);
                }

                return Mono.empty();
            });
}

What is the next step in order to switch the database based on the user (Authentication) performing the request?

UPDATE:

This is almost near to the goal, but almost one year ago @mp911de said it was not possible. Wondering if it is feasible now. How could I implement a true reactive ReactiveMongoDatabaseFactory that returns Mono so that I could access SecurityContext, hence the Authentication, before returning the MongoDatabase?

解决方案

I had the same problem a few months ago and wanted to share how I solved it.

Solution 1: do it yourself

Spring does not provide any out-of-the-box solution for this problem. A few months ago i created a proof of concept how to solve it and just published the example project on Github.

spring-mongodb-multi-tenancy-example

In short: I created a custom MultiTenancyReactiveMongoTemplate which internally delegates to the the actual ReactiveMongoTemplate based on the tenantId from subscriberContext. The tenantId is extracted from a http-request header in a custom WebFilter which puts it in the subscriberContext.

This workaround works for most cases and also supports auto-index creation and the use of the ReactiveMongoRepository's.

But also has some limitations as Transactions, IndexOps on the MultiTenancyReactiveMongoTemplate or the ReactiveGridFSTemplate do not work with the provided solution. Some of the things could be implemented with other delegating 'templates' but some things will never work as these operations simply return scalar values (no Publisher) and there is no way to access the subscriberContext in these cases.

If you do not need these features you could probably go with this solution.

Solution 2:

You spin up and configure instances of the service for each tenant/customer and put a reverse proxy 'before' these services. The reverse proxy decides which service should be used to handle the request. The reverse proxy can be implemented very easy with for example Spring Cloud Gateway which allows you to easily implement predicates which decide (e. g. based on a auth token) which service should be used.

With technologies like CloudFoundry or Kubernetes it is no that hard anymore to orchestrate the deployment of these tenant specific services and this solution makes it also easy to do tenant specific monitoring, alerting or even billing.

We chose solution 2 because overall this was easier and scales better for us.

这篇关于Spring Boot WebFlux Reactive MongoDB:如何在每个请求上切换数据库?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆