arangodb/spring-boot-starter

More databases at the same time?

Closed this issue · 4 comments

I wonder whether the spring-boot-starter is designed to allow access to more than one arangodb database.

What I tried was to create two implementations of ArangoConfiguration interfaces, annotated with @Configuration and
@EnableArangoRepositories. Each scanning packages with its own ArangoRepository.

@Configuration
@EnableArangoRepositories(basePackages = { "my.package.repository.car" })
public class CarArangoConfig implements ArangoConfiguration {

    @Bean(name = "carArangoTemplate")
    @Override
    public ArangoOperations arangoTemplate() throws Exception {
        return ArangoConfiguration.super.arangoTemplate();
    }

    @Override public ArangoDB.Builder arango() { ... }
    @Override public String database() { return "cars"; }
}

@Configuration
@EnableArangoRepositories(basePackages = { "my.package.repository.train" })
public class TrainArangoConfig implements ArangoConfiguration {

    @Bean(name = "trainArangoTemplate")
    @Override
    public ArangoOperations arangoTemplate() throws Exception {
        return ArangoConfiguration.super.arangoTemplate();
    }

    @Override public ArangoDB.Builder arango() { ... }
    @Override public String database() { return "trains"; }
}

First problem was with @Bean on ArangoConfiguration.arangoTemplate(). It made it impossible to instantiate the ArangoConfiguration more than once, giving this nasty exception:

  The bean 'arangoTemplate', defined in class path resource ..., could not be registered. A bean with that name has already been defined in class path resource ...

I was forced to clone the ArangoConfiguration class, and remove the @Bean annotation.. but it worked.

Second problem was with @Autowired on ArangoRepositoryFactoryBean.setArangoOperations(). As there is no option for providing qualifier, it throws this exception:

   Parameter 0 of method setArangoOperations in com.arangodb.springframework.repository.ArangoRepositoryFactoryBean required a single bean, but 2 were found:
   carArangoTemplate
   trainArangoTemplate

Sure I could use @primary to force one in, but it would render both factories using the same arangoTemplate, which would only make one of the databases accessible.

So my question is relatively simple - is there any recommended way to access multiple databases from the same spring boot app?

Thanks in advance for any input :)

The library is designed to deal with a single instance of each bean registered in ArangoConfiguration, so even if you fix the beans registrations I suspect you will get anyway additional issues.
As a workaround I suggest you using a SPEL expression in the database name, so that it will be evaluated dynamically at each invocation. Eg.:

@Configuration
@EnableArangoRepositories(basePackages = { "my.package.repository" })
public class ArangoConfig implements ArangoConfiguration {

    // ...

    @Override public String database() { 
        return "#{databaseNameProvider.getName()}";
    }
}


@Component
public class DatabaseNameProvider {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

and use it this way:

    // ...
    
    @Autowired
    ArangoOperations operations;

    @Autowired
    DatabaseNameProvider databaseNameProvider;
    
    // ...
    
        databaseNameProvider.setName("cars");
        // perform operations on cars db

        databaseNameProvider.setName("trains");
        // perform operations on trains db

Well, it works the way it should :-) Even under parallel load, tried with 20 concurent requests at the time.

It scares me.. as DatabaseNameProvider is not thread safe in any way, so there exists theoretical possibility of database name being changed by other thread.

    // ...
    
    @Autowired
    private CarRepository carRepository;

    @Autowired
    DatabaseNameProvider databaseNameProvider;
    
    // ...
    
        // switch to the right DB
        databaseNameProvider.setName("cars");

        // ???
        // another request changes DB to "trains" in parallel thread
        
        // fire query
        carRepository.searchByName("Audi A4")

Is this somehow covered by how ArangoOperations works under the hood? Do you think any additional steps towards thread security should be made, or it's not necessary?

My suggestion was intended just as a workaround to address your needs, not as a best practise to use the library.
I think you can solve concurrent modifications using request scope for DatabaseNameProvider or using a thread local variable to store the db name.
As reference for this approach you can look at https://github.com/arangodb/spring-data/blob/df545096e1d478557858327c3420c08da6c65b06/src/test/java/com/arangodb/springframework/core/mapping/MultiTenancyDBLevelMappingTest.java

Tried both @Scope(scopeName = "request", proxyMode = ScopedProxyMode.TARGET_CLASS ) and private ThreadLocal<String> String name in DatabaseNameProvider component, both solutions worked as expected.

Many thanks Michele!