Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] redis를 이용하여 cart를 저장/조회/삭제하도록 구현 #134

4 changes: 1 addition & 3 deletions src/main/java/camp/woowak/lab/cart/domain/Cart.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,10 @@ public Cart(String customerId) {
}

/**
* 해당 Domain을 사용하는 같은 패키지내의 클래스, 혹은 자식 클래스는 List를 커스텀할 수 있습니다.
*
* @param customerId 장바구니 소유주의 ID값입니다.
* @param cartItems 장바구니에 사용될 List입니다.
*/
protected Cart(String customerId, List<CartItem> cartItems) {
public Cart(String customerId, List<CartItem> cartItems) {
this.customerId = customerId;
this.cartItems = cartItems;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package camp.woowak.lab.cart.persistence.redis.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import camp.woowak.lab.cart.domain.Cart;
import lombok.Getter;

@RedisHash("cart")
@Getter
public class RedisCartEntity implements Serializable {
@Id
private String customerId;

private List<RedisCartItemEntity> cartItems;

@JsonCreator
private RedisCartEntity(@JsonProperty("customerId") String customerId,
@JsonProperty("cartItems") List<RedisCartItemEntity> cartItems) {
this.customerId = customerId;
this.cartItems = cartItems == null ? new ArrayList<>() : cartItems;
}

public static RedisCartEntity fromDomain(Cart cart) {
List<RedisCartItemEntity> list = cart.getCartItems().stream()
.map(RedisCartItemEntity::fromDomain)
.toList();
return new RedisCartEntity(cart.getCustomerId(), list);
}

public Cart toDomain() {
return new Cart(customerId, cartItems.stream()
.map(RedisCartItemEntity::toDomain)
.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package camp.woowak.lab.cart.persistence.redis.entity;

import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import camp.woowak.lab.cart.domain.vo.CartItem;
import lombok.Getter;

@Getter
public class RedisCartItemEntity implements Serializable {
private Long menuId;
private Long storeId;
private int amount;

@JsonCreator
private RedisCartItemEntity(@JsonProperty("menuId") Long menuId,
@JsonProperty("storeId") Long storeId,
@JsonProperty("amount") int amount) {
this.menuId = menuId;
this.storeId = storeId;
this.amount = amount;
}

protected static RedisCartItemEntity fromDomain(CartItem item) {
return new RedisCartItemEntity(item.getMenuId(), item.getStoreId(), item.getAmount());
}

protected CartItem toDomain() {
return new CartItem(menuId, storeId, amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package camp.woowak.lab.cart.persistence.redis.repository;

import java.util.Optional;

import org.springframework.data.repository.CrudRepository;

import camp.woowak.lab.cart.domain.Cart;
import camp.woowak.lab.cart.persistence.redis.entity.RedisCartEntity;
import camp.woowak.lab.cart.repository.CartRepository;

public interface RedisCartRepository extends CrudRepository<RedisCartEntity, String>, CartRepository {
@Override
default Optional<Cart> findByCustomerId(String customerId) {
return findById(customerId).map(RedisCartEntity::toDomain);
}

@Override
default Cart save(Cart cart) {
RedisCartEntity entity = RedisCartEntity.fromDomain(cart);
return save(entity).toDomain();
}

@Override
default void delete(Cart cart) {
deleteById(cart.getCustomerId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

import camp.woowak.lab.cart.domain.Cart;
Expand All @@ -12,6 +13,7 @@
* TODO : Null체크에 대한 Validation
*/
@Repository
@Primary
public class InMemoryCartRepository implements CartRepository {
private static final Map<String, Cart> cartMap = new ConcurrentHashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ public long getTotalPrice(CartTotalPriceCommand command) {

private Cart getCart(String customerId) {
return cartRepository.findByCustomerId(customerId)
.orElse(cartRepository.save(new Cart(customerId)));
.orElseGet(() -> cartRepository.save(new Cart(customerId)));
}
}
35 changes: 35 additions & 0 deletions src/main/java/camp/woowak/lab/infra/config/RedisConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package camp.woowak.lab.infra.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.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableRedisRepositories
public class RedisConfiguration {
@Value("${spring.data.redis.port}")
private int redisPort;
@Value("${spring.data.redis.host}")
private String redisHost;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());

template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
6 changes: 5 additions & 1 deletion src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ spring:
properties:
hibernate:
format_sql: true
show_sql: true
show_sql: true
data:
redis:
host: localhost #변경예정
port: 6379
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package camp.woowak.lab.cart.persistence.redis.repository;

import static org.assertj.core.api.Assertions.*;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import camp.woowak.lab.cart.domain.Cart;
import camp.woowak.lab.cart.domain.vo.CartItem;

@SpringBootTest
@Testcontainers
@ContextConfiguration(initializers = {RedisCartRepositoryTest.Initializer.class})
public class RedisCartRepositoryTest {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Container
public static GenericContainer<?> redis = new GenericContainer<>("redis:6-alpine")
.withExposedPorts(6379);

static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort()
).applyTo(configurableApplicationContext.getEnvironment());
}
}

@Autowired
private RedisCartRepository repository;

private static final String CUSTOMER_ID_EXIST = UUID.randomUUID().toString();
private static final String CUSTOMER_ID_NOT_EXIST = UUID.randomUUID().toString();
private Cart cart;

@BeforeEach
void setUp() {
List<CartItem> cartItemList = List.of(new CartItem(1L, 1L, 1),
new CartItem(2L, 2L, 2),
new CartItem(3L, 3L, 3));
cart = new Cart(CUSTOMER_ID_EXIST, cartItemList);
repository.save(cart);
}

@AfterEach
void clear() {
repository.delete(cart);
redisTemplate.execute((RedisConnection connection) -> {
connection.serverCommands().flushAll();
return null;
});
}

@Nested
@DisplayName("findByCustomerId 메서드")
class FindByCustomerIdTest {
@Test
@DisplayName("customerId에 해당하는 Cart가 없으면 Optional(null)을 반환한다.")
void NullReturnWhenCustomerIdNotExists() {
//when
Optional<Cart> foundCart = repository.findByCustomerId(CUSTOMER_ID_NOT_EXIST);

//then
assertThat(foundCart).isEmpty();
}

@Test
@DisplayName("customerId에 해당하는 Cart가 있으면 해당 Cart를 반환한다.")
void cartReturnWhenCustomerIdExists() {
//when
Optional<Cart> foundCart = repository.findByCustomerId(CUSTOMER_ID_EXIST);

//then
assertThat(foundCart).isPresent();
assertCart(cart, foundCart.get());
}
}

@Nested
@DisplayName("save 메서드")
class SaveTest {
@Test
@DisplayName("cart를 저장할 수 있다.")
void saveTest() {
//given
Cart newCart = new Cart(CUSTOMER_ID_NOT_EXIST);

//when
Cart savedCart = repository.save(newCart);

//then
assertCart(savedCart, newCart);
Optional<Cart> foundCart = repository.findByCustomerId(CUSTOMER_ID_NOT_EXIST);
assertThat(foundCart).isPresent();
Cart foundedCart = foundCart.get();
assertCart(savedCart, foundedCart);
}
}

@Nested
@DisplayName("delete 메서드")
class DeleteTest {
@Test
@DisplayName("존재하는 customerId의 cart를 삭제할 수 있다.")
void deleteTest() {
//when
repository.delete(cart);

//then
Optional<Cart> foundCart = repository.findByCustomerId(CUSTOMER_ID_EXIST);
assertThat(foundCart).isEmpty();
}
}

private void assertCart(Cart expected, Cart actual) {
assertThat(actual.getCustomerId()).isEqualTo(expected.getCustomerId());
assertThat(actual.getCartItems().size()).isEqualTo(expected.getCartItems().size());
for (int i = 0; i < expected.getCartItems().size(); i++) {
assertThat(actual.getCartItems().get(i).getAmount()).isEqualTo(expected.getCartItems().get(i).getAmount());
assertThat(actual.getCartItems().get(i).getMenuId()).isEqualTo(expected.getCartItems().get(i).getMenuId());
assertThat(actual.getCartItems().get(i).getStoreId()).isEqualTo(
expected.getCartItems().get(i).getStoreId());
}
}
}
60 changes: 0 additions & 60 deletions src/test/java/camp/woowak/lab/container/ContainerSettingTest.java

This file was deleted.