-
Notifications
You must be signed in to change notification settings - Fork 120
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
충남대 BE_김민성_1주차 과제(1단계~3단계) #165
base: main
Are you sure you want to change the base?
충남대 BE_김민성_1주차 과제(1단계~3단계) #165
Conversation
민성님 안녕하세요 ~ 😄 Repository의 interface를 두고, ~impl을 구현하면 어떤 장점이 있을까요?
1주차 과제 잘 수행해주셨어요~ |
@RestController | ||
public class ProductController { | ||
|
||
private final ProductRepository products; |
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.
ProductRepository와 같은 이름으로 변수명을 짓는 건 어떨까요?
private final ProductRepository productRepository;
Repository 성격임을 직관적으로 나타낼 수 있을 것 같아요~
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.
좋은것 같습니다! 감사합니다
this.products = products; | ||
} | ||
|
||
@GetMapping("/products") |
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.
각 mapping에 존재하는 "/products" 를 공통화할 수 있지 않을까요?
@RequestMapping
어노테이션을 사용해보세요~
public record Product( | ||
Long id, | ||
String name, | ||
Integer price, | ||
String imageUrl | ||
) { | ||
|
||
} |
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.
Domain 객체인 Product를 class가 아닌 record로 사용하면 어떤 문제점이 발생할 수 있을지 고민해보세요!
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.
아래와 같은 문제점이 발생할 수 있을것 같아 class가 더 적합할것 같습니다!
-
이뮤터블한 특성으로 인한 제약
record
는 불변이므로 상태 변경이 필요할 때 새로운 객체를 생성해야 합니다. 이는 성능과 메모리 사용 측면에서 비효율적일 수 있습니다. (예를 들어,Product
객체의 가격이나 재고 수량이 변할 수 있는 상황 등 )
-
프록시 객체 사용시 제약:
- ORM 프레임워크나 AOP 등을 사용할 때 프록시 객체가 필요할 수 있습니다.
record
는 프록시 객체 생성에 적합하지 않을 수 있으며, 이러한 프레임워크와의 호환성이 떨어질 수 있습니다.
- ORM 프레임워크나 AOP 등을 사용할 때 프록시 객체가 필요할 수 있습니다.
-
상속 지원 X
Product
를 상속 구조로 표현하고 싶을 때record
로는 이를 구현하기 어렵습니다.
record
는 간결하고 불변성을 보장하는 특성 때문에 도메인 객체보다 데이터 전송 객체(DTO) 등에서 더 유용할것 같다는 생각이 들었습니다!
import gift.controller.dto.ProductCreateRequestDto; | ||
import gift.controller.dto.ProductResponseDto; | ||
import gift.controller.dto.ProductUpdateRequestDto; | ||
import gift.domain.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.
Controller에서 Product domain에 대해 알 필요가 있을까요?
Service Layer에게 작업을 위임하는 것은 어떨까요?
Controller는 Request를 받고 --> Service가 Request를 받아 어떤 행위를 수행해서 Response를 넘겨주고 --> Controller가 그 Response 를 그저 넘겨주는 역할을 하면 어떨까요?
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.
오 피드백 감사합니다!
다만 도메인 서비스는 도메인 클래스로 표현하기 어려운 도메인 규칙이나 비즈니스 로직을 캡슐화하는 역할을 한다고 생각하는데요!
그래서 지금 로직상으로는 단순 조회, 수정, 삭제으로 단순하여 도메인 서비스를 정의하지 않았습니다!
말씀해주신대로 Presentaion layer에서 domain을 직접 참조하는것에 대한 우려도 동시에 드는데 적절한 타협점에 대한 아이디어를 구하고 싶습니다 :)
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.
현재 구현 사항이 단순 CRUD 로직이라서, 굳이 클래스 하나 더 만들면서 서비스 레이어를 도입해야하는가? 에 대한 의문점이 분명히 있으실텐데요~ 저도 공감합니다.
다만, 현재 로직을 보면, controller의 역할이라고는 볼 수 없는, "요청을 받아 ~ 응답을 던진다" 라는 행위 외에도 update를 수행하고, dto를 객체로 mapping 하는 것과 같은 행위(비즈니스 로직)도 수행하고 있어요. 이러한 과정은 Controller (Presentation Layer)의 관심사는 아니라고 생각해요.
https://incheol-jung.gitbook.io/docs/q-and-a/architecture/ddd
한 번 읽어보셔도 좋을 것 같아요!
) { | ||
Product originalProduct = products.get(id); | ||
if (originalProduct == null) { | ||
throw new RuntimeException("Product not found"); |
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.
조금 더 유의미한 메시지를 던지기 위해, Product not found, productId : {} 처럼 넘겨주면 좋을 것 같아요.
RuntimeException 대신, 조금 더 구체적인 Exception을 던지는 것은 어떨까요? NoSuchElementException도 가능할 것 같아요~
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.
좋습니다 exception message는 NoSuchElementException
클래스 안에 캡슐화 해보겠습니다 :)
|
||
private final JdbcTemplate jdbcTemplate; | ||
|
||
private final RowMapper<Product> rowMapper = (rs, rowNum) -> new 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.
private static final 이 되어도 좋을 것 같아요~
어떤 차이점이 있을까요?
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.
메모리를 효율적으로 사용할 수 있고 오버헤드 비용 감소하는 이점이 있을것 같습니다!
구체적으로 static 필드는 클래스 로드 시 한 번만 초기화되고. 모든 인스턴스가 하나의 rowMapper를 사용하게 되어 메모리를 효율적으로 사용할 수 있을것이고 클래스가 로드될 때 한 번만 초기화되므로, 여러 인스턴스를 생성할 때 초기화 비용이 줄어들것 같습니다 :)
src/main/resources/static/schma.sql
Outdated
create table `products` ( | ||
`id` bigint auto_increment primary key, | ||
`name` varchar(255) not null, | ||
`price` int not null, | ||
`image_url` varchar(255) not null | ||
); |
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.
테이블 설계 잘 해주셨는데,
application.properties (application.yml) 에 database 관련 설정을 안해주신 것 같아요~
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.
앗 누락됐네요 추가해보겠습니다!
@nimunsang 피드백 감사합니다! |
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.
민성님~ 요구사항 잘 반영해주셨네요 👍🏻
몇 가지 더 코멘트 남겼는데, 한 번 확인해보세요!
public void setId(Long id) { | ||
this.id = id; | ||
} |
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.
모든 setter 가 현재 코드에서 사용되지 않는 것 같은데, 사용하지 않는 setter는 닫아두는 것이 어떨까요~?
.orElseThrow(() -> new RuntimeException("Product not found")); | ||
return ProductResponseDto.from(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.
NoSuchElementException을 활용해보셔도 좋을 것 같아요~
public Long id() { | ||
return id; | ||
} |
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.
record에서 class로 변환된 만큼, getId() 처럼 get을 prefix로 붙이는 관행을 따르면 좋을 것 같아요~
package gift.controller; | ||
|
||
public class NoSuchElementException extends RuntimeException { | ||
|
||
private final Long productId; | ||
|
||
public NoSuchElementException(Long productId) { | ||
super("Product not found with id: " + productId); | ||
this.productId = productId; | ||
} | ||
|
||
public Long getProductId() { | ||
return productId; | ||
} | ||
} |
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.
NoSuchElementException을 따로 구현해주시는 것도 좋지만,
이미 구현되어 있는 것을 사용하면 어떨까요?
java.util
패키지에 이미 구현되어 있어요~!
@@ -1 +1,6 @@ | |||
spring.application.name=spring-gift | |||
spring.h2.console.enabled=true | |||
spring.datasource.url=jdbc:h2:mem:test | |||
spring.sql.init.schema-locations=classpath:/static/sql/schema.sql |
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.
schema.sql, data.sql이 정확한 위치에 없어서 에러가 발생할 것 같아요~
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.
와 매의눈 이십니다!! 👍
@@ -15,7 +15,7 @@ | |||
import org.springframework.web.bind.annotation.RequestBody; | |||
import org.springframework.web.bind.annotation.RestController; | |||
|
|||
@RestController | |||
@RestController("/products") |
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.
@RequestMapping 과 헷갈리신 것 같아요 😅
@nimunsang 피드백 반영해보았습니다! 덕분에 많이 배웠습니다 감사합니다 🙇 |
고생하셨어요 👍🏻 👍🏻 Base 브랜치만 바꿔주시면 머지하겠습니다~ |
step3.
본 애플리케이션에서 H2 DB만을 사용할게 분명한 상황이라고 생각합니다. 이 상황에서 Repository의
Interface
를 두고~impl
를 구현하는게 옳바른 방향인지 궁금합니다.