Skip to content

Commit

Permalink
Merge branch 'develop' into feat/auction-history
Browse files Browse the repository at this point in the history
  • Loading branch information
wakkpu authored Jan 16, 2024
2 parents b5dfa16 + e19781a commit b24716a
Show file tree
Hide file tree
Showing 18 changed files with 121 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ public void setTimezoneToSeoul() {
@PostConstruct
@Profile({"!test"})
public void setDynamoDB() {
TableUtils.deleteTableIfExists(
dynamoDB, dynamoDBMapper.generateDeleteTableRequest(Auction.class));
// TableUtils.deleteTableIfExists(
// dynamoDB, dynamoDBMapper.generateDeleteTableRequest(Auction.class));

TableUtils.deleteTableIfExists(
dynamoDB, dynamoDBMapper.generateDeleteTableRequest(BidHistory.class));
// TableUtils.deleteTableIfExists(
// dynamoDB, dynamoDBMapper.generateDeleteTableRequest(BidHistory.class));

TableUtils.deleteTableIfExists(
dynamoDB, dynamoDBMapper.generateDeleteTableRequest(AuctionHistory.class));
Expand Down
29 changes: 28 additions & 1 deletion src/main/java/com/dailyon/auctionservice/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.dailyon.auctionservice.config;

import com.dailyon.auctionservice.chat.messaging.RedisChatMessageListener;
import com.dailyon.auctionservice.document.BidHistory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
Expand All @@ -13,7 +17,11 @@
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.HashSet;
import java.util.Objects;
Expand Down Expand Up @@ -53,7 +61,26 @@ ReactiveStringRedisTemplate reactiveStringRedisTemplate(
ReactiveRedisConnectionFactory connectionFactory) {
return new ReactiveStringRedisTemplate(connectionFactory);
}
// Redis Atomic Counter to store no. of total messages sent from multiple app instances.

@Bean("reactiveRedisTemplateForBid")
public ReactiveRedisTemplate<String, BidHistory> reactiveRedisTemplate(
ReactiveRedisConnectionFactory factory) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule()); // Java 8 날짜/시간 모듈 등록
objectMapper.disable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 날짜를 timestamp가 아닌 ISO 형식으로 출력

Jackson2JsonRedisSerializer<BidHistory> serializer =
new Jackson2JsonRedisSerializer<>(BidHistory.class);
serializer.setObjectMapper(objectMapper);
RedisSerializationContext<String, BidHistory> serializationContext =
RedisSerializationContext.<String, BidHistory>newSerializationContext(
new StringRedisSerializer())
.value(serializer)
.build();

return new ReactiveRedisTemplate<>(factory, serializationContext);
}

@Bean
ApplicationRunner applicationRunner(RedisChatMessageListener redisChatMessageListener) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package com.dailyon.auctionservice.config;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
@RequiredArgsConstructor
public class WebClientConfig {
@Value("${endpoint.product-service}")
private String endpoint;
private final Environment env;

@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl(endpoint)
.baseUrl(env.getProperty("endpoint.product-service"))
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 *1024))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import com.dailyon.auctionservice.dto.request.CreateBidRequest;
import com.dailyon.auctionservice.facade.BidFacade;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

