-
Notifications
You must be signed in to change notification settings - Fork 7
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
알림 기능 구현 (이벤트 발행 및 구독, 알림 조회, 알림 삭제, 알림 업데이트) #625
Conversation
public enum AlarmText { | ||
|
||
TITLE_RUNNER_POST_APPLICANT("서포터의 제안이 왔습니다."), | ||
TITLE_RUNNER_POST_ASSIGN_SUPPORTER("코드 리뷰 매칭이 완료되었습니다."), | ||
TITLE_RUNNER_POST_REVIEW_STATUS_DONE("코드 리뷰 상태가 완료로 변경되었습니다."), | ||
|
||
MESSAGE_REFERENCED_BY_RUNNER_POST("관련 게시글 - %s"); |
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.
나중에 쪽지가 생기게 되었을 때 runner_post 관련 텍스트와 쪽지 관련 텍스트가 같은 파일에 위치하게 될 것 같아요.
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.
RunnerPost 관련 AlarmText라는 의미로 RunnerPostAlarmText로 enum 명을 바꾸면 어때요?
또, AlarmType과 연관시켜서 AlarmText를 정의하는건 어때요? AlarmType에 따라서 다른 텍스트를 선택하도록 하는 것이 더 자연스러워 보여요. 지금 AlarmType은 상수파일 같아서요ㅠㅠ
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.
mvc 할 때 HandlerMapper, HandlerAdapter 비슷하게용
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.
완전 비슷하진 않지만 망나니 형님의 enum 활용입니다.
https://mangkyu.tistory.com/74
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와 enum 중 어떤 거로 만들어 둘까 고민했는데 enum으로 만들게 되면서 저의 의도와 다르게 다른 방식으로 이용할 수 있다라는 생각을 들게한 코드가 된거 같네요 🥲
AlarmType은 AlarmTitle이나 AlarmMessage와 연관이 없고 AlarmReferencedId와 연관이 있습니다.
AlarmType 알람과 연관된 타입의 식별자값을 구별하기 위한 필드입니다.
AlarmText를 다른 곳에 고정적으로 넣지 않은 부분은 제 의도가 맞습니다.
확실하지 않은 정책에 대해서 고정된 코드가 되는 듯한 느낌을 받아서 일부러 AlarmText를 단순한 상수로만 사용하게 만들었습니다.
AlarmType에 AlarmText을 두게 될 경우에는 일대일로 매핑되거나 필요한 AlarmText를 꺼내기 위해서 조회 로직이 추가적으로 들어가야 할 거 같습니다.
@Column(name = "title", nullable = false) | ||
private String value; | ||
|
||
public AlarmTitle(final String value) { | ||
validateNotNull(value); | ||
this.value = value; | ||
} | ||
|
||
private void validateNotNull(final String value) { | ||
if (Objects.isNull(value)) { | ||
throw new IllegalArgumentException("AlarmTitle 객체 내부에 title 은 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.
AlarmTItle이 데이터베이스에 저장될 때 3가지 경우로만 저장되고 있습니다. 고정되어있는 title 이라면 enum 자료형으로 데이터베이스를 칼럼을 지정하는 것은 어떤가요? 속도 측면, 관리 측면에서 더 편할 것 같아서요.
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.
Title 같은 경우는 view랑 많이 연결되어 있을것 같아서 변경될 여지가 많아 보여요. 할꺼면 enum으로 class를 만들고 데이터는 기존이랑 동일하게 varchar로 가시는게 어떤가요?
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.
알람 제목이 고정적이지 않을거 같다고 생각했어요.
정책이 확실하게 고정으로 간다면 enum도 좋은거 같아요.
그래도 알람 관련한 부분이 계속해서 추가사항이 있을거라 생각해서 enum으로 하면 이후에 메시지 수정하는데 불편함이 있을거 같아요
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.
위에서 비슷한 내용으로 커멘트 달아서 링크 첨부할게요 👍
@TransactionalEventListener(phase = AFTER_COMMIT) | ||
@Transactional(propagation = REQUIRES_NEW) | ||
public void subscribeRunnerPostReviewStatusDoneEvent(final RunnerPostReviewStatusDoneEvent event) { | ||
final RunnerPost foundRunnerPost = getRunnerPostWithRunnerOrThrowException(event.runnerPostId()); | ||
|
||
alarmCommandRepository.save( | ||
createAlarm(TITLE_RUNNER_POST_REVIEW_STATUS_DONE, foundRunnerPost, foundRunnerPost.getRunner().getMember()) | ||
); | ||
} | ||
|
||
@TransactionalEventListener(phase = AFTER_COMMIT) | ||
@Transactional(propagation = REQUIRES_NEW) | ||
public void subscribeRunnerPostAssignSupporterEvent(final RunnerPostAssignSupporterEvent event) { | ||
final RunnerPost foundRunnerPost = getRunnerPostWithSupporterOrThrowException(event.runnerPostId()); | ||
|
||
alarmCommandRepository.save( | ||
createAlarm(TITLE_RUNNER_POST_ASSIGN_SUPPORTER, foundRunnerPost, foundRunnerPost.getSupporter().getMember()) | ||
); | ||
} |
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.
요즘 제가 함수형 인터페이스에 미쳐있긴 한데요...
3개의 트랜잭션 모두 중복인 것 같아요.
함수형 인터페이스로 중복코드 줄여보는 건 어때요?
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.
생각이 잘 안되는데 혹시 예시 살짝만 보여주실 수 있나요?
@Query(""" | ||
select a | ||
from Alarm a | ||
where a.member.id = :memberId | ||
order by a.id desc | ||
limit :limit | ||
""") |
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.
jpql에 limit가 동작하지 않는 것으로 알고 있는데, 정확히 동작하나용?
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.
native query나 querydsl 써야할 것 같아서요
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.
AlarmQueryRepositoryTest에 테스트 코드 있습니다!
h2, MySQL 둘 다 잘 돌아가고 테스트 코드랑, postman에서 확인했었습니다.
혹시 동작 이상이 예상되는 부분 있으면 말씀해주시면 확인해보겠습니다.
return supporterRunnerPostCommandRepository.save(runnerPostApplicant).getId(); | ||
final Long savedApplicantId = supporterRunnerPostCommandRepository.save(runnerPostApplicant).getId(); | ||
|
||
eventPublisher.publishEvent(new RunnerPostApplySupporterEvent(foundRunnerPost.getId())); |
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.
진짜 신기하네요 이거
final Alarm alarm = AlarmFixture.create(memberHyena, alarmReferencedId(1L)); | ||
|
||
// when | ||
willDoNothing().given(alarmCommandService).deleteAlarmByMember(any(Member.class), anyLong()); |
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.
when 파트에 있으니깐 요건 어때용
willDoNothing().given(alarmCommandService).deleteAlarmByMember(any(Member.class), anyLong()); | |
doNothing().when(alarmCommandService).deleteAlarmByMember(any(Member.class), anyLong()); |
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.
근데 지금까지 전체적으로 when, given 위치 지키지 않고 했어서... 이야기해보아야할 것 같아요.
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.
doNothing().when() 굿굿 좋습니다.
runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId()); | ||
|
||
// then | ||
final int eventPublishedCount = (int) applicationEvents.stream(RunnerPostApplySupporterEvent.class).count(); |
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.
final int eventPublishedCount = (int) applicationEvents.stream(RunnerPostApplySupporterEvent.class).count(); | |
final int eventPublishedCount = Long.valueOf(applicationEvents.stream(RunnerPostApplySupporterEvent.class).count()).intValue(); |
타입캐스팅보다 이렇게 하거나, 그냥 Long 값으로 받는 건 어때요? 타입캐스팅하면 int 범위 벗어나도 예외가 안터져서요...
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.
final int eventPublishedCount = (int) applicationEvents.stream(RunnerPostApplySupporterEvent.class).count(); | |
final int eventPublishedCount = applicationEvents.stream(RunnerPostApplySupporterEvent.class).mapToInt(e -> 1).sum(); |
javadoc에 나온것처럼 하면 이런 방법도 있음
근데 long으로 그냥 쓰는게 더 좋을거 가탕요
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.
테스트 코드라서 long으로 그냥 쓰는게 좋을거 같다에 한 표입니다!
runnerPostCommandService.updateRunnerPostAppliedSupporter(savedRunnerDitto, savedRunnerPost.getId(), runnerPostAssignSupporterRequest); | ||
|
||
// then | ||
final int eventPublishedCount = (int) applicationEvents.stream(RunnerPostAssignSupporterEvent.class).count(); |
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.
마찬가지222
runnerPostCommandService.updateRunnerPostReviewStatusDone(savedRunnerPost.getId(), savedSupporterHyena); | ||
|
||
// then | ||
final int eventPublishedCount = (int) applicationEvents.stream(RunnerPostReviewStatusDoneEvent.class).count(); |
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.
요기도
doReturn(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES)) | ||
.when(spyAlarm) | ||
.getCreatedAt(); |
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.
미친 PR 메시지 감탄했습니다. 한번 보고 코드 보니깐 술술 읽혔습니다. 고생하셨습니다.
하지만 제안 사항이 있어서용!!
아 그리고,,,, big 제안사항인데, 보통 alarm은 시계가 울리는것이나 경고주는거라고 생각해왔었어요. notification이 정보를 전달하는 알림인 것이죠. 그래서 fcm도 보면 notification으로 명시하고 있습니다.
흠 계속 고민했었는데 이제야 말해서 죄송합니다. 그래도 초반에 논의하면 좋을 것 같아서요.
아래는 gpt 형님의 말씀입니다.
"푸시 알람"을 구현하려고 할 때 "alarm"과 "notification" 두 용어 모두 사용될 수 있으나, 그 뜻과 상황에 따라 약간의 차이가 있습니다. 어떤 단어를 선택할지 판단하기 위해 두 용어의 의미와 일반적인 사용 사례를 간략히 살펴보겠습니다.
Alarm:
"알람"은 특정 시간 또는 조건에 대한 경고 또는 알림을 나타내는 데 사용됩니다. 예를 들어, 아침에 일어나기 위한 시계 알람이나 특정 조건을 충족할 때 알려주는 경고 알람 등이 있습니다.
"알람"이 발생하면 즉시 대응이 필요한 경우가 많습니다.
Notification:
"알림"은 일반적으로 사용자에게 정보를 전달하는 데 사용됩니다. 이는 시스템 이벤트, 메시지, 업데이트 등 다양한 종류의 정보를 포함할 수 있습니다.
사용자가 즉시 대응할 필요는 없지만 알림을 확인하면 추가 정보나 액션을 취할 수 있습니다.
따라서 선택할 단어는 구현하려는 기능과 해당 알람/알림의 목적에 따라 결정됩니다.
사용자에게 중요한 이벤트나 조건에 대해 즉시 대응이 필요한 알림을 제공하려면 "alarm"을 선택하는 것이 좋습니다.
사용자에게 정보를 제공하거나 선택적으로 대응할 수 있는 알림을 제공하려면 "notification"을 선택하는 것이 좋습니다.
물론, 프로젝트의 컨텍스트나 팀 내에서의 합의에 따라 이러한 가이드라인이 약간 달라질 수 있습니다.
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.
정성스런 PR 덕분에 빨리 파악할 수 있었습니다. 감사합니당🙇♂️🙇♂️
+2500줄이지만 리뷰할게 별로 없네요.
역시 헤나신👍👍
코멘트 남겨놨으니까 한 번 확인해주세용
private Alarm createAlarm(final AlarmText alarmText, final RunnerPost runnerPost, final Member targetMember) { | ||
return Alarm.builder() | ||
.alarmTitle(new AlarmTitle(alarmText.getText())) | ||
.alarmMessage(new AlarmMessage(String.format(MESSAGE_REFERENCED_BY_RUNNER_POST.getText(), runnerPost.getTitle().getValue()))) |
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.
AlaramText를 바로 @Enumerated
로 사용하면 어떤가요? 이름은 AlaramMessage로 바꿔서. 그러면 메세지 만드는 동작도 넘겨줄 수 있을거 같아요
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.
아 이러면 AlaramTitle이랑 모든게 다 enum으로 바꿔야하네요. 이건 좀 고민~
@Modifying(flushAutomatically = true, clearAutomatically = true) | ||
@Query(""" | ||
update Alarm a | ||
set a.isRead.value = true | ||
where a.id = :alarmId | ||
""") | ||
void updateIsReadTrueByMemberId(@Param("alarmId") Long alarmId); |
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.
오 진짜 이거 왜 만들었지 생각이 드네요 ㅋㅋㅋ 🥲 굿굿
@Column(name = "title", nullable = false) | ||
private String value; | ||
|
||
public AlarmTitle(final String value) { | ||
validateNotNull(value); | ||
this.value = value; | ||
} | ||
|
||
private void validateNotNull(final String value) { | ||
if (Objects.isNull(value)) { | ||
throw new IllegalArgumentException("AlarmTitle 객체 내부에 title 은 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.
Title 같은 경우는 view랑 많이 연결되어 있을것 같아서 변경될 여지가 많아 보여요. 할꺼면 enum으로 class를 만들고 데이터는 기존이랑 동일하게 varchar로 가시는게 어떤가요?
import java.time.LocalDateTime; | ||
import java.time.temporal.ChronoUnit; | ||
|
||
public abstract class TruncatedBaseEntity 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.
굿굿
|
||
private List<Alarm> 러너_게시글_작성자에게_5개의_알람을_저장한다(final Long 생성된_러너_게시글_식별자값) { | ||
final List<Alarm> 예상된_알람_목록 = new ArrayList<>(); | ||
for (int 저장될_알람_카운트_수 = 1; 저장될_알람_카운트_수 <= 5; 저장될_알람_카운트_수++) { |
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.
좋은데요?
runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId()); | ||
|
||
// then | ||
final int eventPublishedCount = (int) applicationEvents.stream(RunnerPostApplySupporterEvent.class).count(); |
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.
final int eventPublishedCount = (int) applicationEvents.stream(RunnerPostApplySupporterEvent.class).count(); | |
final int eventPublishedCount = applicationEvents.stream(RunnerPostApplySupporterEvent.class).mapToInt(e -> 1).sum(); |
javadoc에 나온것처럼 하면 이런 방법도 있음
근데 long으로 그냥 쓰는게 더 좋을거 가탕요
아침에 일어나기 위한 시계 ⏰ ㅋㅋㅋㅋㅋㅋㅋㅋㅋ |
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.
LGTM
* feat: 빈 공백 추가 * github pr 자동 할당 기능 설정 (#563) docs: github pr 자동 할당 기능 설정 * 서포터 피드백 작성 유무 반환 추가, 러너 게시글 조회수 flyway 컬럼명 수정, 서포터 지원자 수 조회시 dto 사용하도록 수정 (#555) * feat: 러너 게시글의 서포터 피드백 유무 필드(컬럼) 구현 * test: 러너 게시글에 서포터 피드백 유무 컬럼 추가 후 테스트 변경 * feat: 러너 게시글 응답에 서포터 피드백 유무 정보 추가 * chore: 러너 게시글에 서포터 피드백 유무 flyway 테이블 컬럼 추가 * chore: 러너 게시글 조회수 컬럼명 flyway 수정 추가 * style: IsReviewed get 메서드 네이밍 get으로 통일되게 수정 * fix: IsReviewed 내부 값 반환 수정 * test: 테스트 내부 entityManager 사용 후 close 하도록 수정 * refactor: 러너 게시글 식별자값 목록으로 서포터 지원자 수 조회시 매핑된 서포터 지원자 수 객체를 반환하도록 레포지터리 메서드 변경 * refactor: 매핑된 서포터 지원자 수 객체를 사용하도록 서비스, 컨트롤러 변경 * style: 공백 및 개행 스타일 수정 * style: toString 메서드 삭제 * refactor: dto 클래스를 레코드로 변경 * test: entityManager flush, close 순서 변경 * 이미 서포터 리뷰를 작성했으면 예외 반환하도록 함 (#552) * refactor: 이미 서포터 리뷰를 작성했으면 예외 반환하도록 함 * test: Repository 테스트 추가 * test: 테스트에 em.flush와 em.clear 추가 * style: 예외 메세지 이름 변경 * feat: Feedback 을 하면 isReviewed 속성이 true 가 되도록 변경 * refreshToken 과 accessToken 이 null 일 때 예외 추가 (#564) * feat: refreshToken 과 accessToken 이 null 일 때 예외 추가 * feat: deploy 에 github branch 설정 추가 * test: AssuredSupport post 에서 필요없는 Application json value 삭제 * feat: CookieValue 에 Nullable 추가 * Merbe deb/BE to feat/559 * feat: max threads 100 으로 변경 * deploy 서브 모듈 리프래쉬 토큰 만료 시간 값 변경 (#573) chore: deploy 리프래쉬 토큰 만료 시간 값 변경 * 기본 쓰레드 300 으로 변경 (#583) chore: 쓰레드 300 으로 변경 * 기본 쓰레드(200)으로 변경 및 min spare 100 으로 변경 (#584) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * min spare 1로 변경 (#586) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * refactor min spare 1로 변경 * 톰캣 쓰레드 관련 설정 롤백 (#587) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * refactor min spare 1로 변경 * chore: tomcat 관련 설정 롤백 * 깃허브 소셜 회원 가입시 계정의 닉네임이 없을 경우 기본값으로 저장 (#589) fix: 사용자 이름 null 로 생성시 기본값 '익명의 사용자'를 사용하도록 수정 * 커서 기반 페이지네이션 (#596) * refactor: 사용하지 않는 상수 제거 * chore: querydsl 추가 * chore: gitignore에 Qclass 숨기기 * chore: build 파일 형태 변경 * feat: QueryDsl config 설정 * feat: querydsl 실험을 위한 임시 컨트롤러 설정 * feat: 커서 기반 페이지네이션 레포지토리 구현 * chore: 서브모듈 업데이트 * refactor: 중복 테스트 제거 * chore: 서브 모듈 적용 * feat: 페이지네이션 쿼리 2개로 개선 * refactor: RunnerPostReadRepository 삭제 * test: 페이지 정보와 리뷰 상태로 게시글 조회 테스트 (두번째 페이지인 경우) * feat: 첫 페이지 조회 기능 구현 * refactor: 필요없는 dto 삭제 * feat: 러너 게시글 지원자 수 저장 객체 생성 * feat: 러너 게시글 페이지 조회 기능 구현 * refactor: 필요 없는 객체 삭제 * refactor: RunnerPost 목록으로 RunnerPostTag 목록 조회하는 기능 RunnerPostTagRepository 로 이동 * refactor: 필요 없는 객체 삭제 * refactor: 태그 이름으로 게시글 조회 기능 구현 * test: 태그 이름으로 조회 기능 테스트 * test: 태그 이름으로 조회 인수 테스트 * test: 태그 이름으로 조회 docs 테스트 수정 * refactor: 충돌 해결 * refactor: 동적 쿼리 적용 * test: runnerPostService 테스트 * refactor: 주석 작성 * chore: 리플랙션 의존성 제거 * chore: 빌드 파일 수정 * chore: 빌드 파일 수정 * 아키텍쳐 개선 (#608) * refactor: Member 패키지 구조 변경 * refactor: Runner 패키지 구조 변경 * refactor: Supporter 패키지 구조 변경 * refactor: Oauth 패키지 구조 변경 * refactor: RunnerPost 패키지 구조 변경 * refactor: Tag 패키지 구조 변경 * refactor: TechnicalTag 패키지 구조 변경 * refactor: Feedback 패키지 구조 변경 * refactor: 패키지 구조 변경 완료 * test: RunnerPostAssured 구조 개선 * test: Member, Feedback 구조 개선 * test: tag 구조 개선 * test: assured 구조 개선 완료 * 최종 페이지네이션 (#611) * test: 전체 조회 인수 테스트 * test: 지원자 수 조회 repo 테스트 * refactor: page model attribute 적용 * refactor: 서포터 관련 게시글 페이지네이션 * refactor: querydsl repository 추상화 해제 * test: 서포터 id 로 러너 게시글 조회 레포지토리 테스트 * feat: runner id 로 러너 게시글 조회 repository 구현 * refactor: response dto query 패키지로 이동 * refactor: PageResponse 로 응답 변경 * refactor: 필요없는 메소드 삭제 * refactor: runnerpost 테스트 패키징 변경 * test: runner로 runnerPost 조회 api 테스트 * docs: 명세서 오류 수정 * refactor: pageResponse 생성자 인자 변경 * refactor: pathParams 에서 limit + 1 되도록 수정 * refactor: querydsl config 테스트용 생성 * refactor: 테스트 자원 정리 * feat: PageInfo에 다음 커서 추가 * refactor: PageParams 패키지 위치 변경 * "/profile/runner" api 삭제 (#619) * test: 전체 조회 인수 테스트 * test: 지원자 수 조회 repo 테스트 * refactor: page model attribute 적용 * refactor: 서포터 관련 게시글 페이지네이션 * refactor: querydsl repository 추상화 해제 * test: 서포터 id 로 러너 게시글 조회 레포지토리 테스트 * feat: runner id 로 러너 게시글 조회 repository 구현 * refactor: response dto query 패키지로 이동 * refactor: PageResponse 로 응답 변경 * refactor: 필요없는 메소드 삭제 * refactor: runnerpost 테스트 패키징 변경 * test: runner로 runnerPost 조회 api 테스트 * docs: 명세서 오류 수정 * refactor: pageResponse 생성자 인자 변경 * refactor: pathParams 에서 limit + 1 되도록 수정 * refactor: querydsl config 테스트용 생성 * refactor: 테스트 자원 정리 * feat: PageInfo에 다음 커서 추가 * refactor: PageParams 패키지 위치 변경 * refactor: api 삭제 * refactor: supporter read response 삭제 * refactor: runner response 두 개 합치기 * refactor: 충돌 해결 * 무중단 배포 실험 (#623) * test: 전체 조회 인수 테스트 * test: 지원자 수 조회 repo 테스트 * refactor: page model attribute 적용 * refactor: 서포터 관련 게시글 페이지네이션 * refactor: querydsl repository 추상화 해제 * test: 서포터 id 로 러너 게시글 조회 레포지토리 테스트 * feat: runner id 로 러너 게시글 조회 repository 구현 * refactor: response dto query 패키지로 이동 * refactor: PageResponse 로 응답 변경 * refactor: 필요없는 메소드 삭제 * refactor: runnerpost 테스트 패키징 변경 * test: runner로 runnerPost 조회 api 테스트 * docs: 명세서 오류 수정 * refactor: pageResponse 생성자 인자 변경 * refactor: pathParams 에서 limit + 1 되도록 수정 * refactor: querydsl config 테스트용 생성 * refactor: 테스트 자원 정리 * feat: PageInfo에 다음 커서 추가 * refactor: PageParams 패키지 위치 변경 * refactor: api 삭제 * refactor: supporter read response 삭제 * refactor: runner response 두 개 합치기 * refactor: 충돌 해결 * refactor: dev cicd 스크립트 변경 * 무중단 배포 스크립트 작성 (#631) * test: 전체 조회 인수 테스트 * test: 지원자 수 조회 repo 테스트 * refactor: page model attribute 적용 * refactor: 서포터 관련 게시글 페이지네이션 * refactor: querydsl repository 추상화 해제 * test: 서포터 id 로 러너 게시글 조회 레포지토리 테스트 * feat: runner id 로 러너 게시글 조회 repository 구현 * refactor: response dto query 패키지로 이동 * refactor: PageResponse 로 응답 변경 * refactor: 필요없는 메소드 삭제 * refactor: runnerpost 테스트 패키징 변경 * test: runner로 runnerPost 조회 api 테스트 * docs: 명세서 오류 수정 * refactor: pageResponse 생성자 인자 변경 * refactor: pathParams 에서 limit + 1 되도록 수정 * refactor: querydsl config 테스트용 생성 * refactor: 테스트 자원 정리 * feat: PageInfo에 다음 커서 추가 * refactor: PageParams 패키지 위치 변경 * refactor: api 삭제 * refactor: supporter read response 삭제 * refactor: runner response 두 개 합치기 * refactor: 충돌 해결 * refactor: dev cicd 스크립트 변경 * feat: CICD 스크립트 수정 * refactor: dev docker compose 삭제 * restdocs 테스트 설정 변경 및 테스트 컨텍스트 로딩 개선 (#633) * test: restdocs 테스트 구성 정보 수정 * test: restdocs 각 테스트 setup 수정 * test: AuthCode 테스트용 클래스명 수정 * test: JwtConfig 테스트용 클래스명 수정 * test: GithubOauthConfig 테스트용 페이크 객체 추가 * test: RestdocsConfig beforeEach 세팅 수정 * test: FakeGithubOauthConfig 삭제 * test: Restdocs 테스트시 로그 print 삭제 * logout시 refreshtoken이 삭제되는 api 구현 (#632) * feat: 로그아웃 API 구현 * refactor: refreshtoken delete api 이름 변경 * refactor: dev에 localhost:3000 cors 추가 * refactor: 리뷰반영 deleteMapping -> patchMapping으로 변경 * test: doPring 제거 * test: 인스턴스 변수 띄어쓰기 추가 * test: repositoryTestConfig에 persistMember 추가 및 리팩토링 * test: given when then 에서 컨벤션 일관화 * zero-downtime-deploy.sh 경로 변경 (#634) * feat: 로그아웃 API 구현 * refactor: refreshtoken delete api 이름 변경 * refactor: dev에 localhost:3000 cors 추가 * refactor: 리뷰반영 deleteMapping -> patchMapping으로 변경 * test: doPring 제거 * test: 인스턴스 변수 띄어쓰기 추가 * test: repositoryTestConfig에 persistMember 추가 및 리팩토링 * test: given when then 에서 컨벤션 일관화 * refactor: zero-downtime-deploy 경로 변경 * 알림 기능 구현 (이벤트 발행 및 구독, 알림 조회, 알림 삭제, 알림 업데이트) (#625) * feat: 알람 내 값객체 구현 (제목, 내용, 연관 식별자값, 읽기 여부, 알람 타입, 알람 메시지 모음) * feat: 알람 엔티티 구현 * feat: 알람 예외 클래스 구현 * feat: 알람 목록 조회 레포지터리 기능 구현 * feat: 알람 소프트 딜리트, 읽기 여부 true 업데이트 레포지터리 기능 구현 * feat: 알람 소프트 딜리트, 읽기 여부 true 업데이트 서비스 기능 구현 * feat: 알람 목록 조회 서비스 기능 구현 * feat: 알람 읽음 여부 기록 및 삭제 API 구현 * feat: 사용자 알람 목록 조회 API 구현 * feat: 러너 게시글 식별자값으로 러너 게시글과 서포터, 사용자 조회 레포지터리 기능 구현 * feat: 알람 이벤트 (러너 게시글 리뷰 완료, 러너 게시글 서포터 할당, 러너 게시글 서포터 지원) 리스너 구현 * feat: 이벤트 (러너 게시글 리뷰 완료, 러너 게시글 서포터 할당, 러너 게시글 서포터 지원) 발행 구현 * feat: 알람 생성 시간 초단위 삭제를 위한 BaseEntity 상속 클래스 구현 * refactor: 알람 이벤트 리스너 내부 메서드 리팩터링 * test: 로그인된 사용자 알람 목록 조회에서 알람 생성시간 테스트 수정 * test: 테스트용 알람 조회 레포지터리 구현 및 테스트용 회원 조회 레포지터리 수정 * test: 알람 읽음 여부 기록 업데이트 인수 테스트 * test: 러너 게시글 지원자 생성 인수 서포트 내부 사용하지 않는 검증문 삭제 * test: 알람 삭제 인수 테스트 * test: 로그인된 사용자 알람 목록 조회 인수 테스트 * chore: flyway 알람 테이블 생성, 알람 사용자(member) 외래키 제약조건 추가 * test: 알람 삭제, 업데이트 restdocs pathVariable 추가 * docs: 로그인된 사용자 알람 목록 조회, 알람 삭제, 알람 읽음 여부 업데이트 api 문서 추가 * style: 사용하지 않는 메서드 삭제 및 정렬 * test: 테스트 로그 삭제 * style: 사용하지 않는 메서드 삭제 * test: 실험용 테스트 코드 삭제 * docs: API 문서 Index 추가 * style: 알람(Alarm)을 알림(Notification)으로 수정 * test: willDoNothing()을 doNothing()으로 수정 * test: 이벤트 발행 카운트 테스트 수정 * refactor: IsRead 를 정적 팩터리 메서드를 이용해서 생성하도록 리팩터링 * feat: 알림 읽음 여부 수정 기능 구현 * feat: 알림 읽음 여부 수정 서비스 리팩터링 * refactor: 알림 이벤트 리스너 내부 NotificationText 를 문자열로 리팩터링 * feat: 알림 목록 조회 querydsl 기능 구현 * refactor: JpaRepository 조회 레포지터리를 Querydsl 레포지터리로 리팩터링 * test: 알림 Restdocs 테스트 수정 * test: 러너 게시글에 서포터 조인 조회 기능 테스트 수정 * 프로덕션 서비스 무중단 배포 (#640) * refactor: cicd 스크립트 수정 * refactor: cicd 스크립트 수정 * flyway 알림 수정 (#644) chore: flyway 알림 테이블 생성 내 콤마 삭제 * 러너 및 서포터의 게시글 개수 조회 api 구현 (#646) * feat: 러너 식별자 값으로 러너 게시글 카운트 쿼리 작성 * refactor: secret 업데이트 * feat: Supporter 와 관련된 게시글 수 조회 repository 구현 * feat: RunnerId 와 ReviewStatus 로 러너 게시글 조회 service 구현 * feat: RunnerId 와 reviewStatus 로 로그인 된 러너의 게시글 개수 조회 api 구현 * test: 로그인한 러너와 관련된 러너 게시글 개수 조회 인수 테스트 * test: 러너와 연관된 러너 게시글 개수 조회 rest docs 테스트 * refactor: SupporterRunnerPostRepository 관련 코드 정리 * test: supporter 테스트 파일 구조 변경 * feat: 서포터 식별자 값으로 러너 게시글 개수 조회 서비스 로직 구현 * feat: 서포터 식별자 값으로 러너 게시글 개수 조회 api 구현 * test: 서포터 관련 러너 게시글 개수 조회 인수 테스트 * test: 로그인한 서포터 관련 러너 게시글 개수 조회 api 테스트 * test: 잘못된 테스트 수정 * test: 서포터가 리뷰 완료한 러너 게시글 개수 조회 api 테스트 * refactor: 서브 모듈 업데이트 * refactor: reference 타입을 primitive 타입으로 변경 * RunnerPost reviewStatus 인덱스 추가 (#650) feat: flyway에 review status enum 추가하는 기능 추가 * 태그 목록 검색 기능 리팩터링 (#637) * refactor: 입력된 문자열로 태그 목록 검색 기능 리팩터링 * refactor: TagQueryService 태그 목록 검색 기능 리팩터링 * chore: 러너 게시글 태그 패키지 이동 * refactor: TagQueryController 태그 목록 검색 기능 리팩터링 * test: 인수 테스트 검증용 TestTagQueryRepository 수정 * refactor: 태그 목록 검색 조건인 TagReducedName 이 null 이거나 공백일 경우 빈 목록을 서비스 계층에서 반환하도록 수정 * Nginx와 Spring 로그가 같은 식별자값을 가지도록 수정 (#654) feat: Nginx의 해쉬값을 받아서 spring에서도 같이 출력하도록 함 --------- Co-authored-by: Ethan <[email protected]> Co-authored-by: HyunSeo Park (Hyena) <[email protected]>
관련이슈
참고사항
23.10.10 피드백 반영
23.10.09 피드백 반영
23.10.07 볼만한 곳
🟩 RunnerPostCommandService 이벤트 발행 장소
RunnerPostApplySupporterEvent
발행RunnerPostAssignSupporterEvent
발행RunnerPostReviewStatusDoneEvent
발행🟩 RunnerPostCommandService 내부 이벤트 발행 테스트
RunnerPostCommandServiceEventTest
이벤트 발생시 이벤트 발행 카운트를 기록하여 테스트를 검증합니다.
@RecordApplicationEvents
를 이용하여 이벤트를 기록합니다.ApplicationEvents
를 주입받아 아래와 같이 예상하는 이벤트 클래스를 넣어 발행된 수를 알 수 있습니다.🟩 AlarmEventListener 구현
이벤트 3개 구독
트랜잭션 전파 및 이벤트 리스너 시작
🟩 AlarmEventListener 테스트
서비스 테스트 조건으로 테스트를 진행했습니다.
알림 이벤트 리스너 내부에서 알림이 생성됩니다.
반환이 void로 알림에 대한 검증을 하기 위해서는 알림을 받은 사용자 식별자값으로 알림 목록을 조회합니다.
예상된 알림 수만 존재하는지 검증한 후에 통과할 경우 내부 검증을 진행하도록 했습니다.
🟩 TruncatedBaseEntity extends BaseEntity
의견 받은 것을 구현했습니다.
BaseEntity를 상속받아 TruncatedBaseEntity를 구현했고 이를 Alarm이 상속하고 있습니다.
TruncatedBaseEntity
는 getter를 오버라이딩하고 있습니다.원하는 것처럼 초단위가 삭제되는 것을 인수테스트에서도 확인할 수 있었습니다.
🚨🚨🚨 하지만 Restdocs 테스트에서 목킹할 때는 주의해야할 점이 있습니다. 🚨🚨🚨
목킹으로 진행하는 테스트의 경우 JpaAuditing이 동작하지 않아 createdAt이 null인 상태입니다.
기존대로 아래처럼 목킹을 진행할 경우 NPE가 발생합니다.
이러한 형태의 목킹은 실제
spyAlarm
내부에createdAt
을 직접 호출하고 반환은 목킹한 값을 줍니다.이 때
TruncatedBaseEntity
내부에getCreatedAt()
메서드를 보면truncatedTo()
을 호출하고 있습니다.예상할 수 있듯이
null.truncatedTo()
의 형태로 NPE가 발생합니다.그래서 아래와 같이 실제 메서드를 호출하지 않도록 목킹해야 합니다.
현재 목킹하는 부분은 외부 API와 의존되는 부분과 restdocs 정도라 큰 문제가 없을 수 있지만 위 방식과 다르게 해결할 수 있다면 좋을 거 같습니다.
이유는 사용하다보면 코드에서 냄새가 스멀스멀 👃 올라온다고 하네요.
🟩 flyway 버전 추가 (알림 테이블 생성, 알림 외래키 제약조건 추가)
테이블 생성과 제약조건 추가를 두 가지로 나눠서 작성했습니다.