Skip to content
This repository has been archived by the owner on Aug 13, 2022. It is now read-only.

[#55] 카테고리 서비스 로직 리팩토링 #57

Open
wants to merge 2 commits into
base: issue/38
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@
*/

plugins {
id 'org.springframework.boot' version '2.4.4'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}

repositories {
mavenCentral()
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
}

test {
useJUnitPlatform()
}
Expand All @@ -18,22 +26,22 @@ dependencies {

//group: '', name: '', version: '' 을 아래와 같인 단순화하여 :을 통해 분리합니다.

implementation 'org.springframework.boot:spring-boot-starter:latest.release'
implementation 'org.springframework.boot:spring-boot-starter-web:latest.release'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:latest.release'
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

compileOnly 'org.projectlombok:lombok:latest.release'
annotationProcessor 'org.projectlombok:lombok:latest.release'

compile 'javax.xml.bind:jaxb-api:latest.release'

implementation 'org.springframework.data:spring-data-redis:latest.release'
implementation 'org.springframework.data:spring-data-redis'
implementation 'io.lettuce:lettuce-core:latest.release'
implementation 'org.springframework.session:spring-session-data-redis:latest.release'
implementation 'org.springframework.boot:spring-boot-starter-validation:latest.release'
implementation 'org.springframework.session:spring-session-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-validation'
runtimeOnly 'mysql:mysql-connector-java:latest.release'

testCompile 'org.springframework.boot:spring-boot-starter-test:latest.release'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

group = 'com.ht.project'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
package com.ht.project.realtimedeliverymarket.category.controller;

import com.ht.project.realtimedeliverymarket.category.model.dto.CategoryParam;
import com.ht.project.realtimedeliverymarket.category.service.CategoryService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequiredArgsConstructor
@RequestMapping("/categories")
public class CategoryController {

private final CategoryService categoryService;

@PostMapping
public HttpStatus addMainCategory (@RequestBody String name) {
@PostMapping("/")
public HttpStatus addMainCategories (@RequestBody @Valid CategoryParam categoryParam) {

categoryService.addMainCategory(name);
return HttpStatus.OK;
categoryService.addMainCategories(categoryParam);
return HttpStatus.CREATED;
}

@PostMapping("/{categoryId}")
public HttpStatus addSubCategories (@PathVariable Long categoryId,
@RequestBody @Valid CategoryParam categoryParam) {

@PostMapping("/{id}")
public HttpStatus addSubCategory (@RequestBody String name, @PathVariable Long id) {

categoryService.addSubCategory(id, name);
return HttpStatus.OK;
categoryService.addSubCategories(categoryId, categoryParam);
return HttpStatus.CREATED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.ht.project.realtimedeliverymarket.category.exception;

public class DuplicateCategoryException extends RuntimeException {

public DuplicateCategoryException() {
}

public DuplicateCategoryException(String message) {
super(message);
}

public DuplicateCategoryException(String message, Throwable cause) {
super(message, cause);
}

public DuplicateCategoryException(Throwable cause) {
super(cause);
}

public DuplicateCategoryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.ht.project.realtimedeliverymarket.category.exception;

public class NotExistCategoryException extends RuntimeException{

public NotExistCategoryException() {
}

public NotExistCategoryException(String message) {
super(message);
}

public NotExistCategoryException(String message, Throwable cause) {
super(message, cause);
}

public NotExistCategoryException(Throwable cause) {
super(cause);
}

public NotExistCategoryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ht.project.realtimedeliverymarket.category.model.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

import javax.validation.constraints.NotBlank;
import java.util.List;

@Getter
@AllArgsConstructor
public class CategoryParam {

List<@NotBlank(message = "카테고리명은 필수입니다.") String> categoryNames;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

import com.ht.project.realtimedeliverymarket.category.model.entity.Category;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

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

@Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {

Optional<Category> findByName(@Param("categories_name") String name);
List<Category> findAllByNameIn(List<String> categoryNames);
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,74 @@
package com.ht.project.realtimedeliverymarket.category.service;

import com.ht.project.realtimedeliverymarket.category.exception.DuplicateCategoryException;
import com.ht.project.realtimedeliverymarket.category.exception.NotExistCategoryException;
import com.ht.project.realtimedeliverymarket.category.model.dto.CategoryParam;
import com.ht.project.realtimedeliverymarket.category.model.entity.Category;
import com.ht.project.realtimedeliverymarket.category.repository.CategoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CategoryService {

private final CategoryRepository categoryRepository;

public void addMainCategory(String name) {
/*
Entity 설계 시, N 쪽에 외래키를 위치시켰기 때문에
데이터를 조작할 때에도 N에 해당하는 child Category 데이터를 조작해야만 값이 적용됩니다.
*/

@Transactional
public void addMainCategories(CategoryParam categoryParam) {

categoryRepository.saveAll(createNewCategories(categoryParam.getCategoryNames()));
}

@Transactional
public void addSubCategories(Long categoryId, CategoryParam categoryParam) {

categoryRepository.saveAll(createNewCategories(categoryId, categoryParam.getCategoryNames()));
}

private List<Category> createNewCategories(List<String> categoryNames) {

isDuplicatedCategoryName(categoryNames);
List<Category> newCategories = new ArrayList<>();
categoryNames.forEach(c -> newCategories.add(new Category(c)));

categoryRepository.findByName(name)
.ifPresent(s -> {throw new IllegalArgumentException();});
categoryRepository.save(new Category(name));
return newCategories;
}

public void addSubCategory(Long id, String name) {
private List<Category> createNewCategories(Long categoryId, List<String> categoryNames) {

Category parentCategory =
categoryRepository.findById(categoryId)
.orElseThrow(
() -> new NotExistCategoryException("부모 카테고리가 존재하지 않습니다."));

isDuplicatedCategoryName(categoryNames);
List<Category> newCategories = new ArrayList<>();
categoryNames.forEach(c -> {

Category newCategory = new Category(c);
newCategory.setParent(parentCategory);
newCategories.add(newCategory);
});

return newCategories;
}

private void isDuplicatedCategoryName(List<String> categoryNames) {

if (!categoryRepository.findAllByNameIn(categoryNames).isEmpty()) {

categoryRepository.findByName(name).ifPresent(s -> {throw new IllegalArgumentException();});
categoryRepository.save(new Category(name,
categoryRepository.findById(id).orElseThrow(IllegalArgumentException::new)));
throw new DuplicateCategoryException("중복된 카테고리가 존재합니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.ht.project.realtimedeliverymarket;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import javax.validation.*;
import java.util.Optional;

public class CategoryParamValidationTest {

private Validator validator;

@BeforeEach
public void setUp() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}

private void validateBean(Object bean) throws AssertionError {
Optional<ConstraintViolation<Object>> violation = validator.validate(bean).stream().findFirst();
violation.ifPresent(v -> {
throw new ValidationException(violation.get().getMessage());
});
}

@Test
@DisplayName("올바른 Category 매개변수가 주어지면 유효성 검사를 통과합니다.")
public void givenRightCategoryParamValidationPassed() {

//TODO 테스트 추가
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ht.project.realtimedeliverymarket;

import com.ht.project.realtimedeliverymarket.category.exception.DuplicateCategoryException;
import com.ht.project.realtimedeliverymarket.category.model.dto.CategoryParam;
import com.ht.project.realtimedeliverymarket.category.model.entity.Category;
import com.ht.project.realtimedeliverymarket.category.repository.CategoryRepository;
import com.ht.project.realtimedeliverymarket.category.service.CategoryService;
Expand All @@ -9,9 +11,9 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.Optional;
import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
Expand All @@ -26,37 +28,49 @@ public class CategoryServiceTest {
private CategoryService categoryService;

@Test
@DisplayName("메인 카테고리가 중복되면 예외가 발생합니다.")
public void insertDuplicatedMainCategoryThrowsException() {
@DisplayName("메인 카테고리 이름이 중복되면 DuplicateCategoryException이 발생합니다.")
public void givenDuplicatedMainCategoryNameThrowsException() {

when(categoryRepository.findByName("고기류")).thenReturn(java.util.Optional.of(new Category()));
//given
List<String> categoryNames = new ArrayList<>();
categoryNames.add("고기류");

assertThrows(IllegalArgumentException.class, () -> ReflectionTestUtils
.invokeMethod(categoryService,
"addMainCategory",
"고기류"));
List<Category> categories = new ArrayList<>();
categories.add(new Category("고기류"));

//when
when(categoryRepository.findAllByNameIn(categoryNames)).thenReturn(categories);

//then
assertThrows(DuplicateCategoryException.class,
() -> categoryService.addMainCategories(new CategoryParam(categoryNames)));
}

@Test
@DisplayName("서브 카테고리가 중복되면 예외가 발생합니다.")
public void insertDuplicatedSubCategoryThrowsException() {
@DisplayName("메인 카테고리가 존재하지 않으면 NotExistCategoryException이 발생합니다.")
public void givenWrongParentIdWhenFindCategoryByIdThrowsException() {

//TODO 테스트 추가
}

when(categoryRepository.findByName("돼지고기")).thenReturn(java.util.Optional.of(new Category()));
@Test
@DisplayName("서브 카테고리 이름이 중복되면 DuplicateCategoryException이 발생합니다.")
public void givenDuplicatedSubCategoryNameThrowsException() {

assertThrows(IllegalArgumentException.class, () -> ReflectionTestUtils
.invokeMethod(categoryService,
"addSubCategory",
1L, "돼지고기"));
//TODO 테스트 추가
}

@Test
@DisplayName("메인 카테고리가 존재하지 않으면 예외가 발생합니다.")
public void insertSubCategoryThrowsException() {
@DisplayName("정상적인 카테고리 이름들이 입력되면 메인 카테고리 추가가 성공합니다.")
public void givenRightCategoryParamAddMainCategoriesPassed() {

//TODO 테스트 추가
}

when(categoryRepository.findById(1L)).thenReturn(Optional.empty());
@Test
@DisplayName("정상적인 부모 카테고리 id와 카테고리 이름들이 입력되면 서브 카테고리 추가가 성공합니다.")
public void givenRightParentCategoryIdAndCategoryParamAddSubCategoriesPassed() {

assertThrows(IllegalArgumentException.class, () ->
ReflectionTestUtils
.invokeMethod(categoryService, "addSubCategory", 1L, "돼지고기"));
//TODO 테스트 추가
}
}