@Slf4j
@CrossOrigin("*")
@RestController
@RequestMapping("/bids")
Expand All @@ -17,6 +19,7 @@ public class BidApiController {
@PostMapping("")
public Mono<Long> bidding(
@RequestHeader("memberId") String memberId, @RequestBody CreateBidRequest request) {
log.info("memberId {} ", memberId);
return bidFacade.createBid(request, memberId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public Mono<Sinks.EmitResult> sendMessage(ChatPayload chatMessage) {
}

public Mono<Void> biddingBroadCast(ChatPayload chatPayload) {
log.info("payload {}", chatPayload);
return objectStringConverter
.objectToString(chatPayload)
.flatMap(redisChatMessagePublisher::publishChatMessage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class Auction implements Comparable<Auction> {
@DynamoDBAttribute(attributeName = "start_bid_price")
private Integer startBidPrice;

@DynamoDBAttribute(attributeName = "asking_price")
private Integer askingPrice;

@DynamoDBAttribute(attributeName = "maximum_winner")
private Integer maximumWinner;

Expand All @@ -55,13 +58,15 @@ public static Auction create(
Long auctionProductId,
String auctionName,
Integer startBidPrice,
Integer askingPrice,
Integer maximumWinner,
LocalDateTime startAt
) {
return Auction.builder()
.auctionProductId(auctionProductId)
.auctionName(auctionName)
.startBidPrice(startBidPrice)
.askingPrice(askingPrice)
.maximumWinner(maximumWinner)
.startAt(startAt)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class CreateAuctionRequest {
private String auctionName;
private LocalDateTime startAt;
private Integer startBidPrice;
private Integer askingPrice;
private Integer maximumWinner;

private CreateProductRequest productRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public static class ReadAuctionResponse {
private Long auctionProductId;
private String auctionName;
private Integer startBidPrice;
private Integer askingPrice;
private Integer maximumWinner;
private LocalDateTime startAt;
private boolean isStarted;
Expand All @@ -47,6 +48,7 @@ public static ReadAuctionResponse of(Auction auction) {
.auctionProductId(auction.getAuctionProductId())
.auctionName(auction.getAuctionName())
.startBidPrice(auction.getStartBidPrice())
.askingPrice(auction.getAskingPrice())
.maximumWinner(auction.getMaximumWinner())
.startAt(auction.getStartAt())
.isStarted(auction.isStarted())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public static class ReadAuctionResponse {
private Long auctionProductId;
private String auctionName;
private Integer startBidPrice;
private Integer askingPrice;
private Integer maximumWinner;
private LocalDateTime startAt;
private boolean isStarted;
Expand All @@ -50,6 +51,7 @@ public static ReadAuctionResponse of(Auction auction) {
.auctionProductId(auction.getAuctionProductId())
.auctionName(auction.getAuctionName())
.startBidPrice(auction.getStartBidPrice())
.askingPrice(auction.getAskingPrice())
.maximumWinner(auction.getMaximumWinner())
.startAt(auction.getStartAt())
.isStarted(auction.isStarted())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
package com.dailyon.auctionservice.dto.response;

import com.dailyon.auctionservice.document.BidHistory;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class TopBidderResponse {
private String memberId;
private String nickname;
private Long bidAmount;
private String memberId;
private String nickname;
private Long bidAmount;

public static TopBidderResponse from(BidHistory bidHistory) {
return TopBidderResponse.builder()
.memberId(bidHistory.getMemberId())
.nickname(bidHistory.getNickname())
.bidAmount(bidHistory.getBidAmount())
.build();
}
;
}
20 changes: 17 additions & 3 deletions src/main/java/com/dailyon/auctionservice/facade/BidFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
import com.dailyon.auctionservice.controller.ChatHandler;
import com.dailyon.auctionservice.document.Auction;
import com.dailyon.auctionservice.dto.request.CreateBidRequest;
import com.dailyon.auctionservice.dto.response.TopBidderResponse;
import com.dailyon.auctionservice.service.AuctionService;
import com.dailyon.auctionservice.service.BidService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class BidFacade {
Expand All @@ -20,10 +25,19 @@ public class BidFacade {

public Mono<Long> createBid(CreateBidRequest request, String memberId) {
Auction auction = auctionService.readAuction(request.getAuctionId());
Mono<Long> bidAmount = bidService.create(request, memberId);

Integer maximumWinner = auction.getMaximumWinner();
return bidService.create(request, memberId);

Mono<Long> bidAmountMono = bidService.create(request, memberId);
Mono<List<TopBidderResponse>> topBidderMono = bidService.getTopBidder(request, maximumWinner);

return bidAmountMono.flatMap(
bidAmount ->
topBidderMono.flatMap(
topBidders -> {
ChatPayload<List<TopBidderResponse>> payload =
ChatPayload.of(ChatCommand.BIDDING, topBidders);
return chatHandler.biddingBroadCast(payload).thenReturn(bidAmount);
}));
}

public void start() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.dailyon.auctionservice.repository;

import com.dailyon.auctionservice.document.BidHistory;
import com.dailyon.auctionservice.dto.request.CreateBidRequest;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveZSetOperations;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.List;

@Profile("!test")
@Repository
Expand All @@ -27,19 +29,20 @@ public ReactiveRedisRepository(
}

public Mono<Void> save(BidHistory history) {
String key = generateKey(history);
String key = generateKey(history.getAuctionId(), history.getRound());
return reactiveRedisZSet
.add(key, history, history.getBidAmount())
.flatMap(success -> reactiveRedisTemplate.expire(key, Duration.ofHours(1L)))
.then();
}

public Mono<List<BidHistory>> getTopBidder(BidHistory history, int range) {
String key = generateKey(history);
return null;
public Flux<BidHistory> getTopBidder(CreateBidRequest request, int maximum) {
String key = generateKey(request.getAuctionId(), request.getRound());
return reactiveRedisZSet.reverseRange(
key, Range.from(Range.Bound.inclusive(0L)).to(Range.Bound.inclusive((long) maximum-1)));
}

private String generateKey(BidHistory history) {
return AUCTION_KEY + history.getAuctionId() + ROUND_KEY + history.getRound();
private String generateKey(String auctionId, String round) {
return AUCTION_KEY + auctionId + ROUND_KEY + round;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public Auction create(CreateAuctionRequest auctionRequest, CreateProductResponse
auctionRequest.getAuctionName(),
auctionRequest.getStartBidPrice(),
auctionRequest.getMaximumWinner(),
auctionRequest.getAskingPrice(),
auctionRequest.getStartAt()
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,11 @@ public Mono<Long> create(CreateBidRequest request, String memberId) {
.then(Mono.just(bidHistoryRepository.save(bidHistory)))
.map(BidHistory::getBidAmount);
}

public Mono<List<TopBidderResponse>> getTopBidder(CreateBidRequest request, int maximumWinner) {
return reactiveRedisRepository
.getTopBidder(request, maximumWinner)
.map(TopBidderResponse::from)
.collectList();
}
}
5 changes: 4 additions & 1 deletion src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ eureka:
instance-id: ${spring.application.name}:${server.port}
# 기본적으로 유레카는 '호스트 이름'으로 접속하는 서비스를 레지스트리에 등록 -> 로컬(localhost)
# 따라서 쿠버네티스, 도커와 같이 임의의 호스트 이름을 가지는 경우는 false로 하면 클라이언트를 인식하지 못한다.
prefer-ip-address: true
prefer-ip-address: true

endpoint:
product-service: http://localhost:8085
5 changes: 4 additions & 1 deletion src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ eureka:
instance-id: ${spring.application.name}:${server.port}
# 기본적으로 유레카는 '호스트 이름'으로 접속하는 서비스를 레지스트리에 등록 -> 로컬(localhost)
# 따라서 쿠버네티스, 도커와 같이 임의의 호스트 이름을 가지는 경우는 false로 하면 클라이언트를 인식하지 못한다.
prefer-ip-address: true
prefer-ip-address: true

endpoint:
product-service: http://product-service:8085
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,13 @@
@AutoConfigureMockMvc
@ActiveProfiles("test")
@TestPropertySource(
properties = {
"cloud.aws.dynamodb.endpoint=http://localhost:8000",
"cloud.aws.credentials.ACCESS_KEY_ID=testkey",
"cloud.aws.credentials.SECRET_ACCESS_KEY=testkey",
"secretKey=test"
})
properties = {
"cloud.aws.dynamodb.endpoint=http://localhost:8000",
"cloud.aws.credentials.ACCESS_KEY_ID=testkey",
"cloud.aws.credentials.SECRET_ACCESS_KEY=testkey",
"secretKey=testkey"
})
public class IntegrationTestSupport {
@MockBean
ReactiveRedisRepository reactiveRedisRepository;

@MockBean
ChatHandler chatHandler;
@MockBean ReactiveRedisRepository reactiveRedisRepository;
@MockBean ChatHandler chatHandler;
}
2 changes: 1 addition & 1 deletion src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ logging:
root: DEBUG

endpoint:
product-service: localhost:8085
product-service: http://localhost:8085

0 comments on commit b24716a

Please sign in to comment.