diff --git a/configuration/pom.xml b/configuration/pom.xml
new file mode 100644
index 0000000..07e8e2b
--- /dev/null
+++ b/configuration/pom.xml
@@ -0,0 +1,26 @@
+
+
+ 4.0.0
+
+ sydney.cheng
+ ec-microservice-commons
+ 1.0.0-SNAPSHOT
+
+
+ ec-microservice-commons-configuration
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
\ No newline at end of file
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/AppConfiguration.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/AppConfiguration.java
new file mode 100644
index 0000000..4b5c390
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/AppConfiguration.java
@@ -0,0 +1,26 @@
+package sydney.cheng.microservice.commons.configuration.properties;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+import org.springframework.context.annotation.Configuration;
+import sydney.cheng.microservice.commons.configuration.properties.auth.SecurityProperties;
+import sydney.cheng.microservice.commons.configuration.properties.database.DatabaseProperties;
+
+@Data
+@ConfigurationPropertiesScan(basePackages = {"sydney.cheng.**.properties"})
+@ConfigurationProperties("app")
+@Configuration
+public class AppConfiguration {
+ private ApplicationConfiguration application;
+ private SecurityProperties security;
+ private DatabaseProperties database;
+
+ @Data
+ @NoArgsConstructor(access = AccessLevel.PRIVATE)
+ public static class ApplicationConfiguration {
+ private String deploymentTarget;
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/CorsProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/CorsProperties.java
new file mode 100644
index 0000000..f1baba5
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/CorsProperties.java
@@ -0,0 +1,32 @@
+package sydney.cheng.microservice.commons.configuration.properties.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ConfigurationProperties(prefix = "app.security.cors")
+public class CorsProperties {
+ private String allowedUrls;
+ private String allowedOrigins;
+ private List allowedHeaders;
+ private List allowedMethods;
+ private long allowedMaxAge = 3600;
+ private boolean allowCredentials = true;
+
+ public List getAllowedUrlList() {
+ return Arrays.asList(allowedUrls.split(","));
+ }
+
+ public List getAllowedOriginList() {
+ return Arrays.asList(allowedOrigins.split(","));
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/OAuth2Properties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/OAuth2Properties.java
new file mode 100644
index 0000000..2a5b0b1
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/OAuth2Properties.java
@@ -0,0 +1,24 @@
+package sydney.cheng.microservice.commons.configuration.properties.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ConfigurationProperties(prefix = "app.security.oauth2")
+public class OAuth2Properties {
+ private RemoteTokenCheckProperties remoteTokenCheck;
+
+ @Getter
+ @Setter
+ public static class RemoteTokenCheckProperties {
+ String checkTokenUrl;
+ String clientId;
+ String clientSecret;
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/SecurityProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/SecurityProperties.java
new file mode 100644
index 0000000..00cda8f
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/SecurityProperties.java
@@ -0,0 +1,12 @@
+package sydney.cheng.microservice.commons.configuration.properties.auth;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties("app.security")
+public class SecurityProperties {
+ private UrlProperties url;
+ private CorsProperties cors;
+ private OAuth2Properties oauth2;
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/UrlProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/UrlProperties.java
new file mode 100644
index 0000000..436fa5c
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/UrlProperties.java
@@ -0,0 +1,13 @@
+package sydney.cheng.microservice.commons.configuration.properties.auth;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "app.security.url")
+public class UrlProperties {
+ private String frontend;
+ private String backend;
+ private String gateway;
+ private String frontendDefaultLogin;
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DataSourceProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DataSourceProperties.java
new file mode 100644
index 0000000..4ec281c
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DataSourceProperties.java
@@ -0,0 +1,18 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.context.annotation.Profile;
+
+import java.io.Serializable;
+
+@Profile("database")
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class DataSourceProperties extends org.springframework.boot.autoconfigure.jdbc.DataSourceProperties implements Serializable {
+ private HikariDataSourceProperties hikari;
+
+ public DataSourceProperties() {
+ super();
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseNodeProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseNodeProperties.java
new file mode 100644
index 0000000..58725d6
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseNodeProperties.java
@@ -0,0 +1,12 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import lombok.Data;
+import org.springframework.context.annotation.Profile;
+
+import java.io.Serializable;
+
+@Profile("database")
+@Data
+public class DatabaseNodeProperties implements Serializable {
+ DataSourceProperties datasource;
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseProperties.java
new file mode 100644
index 0000000..37a988c
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseProperties.java
@@ -0,0 +1,25 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import lombok.Data;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.PropertySource;
+
+import java.io.Serializable;
+
+@Profile("database")
+@Data
+@PropertySource("classpath:application.yml")
+@ConfigurationProperties(prefix = "app.database")
+public class DatabaseProperties {
+ EntityManagerProperties entityManager;
+ JpaProperties jpa;
+ DatabaseNodeProperties master;
+ DatabaseNodeProperties replica;
+
+ @Data
+ public static class EntityManagerProperties implements Serializable {
+ String packages;
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/HikariDataSourceProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/HikariDataSourceProperties.java
new file mode 100644
index 0000000..37a5e62
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/HikariDataSourceProperties.java
@@ -0,0 +1,28 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import com.zaxxer.hikari.HikariConfig;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.context.annotation.Profile;
+
+import java.io.Serializable;
+import java.util.Properties;
+
+@Profile(value = {"database & hikari"})
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class HikariDataSourceProperties extends HikariConfig implements Serializable {
+ private String urlPrefix;
+
+ public HikariDataSourceProperties() {
+ super();
+ }
+
+ public HikariDataSourceProperties(Properties properties) {
+ super(properties);
+ }
+
+ public HikariDataSourceProperties(String propertyFileName) {
+ super(propertyFileName);
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/PrimaryHikariDataSourceProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/PrimaryHikariDataSourceProperties.java
new file mode 100644
index 0000000..0b922f5
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/PrimaryHikariDataSourceProperties.java
@@ -0,0 +1,22 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Profile;
+
+import java.util.Properties;
+
+@Profile(value = {"database & hikari"})
+@ConfigurationProperties("app.database.primary.datasource.hikari")
+public class PrimaryHikariDataSourceProperties extends HikariDataSourceProperties {
+ public PrimaryHikariDataSourceProperties() {
+ super();
+ }
+
+ public PrimaryHikariDataSourceProperties(Properties properties) {
+ super(properties);
+ }
+
+ public PrimaryHikariDataSourceProperties(String propertyFileName) {
+ super(propertyFileName);
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/ReplicaHikariDataSourceProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/ReplicaHikariDataSourceProperties.java
new file mode 100644
index 0000000..47435db
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/ReplicaHikariDataSourceProperties.java
@@ -0,0 +1,22 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Profile;
+
+import java.util.Properties;
+
+@Profile(value = {"database & hikari"})
+@ConfigurationProperties("app.database.replica.datasource.hikari")
+public class ReplicaHikariDataSourceProperties extends HikariDataSourceProperties {
+ public ReplicaHikariDataSourceProperties() {
+ super();
+ }
+
+ public ReplicaHikariDataSourceProperties(String propertyFileName) {
+ super(propertyFileName);
+ }
+
+ public ReplicaHikariDataSourceProperties(Properties properties) {
+ super(properties);
+ }
+}
diff --git a/database/pom.xml b/database/pom.xml
new file mode 100644
index 0000000..7c10480
--- /dev/null
+++ b/database/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+
+ sydney.cheng
+ ec-microservice-commons
+ 1.0.0-SNAPSHOT
+
+
+ ec-microservice-commons-database
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+ sydney.cheng
+ ec-microservice-commons-configuration
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ ${caffeine.version}
+
+
+
\ No newline at end of file
diff --git a/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/DatabaseTransactional.java b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/DatabaseTransactional.java
new file mode 100644
index 0000000..9d3bdcc
--- /dev/null
+++ b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/DatabaseTransactional.java
@@ -0,0 +1,22 @@
+package sydney.cheng.microservice.commons.database.annotation;
+
+import org.springframework.context.annotation.Profile;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Profile("database")
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Transactional("ecTransactionManager")
+public @interface DatabaseTransactional {
+ boolean readOnly() default false;
+
+ Propagation propagation() default Propagation.REQUIRED;
+
+ Class extends Throwable>[] noRollbackFor() default {};
+}
diff --git a/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnection.java b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnection.java
new file mode 100644
index 0000000..0fc5752
--- /dev/null
+++ b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnection.java
@@ -0,0 +1,12 @@
+package sydney.cheng.microservice.commons.database.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ReadOnlyConnection {
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnectionInterceptor.java b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnectionInterceptor.java
new file mode 100644
index 0000000..fb2ed59
--- /dev/null
+++ b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnectionInterceptor.java
@@ -0,0 +1,52 @@
+package sydney.cheng.microservice.commons.database.annotation;
+
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+import sydney.cheng.microservice.commons.database.constant.DbType;
+import sydney.cheng.microservice.commons.database.datasource.DbContextHolder;
+
+@Aspect
+@ToString
+@NoArgsConstructor
+@Component
+public class ReadOnlyConnectionInterceptor implements Ordered {
+ private int order;
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+
+ @Value("20")
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ /*
+ * handle interceptor for any public method execution
+ */
+ @Pointcut(value = "execution(public * *(..))")
+ public void anyPublicMethod() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Around("@annotation(readOnlyConnection)")
+ public Object proceed(ProceedingJoinPoint pjp, ReadOnlyConnection readOnlyConnection) throws Throwable {
+ try {
+ DbContextHolder.setDbType(DbType.REPLICA);
+ Object result = pjp.proceed();
+ DbContextHolder.clearDbType();
+ return result;
+ } finally {
+ // restore state
+ DbContextHolder.clearDbType();
+ }
+ }
+}
diff --git a/database/src/main/java/sydney/cheng/microservice/commons/database/config/CacheManagerConfiguration.java b/database/src/main/java/sydney/cheng/microservice/commons/database/config/CacheManagerConfiguration.java
new file mode 100644
index 0000000..e601eb2
--- /dev/null
+++ b/database/src/main/java/sydney/cheng/microservice/commons/database/config/CacheManagerConfiguration.java
@@ -0,0 +1,26 @@
+package sydney.cheng.microservice.commons.database.config;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+@EnableCaching
+@Configuration
+public class CacheManagerConfiguration {
+ @Bean
+ public Caffeine