The Splittable Monolith with Spring Boot and Gradle multi-module project – Part 2

Configuring isolated persistent units within a single Spring-Boot Application

Since we are running independent modules in the same runtime, we have to ensure isolated data sources within our Spring-Boot application. Going this way we can easily move the persistent units to separate services in the future.

Let’s start by adding dependency org.springframework.boot:spring-boot-starter-data-jpa to build.gradle of the business module. In order to avoid boilerplate code, let’s also add the lombok library dependencies. So, now it should look like this:

plugins {
    id 'java'
}

group 'com.dicelogics.demo'
version '0.0.1-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    compileOnly 'org.projectlombok:lombok:1.18.16'
    annotationProcessor 'org.projectlombok:lombok:1.18.16'
    testCompileOnly 'org.projectlombok:lombok:1.18.16'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.16'

    testCompile group: 'junit', name: 'junit', version: '4.12'
}

Now let’s define the data models and corresponding jpa-model for each business module.

User Registry Module

For that module we defined only one table user with user login data.

User Registry data model

Let’s create package com.dicelogics.service.user.registry.model in the User Registry module add corresponding Java class.

@Entity
@Table(name = "user")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String login;

    @Column
    private String password;

    @Column
    private String username;
}

User Account Module

User Account Module is responsible for the management of a user’s virtual account. User virtual balance and transactions will be stored here.

User Account data model

And corresponding package com.dicelogics.service.user.account.model in the User Account module with jpa-classes.

@Entity
@Table(name = "account")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_id")
    private Long userId;

    @Column
    private Long balance;
}
@Entity
@Table(name = "transaction")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Transaction {

    enum TransactionType {
        TOP_UP, WITHDRAW
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private Long accountId;

    @Column
    private Long amount;

    @Enumerated(EnumType.STRING)
    @Column
    private TransactionType transactionType;
}

Game Service Module

Game service encapsulates the logic of the game. It stores all game instances in the corresponding as well as turns in the table game_transactions. Game Service interacts with the User Account Module for updating of user virtual balance.

Game Service data model

Package com.dicelogics.service.game.model inside Game Service Module and corresponding jpa-classes:

@Entity
@Table(name = "game_instance")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class GameInstance {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "start_ts")
    private Long startTs;

    @Column(name = "end_ts")
    private Long endTs;

    @Column(name = "user_id")
    private Long userId;
}
@Entity
@Table(name = "game_transaction")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class GameTransaction {

    enum Outcome {
        WIN, LOSE
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "game_instance_id")
    private Long gameInstanceId;

    @Column
    private Long bet;

    @Column
    @Enumerated(EnumType.STRING)
    private Outcome outcome;
}

As we mentioned earlier, all business modules supposed to be isolated from each other. So, we’ll need to have a separate DB connection for each module.

For demonstration purposes we will be using H2 in memory database. To start using it, let’s specify additional dependency runtime 'com.h2database:h2:1.4.199' in build.gradle of launcher.

Well, JPA-models are defined and now we can proceed to the configuration of Persistence Units.

Configuring DataSource for Game Service

Let’s start from Game Service module and add application.properties file, containing:

service.game.datasource.url=jdbc:h2:mem:game_service_db
service.game.datasource.driverClassName=org.h2.Driver
service.game.datasource.username=sa
service.game.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Here we defined datasource url, which will be specific for each module as well as driver class name with credentials.

In order to distinguish the same properties between different modules, we’ve added prefix service.game.datasource for each property.

Now we are ready to define the most important part of that tutorial, a configuration of Data Source. Let’s add class below to the package com.dicelogics.service.game.config.

@Configuration
@ConfigurationProperties
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "gameServiceEntityManagerFactory",
        transactionManagerRef = "gameServiceTransactionManager",
        basePackages = {"com.dicelogics.service.game"}
)
public class GameServiceDataSourceConfig {

    @Primary
    @Bean(name = "gameServiceDataSource")
    @ConfigurationProperties(prefix = "service.game.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "gameServiceEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("gameServiceDataSource") DataSource dataSource) {

        return builder
                .dataSource(dataSource)
                .packages("com.dicelogics.service.game.model")
                .persistenceUnit("gameService")
                .build();
    }

