diff --git a/.gitignore b/.gitignore index c2065bc..ce4a75c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +### DATA ### +data \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3a658ec..391208d 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ repositories { dependencies { //data implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-validation' runtimeOnly 'com.h2database:h2' compileOnly 'org.projectlombok:lombok' @@ -37,6 +38,7 @@ dependencies { // test testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.testcontainers:testcontainers:1.17.2' // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6bb8b6b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3" + +services: + redis: + image: redis:6 + volumes: + - ./data/redis:/data + command: redis-server --port 6379 + ports: + - "6379:6379" + networks: + - cakk + +networks: + cakk: + labels: + - cakk \ No newline at end of file diff --git a/src/main/java/prography/cakeke/server/config/RedisConfig.java b/src/main/java/prography/cakeke/server/config/RedisConfig.java new file mode 100644 index 0000000..294a114 --- /dev/null +++ b/src/main/java/prography/cakeke/server/config/RedisConfig.java @@ -0,0 +1,36 @@ +package prography.cakeke.server.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private Long port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); + configuration.setHostName(host); + configuration.setPort(port.intValue()); + return new LettuceConnectionFactory(configuration); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + return redisTemplate; + } +} \ No newline at end of file diff --git a/src/main/java/prography/cakeke/server/image/adaper/out/AwsS3Adapter.java b/src/main/java/prography/cakeke/server/image/adaper/out/AwsS3Adapter.java index 49af7f5..c8dbd9b 100644 --- a/src/main/java/prography/cakeke/server/image/adaper/out/AwsS3Adapter.java +++ b/src/main/java/prography/cakeke/server/image/adaper/out/AwsS3Adapter.java @@ -16,8 +16,8 @@ import com.amazonaws.services.s3.model.PutObjectRequest; import lombok.RequiredArgsConstructor; +import prography.cakeke.server.image.application.port.out.UploadS3Port; import prography.cakeke.server.image.exceptions.InvalidFileNameException; -import prography.cakeke.server.store.application.port.out.UploadS3Port; @Component @RequiredArgsConstructor diff --git a/src/main/java/prography/cakeke/server/image/application/port/out/.gitkeep b/src/main/java/prography/cakeke/server/image/application/port/out/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/prography/cakeke/server/store/application/port/out/UploadS3Port.java b/src/main/java/prography/cakeke/server/image/application/port/out/UploadS3Port.java similarity index 68% rename from src/main/java/prography/cakeke/server/store/application/port/out/UploadS3Port.java rename to src/main/java/prography/cakeke/server/image/application/port/out/UploadS3Port.java index b65a84c..8c0efb0 100644 --- a/src/main/java/prography/cakeke/server/store/application/port/out/UploadS3Port.java +++ b/src/main/java/prography/cakeke/server/image/application/port/out/UploadS3Port.java @@ -1,4 +1,4 @@ -package prography.cakeke.server.store.application.port.out; +package prography.cakeke.server.image.application.port.out; import org.springframework.web.multipart.MultipartFile; diff --git a/src/main/java/prography/cakeke/server/image/application/service/ImageService.java b/src/main/java/prography/cakeke/server/image/application/service/ImageService.java index 4de49b7..909ffe4 100644 --- a/src/main/java/prography/cakeke/server/image/application/service/ImageService.java +++ b/src/main/java/prography/cakeke/server/image/application/service/ImageService.java @@ -10,8 +10,8 @@ import lombok.RequiredArgsConstructor; import prography.cakeke.server.image.application.port.in.ImageUseCase; +import prography.cakeke.server.image.application.port.out.UploadS3Port; import prography.cakeke.server.image.exceptions.NotSupportedFileFormatException; -import prography.cakeke.server.store.application.port.out.UploadS3Port; @Service @RequiredArgsConstructor diff --git a/src/main/java/prography/cakeke/server/store/adapter/out/external/RedisAdapter.java b/src/main/java/prography/cakeke/server/store/adapter/out/external/RedisAdapter.java new file mode 100644 index 0000000..784dcce --- /dev/null +++ b/src/main/java/prography/cakeke/server/store/adapter/out/external/RedisAdapter.java @@ -0,0 +1,35 @@ +package prography.cakeke.server.store.adapter.out.external; + +import java.time.Duration; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import prography.cakeke.server.store.application.port.out.LoadRedisPort; +import prography.cakeke.server.store.application.port.out.SaveRedisPort; + +@Repository +@RequiredArgsConstructor +public class RedisAdapter implements LoadRedisPort, SaveRedisPort { + private final RedisTemplate redisTemplate; + private final Integer TTL = 86400; + + @Override + public String save(String key, String value) { + getOperations().set(key, value, Duration.ofMillis(TTL)); + return value; + } + + @Override + public String getByKey(String key) { + return getOperations().get(key); + } + + private ValueOperations getOperations() { + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class)); + return redisTemplate.opsForValue(); + } +} diff --git a/src/main/java/prography/cakeke/server/store/application/port/out/LoadRedisPort.java b/src/main/java/prography/cakeke/server/store/application/port/out/LoadRedisPort.java new file mode 100644 index 0000000..b68d113 --- /dev/null +++ b/src/main/java/prography/cakeke/server/store/application/port/out/LoadRedisPort.java @@ -0,0 +1,5 @@ +package prography.cakeke.server.store.application.port.out; + +public interface LoadRedisPort { + String getByKey(String key); +} diff --git a/src/main/java/prography/cakeke/server/store/application/port/out/SaveRedisPort.java b/src/main/java/prography/cakeke/server/store/application/port/out/SaveRedisPort.java new file mode 100644 index 0000000..be6f3e8 --- /dev/null +++ b/src/main/java/prography/cakeke/server/store/application/port/out/SaveRedisPort.java @@ -0,0 +1,5 @@ +package prography.cakeke.server.store.application.port.out; + +public interface SaveRedisPort { + String save(String key, String value); +} diff --git a/src/main/java/prography/cakeke/server/store/application/service/StoreService.java b/src/main/java/prography/cakeke/server/store/application/service/StoreService.java index 80afc4b..7bef49c 100644 --- a/src/main/java/prography/cakeke/server/store/application/service/StoreService.java +++ b/src/main/java/prography/cakeke/server/store/application/service/StoreService.java @@ -12,7 +12,9 @@ import prography.cakeke.server.store.adapter.in.web.response.StoreNaverLocalSearchApiResponse; import prography.cakeke.server.store.application.port.in.StoreUseCase; import prography.cakeke.server.store.application.port.out.LoadNaverSearchApiPort; +import prography.cakeke.server.store.application.port.out.LoadRedisPort; import prography.cakeke.server.store.application.port.out.LoadStorePort; +import prography.cakeke.server.store.application.port.out.SaveRedisPort; import prography.cakeke.server.store.domain.District; import prography.cakeke.server.store.domain.Store; import prography.cakeke.server.store.domain.StoreTag; @@ -32,6 +34,8 @@ public class StoreService implements StoreUseCase { private final LoadNaverSearchApiPort loadNaverSearchApiPort; private final LoadStorePort loadStorePort; + private final SaveRedisPort saveRedisPort; + private final LoadRedisPort loadRedisPort; /** * 각 구별 가게의 개수를 반환합니다. @@ -113,8 +117,27 @@ public Store getStore(Long storeId) { */ @Override public StoreNaverLocalSearchApiResponse getNaverLocalApiByStore(Store store) { - final String storeName = store.getName(); - return loadNaverSearchApiPort.getNaverLocalSearchResponse(storeName); + /** + * 1. redis에서 검색해보고 없으면 naver에서 link 정보 가져와 redis에 저장하고 반환. + * 2. redis에 있으면 바로 반환. + */ + String redisResponse = loadRedisPort.getByKey(String.valueOf(store.getId())); + + // redis에 없으면 + if (redisResponse == null) { + StoreNaverLocalSearchApiResponse naverResponse = + loadNaverSearchApiPort.getNaverLocalSearchResponse(store.getName()); + saveRedisPort.save(String.valueOf(store.getId()), naverResponse.getLink()); + return naverResponse; + } + + // redis에 있으면 + return StoreNaverLocalSearchApiResponse.builder() + .link(redisResponse) + .address("") + .phoneNumber("") + .description("") + .build(); } /** diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3dbdb58..ab8b090 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,6 +13,10 @@ spring: database: postgresql hibernate: ddl-auto: update + data: + redis: + host: ENC(k9JRvKt85GZzSZSHbXBXrA==) + port: 6379 management: endpoints: diff --git a/src/test/java/prography/cakeke/server/common/BaseMock.java b/src/test/java/prography/cakeke/server/common/BaseMock.java index e5f5efd..3cae4b4 100644 --- a/src/test/java/prography/cakeke/server/common/BaseMock.java +++ b/src/test/java/prography/cakeke/server/common/BaseMock.java @@ -25,7 +25,7 @@ public class BaseMock { protected final StoreType testStoreType = StoreType.CHARACTER; protected final String testNaverStoreName = "끌레르 봉봉"; - protected final String testNaverStoreAddress = "서울특별시 강남구 논현로114길 8 1층 103호 끌레르봉봉"; + protected final String testNaverStoreLink = "http://pf.kakao.com/_busxnC"; protected Store buildStore(String storeName) { return Store.builder() diff --git a/src/test/java/prography/cakeke/server/config/RedisTestContainers.java b/src/test/java/prography/cakeke/server/config/RedisTestContainers.java new file mode 100644 index 0000000..5292888 --- /dev/null +++ b/src/test/java/prography/cakeke/server/config/RedisTestContainers.java @@ -0,0 +1,26 @@ +package prography.cakeke.server.config; + +import org.junit.jupiter.api.DisplayName; +import org.springframework.context.annotation.Configuration; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +@DisplayName("Redis Test Containers") +@Configuration +public class RedisTestContainers { + + private static final String REDIS_DOCKER_IMAGE = "redis:5.0.3-alpine"; + + static { // (1) + GenericContainer REDIS_CONTAINER = + new GenericContainer<>(DockerImageName.parse(REDIS_DOCKER_IMAGE)) + .withExposedPorts(6379) + .withReuse(true); + + REDIS_CONTAINER.start(); // (2) + + // (3) + System.setProperty("spring.data.redis.host", REDIS_CONTAINER.getHost()); + System.setProperty("spring.data.redis.port", REDIS_CONTAINER.getMappedPort(6379).toString()); + } +} diff --git a/src/test/java/prography/cakeke/server/store/application/service/StoreServiceTest.java b/src/test/java/prography/cakeke/server/store/application/service/StoreServiceTest.java index 0666361..c840e50 100644 --- a/src/test/java/prography/cakeke/server/store/application/service/StoreServiceTest.java +++ b/src/test/java/prography/cakeke/server/store/application/service/StoreServiceTest.java @@ -144,7 +144,7 @@ public void getNaverLocalApiTestSuccess() { StoreNaverLocalSearchApiResponse testStoreNaverLocalSearchApiResponse = storeService.getNaverLocalApiByStore(testStore); - assertThat(testStoreNaverLocalSearchApiResponse.getAddress()).isEqualTo(testNaverStoreAddress); + assertThat(testStoreNaverLocalSearchApiResponse.getLink()).isEqualTo(testNaverStoreLink); } @Test diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index d2d0408..907b310 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -16,6 +16,10 @@ spring: database: postgresql hibernate: ddl-auto: create-drop + data: + redis: + host: ENC(pCIBlNunKRquFLxqCT3VgUicVCF6Z1si) + port: 6379 management: endpoints: diff --git a/src/test/resources/docker-compose.yml b/src/test/resources/docker-compose.yml new file mode 100644 index 0000000..a9ca786 --- /dev/null +++ b/src/test/resources/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3" + +services: + redis: + image: redis:7 + command: redis-server --port 6379 + ports: + - "6380:6379"