Skip to content

Commit

Permalink
f-lab-edu#37 CompletableFuture,Async
Browse files Browse the repository at this point in the history
  • Loading branch information
kimsuyeondev committed Jun 24, 2024
1 parent e6a707e commit e142089
Show file tree
Hide file tree
Showing 7 changed files with 522 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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.sample.service.AsyncService;
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 AsyncService asyncService;
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;
}

}

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();
}

}
Loading

0 comments on commit e142089

Please sign in to comment.