How to choose spring configuration at runtime based on tenant?

I would like to be able to select a specific Spring (or Grails) context configuration based on the tenant to which the user belongs at runtime. Let's say I use Spring Security and I retrieve tenantId during login. Imagine that I have two tenants and they pay a different commission. How to enter a specific service into the controller without unnecessary plumbing? Here are two different contexts. Therefore, I have to introduce a different tenant based ExchangeService.

@Configuration public class FooTenant{ @Bean public ExchangeService bar() { return new ZeroCommisionExchangeService (); } } @Configuration public class BarTenant{ @Bean public ExchangeService bar() { return new StandardCommisionExchangeService (); } } 

Edit: I know that I can get a link to the Spring context and request the service โ€œmanuallyโ€, but I'm looking for a more general solution in which this problem is solved using the IoC infrastructure.

+6
source share
6 answers

A couple of years ago we needed something like this, but only for DataSource and ViewResolvers . We developed a solution using spring ' TargetSource . (We originally used HotswappableTargetSource , but that was not enough for our use case.

We developed the code here in the multi-tenant directory.

It is fully customizable and flexible.

Basically, you create a ContextSwappableTargetSource configuration and tell you what type of interface / class to return.

 <bean id="yourTentantBasedServiceId" class="biz.deinum.multitenant.aop.target.ContextSwappableTargetSource"> <constructor-arg value="ExchangeService" /> </bean> 

By default, beans search in ApplicationContext based on tenantId is used (see BeanFactoryTargetRegistry for this). However, you can specify one or more of them (we used JndiLookupTargetRegistry to dynamically search for a data source, which allowed us to add tenants on the fly without restarting the application).

If you explicitly configured BeanFactoryTargetRegistry , you can add prefix and suffix .

 <bean id="exchangeService" class="biz.deinum.multitenant.aop.target.ContextSwappableTargetSource"> <constructor-arg value="ExchangeService" /> <property name="targetRegistry> <bean class="biz.deinum.multitenant.aop.target.registry.impl.BeanFactoryTargetRegistry"> <property suffix="ExchangeService"/> </bean> </property> </bean> 

Now for foo it will look for a bean named fooExchangeService and for the string barExchangeService .

