From 94d20ff19f3b762f59e9369d33cf72f074375506 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Fri, 13 Feb 2026 12:56:51 +0900 Subject: [PATCH 01/17] =?UTF-8?q?chore(config)=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=9C=20Config=20Server=20=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20optional:configserver=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- product/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product/src/main/resources/application.yml b/product/src/main/resources/application.yml index 727f30d..4218e99 100644 --- a/product/src/main/resources/application.yml +++ b/product/src/main/resources/application.yml @@ -2,7 +2,7 @@ spring: application: name: product-service config: - import: "configserver:" + import: "optional:configserver:" cloud: config: discovery: From 5f294300596a199898940812c942b8694e677192 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Fri, 13 Feb 2026 17:25:46 +0900 Subject: [PATCH 02/17] =?UTF-8?q?test(config):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20yml=EC=97=90=20.env=20import=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=8F=20Config/Eureka=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EC=84=A4=EC=A0=95=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/resources/application-test.yml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 product/src/test/resources/application-test.yml diff --git a/product/src/test/resources/application-test.yml b/product/src/test/resources/application-test.yml new file mode 100644 index 0000000..0b28448 --- /dev/null +++ b/product/src/test/resources/application-test.yml @@ -0,0 +1,22 @@ +spring: + config: + import: "optional:file:.env[.properties],optional:file:../.env[.properties]" + cloud: + config: + enabled: false + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/hubeleven_test?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8 + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + jpa: + hibernate: + ddl-auto: create-drop + properties: + hibernate: + format_sql: true + show-sql: true + +eureka: + client: + enabled: false From fa67b133008a29bda2ba0a41b193c0e14dd5b3ac Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Fri, 13 Feb 2026 17:27:48 +0900 Subject: [PATCH 03/17] =?UTF-8?q?test:=20Product=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=8E=B8=EC=9D=98?= =?UTF-8?q?=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20Fixture=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/fixtures/ProductFixture.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 product/src/test/java/com/hubEleven/product/stock/application/fixtures/ProductFixture.java diff --git a/product/src/test/java/com/hubEleven/product/stock/application/fixtures/ProductFixture.java b/product/src/test/java/com/hubEleven/product/stock/application/fixtures/ProductFixture.java new file mode 100644 index 0000000..91ad592 --- /dev/null +++ b/product/src/test/java/com/hubEleven/product/stock/application/fixtures/ProductFixture.java @@ -0,0 +1,26 @@ +package com.hubEleven.product.stock.application.fixtures; + +import com.hubEleven.product.domain.model.Product; +import java.util.UUID; + +public class ProductFixture { + + // ===== ID ===== + + public static final UUID COMPANY_ID = UUID.randomUUID(); + + public static final UUID HUB_ID = UUID.randomUUID(); + + // ===== Factory Methods ===== + + public static Product createDefault() { + return Product.create( + "Default Product", + COMPANY_ID, + HUB_ID + ); + } + + private ProductFixture() { + } +} From 13cfe95abf2630559a07d71a4c835d6ae98b7ef1 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Fri, 13 Feb 2026 17:29:02 +0900 Subject: [PATCH 04/17] =?UTF-8?q?test:=20Stock=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=8E=B8=EC=9D=98?= =?UTF-8?q?=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20Fixture=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/fixtures/StockFixture.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 product/src/test/java/com/hubEleven/product/stock/application/fixtures/StockFixture.java diff --git a/product/src/test/java/com/hubEleven/product/stock/application/fixtures/StockFixture.java b/product/src/test/java/com/hubEleven/product/stock/application/fixtures/StockFixture.java new file mode 100644 index 0000000..34453c1 --- /dev/null +++ b/product/src/test/java/com/hubEleven/product/stock/application/fixtures/StockFixture.java @@ -0,0 +1,29 @@ +package com.hubEleven.product.stock.application.fixtures; + +import com.hubEleven.product.domain.model.Product; +import com.hubEleven.stock.domain.model.Stock; +import com.hubEleven.stock.presentation.dto.request.StockRequests; + +public class StockFixture { + + // ===== Factory Methods ===== + + public static Stock createFromProductWithQuantity(Product product, int quantity) { + return Stock.create( + product.getProductId(), + product.getCompanyId(), + product.getHubId(), + quantity + ); + } + + public static StockRequests.Decrease decreaseRequest(Product product, int quantity) { + return new StockRequests.Decrease( + product.getProductId(), + quantity + ); + } + + private StockFixture() { + } +} From cd1478c88112c658579c102c0ae83ff35d29e694 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Fri, 13 Feb 2026 17:30:51 +0900 Subject: [PATCH 05/17] =?UTF-8?q?test(stock):=20StockServiceImpl=20?= =?UTF-8?q?=EC=9E=AC=EA=B3=A0=20=EA=B0=90=EC=86=8C=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/StockServiceImplTest.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java diff --git a/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java b/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java new file mode 100644 index 0000000..18ff9fa --- /dev/null +++ b/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java @@ -0,0 +1,74 @@ +package com.hubEleven.product.stock.application.service; + + +import static org.assertj.core.api.Assertions.assertThat; + +import com.hubEleven.product.domain.model.Product; +import com.hubEleven.product.infrastructure.repository.JpaProductRepository; +import com.hubEleven.product.stock.application.fixtures.ProductFixture; +import com.hubEleven.product.stock.application.fixtures.StockFixture; +import com.hubEleven.stock.application.dto.StockResult; +import com.hubEleven.stock.application.service.StockServiceImpl; +import com.hubEleven.stock.domain.model.Stock; +import com.hubEleven.stock.infrastructure.repository.JpaStockRepository; +import com.hubEleven.stock.presentation.dto.request.StockRequests; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +@SpringBootTest +class StockServiceImplTest { + + @Autowired + private StockServiceImpl stockServiceImpl; + + @Autowired + private JpaStockRepository jpaStockRepository; + + @Autowired + private JpaProductRepository jpaProductRepository; + + private Product product; + + // 테스트 전 상품 재고 입력 + @BeforeEach + public void setUp() { + + product = ProductFixture.createDefault(); + jpaProductRepository.saveAndFlush(product); + + Stock stock = StockFixture.createFromProductWithQuantity(product, 100); + jpaStockRepository.saveAndFlush(stock); + } + + @AfterEach + public void after() { + jpaStockRepository.deleteAll(); + jpaProductRepository.deleteAll(); + } + + @Test + @DisplayName("재고 감소 - 단일 요청 성공") + void decreaseStock_success() { + + // given + int decreaseAmount = 10; + + StockRequests.Decrease request = StockFixture.decreaseRequest( + product, decreaseAmount); + + // when + StockResult result = stockServiceImpl.decreaseStock(request); + + // then - 반환값 검증 + assertThat(result.productId()).isEqualTo(product.getProductId()); + assertThat(result.companyId()).isEqualTo(product.getCompanyId()); + assertThat(result.hubId()).isEqualTo(product.getHubId()); + assertThat(result.quantity()).isEqualTo(90); + } +} \ No newline at end of file From 2cf5cf2cb4e94c1b68837ea02dff09e7b30ac0b5 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Mon, 1 Jun 2026 22:20:40 +0900 Subject: [PATCH 06/17] =?UTF-8?q?feat(stock)=20:=20Redisson=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EB=B0=8F=20Redis=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Redisson 의존성 추가 - Redis host/port 설정 추가 --- config/src/main/resources/config-repo/product-service.yml | 6 +++++- product/build.gradle | 3 +++ product/src/main/resources/application.yml | 6 +++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/config/src/main/resources/config-repo/product-service.yml b/config/src/main/resources/config-repo/product-service.yml index fec5d41..2499cc6 100644 --- a/config/src/main/resources/config-repo/product-service.yml +++ b/config/src/main/resources/config-repo/product-service.yml @@ -9,6 +9,10 @@ spring: username: ${DB_USERNAME} password: ${DB_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver + data: + redis: + host: ${REDIS_HOST:localhost} + port: ${REDIS_PORT:6379} jpa: hibernate: @@ -25,4 +29,4 @@ management: endpoint: "http://localhost:9411/api/v2/spans" tracing: sampling: - probability: 1.0 \ No newline at end of file + probability: 1.0 diff --git a/product/build.gradle b/product/build.gradle index 716931d..6fbed9c 100644 --- a/product/build.gradle +++ b/product/build.gradle @@ -36,6 +36,9 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-config' implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + // Redis / Redisson + implementation 'org.redisson:redisson:3.27.2' + // External Libraries implementation 'com.github.ElevenHub:HubEleven-common:v0.0.1' diff --git a/product/src/main/resources/application.yml b/product/src/main/resources/application.yml index 4218e99..ad55216 100644 --- a/product/src/main/resources/application.yml +++ b/product/src/main/resources/application.yml @@ -1,6 +1,10 @@ spring: application: name: product-service + data: + redis: + host: ${REDIS_HOST:localhost} + port: ${REDIS_PORT:6379} config: import: "optional:configserver:" cloud: @@ -16,4 +20,4 @@ eureka: service-url: defaultZone: http://localhost:8761/eureka/ instance: - instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}} \ No newline at end of file + instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}} From 509871158eba8d2e0ceece425f058045373eaa03 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Mon, 1 Jun 2026 22:42:45 +0900 Subject: [PATCH 07/17] =?UTF-8?q?feat(stock)=20:=20RedissonClient=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RedissonClient Bean 등록 --- .../configuration/RedissonConfig.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 product/src/main/java/com/hubEleven/stock/infrastructure/configuration/RedissonConfig.java diff --git a/product/src/main/java/com/hubEleven/stock/infrastructure/configuration/RedissonConfig.java b/product/src/main/java/com/hubEleven/stock/infrastructure/configuration/RedissonConfig.java new file mode 100644 index 0000000..0e3ba4a --- /dev/null +++ b/product/src/main/java/com/hubEleven/stock/infrastructure/configuration/RedissonConfig.java @@ -0,0 +1,21 @@ +package com.hubEleven.stock.infrastructure.configuration; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RedissonConfig { + + @Bean(destroyMethod = "shutdown") + public RedissonClient redissonClient( + @Value("${spring.data.redis.host:localhost}") String host, + @Value("${spring.data.redis.port:6379}") int port) { + Config config = new Config(); + config.useSingleServer().setAddress("redis://" + host + ":" + port); + return Redisson.create(config); + } +} From 13610091da533cb4764d881dcd92d0b96ad8dfef Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Tue, 2 Jun 2026 22:02:21 +0900 Subject: [PATCH 08/17] =?UTF-8?q?feat(stock):=20=EC=9E=AC=EA=B3=A0=20?= =?UTF-8?q?=EA=B0=90=EC=86=8C=20=EB=9D=BD=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stock/application/port/StockLockManager.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 product/src/main/java/com/hubEleven/stock/application/port/StockLockManager.java diff --git a/product/src/main/java/com/hubEleven/stock/application/port/StockLockManager.java b/product/src/main/java/com/hubEleven/stock/application/port/StockLockManager.java new file mode 100644 index 0000000..efb4381 --- /dev/null +++ b/product/src/main/java/com/hubEleven/stock/application/port/StockLockManager.java @@ -0,0 +1,8 @@ +package com.hubEleven.stock.application.port; + +import java.util.function.Supplier; + +public interface StockLockManager { + + T executeWithLock(String lockKey, Supplier supplier); +} From 09b243f952bec1568997b8a0f0bb628bea363d6d Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Wed, 3 Jun 2026 14:04:38 +0900 Subject: [PATCH 09/17] =?UTF-8?q?feat(stock):=20Redisson=20=EB=B6=84?= =?UTF-8?q?=EC=82=B0=20=EB=9D=BD=20=EA=B5=AC=ED=98=84=EC=B2=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/exception/StockErrorCode.java | 1 + .../lock/RedissonStockLockManager.java | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java diff --git a/product/src/main/java/com/hubEleven/stock/domain/exception/StockErrorCode.java b/product/src/main/java/com/hubEleven/stock/domain/exception/StockErrorCode.java index 69a3b3b..8a6a732 100644 --- a/product/src/main/java/com/hubEleven/stock/domain/exception/StockErrorCode.java +++ b/product/src/main/java/com/hubEleven/stock/domain/exception/StockErrorCode.java @@ -12,6 +12,7 @@ public enum StockErrorCode implements ErrorCode { DECREASE_QUANTITY_INVALID(HttpStatus.BAD_REQUEST, "재고 감소 수량은 1 이상이어야 합니다."), RESTORE_QUANTITY_INVALID(HttpStatus.BAD_REQUEST, "재고 복원 수량은 1 이상이어야 합니다."), INSUFFICIENT_STOCK(HttpStatus.BAD_REQUEST, "재고가 부족합니다."), + STOCK_LOCK_TIMEOUT(HttpStatus.CONFLICT, "재고 감소 요청이 많아 잠시 후 다시 시도해 주세요."), INITIAL_QUANTITY_INVALID(HttpStatus.BAD_REQUEST, "초기 재고 수량은 0 이상이어야 합니다."); private final HttpStatus httpStatus; diff --git a/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java b/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java new file mode 100644 index 0000000..c542488 --- /dev/null +++ b/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java @@ -0,0 +1,47 @@ +package com.hubEleven.stock.infrastructure.lock; + +import static com.hubEleven.stock.domain.exception.StockErrorCode.STOCK_LOCK_TIMEOUT; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Component; + +import com.commonLib.common.exception.GlobalException; +import com.hubEleven.stock.application.port.StockLockManager; + +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class RedissonStockLockManager implements StockLockManager { + + private static final long WAIT_TIME_SECONDS = 3L; + private static final long LEASE_TIME_SECONDS = 5L; + + private final RedissonClient redissonClient; + + @Override + public T executeWithLock(String lockKey, Supplier supplier) { + RLock lock = redissonClient.getLock(lockKey); + boolean locked = false; + + try { + locked = lock.tryLock(WAIT_TIME_SECONDS, LEASE_TIME_SECONDS, TimeUnit.SECONDS); + if (!locked) { + throw new GlobalException(STOCK_LOCK_TIMEOUT); + } + + return supplier.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new GlobalException(STOCK_LOCK_TIMEOUT); + } finally { + if (locked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } +} From ab1fff5217c00c195095767fab4407bbd09a4aee Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Tue, 16 Jun 2026 23:20:44 +0900 Subject: [PATCH 10/17] =?UTF-8?q?test(stock)=20:=20=EC=9E=AC=EA=B3=A0=20?= =?UTF-8?q?=EA=B0=90=EC=86=8C=20=EB=8F=99=EC=8B=9C=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/StockConcurrencyTest.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 product/src/test/java/com/hubEleven/product/stock/application/service/StockConcurrencyTest.java diff --git a/product/src/test/java/com/hubEleven/product/stock/application/service/StockConcurrencyTest.java b/product/src/test/java/com/hubEleven/product/stock/application/service/StockConcurrencyTest.java new file mode 100644 index 0000000..f1a42e9 --- /dev/null +++ b/product/src/test/java/com/hubEleven/product/stock/application/service/StockConcurrencyTest.java @@ -0,0 +1,105 @@ +package com.hubEleven.product.stock.application.service; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Queue; +import java.util.stream.Collectors; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import com.hubEleven.product.domain.model.Product; +import com.hubEleven.product.infrastructure.repository.JpaProductRepository; +import com.hubEleven.product.stock.application.fixtures.ProductFixture; +import com.hubEleven.product.stock.application.fixtures.StockFixture; +import com.hubEleven.stock.application.service.StockServiceImpl; +import com.hubEleven.stock.domain.model.Stock; +import com.hubEleven.stock.infrastructure.repository.JpaStockRepository; + +@ActiveProfiles("test") +@SpringBootTest +public class StockConcurrencyTest { + + @Autowired + private StockServiceImpl stockServiceImpl; + + @Autowired + private JpaStockRepository jpaStockRepository; + + @Autowired + private JpaProductRepository jpaProductRepository; + + private Product product; + + @BeforeEach + void setUp() { + product = ProductFixture.createDefault(); + jpaProductRepository.saveAndFlush(product); + + Stock stock = StockFixture.createFromProductWithQuantity(product, 100); + jpaStockRepository.saveAndFlush(stock); + } + + @AfterEach + void tearDown() { + jpaStockRepository.deleteAll(); + jpaProductRepository.deleteAll(); + } + + @Test + @DisplayName("재고 감소 - 100개 동시 요청 시 정확히 차감") + void decreaseStock_when100ConcurrentRequests_thenSuccess() throws Exception { + + // given + int threadCount = 100; + + ExecutorService executorService = Executors.newFixedThreadPool(threadCount); + + CountDownLatch readyLatch = new CountDownLatch(threadCount); + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch doneLatch = new CountDownLatch(threadCount); + Queue exceptions = new ConcurrentLinkedQueue<>(); + + // when + for (int i = 0; i < threadCount; i++) { + executorService.submit( + () -> { + try { + readyLatch.countDown(); + startLatch.await(); + stockServiceImpl.decreaseStock(StockFixture.decreaseRequest(product, 1)); + } catch (Throwable e) { + exceptions.add(e); + } finally { + doneLatch.countDown(); + } + }); + } + + readyLatch.await(); + startLatch.countDown(); + doneLatch.await(); + executorService.shutdown(); + + // then + Stock updatedStock = jpaStockRepository.findByProductId(product.getProductId()).orElseThrow(); + + assertTrue(exceptions.isEmpty(), exceptionMessages(exceptions)); + assertEquals(0, updatedStock.getQuantity()); + } + + private String exceptionMessages(Queue exceptions) { + return exceptions.stream() + .map(throwable -> throwable.getClass().getName() + ": " + throwable.getMessage()) + .collect(Collectors.joining(System.lineSeparator())); + } +} From c6343cb96dc09ed3ad63e56839bbf381a49cbba9 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Tue, 16 Jun 2026 23:21:05 +0900 Subject: [PATCH 11/17] =?UTF-8?q?feat(stock):=20=EC=9E=AC=EA=B3=A0=20?= =?UTF-8?q?=EA=B0=90=EC=86=8C=20=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=EB=A1=9C=20=EB=9D=BD=20=ED=95=B4=EC=A0=9C=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/StockDecreaseProcessor.java | 40 +++++++++++++++++++ .../application/service/StockServiceImpl.java | 14 +++---- 2 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 product/src/main/java/com/hubEleven/stock/application/service/StockDecreaseProcessor.java diff --git a/product/src/main/java/com/hubEleven/stock/application/service/StockDecreaseProcessor.java b/product/src/main/java/com/hubEleven/stock/application/service/StockDecreaseProcessor.java new file mode 100644 index 0000000..cd616c8 --- /dev/null +++ b/product/src/main/java/com/hubEleven/stock/application/service/StockDecreaseProcessor.java @@ -0,0 +1,40 @@ +package com.hubEleven.stock.application.service; + +import static com.hubEleven.product.domain.exception.ProductErrorCode.PRODUCT_NOT_FOUND; +import static com.hubEleven.stock.domain.exception.StockErrorCode.STOCK_NOT_FOUND; + +import com.commonLib.common.exception.GlobalException; +import com.hubEleven.product.domain.model.Product; +import com.hubEleven.product.domain.repository.ProductRepository; +import com.hubEleven.stock.application.dto.StockResult; +import com.hubEleven.stock.domain.model.Stock; +import com.hubEleven.stock.domain.repository.StockRepository; +import com.hubEleven.stock.presentation.dto.request.StockRequests; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class StockDecreaseProcessor { + + private final StockRepository stockRepository; + private final ProductRepository productRepository; + + @Transactional + public StockResult decrease(StockRequests.Decrease request) { + Product product = + productRepository + .findByIdNotDeleted(request.productId()) + .orElseThrow(() -> new GlobalException(PRODUCT_NOT_FOUND)); + + Stock stock = + stockRepository + .findByProductId(request.productId()) + .orElseThrow(() -> new GlobalException(STOCK_NOT_FOUND)); + + stock.decreaseQuantity(request.quantity()); + + return StockResult.from(stock, product.getName()); + } +} diff --git a/product/src/main/java/com/hubEleven/stock/application/service/StockServiceImpl.java b/product/src/main/java/com/hubEleven/stock/application/service/StockServiceImpl.java index 9421201..d6dbc73 100644 --- a/product/src/main/java/com/hubEleven/stock/application/service/StockServiceImpl.java +++ b/product/src/main/java/com/hubEleven/stock/application/service/StockServiceImpl.java @@ -7,6 +7,7 @@ import com.hubEleven.product.domain.model.Product; import com.hubEleven.product.domain.repository.ProductRepository; import com.hubEleven.stock.application.dto.StockResult; +import com.hubEleven.stock.application.port.StockLockManager; import com.hubEleven.stock.domain.model.Stock; import com.hubEleven.stock.domain.repository.StockRepository; import com.hubEleven.stock.presentation.dto.request.StockRequests; @@ -21,6 +22,8 @@ public class StockServiceImpl implements StockService { private final StockRepository stockRepository; private final ProductRepository productRepository; + private final StockLockManager stockLockManager; + private final StockDecreaseProcessor stockDecreaseProcessor; private Product getProductOrThrow(UUID productId) { return productRepository @@ -60,16 +63,9 @@ public StockResult getStockByProductId(UUID productId) { } @Override - @Transactional public StockResult decreaseStock(StockRequests.Decrease request) { - - Product product = getProductOrThrow(request.productId()); - - Stock stock = getStockOrThrow(request.productId()); - - stock.decreaseQuantity(request.quantity()); - - return StockResult.from(stock, product.getName()); + return stockLockManager.executeWithLock( + "stock:decrease:" + request.productId(), () -> stockDecreaseProcessor.decrease(request)); } @Override From 07a2889dd5c18456a5699d239a0d241738bd9308 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Tue, 16 Jun 2026 23:22:06 +0900 Subject: [PATCH 12/17] =?UTF-8?q?feat(stock):=20=EB=9D=BD=20=ED=9A=8D?= =?UTF-8?q?=EB=93=9D=20=EC=8B=A4=ED=8C=A8=20=EC=8B=9C=20=EC=A0=9C=ED=95=9C?= =?UTF-8?q?=EC=A0=81=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lock/RedissonStockLockManager.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java b/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java index c542488..e1f4f9a 100644 --- a/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java +++ b/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java @@ -18,8 +18,10 @@ @RequiredArgsConstructor public class RedissonStockLockManager implements StockLockManager { + private static final int MAX_RETRY_COUNT = 3; private static final long WAIT_TIME_SECONDS = 3L; private static final long LEASE_TIME_SECONDS = 5L; + private static final long RETRY_BACKOFF_MILLIS = 100L; private final RedissonClient redissonClient; @@ -29,12 +31,16 @@ public T executeWithLock(String lockKey, Supplier supplier) { boolean locked = false; try { - locked = lock.tryLock(WAIT_TIME_SECONDS, LEASE_TIME_SECONDS, TimeUnit.SECONDS); - if (!locked) { - throw new GlobalException(STOCK_LOCK_TIMEOUT); + for (int retryCount = 0; retryCount < MAX_RETRY_COUNT; retryCount++) { + locked = lock.tryLock(WAIT_TIME_SECONDS, LEASE_TIME_SECONDS, TimeUnit.SECONDS); + if (locked) { + return supplier.get(); + } + + Thread.sleep(RETRY_BACKOFF_MILLIS); } - return supplier.get(); + throw new GlobalException(STOCK_LOCK_TIMEOUT); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new GlobalException(STOCK_LOCK_TIMEOUT); From 0578fca5173971928a9e347dfa5d0d1d59302306 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Tue, 16 Jun 2026 23:23:09 +0900 Subject: [PATCH 13/17] =?UTF-8?q?test(stock)=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9A=A9=20Redis=20yml=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- product/src/test/resources/application-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/product/src/test/resources/application-test.yml b/product/src/test/resources/application-test.yml index 0b28448..7c5f145 100644 --- a/product/src/test/resources/application-test.yml +++ b/product/src/test/resources/application-test.yml @@ -16,6 +16,10 @@ spring: hibernate: format_sql: true show-sql: true + data: + redis: + host: localhost + port: 6379 eureka: client: From 71ac6e2dff180b3ff1f0cbb5c9b92daf86046f7f Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Tue, 16 Jun 2026 23:59:59 +0900 Subject: [PATCH 14/17] =?UTF-8?q?chore=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=ED=8C=85(spotlessApply)=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lock/RedissonStockLockManager.java | 9 +-- .../application/fixtures/ProductFixture.java | 21 +++--- .../application/fixtures/StockFixture.java | 26 +++---- .../service/StockConcurrencyTest.java | 53 +++++++------- .../service/StockServiceImplTest.java | 69 +++++++++---------- 5 files changed, 76 insertions(+), 102 deletions(-) diff --git a/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java b/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java index e1f4f9a..59f7e7b 100644 --- a/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java +++ b/product/src/main/java/com/hubEleven/stock/infrastructure/lock/RedissonStockLockManager.java @@ -2,18 +2,15 @@ import static com.hubEleven.stock.domain.exception.StockErrorCode.STOCK_LOCK_TIMEOUT; +import com.commonLib.common.exception.GlobalException; +import com.hubEleven.stock.application.port.StockLockManager; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; - +import lombok.RequiredArgsConstructor; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.stereotype.Component; -import com.commonLib.common.exception.GlobalException; -import com.hubEleven.stock.application.port.StockLockManager; - -import lombok.RequiredArgsConstructor; - @Component @RequiredArgsConstructor public class RedissonStockLockManager implements StockLockManager { diff --git a/product/src/test/java/com/hubEleven/product/stock/application/fixtures/ProductFixture.java b/product/src/test/java/com/hubEleven/product/stock/application/fixtures/ProductFixture.java index 91ad592..54f1556 100644 --- a/product/src/test/java/com/hubEleven/product/stock/application/fixtures/ProductFixture.java +++ b/product/src/test/java/com/hubEleven/product/stock/application/fixtures/ProductFixture.java @@ -5,22 +5,17 @@ public class ProductFixture { - // ===== ID ===== + // ===== ID ===== - public static final UUID COMPANY_ID = UUID.randomUUID(); + public static final UUID COMPANY_ID = UUID.randomUUID(); - public static final UUID HUB_ID = UUID.randomUUID(); + public static final UUID HUB_ID = UUID.randomUUID(); - // ===== Factory Methods ===== + // ===== Factory Methods ===== - public static Product createDefault() { - return Product.create( - "Default Product", - COMPANY_ID, - HUB_ID - ); - } + public static Product createDefault() { + return Product.create("Default Product", COMPANY_ID, HUB_ID); + } - private ProductFixture() { - } + private ProductFixture() {} } diff --git a/product/src/test/java/com/hubEleven/product/stock/application/fixtures/StockFixture.java b/product/src/test/java/com/hubEleven/product/stock/application/fixtures/StockFixture.java index 34453c1..7f21d8c 100644 --- a/product/src/test/java/com/hubEleven/product/stock/application/fixtures/StockFixture.java +++ b/product/src/test/java/com/hubEleven/product/stock/application/fixtures/StockFixture.java @@ -6,24 +6,16 @@ public class StockFixture { - // ===== Factory Methods ===== + // ===== Factory Methods ===== - public static Stock createFromProductWithQuantity(Product product, int quantity) { - return Stock.create( - product.getProductId(), - product.getCompanyId(), - product.getHubId(), - quantity - ); - } + public static Stock createFromProductWithQuantity(Product product, int quantity) { + return Stock.create( + product.getProductId(), product.getCompanyId(), product.getHubId(), quantity); + } - public static StockRequests.Decrease decreaseRequest(Product product, int quantity) { - return new StockRequests.Decrease( - product.getProductId(), - quantity - ); - } + public static StockRequests.Decrease decreaseRequest(Product product, int quantity) { + return new StockRequests.Decrease(product.getProductId(), quantity); + } - private StockFixture() { - } + private StockFixture() {} } diff --git a/product/src/test/java/com/hubEleven/product/stock/application/service/StockConcurrencyTest.java b/product/src/test/java/com/hubEleven/product/stock/application/service/StockConcurrencyTest.java index f1a42e9..073d137 100644 --- a/product/src/test/java/com/hubEleven/product/stock/application/service/StockConcurrencyTest.java +++ b/product/src/test/java/com/hubEleven/product/stock/application/service/StockConcurrencyTest.java @@ -2,13 +2,19 @@ import static org.junit.jupiter.api.Assertions.*; +import com.hubEleven.product.domain.model.Product; +import com.hubEleven.product.infrastructure.repository.JpaProductRepository; +import com.hubEleven.product.stock.application.fixtures.ProductFixture; +import com.hubEleven.product.stock.application.fixtures.StockFixture; +import com.hubEleven.stock.application.service.StockServiceImpl; +import com.hubEleven.stock.domain.model.Stock; +import com.hubEleven.stock.infrastructure.repository.JpaStockRepository; import java.util.Queue; -import java.util.stream.Collectors; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; - +import java.util.stream.Collectors; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -17,26 +23,15 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -import com.hubEleven.product.domain.model.Product; -import com.hubEleven.product.infrastructure.repository.JpaProductRepository; -import com.hubEleven.product.stock.application.fixtures.ProductFixture; -import com.hubEleven.product.stock.application.fixtures.StockFixture; -import com.hubEleven.stock.application.service.StockServiceImpl; -import com.hubEleven.stock.domain.model.Stock; -import com.hubEleven.stock.infrastructure.repository.JpaStockRepository; - @ActiveProfiles("test") @SpringBootTest public class StockConcurrencyTest { - @Autowired - private StockServiceImpl stockServiceImpl; + @Autowired private StockServiceImpl stockServiceImpl; - @Autowired - private JpaStockRepository jpaStockRepository; + @Autowired private JpaStockRepository jpaStockRepository; - @Autowired - private JpaProductRepository jpaProductRepository; + @Autowired private JpaProductRepository jpaProductRepository; private Product product; @@ -72,17 +67,17 @@ void decreaseStock_when100ConcurrentRequests_thenSuccess() throws Exception { // when for (int i = 0; i < threadCount; i++) { executorService.submit( - () -> { - try { - readyLatch.countDown(); - startLatch.await(); - stockServiceImpl.decreaseStock(StockFixture.decreaseRequest(product, 1)); - } catch (Throwable e) { - exceptions.add(e); - } finally { - doneLatch.countDown(); - } - }); + () -> { + try { + readyLatch.countDown(); + startLatch.await(); + stockServiceImpl.decreaseStock(StockFixture.decreaseRequest(product, 1)); + } catch (Throwable e) { + exceptions.add(e); + } finally { + doneLatch.countDown(); + } + }); } readyLatch.await(); @@ -99,7 +94,7 @@ void decreaseStock_when100ConcurrentRequests_thenSuccess() throws Exception { private String exceptionMessages(Queue exceptions) { return exceptions.stream() - .map(throwable -> throwable.getClass().getName() + ": " + throwable.getMessage()) - .collect(Collectors.joining(System.lineSeparator())); + .map(throwable -> throwable.getClass().getName() + ": " + throwable.getMessage()) + .collect(Collectors.joining(System.lineSeparator())); } } diff --git a/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java b/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java index 18ff9fa..901deed 100644 --- a/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java +++ b/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java @@ -1,6 +1,5 @@ package com.hubEleven.product.stock.application.service; - import static org.assertj.core.api.Assertions.assertThat; import com.hubEleven.product.domain.model.Product; @@ -24,51 +23,47 @@ @SpringBootTest class StockServiceImplTest { - @Autowired - private StockServiceImpl stockServiceImpl; + @Autowired private StockServiceImpl stockServiceImpl; - @Autowired - private JpaStockRepository jpaStockRepository; + @Autowired private JpaStockRepository jpaStockRepository; - @Autowired - private JpaProductRepository jpaProductRepository; + @Autowired private JpaProductRepository jpaProductRepository; - private Product product; + private Product product; - // 테스트 전 상품 재고 입력 - @BeforeEach - public void setUp() { + // 테스트 전 상품 재고 입력 + @BeforeEach + public void setUp() { - product = ProductFixture.createDefault(); - jpaProductRepository.saveAndFlush(product); + product = ProductFixture.createDefault(); + jpaProductRepository.saveAndFlush(product); - Stock stock = StockFixture.createFromProductWithQuantity(product, 100); - jpaStockRepository.saveAndFlush(stock); - } + Stock stock = StockFixture.createFromProductWithQuantity(product, 100); + jpaStockRepository.saveAndFlush(stock); + } - @AfterEach - public void after() { - jpaStockRepository.deleteAll(); - jpaProductRepository.deleteAll(); - } + @AfterEach + public void after() { + jpaStockRepository.deleteAll(); + jpaProductRepository.deleteAll(); + } - @Test - @DisplayName("재고 감소 - 단일 요청 성공") - void decreaseStock_success() { + @Test + @DisplayName("재고 감소 - 단일 요청 성공") + void decreaseStock_success() { - // given - int decreaseAmount = 10; + // given + int decreaseAmount = 10; - StockRequests.Decrease request = StockFixture.decreaseRequest( - product, decreaseAmount); + StockRequests.Decrease request = StockFixture.decreaseRequest(product, decreaseAmount); - // when - StockResult result = stockServiceImpl.decreaseStock(request); + // when + StockResult result = stockServiceImpl.decreaseStock(request); - // then - 반환값 검증 - assertThat(result.productId()).isEqualTo(product.getProductId()); - assertThat(result.companyId()).isEqualTo(product.getCompanyId()); - assertThat(result.hubId()).isEqualTo(product.getHubId()); - assertThat(result.quantity()).isEqualTo(90); - } -} \ No newline at end of file + // then - 반환값 검증 + assertThat(result.productId()).isEqualTo(product.getProductId()); + assertThat(result.companyId()).isEqualTo(product.getCompanyId()); + assertThat(result.hubId()).isEqualTo(product.getHubId()); + assertThat(result.quantity()).isEqualTo(90); + } +} From f9d1498214bfb1fa4a75d7f583c84e419301c86a Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Wed, 17 Jun 2026 21:50:50 +0900 Subject: [PATCH 15/17] =?UTF-8?q?fix=20:=20Docker=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=9D=98=20Redis=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EC=9D=BC=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/src/main/resources/config-repo/product-service.yml | 4 ++-- docker-compose.yml | 6 +++--- product/src/main/resources/application.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/config/src/main/resources/config-repo/product-service.yml b/config/src/main/resources/config-repo/product-service.yml index 2499cc6..29c457a 100644 --- a/config/src/main/resources/config-repo/product-service.yml +++ b/config/src/main/resources/config-repo/product-service.yml @@ -11,8 +11,8 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver data: redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} + host: ${SPRING_DATA_REDIS_HOST:localhost} + port: ${SPRING_DATA_REDIS_PORT:6379} jpa: hibernate: diff --git a/docker-compose.yml b/docker-compose.yml index e0f9992..bc3c752 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: networks: - hubeleven-network healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8761/actuator/health"] + test: [ "CMD", "curl", "-f", "http://localhost:8761/actuator/health" ] interval: 10s timeout: 5s retries: 5 @@ -103,8 +103,8 @@ services: - SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD} - SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.MySQL8Dialect - SPRING_JPA_HIBERNATE_DDL_AUTO=update - - SPRING_REDIS_HOST=${REDIS_HOST} - - SPRING_REDIS_PORT=${REDIS_PORT} + - SPRING_DATA_REDIS_HOST=${REDIS_HOST} + - SPRING_DATA_REDIS_PORT=${REDIS_PORT} - MANAGEMENT_ZIPKIN_TRACING_ENDPOINT=${ZIPKIN_ENDPOINT} depends_on: - eurekaServer diff --git a/product/src/main/resources/application.yml b/product/src/main/resources/application.yml index ad55216..983aa86 100644 --- a/product/src/main/resources/application.yml +++ b/product/src/main/resources/application.yml @@ -3,8 +3,8 @@ spring: name: product-service data: redis: - host: ${REDIS_HOST:localhost} - port: ${REDIS_PORT:6379} + host: ${SPRING_DATA_REDIS_HOST:localhost} + port: ${SPRING_DATA_REDIS_PORT:6379} config: import: "optional:configserver:" cloud: From 29216680f6450e54470d0dde2ec5d1a95e0e5551 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Wed, 17 Jun 2026 22:45:22 +0900 Subject: [PATCH 16/17] =?UTF-8?q?fix=20:=20prod=20=EB=B0=8F=20test=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EB=B3=84=20Config=20Server=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- product/src/main/resources/application.yml | 24 +++++++++++++++-- .../product/ProductApplicationTests.java | 2 ++ .../service/StockServiceImplTest.java | 26 +++++++++++-------- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/product/src/main/resources/application.yml b/product/src/main/resources/application.yml index 983aa86..79273fe 100644 --- a/product/src/main/resources/application.yml +++ b/product/src/main/resources/application.yml @@ -5,8 +5,6 @@ spring: redis: host: ${SPRING_DATA_REDIS_HOST:localhost} port: ${SPRING_DATA_REDIS_PORT:6379} - config: - import: "optional:configserver:" cloud: config: discovery: @@ -21,3 +19,25 @@ eureka: defaultZone: http://localhost:8761/eureka/ instance: instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}} + +--- +# Product Profile +spring: + config: + activate: + on-profile: prod + import: "configserver:" + +--- +# Test Profile +spring: + config: + activate: + on-profile: test + cloud: + config: + enabled: false + +eureka: + client: + enabled: false diff --git a/product/src/test/java/com/hubEleven/product/ProductApplicationTests.java b/product/src/test/java/com/hubEleven/product/ProductApplicationTests.java index 35bcfd5..7bec26d 100644 --- a/product/src/test/java/com/hubEleven/product/ProductApplicationTests.java +++ b/product/src/test/java/com/hubEleven/product/ProductApplicationTests.java @@ -2,8 +2,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest +@ActiveProfiles("test") class ProductApplicationTests { @Test diff --git a/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java b/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java index 901deed..caa1246 100644 --- a/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java +++ b/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java @@ -1,6 +1,14 @@ package com.hubEleven.product.stock.application.service; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import com.hubEleven.product.domain.model.Product; import com.hubEleven.product.infrastructure.repository.JpaProductRepository; @@ -11,23 +19,19 @@ import com.hubEleven.stock.domain.model.Stock; import com.hubEleven.stock.infrastructure.repository.JpaStockRepository; import com.hubEleven.stock.presentation.dto.request.StockRequests; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; @ActiveProfiles("test") @SpringBootTest class StockServiceImplTest { - @Autowired private StockServiceImpl stockServiceImpl; + @Autowired + private StockServiceImpl stockServiceImpl; - @Autowired private JpaStockRepository jpaStockRepository; + @Autowired + private JpaStockRepository jpaStockRepository; - @Autowired private JpaProductRepository jpaProductRepository; + @Autowired + private JpaProductRepository jpaProductRepository; private Product product; From b803d7f2fe89644e0b6880189cd2c819cc2b7bd0 Mon Sep 17 00:00:00 2001 From: hellonaeunkim Date: Wed, 17 Jun 2026 22:48:38 +0900 Subject: [PATCH 17/17] =?UTF-8?q?chore=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EB=A7=B7=ED=8C=85(spotlessApply)=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/StockServiceImplTest.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java b/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java index caa1246..0f13dd7 100644 --- a/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java +++ b/product/src/test/java/com/hubEleven/product/stock/application/service/StockServiceImplTest.java @@ -2,14 +2,6 @@ import static org.assertj.core.api.Assertions.*; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - import com.hubEleven.product.domain.model.Product; import com.hubEleven.product.infrastructure.repository.JpaProductRepository; import com.hubEleven.product.stock.application.fixtures.ProductFixture; @@ -19,19 +11,23 @@ import com.hubEleven.stock.domain.model.Stock; import com.hubEleven.stock.infrastructure.repository.JpaStockRepository; import com.hubEleven.stock.presentation.dto.request.StockRequests; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @ActiveProfiles("test") @SpringBootTest class StockServiceImplTest { - @Autowired - private StockServiceImpl stockServiceImpl; + @Autowired private StockServiceImpl stockServiceImpl; - @Autowired - private JpaStockRepository jpaStockRepository; + @Autowired private JpaStockRepository jpaStockRepository; - @Autowired - private JpaProductRepository jpaProductRepository; + @Autowired private JpaProductRepository jpaProductRepository; private Product product;