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

refactor: 메시지 서비스에서 Querydsl 로직 분리 #678

Merged
merged 6 commits into from
Oct 28, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,18 @@
import com.pickpick.channel.domain.ChannelSubscriptionRepository;
import com.pickpick.member.domain.Member;
import com.pickpick.member.domain.MemberRepository;
import com.pickpick.message.domain.Message;
import com.pickpick.message.domain.MessageRepository;
import com.pickpick.message.domain.QBookmark;
import com.pickpick.message.domain.QMessage;
import com.pickpick.message.domain.QReminder;
import com.pickpick.message.domain.QMessageRepository;
import com.pickpick.message.support.SlackIdExtractor;
import com.pickpick.message.ui.dto.MessageRequest;
import com.pickpick.message.ui.dto.MessageResponse;
import com.pickpick.message.ui.dto.MessageResponses;
import com.querydsl.core.types.ConstructorExpression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

@Transactional(readOnly = true)
@Service
Expand All @@ -40,21 +26,17 @@ public class MessageService {
private static final String MENTION_MARK = "@";

private final MemberRepository members;
private final MessageRepository messages;
private final ChannelSubscriptionRepository channelSubscriptions;
private final JPAQueryFactory jpaQueryFactory;
private final Clock clock;
private final QMessageRepository messages;
private final SlackIdExtractor slackIdExtractor;

public MessageService(final MemberRepository members, final MessageRepository messages,
public MessageService(final MemberRepository members,
final ChannelSubscriptionRepository channelSubscriptions,
final JPAQueryFactory jpaQueryFactory, final Clock clock,
final QMessageRepository messages,
final SlackIdExtractor slackIdExtractor) {
this.members = members;
this.messages = messages;
this.channelSubscriptions = channelSubscriptions;
this.jpaQueryFactory = jpaQueryFactory;
this.clock = clock;
this.slackIdExtractor = slackIdExtractor;
}

Expand All @@ -71,7 +53,7 @@ public MessageResponses find(final Long memberId, final MessageRequest messageRe
private List<Long> findChannelId(final Long memberId, final MessageRequest messageRequest) {
List<Long> channelIds = messageRequest.getChannelIds();

if (isNonNullNorEmpty(channelIds)) {
if (isNonNullAndNotEmpty(channelIds)) {
return channelIds;
}

Expand All @@ -80,27 +62,22 @@ private List<Long> findChannelId(final Long memberId, final MessageRequest messa
return List.of(firstSubscription.getChannelId());
}

private static boolean isNonNullNorEmpty(final List<Long> channelIds) {
return Objects.nonNull(channelIds) && !channelIds.isEmpty();
private boolean isNonNullAndNotEmpty(final List<Long> channelIds) {
return channelIds != null && !channelIds.isEmpty();
Copy link
Collaborator

Choose a reason for hiding this comment

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

꼼꼼한 개선👍

Copy link
Collaborator

Choose a reason for hiding this comment

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

!= 굿! 👍 👍 👍 ❤️
근데 이거 원래 예전 메서드명부터 좀 이상했던 것 같아요
image
null이 아니거나 비어있음이 아니라 null이 아니거나 비어있지 않음 이어야할 것 같아서
isNonNullAndNotEmpty()가 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이렇게 적는 게 확실하겠네요! 반영하겠습니다

}

private List<MessageResponse> findMessages(final Long memberId, final List<Long> channelIds,
final MessageRequest messageRequest) {
boolean needPastMessage = messageRequest.isNeedPastMessage();
int messageCount = messageRequest.getMessageCount();

List<MessageResponse> messageResponses = jpaQueryFactory
.select(getMessageResponseConstructor())
.from(QMessage.message)
.leftJoin(QMessage.message.member)
.leftJoin(QBookmark.bookmark)
.on(existsBookmark(memberId))
.leftJoin(QReminder.reminder)
.on(remainReminder(memberId))
.where(meetAllConditions(channelIds, messageRequest))
.orderBy(arrangeDateByNeedPastMessage(needPastMessage))
.limit(messageCount)
.fetch();
List<MessageResponse> messageResponses = messages.findMessages(
memberId,
channelIds,
messageRequest.getKeyword(),
messageRequest.getMessageId(),
messageRequest.getDate(),
messageRequest.isNeedPastMessage(),
messageRequest.getMessageCount());
Comment on lines +73 to +80
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

MessageRequest라는 DTO가 레포지토리까지 넘어가면 레포지토리가 클라이언트에서 넘어오는 값을 알고 있는 상황이 되는 것 같아서, 파라미터에 값을 꺼내서 넣도록 했습니다
조금 너저분한 감이 없지 않지만 jpaQueryFactory 로직보다는 짧으니까 이게 낫지 않을까요...👀


replaceMentionMembers(memberId, messageResponses);

Expand Down Expand Up @@ -136,113 +113,14 @@ private String replaceMentionMemberInText(String text, final Map<String, String>
return text;
}

private ConstructorExpression<MessageResponse> getMessageResponseConstructor() {
return Projections.constructor(MessageResponse.class,
QMessage.message.id,
QMessage.message.member.id,
QMessage.message.member.username,
QMessage.message.member.thumbnailUrl,
QMessage.message.text,
QMessage.message.postedDate,
QMessage.message.modifiedDate,
QBookmark.bookmark.id,
QReminder.reminder.id,
QReminder.reminder.remindDate);
}

private BooleanExpression existsBookmark(final Long memberId) {
return QBookmark.bookmark.member.id.eq(memberId)
.and(QBookmark.bookmark.message.id.eq(QMessage.message.id));
}

private BooleanExpression remainReminder(final Long memberId) {
return QReminder.reminder.member.id.eq(memberId)
.and(QReminder.reminder.message.id.eq(QMessage.message.id))
.and(QReminder.reminder.remindDate.after(LocalDateTime.now(clock)));
}

private BooleanExpression meetAllConditions(final List<Long> channelIds, final MessageRequest request) {
return channelIdsIn(channelIds)
.and(textContains(request.getKeyword()))
.and(messageHasText())
.and(decideMessageIdOrDate(request.getMessageId(), request.getDate(), request.isNeedPastMessage()));
}

private BooleanExpression channelIdsIn(final List<Long> channelIds) {
return QMessage.message.channel.id.in(channelIds);
}

private BooleanExpression textContains(final String keyword) {
if (StringUtils.hasText(keyword)) {
return QMessage.message.text.containsIgnoreCase(keyword);
}

return null;
}

private Predicate decideMessageIdOrDate(final Long messageId,
final LocalDateTime date,
final boolean needPastMessage) {
if (Objects.nonNull(messageId)) {
return messageIdCondition(messageId, needPastMessage);
}

return dateCondition(date, needPastMessage);
}

private BooleanExpression messageHasText() {
return QMessage.message.text.isNotNull()
.and(QMessage.message.text.isNotEmpty());
}


private Predicate messageIdCondition(final Long messageId, final boolean needPastMessage) {
Message message = messages.getById(messageId);

LocalDateTime messageDate = message.getPostedDate();

if (needPastMessage) {
return QMessage.message.postedDate.before(messageDate);
}

return QMessage.message.postedDate.after(messageDate);
}

private Predicate dateCondition(final LocalDateTime date, final boolean needPastMessage) {
if (Objects.isNull(date)) {
return null;
}

if (needPastMessage) {
return QMessage.message.postedDate.eq(date)
.or(QMessage.message.postedDate.before(date));
}

return QMessage.message.postedDate.eq(date)
.or(QMessage.message.postedDate.after(date));
}

private OrderSpecifier<LocalDateTime> arrangeDateByNeedPastMessage(final boolean needPastMessage) {
if (needPastMessage) {
return QMessage.message.postedDate.desc();
}

return QMessage.message.postedDate.asc();
}

private boolean hasPast(final List<Long> channelIds, final MessageRequest messageRequest,
final List<MessageResponse> messages) {
if (messages.isEmpty()) {
return false;
}

Integer result = jpaQueryFactory
.selectOne()
.from(QMessage.message)
.where(meetAllHasPastCondition(channelIds, messageRequest, messages))
.fetchFirst();

return result != null;
MessageResponse message = messages.get(messages.size() - 1);
return this.messages.existsByChannelsBeforePostedDate(channelIds, messageRequest, message);
}

private boolean hasFuture(final List<Long> channelIds, final MessageRequest messageRequest,
Expand All @@ -251,30 +129,7 @@ private boolean hasFuture(final List<Long> channelIds, final MessageRequest mess
return false;
}

Integer result = jpaQueryFactory
.selectOne()
.from(QMessage.message)
.where(meetAllHasFutureCondition(channelIds, messageRequest, messages))
.fetchFirst();

return result != null;
}

private BooleanExpression meetAllHasPastCondition(final List<Long> channelIds, final MessageRequest request,
final List<MessageResponse> messages) {
MessageResponse targetMessage = messages.get(messages.size() - 1);

return channelIdsIn(channelIds)
.and(textContains(request.getKeyword()))
.and(QMessage.message.postedDate.before(targetMessage.getPostedDate()));
}

private BooleanExpression meetAllHasFutureCondition(final List<Long> channelIds, final MessageRequest request,
final List<MessageResponse> messages) {
MessageResponse targetMessage = messages.get(0);

return channelIdsIn(channelIds)
.and(textContains(request.getKeyword()))
.and(QMessage.message.postedDate.after(targetMessage.getPostedDate()));
MessageResponse message = messages.get(0);
return this.messages.existsByChannelsAfterPostedDate(channelIds, messageRequest, message);
}
}
Loading