TenantId is stored in ThreadLocal , which is enclosed inside a ContextHolder . You need to find a way to fill and clear this stream locally (in general, the Filter servlet does this trick.

In your code, now you can simply use the ExchangeService interface, and at runtime based on tenantId correct implementation will be checked.

Also see http://mdeinum.wordpress.com/2007/01/05/one-application-per-client-database/

+2
source

Assuming you already have certain services, you can get their beans from the context and use it. In my example, all services have an implementation of serviceMethod and, based on some criteria, select your correct service. The only thing I'm not sure about is how Multitenancy can affect this.

 import org.springframework.context.ApplicationContext class ServiceManagerController { def serviceManager def index() { ApplicationContext ctx = grails.util.Holders.grailsApplication.mainContext serviceManager = ctx.getBean(params.serviceName); //firstService or secondService render serviceManager.serviceMethod() } } 

Firstservice

 class FirstService { def serviceMethod() { return "first" } } 

SecondService:

 class SecondService { def serviceMethod() { return "second" } } 
+1
source

Converting my comment into a response, one possible solution is to create a spring factory bean that gets everything it needs to decide which service should be returned when the instance is created.

Translation in Grails:

 public interface ChoosableServiceIntf { String getName(); } class NormalService implements ChoosableServiceIntf { public String getName() { return getClass().name; } } class ExtendedService implements ChoosableServiceIntf { public String getName() { return getClass().name } } class ChoosableServiceFactory { static ChoosableServiceIntf getInstance(String decisionParam) { if(decisionParam == 'X') { return applicationContext.getBean('extendedService') } return applicationContext.getBean('normalService') } static ApplicationContext getApplicationContext() { return Holders.grailsApplication.mainContext } } 

Here we have two services, and the ChoosableServiceFactory is responsible for ensuring that the witch is correct.

Then you will need to use the ApplicationContext#getBean(String, Object[]) method to return the correct instance, and also make the factory prototyped scope due to the runtime parameters.

Controller to check:

 class MyController { def grailsApplication def index() { ChoosableServiceIntf service = grailsApplication.mainContext.getBean('choosableServiceFactory', ["X"] as Object[]) ChoosableServiceIntf serviceNormal = grailsApplication.mainContext.getBean('choosableServiceFactory', ["N"] as Object[]) render text: "#1 - ${service.class.name} , #2 - ${serviceNormal.class.name}" } } 

Opens #1 - dummy.ExtendedService , #2 - dummy.NormalService

The beans declaration will be:

 choosableServiceFactory(ChoosableServiceFactory) { bean -> bean.scope = 'prototype' bean.factoryMethod = 'getInstance' } normalService(NormalService) extendedService(ExtendedService) 
0
source

I am using the following code:

 public class ConfigurableProxyFactoryBean implements FactoryBean<Object>, BeanNameAware { @Autowired private ApplicationContextProvider applicationContextProvider; private Class<?> proxyType; private String beanName; private Object object; private Object fallbackObject; private Object monitor = new Object(); private ConfigurableProxy proxy; public ConfigurableProxyFactoryBean(Class<?> proxyType) { this.proxyType = proxyType; } public Object getFallbackObject() { return fallbackObject; } public void setFallbackObject(Object fallbackObject) { synchronized (monitor) { this.fallbackObject = fallbackObject; if (proxy != null) { proxy.setFallbackObject(fallbackObject); } } } @Override public void setBeanName(String name) { beanName = name; } @Override public Object getObject() throws Exception { synchronized (monitor) { if (object == null) { @SuppressWarnings("unchecked") Class<Object> type = (Class<Object>)proxyType; proxy = new ConfigurableProxy(applicationContextProvider, beanName); proxy.setFallbackObject(fallbackObject); object = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[] { type }, proxy); } return object; } } @Override public Class<?> getObjectType() { return proxyType; } @Override public boolean isSingleton() { return true; } } class ConfigurableProxy implements InvocationHandler { public ConfigurableProxy(ApplicationContextProvider appContextProvider, String beanName) { this.appContextProvider = appContextProvider; this.beanName = beanName; } private ApplicationContextProvider appContextProvider; private String beanName; private Object fallbackObject; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ApplicationContext appContext = appContextProvider.getApplicationContext(); String name = "$&&#" + beanName; Object bean = appContext.containsBean(name) ? appContext.getBean(name) : fallbackObject; return method.invoke(bean, args); } public void setFallbackObject(Object fallbackObject) { this.fallbackObject = fallbackObject; } } 

ApplicationContextProvider has an implementation that selects ApplicationContext according to the current tenant.

In the XML configuration, it is used as follows:

 <bean class="my.package.infrastructure.ConfigurableProxyFactoryBean" name="beanName"> <constructor-arg> <value type="java.lang.Class">my.package.model.ServiceInterface</value> </constructor-arg> <property name="fallbackObject"> <bean class="my.package.service.DefaultServiceImplementation"/> </property> </bean> 

And in this configuration:

 <bean class="my.package.service.ServiceImplementationA" name="$&&#beanName"/> 

To enter this service somewhere, you simply write:

 public class MyController { @Autowired private ServiceInterface service; } 

Also you must implement ApplicationContextProvider , I will not use mine. This is not very difficult to implement. For example, your implementation may simply save the context in ThreadLocal . And you create your own ServletContextListener , which for each request receives the current tenant and stores it in your implementation of ApplicationContextProvider .

0
source

A new tenant area and servicelocator can help

The tenant's area ensures that the service is created once for the tenant

Code example:

 <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="tenant" value="foo.TenantScope"/> </map> </property> </bean> <bean id="service" class="foo.Service" factory-bean="tenantServiceLocator" factory-method="createInstance" scope="tenant"/> <bean id="fooService" class="FooService"> <bean id="barService" class="BarService"> <bean id="tenantServiceLocator" class="foo.TenantServiceLocator"> <property name="services"> <map> <entry key="foo" value-ref="fooService"/> <entry key="bar" value-ref="barService"/> </map> </property> </bean> 

TenantServiceLocator must know tenantId

 public class TenantServiceLocator { private Map<String, Service> services; public String getTenantId() { return "foo"; // get it from user in session } public Map<String, Service> getServices() { return services; } public void setServices(Map<String, Service> services) { this.services = services; } public Service createInstance(){ return services.get(tenantId); } } public class FooController{ @Autowired private Service service; } 

TenantScope implementation example

 public class TenantScope implements Scope { private static Map<String, Map<String, Object>> scopeMap = new HashMap<String, Map<String, Object>>(); @Override public Object get(String name, ObjectFactory<?> objectFactory) { Map<String, Object> scope = getTenantScope(getTenantId()); Object object = scope.get(name); if(object == null){ object = objectFactory.getObject(); scope.put(name, object); } return object; } private Map<String, Object> getTenantScope(String tenantId) { if (!scopeMap.containsKey(tenantId)) { scopeMap.put(tenantId, new HashMap<String, Object>()); } return scopeMap.get(tenantId); } private String getTenantId() { return "foo"; // load you tenantId } @Override public Object remove(String name) { Map<String, Object> scope = getTenantScope(getTenantId()); return scope.remove(name); } @Override public void registerDestructionCallback(String name, Runnable callback) { } @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return null; } } 
0
source

While at runtime, spring can replace beans created in the context of spring ( HotswappableTargetSource ), it is not intended for cases like yours.

Remember that there is one spring Context for your application, all threads use the same instances (in most cases), this means that when replacing a bean implementation, you emotionally do this for all users of your application. To prevent this, you are facing Thread security issues using thread locators, as indicated in another answer.

Although you can continue this approach and come to an implementation that does its job, it will definitely be a very far-fetched way to solve this problem.

You should take a step back and look at your problem from a more useful, systemic point of view. Rid your books on templates and see how this can be resolved, whether you use spring or another structure. Service Locator, Factory bean, etc., described in some of the answers above, is a step in the right direction.

Your use case is fairly common for multi-tenant applications.

For example, as mentioned in the question, each Tenant may have a different commission amount or even a different commission calculation algorithm. A simple solution for this would be to implement the CommissionCalculationService , which accepts tenantId , and any other domain object, on the basis of which the commission should be calculated, I would assume that it would be something like Order or Sale , which makes sense in your application.

Now you need a CommissionServiceFactory or ServiceLocator that will contain tenant-specific implementations of the CommissionCalculationService . Service Locator is created when the spring context is loaded, and implementation classes are also introduced at application startup.

When you want to calculate the commission for the tenant, you basically get tenantId from the userโ€™s login, pass the tenant ID to your service locator, based on the transmitted tenantId , the service locator returns the corresponding instance of the service implementation. In your calling class, use this instance to calculate the commission for the tenant.

Another template to consider is the Strategy Pattern or even the Template Pattern .

On the bottom line, even if you want a specific logical tenant to run cleanly, don't do anything about changing the beans loaded into the context. You have classes in your context that can handle all your specific tenant logic. Rely on design patterns to use the right bean from the context based on the tenant ID.

Sorry if the answer was a bit detailed, I thought it was necessary to explain why I think that updating beans in a loaded spring Context is not a suitable solution.

0
source

Source: https://habr.com/ru/post/958819/


All Articles