diff --git a/cadio-core/build.gradle b/cadio-core/build.gradle index 7eab2415..8b50e815 100644 --- a/cadio-core/build.gradle +++ b/cadio-core/build.gradle @@ -1,7 +1,6 @@ dependencies { // Spring Boot implementation("org.springframework.boot:spring-boot-starter") - implementation("org.springframework.boot:spring-boot-starter-json") //TODO ClusterFactory 만들어지면 변경 api("com.datastax.oss:java-driver-core:${datastaxJavaDriverVersion}") @@ -11,12 +10,18 @@ dependencies { // implementation("com.datastax.oss:java-driver-query-builder:${datastaxJavaDriverVersion}") // implementation("com.datastax.oss:java-driver-mapper-runtime:${datastaxJavaDriverVersion}") - api("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1") + // Json + implementation("org.springframework.boot:spring-boot-starter-json") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1") // CommonsLang3 api("org.apache.commons:commons-lang3:3.13.0") api("com.google.guava:guava:33.0.0-jre") api("org.apache.commons:commons-collections4:4.4") + + // Cache + implementation("org.springframework.boot:spring-boot-starter-cache") + implementation("com.github.ben-manes.caffeine:caffeine:3.1.8") } bootJar { diff --git a/cadio-core/src/main/java/kr/hakdang/cadio/core/support/cache/CacheType.java b/cadio-core/src/main/java/kr/hakdang/cadio/core/support/cache/CacheType.java new file mode 100644 index 00000000..4cc952e3 --- /dev/null +++ b/cadio-core/src/main/java/kr/hakdang/cadio/core/support/cache/CacheType.java @@ -0,0 +1,43 @@ +package kr.hakdang.cadio.core.support.cache; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.Duration; + +/** + * CacheType + * + * @author seungh0 + * @since 2024-07-04 + */ +@Getter +public enum CacheType { + + // TODO: 만료시간은 5분으로 가져가돼, N분마다 api를 쏴서 클러스터에 대한 정보를 갱신하는 것도 좋을듯 + CLUSTER_LIST(CacheTypeNames.CLUSTER_LIST, Duration.ofMinutes(5)), + CLUSTER(CacheTypeNames.CLUSTER, Duration.ofMinutes(5)), + TABLE_LIST(CacheTypeNames.TABLE_LIST, Duration.ofMinutes(5)), + TABLE(CacheTypeNames.TABLE, Duration.ofMinutes(5)), + ; + + private final String key; + private final Duration duration; + + CacheType(String key, Duration duration) { + this.key = key; + this.duration = duration; + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class CacheTypeNames { + + public static final String CLUSTER_LIST = "clusterList"; + public static final String CLUSTER = "cluster"; + public static final String TABLE_LIST = "tableList"; + public static final String TABLE = "table"; + + } + +} diff --git a/cadio-core/src/main/java/kr/hakdang/cadio/core/support/cache/LocalCacheConfig.java b/cadio-core/src/main/java/kr/hakdang/cadio/core/support/cache/LocalCacheConfig.java new file mode 100644 index 00000000..d1e9092c --- /dev/null +++ b/cadio-core/src/main/java/kr/hakdang/cadio/core/support/cache/LocalCacheConfig.java @@ -0,0 +1,38 @@ +package kr.hakdang.cadio.core.support.cache; + +import com.github.benmanes.caffeine.cache.Caffeine; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.support.SimpleCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * LocalCacheConfig + * + * @author seungh0 + * @since 2024-07-04 + */ +@EnableCaching +@Configuration +public class LocalCacheConfig { + + @Bean + public CacheManager cacheManager() { + SimpleCacheManager cacheManager = new SimpleCacheManager(); + List caches = Arrays.stream(CacheType.values()) + .map(cache -> new CaffeineCache(cache.getKey(), Caffeine.newBuilder() + .expireAfterWrite(cache.getDuration()) + .build() + )) + .collect(Collectors.toList()); + cacheManager.setCaches(caches); + return cacheManager; + } + +} diff --git a/cadio-web/src/main/java/kr/hakdang/cadio/web/common/dto/request/CursorRequest.java b/cadio-web/src/main/java/kr/hakdang/cadio/web/common/dto/request/CursorRequest.java index 30a67e26..297f0f8d 100644 --- a/cadio-web/src/main/java/kr/hakdang/cadio/web/common/dto/request/CursorRequest.java +++ b/cadio-web/src/main/java/kr/hakdang/cadio/web/common/dto/request/CursorRequest.java @@ -3,6 +3,7 @@ import kr.hakdang.cadio.common.model.Direction; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; @@ -14,6 +15,7 @@ * @since 2024-07-01 */ @ToString +@EqualsAndHashCode @Getter @NoArgsConstructor(access = AccessLevel.PRIVATE) public class CursorRequest { diff --git a/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/ClusterNodeApi.java b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/ClusterNodeApi.java index f2f59847..230df692 100644 --- a/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/ClusterNodeApi.java +++ b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/ClusterNodeApi.java @@ -60,5 +60,4 @@ public ApiResponse nodeDetail( } } - } diff --git a/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/keyspace/ClusterKeyspaceApi.java b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/keyspace/ClusterKeyspaceApi.java index 64079c85..b19290cd 100644 --- a/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/keyspace/ClusterKeyspaceApi.java +++ b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/keyspace/ClusterKeyspaceApi.java @@ -1,14 +1,11 @@ package kr.hakdang.cadio.web.route.cluster.keyspace; -import com.datastax.oss.driver.api.core.CqlSession; -import kr.hakdang.cadio.core.domain.cluster.TempClusterConnector; -import kr.hakdang.cadio.core.domain.cluster.keyspace.ClusterKeyspaceCommander; -import kr.hakdang.cadio.core.domain.cluster.keyspace.ClusterKeyspaceDescribeArgs; import kr.hakdang.cadio.core.domain.cluster.keyspace.ClusterKeyspaceListResult; import kr.hakdang.cadio.web.common.dto.response.ApiResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -26,15 +23,12 @@ @RequestMapping("/api/cassandra/cluster/{clusterId}") public class ClusterKeyspaceApi { - private final TempClusterConnector tempClusterConnector; - private final ClusterKeyspaceCommander clusterKeyspaceCommander; + private final ClusterKeyspaceReader clusterKeyspaceReader; public ClusterKeyspaceApi( - TempClusterConnector tempClusterConnector, - ClusterKeyspaceCommander clusterKeyspaceCommander + ClusterKeyspaceReader clusterKeyspaceReader ) { - this.tempClusterConnector = tempClusterConnector; - this.clusterKeyspaceCommander = clusterKeyspaceCommander; + this.clusterKeyspaceReader = clusterKeyspaceReader; } @GetMapping("/keyspace") @@ -42,30 +36,27 @@ public ApiResponse> keyspaceList( @PathVariable String clusterId ) { Map result = new HashMap<>(); - try (CqlSession session = tempClusterConnector.makeSession(clusterId)) { //TODO : interface 작업할 때 facade layer 로 변경 예정 - ClusterKeyspaceListResult result1 = clusterKeyspaceCommander.keyspaceList(session); - - result.put("keyspaceList", result1.getKeyspaceList()); - } - + ClusterKeyspaceListResult response = clusterKeyspaceReader.listKeyspace(clusterId); + result.put("keyspaceList", response.getKeyspaceList()); return ApiResponse.ok(result); } + @PutMapping("/keyspace/cache-evict") + public ApiResponse evictKeyspaceListCache( + @PathVariable String clusterId + ) { + clusterKeyspaceReader.refreshKeyspaceCache(clusterId); + return ApiResponse.OK; + } + @GetMapping("/keyspace/{keyspace}") public Map keyspaceDetail( @PathVariable String clusterId, @PathVariable String keyspace ) { Map result = new HashMap<>(); - try (CqlSession session = tempClusterConnector.makeSession(clusterId)) { //TODO : interface 작업할 때 facade layer 로 변경 예정 - result.put("describe", clusterKeyspaceCommander.describe(session, ClusterKeyspaceDescribeArgs.builder() - .keyspace(keyspace) - .withChildren(false) - .pretty(true) - .build())); - - } - + String keyspaceDescribe = clusterKeyspaceReader.getKeyspace(clusterId, keyspace); + result.put("describe", keyspaceDescribe); return result; } } diff --git a/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/keyspace/ClusterKeyspaceReader.java b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/keyspace/ClusterKeyspaceReader.java new file mode 100644 index 00000000..a639da74 --- /dev/null +++ b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/keyspace/ClusterKeyspaceReader.java @@ -0,0 +1,58 @@ +package kr.hakdang.cadio.web.route.cluster.keyspace; + +import com.datastax.oss.driver.api.core.CqlSession; +import kr.hakdang.cadio.core.domain.cluster.TempClusterConnector; +import kr.hakdang.cadio.core.domain.cluster.keyspace.ClusterKeyspaceCommander; +import kr.hakdang.cadio.core.domain.cluster.keyspace.ClusterKeyspaceDescribeArgs; +import kr.hakdang.cadio.core.domain.cluster.keyspace.ClusterKeyspaceListResult; +import kr.hakdang.cadio.core.support.cache.CacheType; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +/** + * ClusterKeyspaceReader + * + * @author seungh0 + * @since 2024-07-04 + */ +@Slf4j +@Service +public class ClusterKeyspaceReader { + + private final TempClusterConnector tempClusterConnector; + private final ClusterKeyspaceCommander clusterKeyspaceCommander; + + public ClusterKeyspaceReader( + TempClusterConnector tempClusterConnector, + ClusterKeyspaceCommander clusterKeyspaceCommander + ) { + this.tempClusterConnector = tempClusterConnector; + this.clusterKeyspaceCommander = clusterKeyspaceCommander; + } + + @Cacheable(value = CacheType.CacheTypeNames.CLUSTER_LIST) + public ClusterKeyspaceListResult listKeyspace(String clusterId) { + try (CqlSession session = tempClusterConnector.makeSession(clusterId)) { + return clusterKeyspaceCommander.keyspaceList(session); + } + } + + @CacheEvict(value = CacheType.CacheTypeNames.CLUSTER_LIST) + public void refreshKeyspaceCache(String clusterId) { + log.info("ClusterList ({}) is evicted", clusterId); + } + + @Cacheable(value = CacheType.CacheTypeNames.CLUSTER_LIST) + public String getKeyspace(String clusterId, String keyspace) { + try (CqlSession session = tempClusterConnector.makeSession(clusterId)) { + return clusterKeyspaceCommander.describe(session, ClusterKeyspaceDescribeArgs.builder() + .keyspace(keyspace) + .withChildren(false) + .pretty(true) + .build()); + } + } + +} diff --git a/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/table/ClusterTableApi.java b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/table/ClusterTableApi.java index 35df9d60..e33ec63f 100644 --- a/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/table/ClusterTableApi.java +++ b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/table/ClusterTableApi.java @@ -1,18 +1,10 @@ package kr.hakdang.cadio.web.route.cluster.table; -import com.datastax.oss.driver.api.core.CqlSession; import jakarta.validation.Valid; -import kr.hakdang.cadio.core.domain.cluster.TempClusterConnector; import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTable; -import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableArgs; -import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableArgs.ClusterTableGetArgs; -import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableGetCommander; import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableGetResult; -import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableListCommander; -import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableListResult; import kr.hakdang.cadio.web.common.dto.request.CursorRequest; import kr.hakdang.cadio.web.common.dto.response.ApiResponse; -import kr.hakdang.cadio.web.common.dto.response.CursorResponse; import kr.hakdang.cadio.web.common.dto.response.ItemListWithCursorResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -30,18 +22,12 @@ @RequestMapping("/api/cassandra/cluster/{clusterId}/keyspace/{keyspace}") public class ClusterTableApi { - private final TempClusterConnector tempClusterConnector; - private final ClusterTableListCommander clusterTableListCommander; - private final ClusterTableGetCommander clusterTableGetCommander; + private final ClusterTableReader clusterTableReader; public ClusterTableApi( - TempClusterConnector tempClusterConnector, - ClusterTableListCommander clusterTableListCommander, - ClusterTableGetCommander clusterTableGetCommander + ClusterTableReader clusterTableReader ) { - this.tempClusterConnector = tempClusterConnector; - this.clusterTableListCommander = clusterTableListCommander; - this.clusterTableGetCommander = clusterTableGetCommander; + this.clusterTableReader = clusterTableReader; } @GetMapping("/table") @@ -50,14 +36,8 @@ public ApiResponse> listTables( @PathVariable String keyspace, @Valid CursorRequest cursorRequest ) { - try (CqlSession session = tempClusterConnector.makeSession(clusterId)) { - ClusterTableListResult result = clusterTableListCommander.listTables(session, ClusterTableArgs.ClusterTableListArgs.builder().build().builder() - .keyspace(keyspace) - .limit(cursorRequest.getSize()) - .nextPageState(cursorRequest.getCursor()) - .build()); - return ApiResponse.ok(ItemListWithCursorResponse.of(result.getTables(), CursorResponse.withNext(result.getNextPageState()))); - } + ItemListWithCursorResponse tables = clusterTableReader.listTables(clusterId, keyspace, cursorRequest); + return ApiResponse.ok(tables); } @GetMapping("/table/{table}") @@ -67,14 +47,8 @@ public ApiResponse getTable( @PathVariable String table, @RequestParam(required = false, defaultValue = "false") boolean withTableDescribe ) { - try (CqlSession session = tempClusterConnector.makeSession(clusterId)) { - ClusterTableGetResult result = clusterTableGetCommander.getTable(session, ClusterTableGetArgs.builder() - .keyspace(keyspace) - .table(table) - .withTableDescribe(withTableDescribe) - .build()); - return ApiResponse.ok(result); - } + ClusterTableGetResult cluster = clusterTableReader.getTable(clusterId, keyspace, table, withTableDescribe); + return ApiResponse.ok(cluster); } } diff --git a/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/table/ClusterTableReader.java b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/table/ClusterTableReader.java new file mode 100644 index 00000000..a4561ec8 --- /dev/null +++ b/cadio-web/src/main/java/kr/hakdang/cadio/web/route/cluster/table/ClusterTableReader.java @@ -0,0 +1,65 @@ +package kr.hakdang.cadio.web.route.cluster.table; + +import com.datastax.oss.driver.api.core.CqlSession; +import kr.hakdang.cadio.core.domain.cluster.TempClusterConnector; +import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTable; +import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableArgs; +import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableGetCommander; +import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableGetResult; +import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableListCommander; +import kr.hakdang.cadio.core.domain.cluster.keyspace.table.ClusterTableListResult; +import kr.hakdang.cadio.core.support.cache.CacheType; +import kr.hakdang.cadio.web.common.dto.request.CursorRequest; +import kr.hakdang.cadio.web.common.dto.response.CursorResponse; +import kr.hakdang.cadio.web.common.dto.response.ItemListWithCursorResponse; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +/** + * ClusterTableReader + * + * @author seungh0 + * @since 2024-07-04 + */ +@Service +public class ClusterTableReader { + + private final TempClusterConnector tempClusterConnector; + private final ClusterTableListCommander clusterTableListCommander; + private final ClusterTableGetCommander clusterTableGetCommander; + + public ClusterTableReader( + TempClusterConnector tempClusterConnector, + ClusterTableListCommander clusterTableListCommander, + ClusterTableGetCommander clusterTableGetCommander + ) { + this.tempClusterConnector = tempClusterConnector; + this.clusterTableListCommander = clusterTableListCommander; + this.clusterTableGetCommander = clusterTableGetCommander; + } + + @Cacheable(value = CacheType.CacheTypeNames.TABLE_LIST, condition = "#cursorRequest.cursor == null") + public ItemListWithCursorResponse listTables(String clusterId, String keyspace, CursorRequest cursorRequest) { + try (CqlSession session = tempClusterConnector.makeSession(clusterId)) { + ClusterTableListResult result = clusterTableListCommander.listTables(session, ClusterTableArgs.ClusterTableListArgs.builder().build().builder() + .keyspace(keyspace) + .limit(cursorRequest.getSize()) + .nextPageState(cursorRequest.getCursor()) + .build()); + + return ItemListWithCursorResponse.of(result.getTables(), CursorResponse.withNext(result.getNextPageState())); + } + } + + @Cacheable(value = CacheType.CacheTypeNames.TABLE) + public ClusterTableGetResult getTable(String clusterId, String keyspace, String table, boolean withTableDescribe) { + try (CqlSession session = tempClusterConnector.makeSession(clusterId)) { + return clusterTableGetCommander.getTable(session, ClusterTableArgs.ClusterTableGetArgs.builder() + .keyspace(keyspace) + .table(table) + .withTableDescribe(withTableDescribe) + .build()); + } + } + +}