-
Notifications
You must be signed in to change notification settings - Fork 81
4주차 : 고양이 장난감가게 만들기 #92
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
Changes from 36 commits
b6dc20d
b7fab18
7a652f1
7f83bbb
ce8b6da
f152427
c89696c
a3e15d3
a8be822
9fb095d
6987f97
3fcdf27
2629f0c
6e5c2a9
feb3230
07895af
a2ab229
cf1fbd0
e8cafd3
c9a9e37
921019e
377cd7f
b0f3227
c693974
1ba2012
f2c036d
d5e7b72
d72292b
6134110
908298d
95e3bbd
b2395a3
227d922
d237689
07dc77c
22dbd7e
7357651
0e31033
2a2513b
a95ec89
5a564ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.codesoom.assignment.common; | ||
|
|
||
| import com.codesoom.assignment.common.dto.ErrorResponse; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| public abstract class baseException extends RuntimeException { | ||
| public final Map<String, String> validation = new HashMap<>(); | ||
|
|
||
| public baseException() { | ||
| } | ||
|
|
||
| public baseException(String message) { | ||
| super(message); | ||
| } | ||
|
|
||
| public abstract int getStatusCode(); | ||
|
|
||
| public void addValidation(String fieldName, String message) { | ||
| validation.put(fieldName, message); | ||
| } | ||
|
|
||
| public ErrorResponse toErrorResponse() { | ||
| return new ErrorResponse( | ||
| String.valueOf(getStatusCode()), | ||
| getMessage(), | ||
| validation | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.codesoom.assignment.common.dto; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import lombok.ToString; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @ToString | ||
| public class ErrorResponse { | ||
| private String code; | ||
| private String message; | ||
| private Map<String, String> validation; | ||
|
|
||
| public ErrorResponse(String code, String message, Map<String, String> validation) { | ||
| this.code = code; | ||
| this.message = message; | ||
| this.validation = validation != null ? validation : new HashMap<>(); | ||
| } | ||
|
|
||
| public void addValidation(String field, String message) { | ||
| validation.put(field, message); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.codesoom.assignment.product.application; | ||
|
|
||
| import com.codesoom.assignment.product.domain.Product; | ||
| import com.codesoom.assignment.product.domain.dto.ProductRequest; | ||
| import com.codesoom.assignment.product.infra.persistence.ProductRepository; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| public class ProductCreator { | ||
| private final ProductRepository productRepository; | ||
|
|
||
| public ProductCreator(ProductRepository productRepository) { | ||
| this.productRepository = productRepository; | ||
| } | ||
|
|
||
| public Product createProduct(ProductRequest product) { | ||
| return productRepository.save(product.toProductEntity()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.codesoom.assignment.product.application; | ||
|
|
||
| import com.codesoom.assignment.product.domain.Product; | ||
| import com.codesoom.assignment.product.infra.persistence.ProductRepository; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| public class ProductDeleter { | ||
|
|
||
| private final ProductRepository productRepository; | ||
| private final ProductReader productReader; | ||
|
|
||
| public ProductDeleter(ProductRepository productRepository, ProductReader productReader) { | ||
| this.productRepository = productRepository; | ||
| this.productReader = productReader; | ||
| } | ||
|
|
||
| public void deleteProduct(Long id) { | ||
| Product product = productReader.getProduct(id); | ||
| productRepository.delete(product); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.codesoom.assignment.product.application; | ||
|
|
||
| import com.codesoom.assignment.common.baseException; | ||
|
|
||
| public class ProductNotFoundException extends baseException { | ||
| public static final String MESSAGE = "해당하는 상품이 존재하지 않습니다"; | ||
|
|
||
| public ProductNotFoundException() { | ||
| super(MESSAGE); | ||
| } | ||
|
|
||
| @Override | ||
| public int getStatusCode() { | ||
| return 404; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package com.codesoom.assignment.product.application; | ||
|
|
||
| import com.codesoom.assignment.product.domain.Product; | ||
| import com.codesoom.assignment.product.infra.persistence.ProductRepository; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.StreamSupport; | ||
|
|
||
| @Service | ||
| public class ProductReader { | ||
|
|
||
| private final ProductRepository productRepository; | ||
|
|
||
| public ProductReader(ProductRepository productRepository) { | ||
| this.productRepository = productRepository; | ||
| } | ||
|
|
||
| public List<Product> getProductList() { | ||
| return productRepository.findAll(); | ||
| } | ||
|
|
||
| public Product getProduct(Long id) { | ||
| return productRepository.findById(id).orElseThrow(ProductNotFoundException::new); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.codesoom.assignment.product.application; | ||
|
|
||
| import com.codesoom.assignment.product.domain.Product; | ||
| import com.codesoom.assignment.product.domain.dto.ProductRequest; | ||
| import com.codesoom.assignment.product.infra.persistence.ProductRepository; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import javax.transaction.Transactional; | ||
|
|
||
| @Service | ||
| public class ProductUpdater { | ||
| private final ProductRepository productRepository; | ||
|
|
||
| public ProductUpdater(ProductRepository productRepository) { | ||
| this.productRepository = productRepository; | ||
| } | ||
|
|
||
| @Transactional | ||
| public Product updateProduct(Long id, ProductRequest productRequest) { | ||
| Product product = productRepository.findById(id).orElseThrow(ProductNotFoundException::new); | ||
| product.update(productRequest.toProductEntity()); | ||
| return product; | ||
| } | ||
|
Comment on lines
+18
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Transaction때문에 Entity.save를 명시적으로 호출하지 않아도 자동으로 반영되겠네요.
하지만 추상화와 일관성을 위해서 명시적으로 작성하는 것도 좋다는 의견도 있으니 참고하시면 좋을 것 같습니다. 참고 |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.codesoom.assignment.product.domain; | ||
|
|
||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.ToString; | ||
|
|
||
| import javax.persistence.Entity; | ||
| import javax.persistence.GeneratedValue; | ||
| import javax.persistence.Id; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @ToString | ||
| public class Product { | ||
| @Id | ||
| @GeneratedValue | ||
| private Long id; | ||
| private String name; | ||
| private String maker; | ||
| private Integer price; | ||
| private String imageUrl; | ||
|
|
||
| public Product() { | ||
| } | ||
|
|
||
| @Builder | ||
| public Product(Long id, String name, String maker, Integer price, String imageUrl) { | ||
| this.id = id; | ||
| this.name = name; | ||
| this.maker = maker; | ||
| this.price = price; | ||
| this.imageUrl = imageUrl; | ||
| } | ||
|
|
||
| public void update(Product source) { | ||
| this.name = source.name; | ||
| this.maker = source.maker; | ||
| this.price = source.price; | ||
| this.imageUrl = source.imageUrl; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.codesoom.assignment.product.domain.dto; | ||
|
|
||
| import com.codesoom.assignment.product.domain.Product; | ||
| import lombok.Getter; | ||
| import lombok.ToString; | ||
|
|
||
| @ToString | ||
| @Getter | ||
| public class ProductRequest { | ||
| private final String name; | ||
| private final String maker; | ||
| private final Integer price; | ||
| private final String imageUrl; | ||
|
|
||
| public ProductRequest(String name, String maker, Integer price, String imageUrl) { | ||
| this.name = name; | ||
| this.maker = maker; | ||
| this.price = price; | ||
| this.imageUrl = imageUrl; | ||
| } | ||
|
|
||
| public Product toProductEntity() { | ||
| return Product.builder() | ||
| .name(this.name) | ||
| .maker(this.maker) | ||
| .price(this.price) | ||
| .imageUrl(this.imageUrl).build(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.codesoom.assignment.product.domain.dto; | ||
|
|
||
| import com.codesoom.assignment.product.domain.Product; | ||
| import lombok.Getter; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Getter | ||
| public class ProductResponse { | ||
| private final Long id; | ||
| private final String name; | ||
| private final String maker; | ||
| private final Integer price; | ||
| private final String imageUrl; | ||
|
|
||
| public ProductResponse(Product product) { | ||
| this.id = product.getId(); | ||
| this.name = product.getName(); | ||
| this.maker = product.getMaker(); | ||
| this.price = product.getPrice(); | ||
| this.imageUrl = product.getImageUrl(); | ||
| } | ||
|
|
||
| public static List<ProductResponse> listOf(Iterable<Product> list) { | ||
| ArrayList<ProductResponse> productResponseArrayList = new ArrayList<>(); | ||
| for (Product product : list) { | ||
| productResponseArrayList.add(new ProductResponse(product)); | ||
| } | ||
| return productResponseArrayList; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.codesoom.assignment.product.infra.api.web.v1; | ||
|
|
||
| import com.codesoom.assignment.common.dto.ErrorResponse; | ||
| import com.codesoom.assignment.product.application.ProductNotFoundException; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.ControllerAdvice; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| import org.springframework.web.bind.annotation.ResponseBody; | ||
| import org.springframework.web.bind.annotation.ResponseStatus; | ||
|
|
||
| @ControllerAdvice | ||
| public class ProductAdvice { | ||
|
|
||
| @ExceptionHandler(ProductNotFoundException.class) | ||
| @ResponseStatus(HttpStatus.NOT_FOUND) | ||
| @ResponseBody | ||
| public ErrorResponse productNotFoundExceptionHandler(ProductNotFoundException e) { | ||
| return e.toErrorResponse(); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.codesoom.assignment.product.infra.api.web.v1; | ||
|
|
||
| import com.codesoom.assignment.product.application.ProductCreator; | ||
| import com.codesoom.assignment.product.application.ProductDeleter; | ||
| import com.codesoom.assignment.product.application.ProductReader; | ||
| import com.codesoom.assignment.product.application.ProductUpdater; | ||
| import com.codesoom.assignment.product.domain.dto.ProductRequest; | ||
| import com.codesoom.assignment.product.domain.dto.ProductResponse; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @CrossOrigin | ||
| @RequiredArgsConstructor | ||
| public class ProductCreateController { | ||
| private final ProductCreator productCreator; | ||
|
|
||
| @PostMapping("/products") | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| public ProductResponse createProduct(@RequestBody ProductRequest productRequest) { | ||
| return new ProductResponse(productCreator.createProduct(productRequest)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.codesoom.assignment.product.infra.api.web.v1; | ||
|
|
||
| import com.codesoom.assignment.product.application.ProductDeleter; | ||
| import com.codesoom.assignment.product.application.ProductReader; | ||
| import com.codesoom.assignment.product.application.ProductUpdater; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @CrossOrigin | ||
| @RequiredArgsConstructor | ||
| public class ProductDeleteController { | ||
|
|
||
| private final ProductDeleter productDeleter; | ||
|
|
||
| @DeleteMapping("/products/{id}") | ||
| @ResponseStatus(HttpStatus.NO_CONTENT) | ||
| public void deleteProduct(@PathVariable Long id) { | ||
| productDeleter.deleteProduct(id); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.codesoom.assignment.product.infra.api.web.v1; | ||
|
|
||
| import com.codesoom.assignment.product.application.ProductReader; | ||
| import com.codesoom.assignment.product.application.ProductUpdater; | ||
| import com.codesoom.assignment.product.domain.Product; | ||
| import com.codesoom.assignment.product.domain.dto.ProductResponse; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.web.bind.annotation.CrossOrigin; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @RestController | ||
| @CrossOrigin | ||
| @RequiredArgsConstructor | ||
| public class ProductReadController { | ||
| private final ProductReader productReader; | ||
|
|
||
|
|
||
| @GetMapping("/products") | ||
| public List<ProductResponse> getProductList() { | ||
| return ProductResponse.listOf(productReader.getProductList()); | ||
| } | ||
|
|
||
| @GetMapping("/products/{id}") | ||
| public ProductResponse getProduct(@PathVariable Long id) { | ||
| Product product = productReader.getProduct(id); | ||
| return new ProductResponse(product); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러 응답 형식을 규격화 하셨네요. 애플리케이션에서 일관성있는 에러 응답으로 인해 예측 가능성이 높아졌네요. 규격을 정한다면 다양한 상황에서도 적절히 사용할 수 있어야겠습니다. 그래서 여러개의 에러 응답이 있는 경우에도 대응이 가능하도록 만들면 더 좋을 것 같습니다.
예를들어서 세탁기가 문이 안닫혀 있다고 해서 문을 닫았더니, 물이 부족하다는 에러가 발생하고, 물을 채웠더니 세제가 부족하다고 하고, 세제를 채웠더니 옷이 너무 많다고 에러가 나면 짜증나겠죠? 여러 에러가 있을 경우 한 번에 에러를 반환할 수 있어야 합니다. 에를들어서 다음과 같이 할 수 있습니다.
{ "message": "Invalid Request", "errors": [ { "source": "Door", "type": "DOOR_IS_OPENED", "message": "Door must be closed" }, { "source": "Detergent", "type": "DETERGENT_MISSING", "message": "Detegent is mandatory" } ] }이렇게 한 번에 피드백을 전달하면 상호작용을 단순화할 수 있고 리퀘스트 요청 횟수도 줄일 수 있습니다.
See also