Multi-Tenancy в загрузочном приложении Reactive Spring с использованием mongodb-реактивного - PullRequest
0 голосов
/ 22 апреля 2019

Как мы можем создать мультитенантное приложение в весеннем webflux, используя Mongodb-реактивный репозиторий?

Я не могу найти в Интернете полных ресурсов для реактивных приложений.Все доступные ресурсы предназначены для нереактивных приложений.

ОБНОВЛЕНИЕ:

В нереактивном приложении мы использовали для хранения контекстных данных в ThreadLocal, но это не может быть сделано с реактивными приложениями, поскольку имеется переключение потоков.Есть способ хранить контекстную информацию в реакторе Context внутри WebFilter, но я не знаю, как получить эти данные в классе ReactiveMongoDatabaseFactory.

Спасибо.

1 Ответ

0 голосов
/ 23 апреля 2019

Мне удалось реализовать приложение Multi-Tenancy in Spring Reactive с помощью mangodb. Основными классами, ответственными за реализацию, были: пользовательский класс MongoDbFactory, класс WebFilter (вместо фильтра сервлета) для сбора информации об арендаторе и класс ThreadLocal для хранения информации об арендаторе. Поток очень прост:

  1. Захват информации об арендаторе из запроса в WebFilter и установка ее в ThreadLocal. Здесь я отправляю информацию об арендаторе, используя заголовок: X-Tenant
  2. Реализация пользовательского класса MondoDbFactory и переопределение getMongoDatabase() метода для возврата базы данных на основе текущего арендатора, доступного в классе ThreadLocal.

Исходный код:

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

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

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

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;
    }
}

UPDATE:

В реактивном потоке мы больше не можем хранить контекстную информацию в ThreadLocal, поскольку запрос не привязан к одному потоку, поэтому это не является правильным решением.

Тем не менее, контекстная информация может храниться в реакторе Context в WebFilter следующим образом. chain.filter(exchange).subscriberContext(context -> context.put("tenant", tenant));. Проблема в том, как получить эту контекстную информацию в ReactiveMongoDatabaseFactory классе реализации.

...