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

[BE] 변경된 ERD 설계에 따른 전체 코드 리팩토링 #72

Merged
merged 33 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6363ef7
refactor(AttendeeName): 이름 길이 검증 시 커스텀 예외와 에러코드를 사용하도록 변경
seokmyungham Jul 21, 2024
8ad0fb1
refactor(MeetingErrorCode): uuid 에러 코드를 MeetingErrorCode 로 이동
seokmyungham Jul 21, 2024
bf1a4d1
refactor(Attendee): 원시값 포장에 따른 제약조건 어노테이션 수정
seokmyungham Jul 21, 2024
3b36199
refactor(ScheduleService): 변경된 ERD 에 따라 스케줄을 생성하는 로직을 변경
seokmyungham Jul 22, 2024
df69986
refactor(ScheduleService): `generateSchedules()` 메서드 분리 및 인덴트 개선
hw0603 Jul 22, 2024
a6f1369
fix(Timeslot): 이전 시간 검증 로직의 동등성 비교 수정
hw0603 Jul 22, 2024
25bcfa6
refactor(ScheduleService): `flatMap`에서 사용할 스트림을 리스트로 변환하지 않고 반환하도록 개선
hw0603 Jul 22, 2024
20b15eb
refactor(ScheduleService): 일급 컬렉션을 활용하여 불필요한 데이터베이스 입출력을 개선
seokmyungham Jul 22, 2024
c97438b
refactor(MeetingService): 약속 및 스케쥴 정보 조회 api에서 약속 조회 api를 분리 및 개선
seokmyungham Jul 22, 2024
63052ae
refactor(TimeslotInterval): 시간슬롯 인터벌을 계산하는 로직을 제거하고 기존 로직을 개선
seokmyungham Jul 24, 2024
7216cf9
test(AvailableDatesTest): findByDate() 테스트 추가
seokmyungham Jul 24, 2024
dd3d17d
test(AttendeePasswordTest): verifyPassword() 테스트 추가
seokmyungham Jul 24, 2024
b19d69e
refactor(Meeting): TimeslotInterval 필드 명 변경에 따른 메서드 리팩토링
seokmyungham Jul 24, 2024
3165e0d
refactor(ScheduleService): 스케줄 생성 리팩토링 및 테스트 추가
seokmyungham Jul 24, 2024
5d782e9
remove(ScheduleTimeResponse): 사용하지 않는 응답 DTO 삭제
seokmyungham Jul 24, 2024
d74772f
fix(Controller): Bean Validation 누락된 @Valid 추가
seokmyungham Jul 24, 2024
d7f7851
refactor(Meeting): 원시 값으로 엔티티를 생성하여 필드 객체 생성 로직 개선
seokmyungham Jul 24, 2024
3b1f165
refactor(MeetingFixture): 엔티티 생성자 수정에 따른 픽스처 필드 변경
seokmyungham Jul 24, 2024
2e3ab79
refactor(AvailableDate): 불필요한 생성자 제거
seokmyungham Jul 24, 2024
729de3e
style: 메서드 순서 변경 및 오타 수정
seokmyungham Jul 24, 2024
885b3be
refactor(MeetingService): 읽기 전용 @Transactional 사용 및 리팩토링
seokmyungham Jul 24, 2024
fe830bc
test(MeetingServiceTest): 불필요한 테스트 제거 및 개선
seokmyungham Jul 24, 2024
8406aef
style: 메서드 순서 변경 및 공백라인 제거
seokmyungham Jul 24, 2024
955ed50
chore: 테스트 시 data.sql이 실행되지 않도록 환경설정 추가
seokmyungham Jul 25, 2024
a28515b
style: 사용하지 않는 필드 제거 및 import 제거
seokmyungham Jul 25, 2024
d77f72a
fix: 누락된 엔티티 컬럼 nullable=false 옵션 추가
seokmyungham Jul 25, 2024
f73fea4
test(TimeslotIntervalTest): 시작 끝이 같은 시간에 대한 시간슬롯 인터벌을 생성하는 테스트 추가
seokmyungham Jul 25, 2024
28f6195
refactor(MeetingService): 약속의 주최자를 저장하는 메서드 명을 명확하게 변경
seokmyungham Jul 25, 2024
82f8ce4
refactor: 인수 테스트시 각 테스트 메서드에서 데이터를 셋업하도록 변경
seokmyungham Jul 25, 2024
2227130
refactor(ScheduleService): 메서드 반환 형식을 개선하여 불필요한 Stream 체인을 개선
seokmyungham Jul 25, 2024
022290e
refactor(DateWithTimesRequest): 클래스 명을 DateTimesCreateRequest 로 변경
seokmyungham Jul 25, 2024
bc4def3
refactor: 테스트 데이터 셋업 리팩토링
seokmyungham Jul 25, 2024
3514227
refactor: 클래스명 변경에 따른 변수명 변경
seokmyungham Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class AttendeeController {

@PostMapping("/api/v1/login/{uuid}")
public MomoApiResponse<TokenResponse> login(
@PathVariable String uuid, @Valid @RequestBody AttendeeLoginRequest request
@PathVariable String uuid, @RequestBody @Valid AttendeeLoginRequest request
Copy link
Member

Choose a reason for hiding this comment

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

누락된 어노테이션을 잘 짚어 주셨군요ㅎㅎ

) {
return new MomoApiResponse<>(attendeeService.login(uuid, request));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.woowacourse.momo.service.meeting.dto.MeetingCreateRequest;
import com.woowacourse.momo.service.meeting.dto.MeetingResponse;
import com.woowacourse.momo.service.meeting.dto.MeetingSharingResponse;
import jakarta.validation.Valid;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand All @@ -27,7 +28,7 @@ public MomoApiResponse<MeetingResponse> find(@PathVariable String uuid) {
}

@PostMapping("/api/v1/meeting")
public ResponseEntity<Void> create(@RequestBody MeetingCreateRequest request) {
public ResponseEntity<Void> create(@RequestBody @Valid MeetingCreateRequest request) {
String uuid = meetingService.create(request);
return ResponseEntity.created(URI.create("/meeting/" + uuid))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.woowacourse.momo.service.schedule.ScheduleService;
import com.woowacourse.momo.service.schedule.dto.ScheduleCreateRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -13,8 +15,8 @@ public class ScheduleController {

private final ScheduleService scheduleService;

@PostMapping("/api/v1/schedule")
public void create(@RequestBody ScheduleCreateRequest request) {
scheduleService.create(request);
@PostMapping("/api/v1/schedule/{uuid}")
public void create(@PathVariable String uuid, @RequestBody @Valid ScheduleCreateRequest request) {
scheduleService.create(uuid, request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,13 @@ public class Attendee extends BaseEntity {
private Meeting meeting;

@Embedded
@Column(nullable = false, length = 20)
private AttendeeName name;

@Embedded
@Column(nullable = false, length = 20)
private AttendeePassword password;

@Enumerated(EnumType.STRING)
@Column(length = 10)
@Column(nullable = false, length = 10)
private Role role;

public Attendee(Meeting meeting, AttendeeName name, AttendeePassword password, Role role) {
Expand All @@ -64,4 +62,8 @@ public void verifyPassword(AttendeePassword other) {
public String name() {
return this.name.getName();
}

public String password() {
return this.password.getPassword();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.woowacourse.momo.domain.attendee;

import com.woowacourse.momo.exception.MomoException;
import com.woowacourse.momo.exception.code.AttendeeErrorCode;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.Getter;
Expand All @@ -10,6 +13,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AttendeeName {

@Column(nullable = false, length = 20)
private String name;

public AttendeeName(String name) {
Expand All @@ -19,7 +23,7 @@ public AttendeeName(String name) {

private void validateNameLength(String name) {
if (name.length() > 20) {
throw new IllegalArgumentException("이름의 길이는 20글자까지 가능합니다.");
throw new MomoException(AttendeeErrorCode.INVALID_NAME_LENGTH);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.woowacourse.momo.exception.MomoException;
import com.woowacourse.momo.exception.code.AttendeeErrorCode;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
Expand All @@ -14,6 +15,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AttendeePassword {

@Column(nullable = false, length = 20)
private String password;

public AttendeePassword(String password) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@

public interface AttendeeRepository extends JpaRepository<Attendee, Long> {

Optional<Attendee> findByName(AttendeeName name);

Optional<Attendee> findByMeetingAndName(Meeting meeting, AttendeeName name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public class AvailableDate extends BaseEntity {
private Meeting meeting;

public AvailableDate(LocalDate date, Meeting meeting) {
this(null, date, meeting);
this.date = date;
this.meeting = meeting;
}

public boolean isSameDate(LocalDate other) {
return this.date.equals(other);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package com.woowacourse.momo.domain.availabledate;

import com.woowacourse.momo.domain.meeting.Meeting;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AvailableDateRepository extends JpaRepository<AvailableDate, Long> {

Optional<AvailableDate> findByMeetingAndDate(Meeting meeting, LocalDate date);

List<AvailableDate> findAllByMeeting(Meeting meeting);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,30 @@
@Getter
public class AvailableDates {

private final List<AvailableDate> dates;
private final List<AvailableDate> availableDates;

public AvailableDates(List<LocalDate> dates, Meeting meeting) {
validateDuplicatedDates(dates);
this.dates = dates.stream()
this.availableDates = dates.stream()
.map(date -> new AvailableDate(date, meeting))
.toList();
}

public AvailableDates(List<AvailableDate> availableDates) {
this.availableDates = availableDates;
}

private void validateDuplicatedDates(List<LocalDate> dates) {
Set<LocalDate> dateSet = new HashSet<>(dates);
if (dateSet.size() != dates.size()) {
throw new MomoException(AvailableDateErrorCode.DUPLICATED_DATE);
}
}

public AvailableDate findByDate(LocalDate other) {
return availableDates.stream()
.filter(availableDate -> availableDate.isSameDate(other))
.findFirst()
.orElseThrow(() -> new MomoException(AvailableDateErrorCode.INVALID_AVAILABLE_DATE));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,22 @@

import com.woowacourse.momo.domain.BaseEntity;
import com.woowacourse.momo.domain.timeslot.Timeslot;
import com.woowacourse.momo.exception.MomoException;
import com.woowacourse.momo.exception.code.MeetingErrorCode;
import com.woowacourse.momo.domain.timeslot.TimeslotInterval;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Table(name = "meeting")
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Meeting extends BaseEntity {

Expand All @@ -35,32 +31,24 @@ public class Meeting extends BaseEntity {
@Column(nullable = false, length = 40)
private String uuid;

@Enumerated(EnumType.STRING)
@Column(length = 10)
private Timeslot firstTimeslot;
@Embedded
private TimeslotInterval timeslotInterval;

@Enumerated(EnumType.STRING)
@Column(length = 10)
private Timeslot lastTimeslot;

public Meeting(String name, String uuid, Timeslot firstTimeslot, Timeslot lastTimeslot) {
validateTimeRange(firstTimeslot, lastTimeslot);
public Meeting(String name, String uuid, LocalTime firstTime, LocalTime lastTime) {
this.name = name;
this.uuid = uuid;
this.firstTimeslot = firstTimeslot;
this.lastTimeslot = lastTimeslot;
this.timeslotInterval = new TimeslotInterval(firstTime, lastTime.minusMinutes(30));
}

public Meeting(String name, String uuid, LocalTime firstTime, LocalTime lastTime) {
this(name, uuid, Timeslot.from(firstTime), Timeslot.from(lastTime.minusMinutes(30)));
public Timeslot getValidatedTimeslot(LocalTime other) {
return this.timeslotInterval.getValidatedTimeslot(other);
}

public LocalTime startTimeslotTime() {
return this.timeslotInterval.getStartTimeslot().getLocalTime();
}

private void validateTimeRange(Timeslot firstTimeslot, Timeslot lastTimeslot) {
if (firstTimeslot == lastTimeslot) {
return;
}
if (firstTimeslot.isNotBefore(lastTimeslot)) {
throw new MomoException(MeetingErrorCode.INVALID_TIME_RANGE);
}
public LocalTime endTimeslotTime() {
return this.timeslotInterval.getEndTimeslot().getLocalTime();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,17 @@ public class Schedule extends BaseEntity {
@JoinColumn(name = "attendee_id", nullable = false)
private Attendee attendee;


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "available_date_id", nullable = false)
private AvailableDate availableDate;

@Enumerated(EnumType.STRING)
@Column(length = 10)
private Timeslot firstTimeslot;

@Enumerated(EnumType.STRING)
@Column(length = 10)
private Timeslot lastTimeslot;
@Column(nullable = false, length = 10)
private Timeslot timeslot;

public Schedule(Attendee attendee, AvailableDate availableDate, Timeslot firstTimeslot, Timeslot lastTimeslot) {
public Schedule(Attendee attendee, AvailableDate availableDate, Timeslot timeslot) {
this.attendee = attendee;
this.availableDate = availableDate;
this.firstTimeslot = firstTimeslot;
this.lastTimeslot = lastTimeslot;
this.timeslot = timeslot;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.woowacourse.momo.domain.timeslot;

import com.woowacourse.momo.exception.MomoException;
import com.woowacourse.momo.exception.code.TimeslotErrorCode;
import java.time.LocalTime;
import java.util.Arrays;
import lombok.Getter;
Expand Down Expand Up @@ -56,20 +58,24 @@ public enum Timeslot {
TIME_2300(LocalTime.of(23, 0)),
TIME_2330(LocalTime.of(23, 30));

private final LocalTime time;
private final LocalTime localTime;

Timeslot(LocalTime time) {
this.time = time;
Timeslot(LocalTime localTime) {
this.localTime = localTime;
}

public static Timeslot from(LocalTime localTime) {
return Arrays.stream(values())
.filter(timeslot -> timeslot.time.equals(localTime))
.filter(timeslot -> timeslot.localTime.equals(localTime))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 타임슬롯입니다."));
.orElseThrow(() -> new MomoException(TimeslotErrorCode.INVALID_TIMESLOT));
}

public boolean isNotBefore(Timeslot timeslot) {
return !this.time.isBefore(timeslot.time);
public boolean isAfter(LocalTime other) {
return this.localTime.isAfter(other);
}

public boolean isBefore(LocalTime other) {
return this.localTime.isBefore(other);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.woowacourse.momo.domain.timeslot;

import com.woowacourse.momo.exception.MomoException;
import com.woowacourse.momo.exception.code.MeetingErrorCode;
import com.woowacourse.momo.exception.code.ScheduleErrorCode;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import java.time.LocalTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Embeddable
@Getter
@EqualsAndHashCode(of = {"startTimeslot", "endTimeslot"})
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TimeslotInterval {
Copy link
Contributor

Choose a reason for hiding this comment

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

이 클래스는 Meeting의 가능시간과 끝 시간을 따로 분리해 둔 클래스로 이해했어요.
그런데 timeslot 도메인 쪽에 있으니, Meeting과 연관되어 있다는 것을 직관적으로 알기 어려워요..!
TimeslotInterval은 단순히 임베디드 타입이니 이 클래스를 Meeting에 넣어주는 것이 어떨까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

무슨 말인지 이해했습니다.
추후에 모든 백엔드 크루와 같이 패키지 구조를 개선하는 시간을 가져봐요 😊

Copy link
Contributor

Choose a reason for hiding this comment

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

네 좋습니다~!


@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 10)
private Timeslot startTimeslot;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 10)
private Timeslot endTimeslot;

public TimeslotInterval(LocalTime startTime, LocalTime lastTime) {
validateTimeRange(startTime, lastTime);
this.startTimeslot = Timeslot.from(startTime);
this.endTimeslot = Timeslot.from(lastTime);
}

private void validateTimeRange(LocalTime firstTime, LocalTime lastTime) {
if (firstTime.isAfter(lastTime)) {
throw new MomoException(MeetingErrorCode.INVALID_TIME_RANGE);
}
}
Comment on lines +38 to +42
Copy link
Contributor

Choose a reason for hiding this comment

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

좋네요~~👍


public Timeslot getValidatedTimeslot(LocalTime other) {
if (isNotContainedWithin(other)) {
throw new MomoException(ScheduleErrorCode.INVALID_SCHEDULE_TIMESLOT);
}
return Timeslot.from(other);
}

private boolean isNotContainedWithin(LocalTime other) {
return this.startTimeslot.isAfter(other) || this.endTimeslot.isBefore(other);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

public enum AttendeeErrorCode implements ErrorCodeType {

INVALID_NAME_LENGTH(HttpStatus.BAD_REQUEST, "이름 길이는 최대 20글자까지 가능합니다."),
INVALID_PASSWORD_LENGTH(HttpStatus.BAD_REQUEST, "비밀번호 길이는 최대 20글자까지 가능합니다."),
INVALID_UUID(HttpStatus.BAD_REQUEST, "유효하지 않은 UUID 입니다."),
PASSWORD_MISMATCHED(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다.");
PASSWORD_MISMATCHED(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다."),
INVALID_ATTENDEE(HttpStatus.BAD_REQUEST, "해당 약속에 참여하는 참가자 정보가 없습니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Loading