Performs database backup and custom directories backups.
Strategy pattern is widely used to facilitate easier extension of database adapters.
Each strategy implementation must have: @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Strategies are based on:
public enum DBType {
MYSQL(DBTypeStrategyConstants.MYSQL_STRATEGY),
POSTGRESQL(DBTypeStrategyConstants.POSTGRESQL_STRATEGY);
private final String strategyName;
DBType(String strategyName) {
this.strategyName = strategyName;
}
@Override
public String toString() {
return this.strategyName;
}
}@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DBTypeStrategyConstants {
public static final String MYSQL_STRATEGY = "mysqlStrategy";
public static final String POSTGRESQL_STRATEGY = "postgresqlStrategy";
}public interface BackupStrategy {
DBType dbType();
void backup(DBConfig databaseConfig, String destination, List<String> otherDirsToBackup);
}Creates new BackupStrategy implementation from the application context on demand
@Slf4j
public final class Helpers {
public static BackupStrategy createNewBackupStrategyInstance(ApplicationContext applicationContext, DBType dbType) {
Map<DBType, Function<ApplicationContext, BackupStrategy>> backupInstances = new HashMap<>();
Arrays.stream(DBType.values())
.forEach(dbTypeEnum -> backupInstances
.put(dbTypeEnum,
(appContext) -> appContext.getBean(dbTypeEnum.toString(), BackupStrategy.class)));
return backupInstances.get(dbType).apply(applicationContext);
}
}Wrapper object for new BackupStrategy. Calls createInstance method to create a new instance.
@RequiredArgsConstructor
public class BackupStrategyFactory {
private final DBType dbType;
private final ApplicationContext applicationContext;
public BackupStrategy createInstance(){
return Helpers.createNewBackupStrategyInstance(applicationContext,dbType);
}
}Bean to create all instances of BackupStrategyFactory derived from DBType
@Configuration
@RequiredArgsConstructor
class StrategyConfig {
private final ApplicationContext applicationContext;
@Bean
public Map<DBType, BackupStrategyFactory> backupDataByDBType() {
Map<DBType, BackupStrategyFactory> backupStrategyFactoryMap = new EnumMap<>(DBType.class);
Arrays.stream(DBType.values())
.forEach(dbType -> backupStrategyFactoryMap.put(dbType, new BackupStrategyFactory(dbType, applicationContext)));
return backupStrategyFactoryMap;
}
}Loads the relevant strategy depending on dbConfig.dbType()
@Slf4j
@Component
@RequiredArgsConstructor
public class BackupContext {
private final Map<DBType, BackupStrategyFactory> backupDataByDBType;
public void backup(DBConfig dbConfig, String destination, List<String> otherDirsToBackup) {
BackupStrategyFactory strategyFactory = backupDataByDBType.getOrDefault(dbConfig.dbType(), null);
if (Objects.isNull(strategyFactory)) {
log.error("DBType: {} does not have an existing implementation", dbConfig.dbType());
return;
}
//get a new instance of the strategy since each request can have different db configs
BackupStrategy backupStrategy = strategyFactory.createInstance();
backupStrategy.backup(dbConfig, destination, otherDirsToBackup);
}
}MysqlBackupStrategy one of the implementations of the BackupStrategy
@Slf4j
@Component(DBType.DBTypeStrategyConstants.MYSQL_STRATEGY)
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class MysqlBackupStrategy implements BackupStrategy {
@Override
public DBType dbType() {
return DBType.MYSQL;
}
@Override
public void backup(DBConfig databaseConfig, String destination, List<String> otherDirsToBackup) {
}
}DataSourceFactory Uses Functional Interfaces with custom TriFunction. Enables extendability of JDBC URL generation.
public final class DataSourceFactory {
private static final Map<DBType, TriFunction<String, Integer, String, String>> jdbcUrlMap = Map.of(
DBType.MYSQL, (host, port, dbName) -> "jdbc:mysql://" + host + ":" + port + "/" + dbName
);
private DataSourceFactory() {
}
public static DataSource createDataSource(DBType dbType, DBConfig dbConfig) {
var jdbcUrlFunc = jdbcUrlMap.getOrDefault(dbType, null);
if (Objects.isNull(jdbcUrlFunc)) {
throw new RuntimeException("jdbcUrlMap does not have " + dbType);
}
HikariConfig config = new HikariConfig();
config.setJdbcUrl(jdbcUrlFunc.apply(Helpers.parseHost(dbConfig.host()), dbConfig.port(), dbConfig.dbName()));
config.setUsername(dbConfig.username());
config.setPassword(dbConfig.password());
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
return new HikariDataSource(config);
}
}- Mysql
- PostgresQL
public enum CloudType {
BACK_BLAZE
}http POST :4934/backup \
schedulerType=EVERY_6_HOURS \
destination="/destination/dir" \
otherDirsToBackup:='["/dir/to/backup"]' \
cloudType=BACK_BLAZE \
cloudDestinationDir="cloud-dir" \
dbConfig:='{
"dbType": "POSTGRESQL",
"host": "***",
"port": ***,
"username": "***",
"password": "***",
"dbName": "***",
"tableBatchesOf": 5
}' \
blazeCloudConfig:='{
"keyId": "***",
"keyName": "***",
"applicationKey": "***",
"bucketName": "***"
}'Native Image Build with GraalVM
- To generate image for your current host
mvn package -Pnative -DskipTests - If on Mac or window, you can generate native image for ubuntu using
dockeras described below- Ubuntu 20.04
docker build -f ./docker/Dockerfile20-04 -t graalvm-native-ubuntu-20-04 .docker run -v $(pwd):/app -w /app graalvm-native-ubuntu-20-04 bash -c "mvn package -Pnative -DskipTests"
- Ubuntu 22.04
docker build -f ./docker/Dockerfile22-04 -t graalvm-native-ubuntu-22-04 .docker run -v $(pwd):/app -w /app graalvm-native-ubuntu-22-04 bash -c "mvn package -Pnative -DskipTests"
- Ubuntu 20.04
- Copy the native image to production host e.g
cp target/AppBackup ~/App/AppBackup - Setup systemd
sudo touch /etc/systemd/system/AppBackup.service
sudo nano /etc/systemd/system/AppBackup.servicePaste this script in above AppBackup.service
[Unit]
Description=App Backup
After=network.target
[Service]
ExecStart=/home/johndoe/App/AppBackup
WorkingDirectory=/home/johndoe/App
EnvironmentFile=/home/johndoe/App/.env
Restart=always
User=johndoe
[Install]
WantedBy=multi-user.targetOnly include EnvironmentFile=/home/johndoe/App/.env if applicable otherwise ignore it.
Then allow the AppBackup app in systemd
sudo systemctl daemon-reload
sudo systemctl start AppBackup
sudo systemctl enable AppBackupIf you experience missing classes or errors in the final executable, run below to get the reachability metadata including the missing classes/resources
mvn clean install -DskipTestsjava -agentlib:native-image-agent=config-output-dir=graalvm-config -jar target/AppBackup-1.0.0.jar- Then Run
http GET :4934/initto generate reachability metadata