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

[Spring 장바구니 - 2단계] 조동현(후디) 미션 제출합니다. #130

Merged
merged 60 commits into from
Jun 13, 2022

Conversation

devHudi
Copy link

@devHudi devHudi commented Jun 9, 2022

안녕하세요. 스티치!
API 명세를 자주 바꾸기도 했고, 프론트엔드 코드와 연결했을 때 CORS와 같은 이슈가 발생해서 다소 코드를 급하게 짠 것 같아 아쉽네요 🥲
질문은 코멘트를 남기도록 하겠습니다! 감사합니다.

API 문서 보기

devHudi added 30 commits June 7, 2022 13:30
프론트엔드 크루와의 회의 결과 반영
- 상품을 찾을 수 없는 경우 400 대신 404를 응답하도록 변경
- 인수 테스트 코드 추가
테스트코드 Display Name 오타 수정
Copy link

@lxxjn0 lxxjn0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

후디, 2단계 미션 진행하시느라 수고하셨습니다 :)

몇 가지 부분에 대해서 간단하게 피드백 남겨놨으니 한번 확인하고 반영 부탁드릴게요. 그리고 추가적으로 1단계에서 남긴 피드백이 반영되지 않은 것 같아요. 해당 부분도 함께 확인하시고 반영해주시면 좋을 것 같습니다!!

1단계에 2번에 나눠서 피드백을 진행하였는데 두 번째 피드백을 남기면서 첫 번째 피드백에 대한 코멘트들도 남겨두었어요. 해당 부분들도 확인하고 진행해주면 좋을 것 같아요 👍🏼

@@ -35,3 +35,60 @@
- [x] 회원 정보 제거 (회원 탈퇴)
- [x] 토큰을 전달받아 유효성이 검증되면, 전달받은 customer id 에 해당되는 유저 정보를 제거한다.
- [x] (예외) 토큰의 유효성이 검증되지 않을 경우(토큰의 customer id 와 전달받은 customer id가 불일치할 경우) 예외가 발생한다.

## 2단계 기능 구현 목록
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

꼼꼼한 기능 구현 목록 작성이군요 👍🏼

return ResponseEntity.noContent().build();
}

@GetMapping("/{productId}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 url도 함께 고민해보면 좋을 것 같아요!! path parameter를 쓰는게 적절한지 한번 고민해보면 좋을 것 같습니다 :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀해주신 API는 특정 유저의 장바구니에 productId 에 해당하는 제품을 담고 있는지 알려주는 API 입니다! 저는 Path Parameter가 제외된 구현을 떠올리기 어려운데, 어떻게 하면 Path Parameter 를 사용하지 않고 구현할 수 있을까요??

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 이 부분에 대한 피드백을 빠트렸군요.. ㅠㅠ query parameter를 사용해서 전달하면 좀 더 좋을 것 같아요!! path parameter는 주로 리소스의 위치를 나타내는 경향이 있는데 그런 파라미터로 적절하지 않을 경우는 query parameter를 사용하면 좋은 것 같습니다

}

@Override
public void update(int id, CustomerEntity customerEntity) {
String sql = "UPDATE CUSTOMER SET password = ?, profile_image_url = ?, terms = ? WHERE id = ?";
String sql = "UPDATE customer SET password = ?, profile_image_url = ?, terms = ? WHERE id = ?";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedParameterJdbcTemplate을 어떤 dao에는 적용하고 어떤 dao에는 적용하지 않았는데 이렇게 작성된 이유가 있을까요?


return jdbcTemplate.query(sql, (rs, rowNum) -> rs.getLong("id"), customerId);
}
public interface CartItemDao {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dao를 인터페이스로 분리한 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나중에는 jdbc를 사용하지 않고 데이터베이스에 접근하는 구현체가 있을수도 있겠다는 생각에 만들어두었는데, 오버엔지니어링 같기도하고, DAO 계층에만 인터페이스를 만들어둔것이 일관성이 없어 보이기도 합니다!.. 스티치는 어떻게 생각하시나요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저같은 경우에는 예상되어지는 부분에 대한 추상화는 진행하지만 그렇지 않은 부분에 대해서는 최대한 오버엔지니어링을 피하려고 하고 있어요!! 후디가 작성한 코드에서 추후 다른 DB 리소스를 붙일 상황이 예상된다거나 그러한 요구사항이 추후 존재한다는 걸 알고 있다면 지금의 추상화는 좋다고 생각해요!! 하지만 '혹시 모르니깐'의 개발은 오버엔지니어링의 시작점이라고 생각합니다!!

지금 다양한 방법으로 시도해보고 수정해보는 것은 좋다고 생각해요!! 제가 리뷰를 남긴 이유도 정말 어떠한 이유가 존재해서 인터페이스로 분리한 것인지 궁금해서 여쭤본 것이었습니다 :)

