-
Notifications
You must be signed in to change notification settings - Fork 8
지원서(application) 테이블에서 학점/어학 인증 분리 및 지원 절차 수정 #109
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
지원서(application) 테이블에서 학점/어학 인증 분리 및 지원 절차 수정 #109
Conversation
1. api naming에 관하여
1.1다음 api endpoint 명명에 관하여 이후 확장성에 문제가 없다면 중복된 부분을 지워도 될 것 같습니다! 1.2api에서 여러 단어로 이루어진 리소스에 대하여 현재 api들을 보면 kebab-case와 camelCase가 혼재되어 있습니다. 이 중 어떤것을 써도 좋지만 통일성을 유지하는게 중요하다고 생각합니다. 그래서 앞으로의 api에 대해서는 kebab-case를 사용하는 것을 제안드립니다.
이러한 이유로 languageTestScore 대신 language-test-score과 같이 카멜케이스를 사용해보는게 어떨까 건의드립니다. 1.3[post] /score/gpaScore : 학점 등록 api 해당 API endpoint에서 post나 get이라는 method 자체가 조회와 등록의 의미를 가지고 있기 때문에, 조회시에 뒤에 /status를 붙이지 않고 post와 같은 endpoint를 써도 괜찮을 것 같습니다. 아직 내부 코드 구현을 확인해보지는 않았으나, 저는 각 학점과 어학 인증을 개별 리소스로 취급하는 구조를 생각했는데, 이 경우 각 인증을 삭제하거나 할때 개별 리소스 api endpoint가 필요할텐데 이때 /score/gpaScore/(id) 와 같이 사용된다면 status를 제외하는게 좋을 것 같다는 이유도 있습니다. 메모: https://learn.microsoft.com/ko-kr/azure/architecture/best-practices/api-design |
|
|
||
| Long gpaScoreId = applyRequest.gpaScoreId(); | ||
| Long languageTestScoreId = applyRequest.languageTestScoreId(); | ||
| GpaScore gpaScore = validateGpaScore(siteUser, gpaScoreId); |
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.
궁금한것이 있는데 기존에 값을 검증하되 따로 리턴값은 없는 로직들을 validate~() 메소드로 정의해서 사용하시는 걸 보고 좋은 아이디어라는 생각을 가지고 있었는데요, validateGpaScore는 검증도 있지만 siteUser와 gpaScoreId를 이용해 find 하는것에 가깝다는 느낌도 들어서 혹시 메서드 명을 짓는 기준이 있으신지 궁금합니다!
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.
우선 꼼꼼히 확인해주셔서 감사합니다.
지금 살펴보니 해당 validate메소드의 경우 검증을 진행하지만 리턴값도 존재하여
검증 및 조회라는 두가지 역할을 수행하는 상황이라고 생각됩니다.
이 부분의 경우 사실 말씀하신대로 getter 느낌이 더 강한 것 같은데
두개의 메소드로 분리하는것이 좋을지, getGpaScore와 같이 네이밍을 바꾸는게 좋을지 고민되네요.
개인적으로는 메소드를 분리하는게 더 좋을 것 같긴한데 의견주시면 좋을 것 같습니다.
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.
상당히 애매한 부분같은데, 확실히 검증의 의미가 포함되어 있기는 하나 저는 다음과 같은 상황을 가정해보았습니다.
현재 저희 코드에서는 scoreId는 유저와 관련없이 고유합니다. 그러나 유저별로 scoreId가 따로 결정되는, 즉 GpaScore의 candidate key가 (userId, scoreId)인 경우를 가정해보았습니다. 이 경우에 findGpaScore(userId, scoreId)를 하는것은 검증을 하기보다는 말 그대로 조회를 해서 엔티티를 가져오는 느낌입니다.
그래서 저는 validate보다는 get~ find~등을 사용해도 좋을 것 같습니다.
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.
반영했습니다.
| LanguageTestScore languageTestScore = validateLanguageTestScore(siteUser, languageTestScoreId); | ||
|
|
||
| Optional<Application> application = applicationRepository.findBySiteUserAndTerm(siteUser, term); | ||
| application.ifPresentOrElse(before -> { |
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이 있을 때 덮어 씌우는 것이 아닌 isDelete = true 등으로 설정하는 방식을 사용하는 것도 좋을 것 같습니다.
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.
이후에 경쟁률 변화에 대한 기록을 남기고 거짓 제출 경향등을 보기 위해서
라는 부분에서 추후 새로운 정보를 얻기 위해서 저장해야할 필요성이 있다고 생각이 드네요.
좋은 의견인 것 같습니다.
soft delete에 대해서 제가 깊게 생각해본적이 없는데 이번기회에 한번 학습해보면 좋을 것 같네요.
감사합니다!
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.
반영했습니다.
| @Entity | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class LanguageTestScore extends BaseEntity { |
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.
// 메모
이후에 GpaScore와 LanguageTestScore에 expirationDate 만료 날짜 필드를 추가해 만료된것은 application시 사용 불가와 같은 로직을 추가하는 것도 좋을 것 같습니다.
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에서 삭제할 생각이였는데
이 역시도 soft delete 느낌을 추천하시는 것 같은데 제가 이해한바가 맞는지 궁금합니다.
성적의 경우 application에 id값이 아닌 value값이 삽입되는 구조라 데이터를 유지해야할 필요를 크게 느끼지 못했던 것 같은데
저장했을때 어떤 측면에서 용이하다고 생각하시는지 궁금합니다.
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.
soft delete 방식을 추구하는 것도 맞지만, 말씀하신것처럼 '만료된 성적을 주기적으로 삭제' 를 할때도 만료날짜를 기록해둔다면 삭제하기 편하니 말 그대로 만료 날짜를 기록하고 만료된 성적은 사용하지 못하게 하는 용도입니다. 실제로 교한학생 지원시에도 만료된 성적은 내지 못하니, 만료된 성적은 disable 시켜버리는 것이지요.
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.
편의성에 대해서는 이해가되네요. 감사합니다.
이후에 GpaScore와 LanguageTestScore에 expirationDate 만료 날짜 필드를 추가해 만료된것은 application시 사용 불가와 같은 로직을 추가하는 것도 좋을 것 같습니다.
추가적으로 이부분에서 만료날짜 설정에 있어서 궁금한점이 있습니다.
만료일자를 한학기로 설정하는게 좋을 지, 어학성적의 경우 실제 어학만료일(2년뒤)을 기입하는게 좋을 지 궁금합니다.
만료일자에 대해서 어떻게 생각하고 제안하신건지 궁금합니다.
wibaek
left a comment
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.
전체 코드 확인하는데 시간이 좀 걸렸습니다. 늦어서 죄송합니다.
코드는 모두 좋아보입니다! 현재 코드대로 확정해도 될 것 같으나, 코드를 보며 2가지 점에 대해서 이런방식으로 하는 건 어떨까 라는 생각이 들어서 커멘트 남겨둡니다!
- application에 성적값 복사해서 저장되는 것을 GpaScore, LanguageTestScore id FK를 저장하는 방식으로 수정하기
우선 해당 사항은 저 역시 FK를 저장하는 것이 아닌 값을 복사해 저장하는 것이 좋겠다고 말씀드렸던 것으로 기억합니다.
당시에 이렇게 복사해서 저장하는것이 좋겠다 생각한 이유는 유저 탈퇴나 성적 데이터 삭제시에 application 정보는 ON DELETE CASCADE가 아닌 ON DELETE SET NULL로 설정하여 과거 경쟁률 데이터들을 보존하려는 목적이었습니다.
그러나 이를 두고 고민 해보았을 때 성적 데이터들도 soft delete를 하거나 ON DELETE SET NULL로 저장하고 정규화를 진행하는 것이 좋겠다는 생각이 들었습니다.
해당 부분을 수정했을 때의 장점은 정규화 했을 때의 장점이 되겠고, 단점은 현재도 복잡한 application 리스트 조회 쿼리에서 성적 join로직이 추가되었을 때 더 복잡해질 수 있다는 것이 되겠습니다.
- GpaScore과 LanguageTestScore 수정 대신 새로 생성
현재 로직을 보았을때 GpaScore는 기존에 GpaScore가 있으면 무조건 덮어쓰기, LanguageTestScore은 같은 테스트 타입이면 덮어쓰기 방식으로 구현되어 있는 것으로 확인했습니다.
저는 처음 application 관련 코드를 보았을 때, 그리고 DB에서 수동으로 성적인증요청을 확인하고 상태를 APPROVE 하며 느낀 점이, 엔티티를 계속 수정해서 사용하는 것이 코드와 로직의 전반적인 복잡도를 올린다는 느낌을 받았습니다.
그래서 각 성적인증을 immutable?한 방식으로 다루는 것을 제안드립니다. 이를 통해 복잡도를 낮추고 데이터의 history를 더욱 잘 남길 수 있다고 생각합니다.
즉 구현에서 현재 score나 이미 존재하는 종류의 시험 데이터가 있을 때 이를 덮어 씌우는 것을 그냥 새로운 엔티티를 만드는 것으로 변경하는 것입니다.
기존에는 s3에 저장된 성적인증 파일들에 대해 성적인증이 덮어씌워지면 기존의 파일들은 추적이 어려워집니다. 즉 파일을 가리키는 포인터가 사라지는 느낌입니다. 이런것들에 대해서도 계속 추적할 수 있고 관리가 용이해집니다.
그리고 created_at, updated_at 기록도 보다 정확하게 할 수 있습니다.
꼭 이런방식으로 진행할 필요는 없고 오늘 오후동안 생각해본 것들을 적어둔것이니 한번 검토만 부탁드립니다!
이 부분에 있어서는 조금 더 고민해보고 답변드리겠습니다. |
|
메모: 2번만 적용 |
|
2번 반영완료 |
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.
세원님 몇가지 질문이 있어 rc 드립니다~
(1) 과정에서 학점과 어학성적을 등록 한 뒤,
(2) 과정을 통해 조회된 성적 중 승인된 성적들을 선택하여
(3) 과정을 통해 지원서를 제출하는 순으로 진행됩니다.
승인된 성적들을 어떻게 선택을 하는건가요?
예를 들어서, 토익도 제출하고 토플도 제출을할 수 있는데, 지원서를 제출할 때 둘 중 하나를 고르게 되는건가요?
| @Schema(description = "지원서 제출") | ||
| public record ApplyRequest( | ||
| @NotNull(message = "gpa score id를 입력해주세요.") | ||
| @Schema(description = "지원하는 유저의 gpa score id", example = "1") | ||
| Long gpaScoreId, | ||
|
|
||
| @NotNull(message = "language test score id를 입력해주세요.") | ||
| @Schema(description = "지원하는 유저의 language test score id", example = "1") | ||
| Long languageTestScoreId, | ||
|
|
||
| UniversityChoiceRequest universityChoiceRequest | ||
| ) { | ||
| } |
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.
사용자가 직접 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.
말씀하신 대로 토익과 토플 성적이 모두 등록될 수 있기에 여기서 골라서 지원하는 방식입니다.
유저가 화면상의 본인 성적을 선택하게되면 해당 성적의 id를 api로 전송하는 방식입니다.
회원마다 성적을 여러개를 보유할 수 있어서 선택하여 지원하는 방식으로 진행했습니다.
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.
충분한 답변이 되었는지 잘 모르겠네요ㅜ
궁금한점 있으시면 말씀해주세요!
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.
본인 성적을 선택하게되면 해당 성적의 id를 api로 전송하는 방식
아하 이해했습니다😊
| public class GpaScore extends BaseEntity { | ||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
| @Embedded | ||
| private Gpa gpa; |
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.
Gpa 가 Embedded 로 Application 에도 저장되고 GpaScore 에도 저장되고 있는게 맞나요?
중복된 저장으로 데이처 정합성 유지가 힘들어지고, 저장 공간을 이중으로 차지할 것 같은데
이렇게 구성하신 이유가 있으신가요?
(혹시 제가 맥락을 이해 못하고 있는거라면 말씀해주세요 😊)
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에 Gpa가 id값이 아닌 값으로 저장되는 방식이 회원탈퇴 시점에도 값을 자연스레 유지할 수 있어 좋다고 생각하였고,
join하는 방식으로 기록을 가져오지 않아서 쿼리 성능에서도 이득을 볼 수 있을 것이라고 생각하였습니다.
물론 말씀하신대로 Application에는 값을 직접 저장하지 않고, id 값을 저장해도 될 것 같습니다.
현재 지원서를 조회하는 api 성능이 매우 저하되어서 해당 api를 리팩토링 할 때 다시 고려해보면 좋을 것 같습니다.
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.
join하는 방식으로 기록을 가져오지 않아서 쿼리 성능에서도 이득을 볼 수 있을 것
비정규화를 통해 성능 향상을 기대해볼 수 있단 말씀이신군요👍
개인적 의견으로는..
하나의 join이 사라진다고 해서 큰 효과가 있을것이라고 생각이 되진 않습니다🥲
하지만 이것도 개인적 의견이니 api를 리팩토링할 때 다시 살펴보겠습니다!!
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.
혹시라도 가능하시다면 두 경우의 성능을 비교해주신다면 더욱 좋을 것 같습니다!
| // 어학성적을 등록하는 api | ||
| @PostMapping("/languageTest") |
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.
api에서 여러 단어로 이루어진 리소스에 대하여 현재 api들을 보면 kebab-case와 camelCase가 혼재되어 있습니다.
이 중 어떤것을 써도 좋지만 통일성을 유지하는게 중요하다고 생각합니다.
위백님이 말씀하신 api 네이밍에 대해서는, 저도 kebab-case 로 통일하는 것 찬성합니다🙋🏻♀️
그런데 위백님도 말씀하셨다시피 통일성이 중요하기 때문에, 모든 api 를 하나로 통일하는게 아니라면 의미가 없겠다는 생각이 듭니다.
그래서 ❗️api 네이밍 수정과 관련된 PR 을 따로 만들어서 전체 api 를 통일❗️하는게 좋겠습니다.
(지금 PR에서 하기에는 PR이 너무 뚱뚱해질 것 같아요)
위백님은 '앞으로는 통일했으면 좋겠다'고 하셨는데, 기존 것을 포함하여 전체를 통일하는 것은 어떻게 생각하시나요?
@wibaek
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.
저도 가능하면 모두 통일하는게 좋다고 생각합니다
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.
추후 issue 등록하여 모두 통일하면 좋을 것 같습니다.
|
추후 배포시점에 신규 테이블은 정상으로 생성될 것이라고 예상되지만, |
관련 이슈
작업 내용
테스트 수행 결과

특이 사항
(1)
[post] /score/gpaScore : 학점 등록 api
[post] /score/languageTestScore : 어학성적 등록 api
(2)
[get] /score/gpaScore/status : 로그인한 유저에게 등록된 학점 정보 및 인증 상태 조회
[get] /score/languageTestScore/status : 로그인한 유저에게 등록된 어학성적 정보 및 인증상태 조회
(3)
[post] /application : 지원서 제출하기 api
(1) 과정에서 학점과 어학성적을 등록 한 뒤,
(2) 과정을 통해 조회된 성적 중 승인된 성적들을 선택하여
(3) 과정을 통해 지원서를 제출하는 순으로 진행됩니다.
아래 두 개의 api의 경우 n+1 문제가 발생하여 추가적인 수정이 필요합니다.
[get] /application
[get] /application/competitors
리뷰 요구사항 (선택)