    @Primary
    @Bean(name = "gameServiceTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("gameServiceEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

In line 14 we create a new DataSource of the module Game Service . In line 13 we are saying that jdbc url, driver and credentials should be taken from properties with prefix service.game.datasource, which we defined earlier in application.properties file. We also define a separate, for game service, EntityManagerFactoryBean in line 20. Starting from line 33, we are defining a separate transaction manager for this module.

Note, since we are going to define the corresponding configurations for other business modules, so, as a result, we will have 3 of DataSource, EntityManagerFactoryBean, and TransactionManager, we should define primary beans, as required by the Spring. That’s why here we are using @Primary annotation.

Let’s configure the same for other modules.

Configuring DataSource for User Account

Add application.properties file, containing:

service.account.datasource.url=jdbc:h2:mem:user_account_db
service.account.datasource.driverClassName=org.h2.Driver
service.account.datasource.username=sa
service.account.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Add data source configuration class for User Account module to the package com.dicelogics.service.user.account.config.

@Configuration
@ConfigurationProperties
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "userAccountEntityManagerFactory",
        transactionManagerRef = "userAccountTransactionManager",
        basePackages = {"com.dicelogics.service.user.account"}
)
public class UserAccountDataSourceConfig {

    @Primary
    @Bean(name = "userAccountDataSource")
    @ConfigurationProperties(prefix = "service.account.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "userAccountEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("userAccountDataSource") DataSource dataSource) {

        return builder
                .dataSource(dataSource)
                .packages("com.dicelogics.service.user.account.model")
                .persistenceUnit("userAccount")
                .build();
    }

    @Primary
    @Bean(name = "userAccountTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("userAccountEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

Configuring DataSource for User Registry

Add application.properties file, containing:

service.registry.datasource.url=jdbc:h2:mem:user_registry_db
service.registry.datasource.driverClassName=org.h2.Driver
service.registry.datasource.username=sa
service.registry.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Add data source configuration class for User Registry module to the package com.dicelogics.service.user.account.config.

@Configuration
@ConfigurationProperties
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "userRegistryEntityManagerFactory",
        transactionManagerRef = "userRegistryTransactionManager",
        basePackages = {"com.dicelogics.service.user.registry"}
)
public class UserRegistryDataSourceConfig {

    @Primary
    @Bean(name = "userRegistryDataSource")
    @ConfigurationProperties(prefix = "service.account.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "userRegistryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("userRegistryDataSource") DataSource dataSource) {

        return builder
                .dataSource(dataSource)
                .packages("com.dicelogics.service.user.registry.model")
                .persistenceUnit("userRegistry")
                .build();
    }

    @Primary
    @Bean(name = "userRegistryTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("userRegistryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

Well done! Now we can run the entire application using:

./gradlew bootRun

Three completely isolated data sources will be initialized. In memory H2 database will be automatically bootstrapped.

505
Leave a Reply

avatar
  Subscribe  
newest oldest most voted
Notify of
Lisashoox
Guest
Kiashoox
Guest
Amyshoox
Guest
Lisashoox
Guest
Amyshoox
Guest
Kiashoox
Guest
Amyshoox
Guest
Markshoox
Guest
Lisashoox
Guest
Amyshoox
Guest
Maryshoox
Guest
Judyshoox
Guest
Maryshoox
Guest
Carlshoox
Guest
Kiashoox
Guest
Jasonshoox
Guest
Denshoox
Guest
Carlshoox
Guest
Eyeshoox
Guest
Jasonshoox
Guest
Alanshoox
Guest
Markshoox
Guest
Denshoox
Guest
Paulshoox
Guest
Markshoox
Guest
Judyshoox
Guest
Paulshoox
Guest
Maryshoox
Guest
Carlshoox
Guest
Lisashoox
Guest
Eyeshoox
Guest
Lisashoox
Guest
Denshoox
Guest
Kiashoox
Guest
Amyshoox
Guest
Carlshoox
Guest
Amyshoox
Guest
Judyshoox
Guest
Alanshoox
Guest
Lisashoox
Guest
Alanshoox
Guest
Markshoox
Guest
Paulshoox
Guest
Maryshoox
Guest
Markshoox
Guest
Jasonshoox
Guest
Carlshoox
Guest
Eyeshoox
Guest
Markshoox
Guest
Kiashoox
Guest