diff --git a/build.gradle b/build.gradle index 814f8402..5008ef47 100644 --- a/build.gradle +++ b/build.gradle @@ -38,11 +38,14 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'mysql:mysql-connector-java:8.0.33' implementation group: 'io.github.lotteon-maven', name: 'blooming-blooms-utils', version: '0.1.0-alpha2' - // https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-dynamodb - implementation group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '1.12.592' runtimeOnly 'com.h2database:h2' implementation 'org.mapstruct:mapstruct:1.5.3.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-mongodb + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb', version: '2.7.17' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo' } dependencyManagement { diff --git a/src/main/java/kr/bb/product/ProductServiceApplication.java b/src/main/java/kr/bb/product/ProductServiceApplication.java index 255ac993..35e728d4 100644 --- a/src/main/java/kr/bb/product/ProductServiceApplication.java +++ b/src/main/java/kr/bb/product/ProductServiceApplication.java @@ -4,10 +4,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.mongodb.config.EnableReactiveMongoAuditing; @SpringBootApplication @EnableEurekaClient @EnableJpaAuditing +@EnableReactiveMongoAuditing public class ProductServiceApplication { public static void main(String[] args) { diff --git a/src/main/java/kr/bb/product/config/DynamoDBConfiguration.java b/src/main/java/kr/bb/product/config/DynamoDBConfiguration.java deleted file mode 100644 index 385a0180..00000000 --- a/src/main/java/kr/bb/product/config/DynamoDBConfiguration.java +++ /dev/null @@ -1,57 +0,0 @@ -package kr.bb.product.config; - -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -@Configuration -public class DynamoDBConfiguration { - @Value("${aws.dynamodb.endpoint}") - private String endPoint; - - @Value("${aws.region}") - private String region; - - @Value("${aws.accessKey}") - private String accessKey; - - @Value("${aws.secretKey}") - private String secretKey; - - @Bean - public DynamoDBMapper dynamoDBMapper() { - DynamoDBMapperConfig mapperConfig = - DynamoDBMapperConfig.builder() - .withSaveBehavior(DynamoDBMapperConfig.SaveBehavior.CLOBBER) - .withConsistentReads(DynamoDBMapperConfig.ConsistentReads.CONSISTENT) - .withTableNameOverride(null) - .withPaginationLoadingStrategy( - DynamoDBMapperConfig.PaginationLoadingStrategy.EAGER_LOADING) - .build(); - - return new DynamoDBMapper(amazonDynamoDB(), mapperConfig); - } - - @Primary - @Bean - public AWSCredentialsProvider awsCredentialsProvider() { - return new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)); - } - - @Bean - public AmazonDynamoDB amazonDynamoDB() { - return AmazonDynamoDBClientBuilder.standard() - .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, region)) - .withCredentials(awsCredentialsProvider()) - .build(); - } -} diff --git a/src/main/java/kr/bb/product/dto/category/Category.java b/src/main/java/kr/bb/product/dto/category/Category.java index fc041d03..68f987bf 100644 --- a/src/main/java/kr/bb/product/dto/category/Category.java +++ b/src/main/java/kr/bb/product/dto/category/Category.java @@ -6,12 +6,14 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor +@ToString public class Category extends BaseEntity { - private Long id; + private Long categoryId; private String categoryName; } diff --git a/src/main/java/kr/bb/product/dto/request/ProductRequestData.java b/src/main/java/kr/bb/product/dto/request/ProductRequestData.java new file mode 100644 index 00000000..d46c0aa5 --- /dev/null +++ b/src/main/java/kr/bb/product/dto/request/ProductRequestData.java @@ -0,0 +1,31 @@ +package kr.bb.product.dto.request; + +import javax.validation.constraints.NotBlank; +import kr.bb.product.dto.category.Category; +import kr.bb.product.dto.tag.Tag; +import kr.bb.product.entity.Flowers; +import kr.bb.product.entity.ProductSaleStatus; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class ProductRequestData { + private Category category; + @NotBlank private String productName; + @NotBlank private String productSummary; + @NotBlank private Long productPrice; + @NotBlank private ProductSaleStatus productSaleStatus; + private Tag tag; + private Flowers productFlowers; + @NotBlank private String productDescriptionImage; + @NotBlank private Long reviewCount; + @Builder.Default @NotBlank private Long productSaleAmount = 0L; + @NotBlank private Double averageRating; + @NotBlank private Long storeId; +} diff --git a/src/main/java/kr/bb/product/dto/tag/Tag.java b/src/main/java/kr/bb/product/dto/tag/Tag.java index 5daccdb5..131befec 100644 --- a/src/main/java/kr/bb/product/dto/tag/Tag.java +++ b/src/main/java/kr/bb/product/dto/tag/Tag.java @@ -12,6 +12,6 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Tag extends BaseEntity { - private Long id; - private String categoryName; + private Long tagId; + private String tagName; } diff --git a/src/main/java/kr/bb/product/entity/Product.java b/src/main/java/kr/bb/product/entity/Product.java index cac87f68..b4d75fa3 100644 --- a/src/main/java/kr/bb/product/entity/Product.java +++ b/src/main/java/kr/bb/product/entity/Product.java @@ -1,9 +1,8 @@ package kr.bb.product.entity; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; -import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; +import java.time.LocalDateTime; +import javax.persistence.Id; +import javax.validation.constraints.NotBlank; import kr.bb.product.dto.category.Category; import kr.bb.product.dto.tag.Tag; import lombok.AccessLevel; @@ -11,48 +10,49 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -@DynamoDBTable(tableName = "product") +@ToString +@Document(collation = "product") public class Product { - @DynamoDBHashKey @DynamoDBAutoGeneratedKey private Long productId; + @Id @NotBlank private String productId; - @DynamoDBAttribute(attributeName = "category") private Category category; - @DynamoDBAttribute(attributeName = "product_name") - private String productName; + @NotBlank private String productName; - @DynamoDBAttribute(attributeName = "product_summary") - private String productSummary; + @NotBlank private String productSummary; - @DynamoDBAttribute(attributeName = "product_price") - private Long productPrice; + @NotBlank private Long productPrice; - @DynamoDBAttribute(attributeName = "product_sale_status") - private ProductSaleStatus productSaleStatus; + @NotBlank private ProductSaleStatus productSaleStatus; - @DynamoDBAttribute(attributeName = "tag") private Tag tag; - @DynamoDBAttribute(attributeName = "product_flowers") private Flowers productFlowers; - @DynamoDBAttribute(attributeName = "product_description_image") - private String productDescriptionImage; + @NotBlank private String productDescriptionImage; + @NotBlank private Long reviewCount; + @Builder.Default @NotBlank private Long productSaleAmount = 0L; - @DynamoDBAttribute(attributeName = "review_count") - private Long reviewCount; + @NotBlank private Double averageRating; - @DynamoDBAttribute(attributeName = "product_sale_amount") - private Long productSaleAmount; + @NotBlank private Long storeId; - @DynamoDBAttribute(attributeName = "average_rating") - private Double averageRating; + @NotBlank @CreatedDate private LocalDateTime createdAt; - @DynamoDBAttribute(attributeName = "store_id") - private Long storeId; + @NotBlank @LastModifiedDate private LocalDateTime updatedAt; + + @NotBlank + @Builder.Default + @Field(name = "is_deleted") + private Boolean isDeleted = false; } diff --git a/src/main/java/kr/bb/product/mapper/ProductMapper.java b/src/main/java/kr/bb/product/mapper/ProductMapper.java new file mode 100644 index 00000000..21d466f4 --- /dev/null +++ b/src/main/java/kr/bb/product/mapper/ProductMapper.java @@ -0,0 +1,15 @@ +package kr.bb.product.mapper; + +import kr.bb.product.dto.request.ProductRequestData; +import kr.bb.product.entity.Product; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(componentModel = "spring") +public interface ProductMapper { + @Mapping(target = "productId", ignore = true) + @Mapping(target = "createdAt", ignore = true) + @Mapping(target = "updatedAt", ignore = true) + @Mapping(target = "isDeleted", ignore = true) + Product entityToData(ProductRequestData productRequestData); +} diff --git a/src/main/java/kr/bb/product/repository/ProductMongoRepository.java b/src/main/java/kr/bb/product/repository/ProductMongoRepository.java new file mode 100644 index 00000000..452b0edd --- /dev/null +++ b/src/main/java/kr/bb/product/repository/ProductMongoRepository.java @@ -0,0 +1,6 @@ +package kr.bb.product.repository; + +import kr.bb.product.entity.Product; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface ProductMongoRepository extends MongoRepository {} diff --git a/src/main/java/kr/bb/product/service/ProductService.java b/src/main/java/kr/bb/product/service/ProductService.java new file mode 100644 index 00000000..113cd3e4 --- /dev/null +++ b/src/main/java/kr/bb/product/service/ProductService.java @@ -0,0 +1,18 @@ +package kr.bb.product.service; + +import kr.bb.product.dto.request.ProductRequestData; +import kr.bb.product.mapper.ProductMapper; +import kr.bb.product.repository.ProductMongoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ProductService { + private final ProductMongoRepository productMongoRepository; + private final ProductMapper productMapper; + + public void createProduct(ProductRequestData productRequestData) { + productMongoRepository.save(productMapper.entityToData(productRequestData)); + } +} diff --git a/src/test/java/kr/bb/product/config/DynamoDBConfigurationTest.java b/src/test/java/kr/bb/product/config/DynamoDBConfigurationTest.java deleted file mode 100644 index bf779811..00000000 --- a/src/test/java/kr/bb/product/config/DynamoDBConfigurationTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package kr.bb.product.config; - -import static org.assertj.core.api.BDDAssertions.then; - -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; -import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; -import com.amazonaws.services.dynamodbv2.model.AttributeDefinition; -import com.amazonaws.services.dynamodbv2.model.AttributeValue; -import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; -import com.amazonaws.services.dynamodbv2.model.GetItemRequest; -import com.amazonaws.services.dynamodbv2.model.GetItemResult; -import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex; -import com.amazonaws.services.dynamodbv2.model.KeySchemaElement; -import com.amazonaws.services.dynamodbv2.model.KeyType; -import com.amazonaws.services.dynamodbv2.model.Projection; -import com.amazonaws.services.dynamodbv2.model.ProjectionType; -import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; -import com.amazonaws.services.dynamodbv2.model.PutItemRequest; -import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType; -import com.amazonaws.services.dynamodbv2.util.TableUtils; -import java.util.HashMap; -import java.util.Map; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -class DynamoDBConfigurationTest { - private AmazonDynamoDB amazonDynamoDb; - private Map item; - - @BeforeEach - void setup() { - AWSCredentials awsCredentials = new BasicAWSCredentials("key1", "key2"); - AWSCredentialsProvider awsCredentialsProvider = - new AWSStaticCredentialsProvider(awsCredentials); - EndpointConfiguration endpointConfiguration = - new EndpointConfiguration("http://localhost:9000", "ap-northeast-2"); - - amazonDynamoDb = - AmazonDynamoDBClientBuilder.standard() - .withCredentials(awsCredentialsProvider) - .withEndpointConfiguration(endpointConfiguration) - .build(); - - item = new HashMap<>(); - item.put("id", (new AttributeValue()).withS("uuid")); - item.put("orders", (new AttributeValue()).withN("1")); - item.put("content", (new AttributeValue()).withS("comment content")); - item.put("deleted", (new AttributeValue()).withBOOL(false)); - item.put("createdAt", (new AttributeValue()).withS("to be changed")); - item.put("deletedAt", (new AttributeValue()).withS("to be changed")); - } - - @Test - @Disabled - void createTable() { - CreateTableRequest createTableRequest = - (new CreateTableRequest()) - .withAttributeDefinitions( - new AttributeDefinition("id", ScalarAttributeType.S), - new AttributeDefinition("orders", ScalarAttributeType.N), - new AttributeDefinition("createdAt", ScalarAttributeType.S)) - .withTableName("flower") - .withKeySchema(new KeySchemaElement("id", KeyType.HASH)) - .withGlobalSecondaryIndexes( - (new GlobalSecondaryIndex()) - .withIndexName("byFlower") - .withKeySchema( - new KeySchemaElement("orders", KeyType.HASH), - new KeySchemaElement("createdAt", KeyType.RANGE)) - .withProjection((new Projection()).withProjectionType(ProjectionType.ALL)) - .withProvisionedThroughput(new ProvisionedThroughput(1L, 1L))) - .withProvisionedThroughput(new ProvisionedThroughput(1L, 1L)); - - boolean hasTableBeenCreated = - TableUtils.createTableIfNotExists(amazonDynamoDb, createTableRequest); - then(hasTableBeenCreated).isFalse(); - } - - @Test - @Disabled - void getItem_ShouldBeCalledAfterPuttingItem_FoundItem() { - PutItemRequest putItemRequest = (new PutItemRequest()).withTableName("flower").withItem(item); - amazonDynamoDb.putItem(putItemRequest); - - Map key = new HashMap<>(); - key.put("id", (new AttributeValue()).withS("uuid")); - - GetItemRequest getItemRequest = (new GetItemRequest()).withTableName("flower").withKey(key); - - GetItemResult getItemResult = amazonDynamoDb.getItem(getItemRequest); - System.out.println(getItemResult); - - then(getItemResult.getItem()).containsAllEntriesOf(item); - } -} diff --git a/src/test/java/kr/bb/product/repository/ProductMongoRepositoryTest.java b/src/test/java/kr/bb/product/repository/ProductMongoRepositoryTest.java new file mode 100644 index 00000000..822e25d9 --- /dev/null +++ b/src/test/java/kr/bb/product/repository/ProductMongoRepositoryTest.java @@ -0,0 +1,49 @@ +package kr.bb.product.repository; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDateTime; +import javax.transaction.Transactional; +import kr.bb.product.dto.category.Category; +import kr.bb.product.dto.tag.Tag; +import kr.bb.product.entity.Flowers; +import kr.bb.product.entity.Product; +import kr.bb.product.entity.ProductSaleStatus; +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; + +@SpringBootTest +@Transactional +class ProductMongoRepositoryTest { + @Autowired ProductMongoRepository productMongoRepository; + + @Test + @DisplayName("상품 등록") + void createProduct() { + Product product = + Product.builder() + .productId("123") + .category(Category.builder().categoryName("category").categoryId(1L).build()) + .productName("Example Product") + .productSummary("Product Summary") + .productPrice(100L) + .productSaleStatus(ProductSaleStatus.SALE) + .tag(Tag.builder().tagName("tagname").tagId(1L).build()) + .productFlowers(Flowers.builder().flowerName("flower1").stock(3L).flowerId(1L).build()) + .productDescriptionImage("image_url") + .reviewCount(5L) + .productSaleAmount(50L) + .averageRating(4.5) + .storeId(1L) + .createdAt(LocalDateTime.now()) + .updatedAt(LocalDateTime.now()) + .build(); + Product save = productMongoRepository.save(product); + assertThat(save.getProductId()).isNotNull(); + + System.out.println(save); + } +} diff --git a/src/test/java/kr/bb/product/service/ProductServiceTest.java b/src/test/java/kr/bb/product/service/ProductServiceTest.java new file mode 100644 index 00000000..545420d8 --- /dev/null +++ b/src/test/java/kr/bb/product/service/ProductServiceTest.java @@ -0,0 +1,89 @@ +package kr.bb.product.service; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import javax.transaction.Transactional; +import kr.bb.product.dto.category.Category; +import kr.bb.product.dto.request.ProductRequestData; +import kr.bb.product.dto.tag.Tag; +import kr.bb.product.entity.Flowers; +import kr.bb.product.entity.Product; +import kr.bb.product.entity.ProductSaleStatus; +import kr.bb.product.mapper.ProductMapper; +import kr.bb.product.repository.ProductMongoRepository; +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; + +@SpringBootTest +@Transactional +class ProductServiceTest { + @Autowired ProductMongoRepository productMongoRepository; + @Autowired ProductMapper productMapper; + @Autowired ProductService productService; + + @Test + @DisplayName("상품 등록 service") + void createProduct() { + ProductRequestData product = + ProductRequestData.builder() + .category(Category.builder().categoryName("category").categoryId(1L).build()) + .productName("Example Product") + .productSummary("Product Summary") + .productPrice(100L) + .productSaleStatus(ProductSaleStatus.SALE) + .tag(Tag.builder().tagName("tagname").tagId(1L).build()) + .productFlowers(Flowers.builder().flowerName("flower1").stock(3L).flowerId(1L).build()) + .productDescriptionImage("image_url") + .reviewCount(5L) + .productSaleAmount(50L) + .averageRating(4.5) + .storeId(1L) + .build(); + Product product1 = productMapper.entityToData(product); + Product save = productMongoRepository.save(product1); + assertThat(product1.getProductId()).isEqualTo(save.getProductId()); + } + + @Test + @DisplayName("상품 등록 태그 null case") + void createProductIfTagIsNull() { + ProductRequestData product = + ProductRequestData.builder() + .category(Category.builder().categoryName("category").categoryId(1L).build()) + .productName("Example Product") + .productSummary("Product Summary") + .productPrice(100L) + .productSaleStatus(ProductSaleStatus.SALE) + .productFlowers(Flowers.builder().flowerName("flower1").stock(3L).flowerId(1L).build()) + .productDescriptionImage("image_url") + .reviewCount(5L) + .productSaleAmount(50L) + .averageRating(4.5) + .storeId(1L) + .build(); + productService.createProduct(product); + } + + @Test + @DisplayName("상품 등록 필수 값 null인 경우 에러 반환") + void createProductIfNotNullTest() { + ProductRequestData product = + ProductRequestData.builder() + .category(Category.builder().categoryName("category").categoryId(1L).build()) + .productSummary("Product Summary") + .productPrice(100L) + .productSaleStatus(ProductSaleStatus.SALE) + .productFlowers(Flowers.builder().flowerName("flower1").stock(3L).flowerId(1L).build()) + .productDescriptionImage("image_url") + .reviewCount(5L) + .productSaleAmount(50L) + .averageRating(4.5) + .storeId(1L) + .build(); + Product product1 = productMapper.entityToData(product); + Product save = productMongoRepository.save(product1); + assertThat(save.getProductName()).isNull(); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 5bd2e59d..42b7f506 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -10,9 +10,6 @@ spring: h2: console: enabled: true -aws: - dynamodb: - endpoint: http://localhost:9000 - region: ap-northease-2 - accessKey: accessKey - secretKey: secretKey \ No newline at end of file + mongodb: + embedded: + version: 3.5.5 \ No newline at end of file