-
Notifications
You must be signed in to change notification settings - Fork 301
3단계 - 수강신청(DB 적용) #769
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
base: heewonham
Are you sure you want to change the base?
3단계 - 수강신청(DB 적용) #769
Conversation
@@ -12,8 +12,9 @@ public TuitionFee(int amount) { | |||
this.amount = amount; | |||
} | |||
|
|||
public int getAmount() { | |||
return amount; | |||
public boolean isSameAmount(int amount) { |
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.
#728 (comment)
#728 (comment)
이 두 부분이 약간 상충되는 것 같아 질문드립니다.
어떤 부분은 get을 쓰고 어떤 부분은 이렇게 함수를 만드는데 구분할 수 있는 방법은 무엇일까요?
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.
좋은 질문입니다! 👍
로직 흐름에서 도메인 객체의 책임에 해당하는 동작이라면,
객체에 메시지를 보내는 방식을 사용합니다.
예를 들어 사용자 나이 관련 로직이 있다고 가정해보겠습니다.
// 나이 증가
var age = member.getAge(); // 게터 사용
member.setAge(age + 1);
member.increaseAge(); // 메시지 전달 (게터 사용X)
// 성인 여부로 특정 기능 사용을 판단하는 로직
boolean isAdult = member.getAge() >= 19; // 게터 사용
boolean isAdult = member.isAdult(); // 메시지 전달 권장 (게터 사용X)
// 나이가 증가했는지 검증 (게터 사용)
var member = new Member(age = 10);
member.increaseAge();
member.getAge() == 11 // true 기대, 게터로 충분한 검증 가능
// 외부 응답 규격 DTO 생성 (게터 사용)
new MemberResponse(member.id, member.age);
단순히 값을 꺼내서 검증하거나, 외부로 값을 전달하기 위한 목적이라면
게터를 사용하는 것이 자연스럽다고 생각해요
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
대신 protected
로 선언하는 방법도 있습니다.
보통 테스트 클래스는 테스트 대상 클래스와 같은 패키지에 위치하므로, protected
메서드에도 접근할 수 있어서요.
이러면 테스트의 편의성을 확보하면서도, 외부에서의 불필요한 접근은 제한할 수 있게됩니다 😄
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.
@nooose 아하 이해했습니다. 도메인 객체 책임에 해당하는지를 생각해보면 되겠군요!!
상세한 설명 감사합니다 🙏
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.
희원님 스키마 잘 만들어 주셨는데요
도메인 모델 매핑 관려해서 고민해 보시면 좋을 내용 남겼어요
확인 후 재요청 부탁드립니다.
혹시나 미션 중 질문있다면 코멘트 또는 DM 남겨주세요!
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (!(o instanceof TuitionFee)) return false; | ||
TuitionFee that = (TuitionFee) o; | ||
return amount == that.amount; | ||
} | ||
|
||
@Override | ||
public int hashCode() { |
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.
리뷰 반영 잘 되었네요 😄
String sql = "insert into session (name, type, start_date, end_date, status, capacity, tuition_fee, image_id, course_id) " + | ||
"values (?, ?, ?, ?, ?, ?, ?, ?, ?)"; | ||
|
||
jdbcTemplate.update(sql, | ||
session.getName(), | ||
session.getType().name(), | ||
session.getPeriod().getStartDate(), | ||
session.getPeriod().getEndDate(), | ||
session.getStatus().name(), | ||
session instanceof PaidSession ? ((PaidSession) session).getCapacity() : null, | ||
session instanceof PaidSession ? ((PaidSession) session).getTuitionFee() : null, | ||
imageId, | ||
courseId | ||
); |
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.
바인딩 시킬 ?
순서대로 값을 넣어줘야하는데요
지금은 무난해도 향후 컬럼이 늘어난다면 순서를 지키는 부분에서 실수가 발생할 수 있습니다.
이 문제를 해결할 수 있는 것이 NamedParameterJdbcTemplate
인데요
관련해서 찾아보시면 도움이 될 겁니다.
이번 미션에서 중요한 부분은 아니니 참고만 해주세요!
import java.util.List; | ||
|
||
@Repository | ||
public class JdbcSessionStudentRepository { |
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.
세션과 세션에 연관된 수강생들에 대한 Repository
가 별도 분리되어 있는 구조네요
일반적으로 Repository
라는 네이밍은 특정 도메인 모델의 저장 및 조회 책임을 가지는 객체로 해석하는데요
이런 이유로 도메인 내부의 연관된 객체들까지 함께 관리하는 것이 자연스럽다고 생각합니다.
도메인이 어떻게 저장되거나 불러와지는지는 Repository 외부의 관심사가 되어선 안 됩니다.
// 연관객체가 처리된 로직
var session = sessionRepository.findById(sessionId); // 연관된 수강생 정보도 함께 조회되기를 기대
session.addStudent(수강생); // 연관된 수강생 정보도 함께 처리되기를 기대
sessionRepository.save(session); // 반영
하지만 위 코드와 달르게 현재 구조에서는 도메인 모델에 연관관계 객체가 존재함에도 불구하고,
실제 데이터를 처리하는 과정에서는 연관된 수강생 정보가 반영되지 않아
도메인 모델의 의도와 테이블 매핑 작업 간 불일치가 발생하는 것처럼 보입니다.
JPA를 사용해보셨다면, 연관된 엔티티들을 한 번에 persist 혹은 fetch하는 방식에 익숙하실 텐데요,
그런 방식처럼 SessionRepository만으로 연관 수강생까지 함께 처리되도록 만들면 도메인 불일치 문제를 해결할 수 있다고 생각합니다. 🚀
@@ -12,8 +12,9 @@ public TuitionFee(int amount) { | |||
this.amount = amount; | |||
} | |||
|
|||
public int getAmount() { | |||
return amount; | |||
public boolean isSameAmount(int amount) { |
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.
좋은 질문입니다! 👍
로직 흐름에서 도메인 객체의 책임에 해당하는 동작이라면,
객체에 메시지를 보내는 방식을 사용합니다.
예를 들어 사용자 나이 관련 로직이 있다고 가정해보겠습니다.
// 나이 증가
var age = member.getAge(); // 게터 사용
member.setAge(age + 1);
member.increaseAge(); // 메시지 전달 (게터 사용X)
// 성인 여부로 특정 기능 사용을 판단하는 로직
boolean isAdult = member.getAge() >= 19; // 게터 사용
boolean isAdult = member.isAdult(); // 메시지 전달 권장 (게터 사용X)
// 나이가 증가했는지 검증 (게터 사용)
var member = new Member(age = 10);
member.increaseAge();
member.getAge() == 11 // true 기대, 게터로 충분한 검증 가능
// 외부 응답 규격 DTO 생성 (게터 사용)
new MemberResponse(member.id, member.age);
단순히 값을 꺼내서 검증하거나, 외부로 값을 전달하기 위한 목적이라면
게터를 사용하는 것이 자연스럽다고 생각해요
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.
희원님 안녕하세요
도메인 모델과 DB 매핑 작업에서 불일치되는 부분이 있네요
코멘트 남겼으니 확인 후 재요청 부탁드립니다!
} | ||
|
||
@Transactional | ||
public void save(Session session, Long courseId) { |
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.
Session이 courseId를 가지는 구조는 어떤가요?
|
||
public Session findById(Long id) { | ||
String sql = "select s.*, i.file_name, i.content_type, i.size_in_bytes, i.width, i.height " + |
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.
@Transactional(readOnly = true)
검색만 하니
명시적으로 readOnly 트랜잭션을 사용하면 어떨까요?
} | ||
|
||
@Transactional | ||
public void registerStudent(Long sessionId, Long studentId) { |
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.
이 동작이 틀리지는 않았는데요
Session
도메인 모델 안에 Students
를 가지고 있을 필요가 없어지는 것 같아서요
앞서 리뷰드린 도메인 모델 불일치와 비슷한 맥락입니다.
Session 도메인 모델의 register
메소드를 활용하려면
레포지토리의 registerStudent
호출 없이 동작할 수 있게 만들면 좋을 것 같습니다!
session.register(...);
sessionRepository.save(session); // session 도메인 모델 내 수강생들도 함께 저장
sql 를 추가하고 repository 및 테스트 코드를 추가하자!!