0

I have one database with 3 schemas (OPS, TEST, TRAIN). All of these schemas have a completely identical table structure. Now lets say I have an endpoint /cars that accepts a query param for the schema/environment. When the user makes a GET request to this endpoint, I need the Spring Boot backend to be able to dynamically access either the OPS, TEST, or TRAIN schema based on the query param specified in the client request.

The idea is something like this where the environment is passed as a request param to the endpoint and then is somehow used in the code to set the schema/datasource that the repository will use.

@Autowired
private CarsRepository carsRepository;

@GetMapping("/cars")
public List<Car> getCars(@RequestParam String env) {
    setSchema(env);
    return carsRepository.findAll();
}

private setSchema(String env) {
    // Do something here to set the schema that the CarsRepository
    // will use when it runs the .findAll() method.
}

So, if a client made a GET request to the /cars endpoint with the env request param set to "OPS" then the response would be a list of all the cars in the OPS schema. If a client made the same request but with the env request param set to "TEST", then the response would be all the cars in the TEST schema.

An example of my datasource configuration is below. This one is for the OPS schema. The other schemas are done in the same fashion, but without the @Primary annotation above the beans.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    entityManagerFactoryRef = "opsEntityManagerFactory",
    transactionManagerRef = "opsTransactionManager",
    basePackages = { "com.example.repo" }
)
public class OpsDbConfig {

@Autowired
private Environment env;

@Primary
@Bean(name = "opsDataSource")
@ConfigurationProperties(prefix = "db-ops.datasource")
public DataSource dataSource() {
    return DataSourceBuilder
            .create()
            .url(env.getProperty("db-ops.datasource.url"))
            .driverClassName(env.getProperty("db-ops.database.driverClassName"))
            .username(env.getProperty("db-ops.database.username"))
            .password(env.getProperty("db-ops.database.password"))
            .build();
}

@Primary
@Bean(name = "opsEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean opsEntityManagerFactory(
        EntityManagerFactoryBuilder builder,
        @Qualifier("opsDataSource") DataSource dataSource
) {
    return builder
            .dataSource(dataSource)
            .packages("com.example.domain")
            .persistenceUnit("ops")
            .build();
}

@Primary
@Bean(name = "opsTransactionManager")
public PlatformTransactionManager opsTransactionManager(
        @Qualifier("opsEntityManagerFactory") EntityManagerFactory opsEntityManagerFactory
) {
    return new JpaTransactionManager(opsEntityManagerFactory);
}

}

2 Answers2

0

Personally, I don't feel its right to pass environment as Request Param and toggle the repository based on the value passed.

Instead you can deploy multiple instance of the service pointing to different data source and have a gate keeper(router) to route to the respective service.

By this way clients will be exposed to one gateway service which in turn routes to respective service based on input to gate keeper.

  • Why would it not be right? What do you think the downsides of dynamically switching in a single instance would be? – Wes Weitzel Jan 30 '19 at 15:24
0

You typically don't want TEST/ACPT instances running on the very same machines because it typically gets harder to [keep under] control the extent to which load on these environments will make the PROD environment slow down.

You also don't want the setup you envisage because it makes it nigh impossible to evolve the app and/or its database structure. (You're not going to switch db schema in PROD at the very same time you're doing this in DEV are you ? Not doing that simultaneous switch is wise, but it breaks your presupposition that "all three databases have exactly the same schema".

Erwin Smout
  • 18,113
  • 4
  • 33
  • 52
  • Ah, this makes me see my post is a little confusing. I edited my post to rename the schemas to OPS, TEST, and TRAIN. So, the way you can think about this is that really there are 2 databases (dev database and prod database) each with these 3 schemas (OPS, TEST, TRAIN). The schemas function as "sub" environments within the two databases. So, the user of the application will have the option to select OPS, TEST, or TRAIN when they start the GUI. Hope this makes sense. – Wes Weitzel Jan 30 '19 at 15:46
  • To further clarify, based on the selection the user made in the GUI, all their requests would then have that schema (OPS, TEST, TRAIN) attached as a request param (or however) and then the backend would choose the schema based on that. – Wes Weitzel Jan 30 '19 at 16:02
  • So then one of your questions becomes "are you switching the schemas for OPS and TEST at the same time" and the answer is YES. Then when it is time to put these to the prod environment, these schemas are copies over to the prod database. – Wes Weitzel Jan 30 '19 at 16:10