Skip to content

Commit

Permalink
[#37][12주차] CompletableFuture,Async (#38)
Browse files Browse the repository at this point in the history
* #37 CompletableFuture,Async

* #37 CompletableFuture,Async

* #37 CompletableFuture,Async

* #37 CompletableFuture,Async

* #37 CompletableFuture,Async

* #37 CompletableFuture,Async

* #37 CompletableFuture,Async

* #37 CompletableFuture,Async
  • Loading branch information
kimsuyeondev authored Jul 6, 2024
1 parent 4585719 commit cac8fe8
Show file tree
Hide file tree
Showing 10 changed files with 549 additions and 61 deletions.
12 changes: 7 additions & 5 deletions .github/workflows/pr-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ jobs:
MYSQL_USER: dev_1234
MYSQL_PASSWORD: 1234
MYSQL_ROOT_PASSWORD: 1234
#1. 소스 체크 아웃

#1. 소스 체크 아웃
#2. jdk 설정
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -71,9 +71,11 @@ jobs:
- name: Initialize MySQL database
run: |
mysql -h 127.0.0.1 -u dev_1234 -p1234 < docker/db/mysql/init/init.sql
#8. Gradle 클린 빌드
- name: Build with Gradle0
run: ./gradlew clean build

#5. 테스트 실행
- name: 테스트를 실행
run: ./gradlew --info test
Expand All @@ -89,6 +91,6 @@ jobs:
# if: always()
# with:
# report_paths: 'module-api/build/test-results/test/TEST-*.xml'
#8. Gradle 클린 빌드
- name: Build with Gradle
run: ./gradlew clean build
#8-1. Gradle 클린 빌드
# - name: Build with Gradle
# run: ./gradlew clean build
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.cosmetics.api.controller;

import com.cosmetics.domain.goods.dto.GoodsManagement;
import com.cosmetics.domain.goods.dto.GoodsManagementRequest;
import com.cosmetics.domain.goods.dto.GoodsManagementResponse;
import com.cosmetics.domain.goods.service.GoodsService;
import com.cosmetics.domain.sms.service.SmsService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@RestController
@RequestMapping(value = "/v1/goods")
@RequiredArgsConstructor
@Slf4j
public class GoodsController {

private final GoodsService goodsService;
private final SmsService smsService;

@GetMapping(value = "/{goodsNo}")
public CompletableFuture<GoodsManagementResponse> findGoods(@PathVariable Long goodsNo) {

CompletableFuture<GoodsManagementResponse> goodsResponseFuture = CompletableFuture.supplyAsync(() -> {
log.error("findGoodsThread = {}", Thread.currentThread().getName());
GoodsManagement goodsManagement = goodsService.findByGoodsNo(goodsNo);
return GoodsManagementResponse.toResponseDto(goodsManagement);

}).thenApplyAsync( //callback
(GoodsManagementResponse response) -> {
log.error("findGoods thenApplayAsync= {}", Thread.currentThread().getName());
response.updateSuccess("0000", "조회성공");
return response;
}
);

return goodsResponseFuture;
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public CompletableFuture<GoodsManagementResponse> registerGoods(@RequestBody @Valid GoodsManagementRequest goodsManagementRequest) throws ExecutionException, InterruptedException {

CompletableFuture<GoodsManagementResponse> goodsResponseFuture = CompletableFuture.supplyAsync(() -> {
log.error("goodsName = {}, registerGoodsThread = {}", goodsManagementRequest.getGoodsNm(), Thread.currentThread().getName()); //ForkJoinPool.commonPool-worker-1
return goodsService.save(goodsManagementRequest.toServiceDto()); //등록

}).thenApplyAsync(
//dto ->
(goodsManagement) -> {
log.error("goodsName = {}, goodsNo = {}", goodsManagementRequest.getGoodsNm(), goodsManagement.getGoodsNo());
log.error("registerGoodsThread thenApplyAsync = {}", Thread.currentThread().getName()); //ForkJoinPool.commonPool-worker-1
GoodsManagementResponse response = GoodsManagementResponse.toResponseDto(goodsManagement);
response.updateSuccess("0000", "등록성공");
return response;
}
);

//등록에 성공하면 메세지를 보낸다고 가정
if ("0000".equals(goodsResponseFuture.get().getResultCode())) {
smsService.smsMessage(goodsResponseFuture.get().getGoodsNo()); //Cosmetics-Thread-Pool1
}
return goodsResponseFuture;
}

@DeleteMapping(value = "/{goodsNo}")
public GoodsManagementResponse deleteGoods(@PathVariable Long goodsNo) {
log.error("deleteGoods : {}", goodsNo);
GoodsManagement goodsManagement = goodsService.deleteByGoodsNo(goodsNo);
//dto -> responseDto
GoodsManagementResponse responseGoodsManagement = GoodsManagementResponse.toResponseDto(goodsManagement);
responseGoodsManagement.updateSuccess("0000", "삭제성공");
return responseGoodsManagement;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.cosmetics.api.exception.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;

import java.lang.reflect.Method;
@Slf4j
public class CosmeticsAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("AsyncExceptionHandler = {}", ex.getMessage());
log.error("method = {}", method.getName());
log.error("params = {}", params);
log.error("Thread = {}", Thread.currentThread().getName());
}
}

This file was deleted.

33 changes: 33 additions & 0 deletions module-api/src/main/java/com/cosmetics/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.cosmetics.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
@Slf4j
public class AsyncConfig implements AsyncConfigurer {

private int CORE_POOL_SIZE = 10;
private int MAX_POOL_SIZE = 50;
private int QUEUE_CAPACITY = 1000;

@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE); // 항상 유지할 최소 스레드 수
executor.setMaxPoolSize(MAX_POOL_SIZE); // 생성할 수 있는 최대 스레드 수
executor.setQueueCapacity(QUEUE_CAPACITY); // 대기 큐의 최대 크기
executor.setThreadNamePrefix("Cosmetics-Thread-Pool"); // 생성된 스레드 이름 접두사
executor.initialize(); // 스레드 풀 초기화
log.error("{}======================", Thread.currentThread().getName());
return executor;
//상황에 따라 최소 스레드 수 최대 스레드 수 대기 큐의 크기를 조절해야하는데 그 상황이 어떤 상황에 따라 어떤 걸 보고 어떻게 지정해야할 지
//궁금합니다
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.cosmetics.goods;
package com.cosmetics.api.application;

import com.cosmetics.domain.goods.dto.GoodsManagementRequest;
import com.cosmetics.domain.goods.dto.GoodsManagementResponse;
Expand All @@ -15,6 +15,10 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand Down Expand Up @@ -57,6 +61,33 @@ private static GoodsManagementRequest requestGoods() {
.items(items)
.build();
}
private static GoodsManagementRequest requestGoods(int i) {
List<GoodsItemManagementRequest> items = new ArrayList<>();

items.add(GoodsItemManagementRequest.builder()
.itemNm("건성용" + i)
.itemQty(50).build());
items.add(GoodsItemManagementRequest.builder()
.itemNm("지성용" + i)
.itemQty(30).build());


return GoodsManagementRequest.builder()
.category("스킨케어")
.goodsNm("닥터스킨" + i)
.marketPrice(15000)
.salePrice(12000)
.supplyPrice(10000)
.vendorId(1L)
.stockQty(80)
.brandNm("닥터펫")
.saleStartDtime("2024-05-01 00:00:00")
.saleEndDtime("2024-08-01 00:00:00")
.image("https://cdn.localhost:8081/images/lv202400002/goods/image_1.png")
.addImage("https://cdn.localhost:8081/images/lv202400002/goods/image_2.png")
.items(items)
.build();
}

@DisplayName("상품등록")
@Test
Expand Down Expand Up @@ -104,4 +135,40 @@ public void illegalGoodsTest() throws Exception {
assertThat(jsonPath("errorCode").value("INVALID_PARAMETER"));
assertThat(jsonPath("errorMessage").value("존재하지 않는 상품입니다"));
}

@DisplayName("동시 15개의 요청")
@Test
public void 동시_15개의_요청() throws InterruptedException {
int parallelism = ForkJoinPool.commonPool().getParallelism();
log.error("ForkJoinPool.commonPool() parallelism= {}", parallelism); //7

String url = "http://localhost:" + port + "/v1/goods";
int threadCount = 15;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch(threadCount);

for (int i = 0; i < threadCount; i++) {
int idx = i;
executorService.submit(() -> {
try {
GoodsManagementRequest goodsManagementRequest = requestGoods(idx);
ResponseEntity<GoodsManagementResponse> responseEntity = testRestTemplate.postForEntity(url, goodsManagementRequest, GoodsManagementResponse.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertNotNull(responseEntity.getBody().getGoodsNo());
assertThat(responseEntity.getBody().getResultCode()).isEqualTo("0000");
goodsNo = responseEntity.getBody().getGoodsNo();
String goodsNm = responseEntity.getBody().getGoodsNm();
log.error("등록된 상품번호 = {}", goodsNo);
log.error("등록된 상품명 = {}", goodsNm);
log.error("등록된 idx = {}", idx);
assertThat(responseEntity.getBody().getResultCode()).isEqualTo("0000");
} finally {
latch.countDown();
}
});
}
latch.await();
executorService.shutdown();
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.cosmetics.goods.controller;
package com.cosmetics.controller;

import com.cosmetics.api.goods.controller.GoodsController;
import com.cosmetics.api.controller.GoodsController;
import com.cosmetics.domain.exception.custom.CustomException;
import com.cosmetics.domain.exception.error.GoodsErrorManagement;
import com.cosmetics.domain.goods.dto.GoodsManagementRequest;
import com.cosmetics.domain.goods.dto.item.GoodsItemManagementRequest;
import com.cosmetics.domain.goods.service.GoodsService;
import com.cosmetics.domain.sms.service.SmsService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -25,7 +26,6 @@
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
Expand All @@ -41,6 +41,8 @@ public class GoodsControllerTest2 {

@MockBean
private GoodsService goodsService;
@MockBean
private SmsService smsService;

@Autowired
private MockMvc mockMvc;
Expand Down
Loading

0 comments on commit cac8fe8

Please sign in to comment.