@devHudi
Copy link
Author

devHudi commented Jun 11, 2022

안녕하세요 스티치! 리뷰이 후디입니다!

이번 미션 2단계 첫 피드백 요청 때 퀄리티가 다소 떨어지는 코드를 제출했었던 것 같아요 ㅠ0ㅠ 변명아닌 변명을 하자면, 미션 마감(데모데이 날)까지도 프론트엔드와 코드를 연결하여 이런 저런 버그와 문제점이 발생해서, 일단 동작하는 코드를 만드는데에만 시간을 전부 사용했던 것 같아요. 이제 방학이기도 하고, 시간적 여유가 생겨 그간 쌓아둔 기술부채(?)를 어느정도 리팩토링하게 되었습니다.

가장 큰 변화로는 DAO 계층과 Service 계층 사이에 Repository 계층이 하나 더 생겨 DAO에서 도메인 객체를 생성하는 복잡한 로직을 Repository 계층으로 위임하였습니다!.. 그리고 일관적이지 않은 final 키워드 사용 등 자잘한 부분을 리팩토링했습니다!

그리고 1단계 피드백 주신 내용들도 이번에 한번에 반영하여 커밋하였습니다! 이번 리뷰도 잘 부탁드립니다! 🙏🙏🙏

Copy link

@lxxjn0 lxxjn0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

후디, 장바구니 미션 진행하시느라 수고 많으셨어요!! 👍🏼

이번 리뷰에 개선된 코드는 정말 기존 코드에 비해서 잘 가다듬어지고 깔끔해진 것 같아요!! 💯 제가 몇 가지 부분에 대해서 코멘트 남겨두었는데 해당 부분은 참고만 해보면 될 것 같아요 :)

레벨 2 진행하시느라 수고 많으셨습니다!!

cartItemDao.delete(cartItemId);
}

private CartItem generateCartItem(CartItemEntity cartItemEntity) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용하는 코드 가까이에 둔다면 코드의 가독성을 더 좋게 만들어 줄 수 있을 것 같아요!!

Comment on lines +41 to +50
private Product convertEntityToDomain(ProductEntity productEntity) {
return new Product(
productEntity.getId(),
productEntity.getName(),
productEntity.getDescription(),
productEntity.getPrice(),
productEntity.getStock(),
productEntity.getImageUrl()
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 취향에 따라 갈리긴 하는데 저는 이런 종류의 코드는 보통 (여기서의) entity 내부에 넣어두곤 합니다. 실제 생성에 필요한 정보를 entity가 모두 가지고 있어서요!!

public List<OrdersResponse> findOrdersByCustomerId(Long customerId) {
List<Orders> orders = ordersRepository.findAllByCustomerId(customerId);

return orders.stream().map(this::convertOrderToResponse).collect(Collectors.toList());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return orders.stream().map(this::convertOrderToResponse).collect(Collectors.toList());
return orders.stream()
.map(this::convertOrderToResponse)
.collect(Collectors.toList());

체이닝 메서드는 보통 한 라인당 체이닝 로직을 둬서 가독성을 높이곤 합니다 👍🏼

}

private void validateOrderIdByCustomerId(Orders orders, Long customerId) {
if (!orders.getCustomerId().equals(customerId)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시나 orders.getCustomerId()의 반환 값이 null일 경우 해당 라인에서는 NPE가 발생할 수 있어요!! 이럴 때는 NPE 발생을 방지해주는 Objects.equals 메서드를 활용하는 것도 좋습니다

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class PasswordEncoder {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 객체를 정적 클래스 형식으로 사용하고 있군요. 그렇다면 생성자를 제한해서 객체로 활용되지 못하도록 하면 더 좋을 것 같아요!! 추가로 제가 리뷰에서 생각했었던 방법은 PasswordEncoder를 Password 객체가 가지고 있지 않으면서 어플리케이션에서 1개의 인스턴스만 생성해서 사용하는 방법이에요. 스프링의 빈으로 등록해서 사용한다면 각각의 Password 객체가 값을 가지고 있지 않으면서 모두 동일한 객체를 사용할 수 있을 것 같아요!!

private static String bytesToHex(byte[] bytes) {
StringBuilder builder = new StringBuilder();
for (byte b : bytes) {
builder.append(String.format("%02x", b));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매직넘버는 상수로 추출해주세요!!

@lxxjn0 lxxjn0 merged commit 991237f into woowacourse:devhudi Jun 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants